import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react'; import type { CatalogListParams } from '../api/catalogApi'; import { getApiErrorMessage } from '../api/errors'; import type { PageResponse } from '../api/types'; import { useAuth } from '../auth/AuthContext'; import { DataTable, type TableColumn } from '../components/DataTable'; import { Layout } from '../components/Layout'; import { Badge } from '../components/ui/Badge'; import { Button } from '../components/ui/Button'; import { Card } from '../components/ui/Card'; import { ConfirmDialog } from '../components/ui/ConfirmDialog'; import { ErrorState } from '../components/ui/ErrorState'; import { FormField } from '../components/ui/FormField'; import { Input } from '../components/ui/Input'; import { LoadingState } from '../components/ui/LoadingState'; import { PageHeader } from '../components/ui/PageHeader'; import { Pagination } from '../components/ui/Pagination'; import { Select } from '../components/ui/Select'; import { Textarea } from '../components/ui/Textarea'; type CatalogItem = { id: string; active: boolean; }; export type CatalogFormState = Record; export type CatalogField = { name: string; label: string; required?: boolean; type?: 'text' | 'email' | 'textarea'; disabledOnEdit?: boolean; }; type CatalogPageProps = { title: string; description: string; columns: TableColumn[]; fields: CatalogField[]; emptyForm: CatalogFormState; list: (params: CatalogListParams) => Promise>; create: (form: CatalogFormState) => Promise; update: (id: string, form: CatalogFormState, item: TItem) => Promise; remove: (id: string) => Promise; }; const PAGE_SIZE = 20; export function CatalogPage({ title, description, columns, fields, emptyForm, list, create, update, remove, }: CatalogPageProps) { const { user } = useAuth(); const canWrite = user?.role === 'ADMIN' || user?.role === 'MANAGER'; const [pageData, setPageData] = useState | null>(null); const [page, setPage] = useState(0); const [searchInput, setSearchInput] = useState(''); const [search, setSearch] = useState(''); const [activeFilter, setActiveFilter] = useState<'true' | 'false'>('true'); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(''); const [form, setForm] = useState(emptyForm); const [editingItem, setEditingItem] = useState(null); const [deactivateItem, setDeactivateItem] = useState(null); const [isFormOpen, setIsFormOpen] = useState(false); const loadData = useCallback(async () => { setIsLoading(true); setError(''); try { const data = await list({ page, size: PAGE_SIZE, search, active: activeFilter === 'true', }); setPageData(data); } catch (caughtError) { setError(getApiErrorMessage(caughtError, 'Не удалось загрузить записи справочника')); } finally { setIsLoading(false); } }, [activeFilter, list, page, search]); useEffect(() => { void loadData(); }, [loadData]); const tableColumns = useMemo[]>( () => [ ...columns, { key: 'active', label: 'Статус', render: (item) => {item.active ? 'Активен' : 'Неактивен'}, }, ], [columns], ); const openCreateForm = () => { setEditingItem(null); setForm(emptyForm); setIsFormOpen(true); setError(''); }; const openEditForm = (item: TItem) => { const nextForm = fields.reduce((acc, field) => { const value = item[field.name as keyof TItem]; acc[field.name] = value == null ? '' : String(value); return acc; }, {}); setEditingItem(item); setForm(nextForm); setIsFormOpen(true); setError(''); }; const closeForm = () => { setEditingItem(null); setForm(emptyForm); setIsFormOpen(false); }; const handleSearchSubmit = (event: FormEvent) => { event.preventDefault(); setPage(0); setSearch(searchInput.trim()); }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setIsSaving(true); setError(''); try { if (editingItem) { await update(editingItem.id, form, editingItem); } else { await create(form); setPage(0); } closeForm(); await loadData(); } catch (caughtError) { setError(getApiErrorMessage(caughtError, 'Не удалось сохранить запись')); } finally { setIsSaving(false); } }; const handleDeactivate = async () => { if (!deactivateItem) { return; } setError(''); try { await remove(deactivateItem.id); setDeactivateItem(null); await loadData(); } catch (caughtError) { setError(getApiErrorMessage(caughtError, 'Не удалось деактивировать запись')); } }; return (
Создать ) : null } />
setSearchInput(event.target.value)} placeholder="Поиск" className="min-w-0 flex-1" />
{isFormOpen && (

{editingItem ? 'Редактировать запись' : 'Создать запись'}

{fields.map((field) => { const disabled = Boolean(editingItem && field.disabledOnEdit); return ( {field.type === 'textarea' ? (