import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useMetadata } from '../../services/hooks/useMetadata'; import { BaseMockRepository } from '../../services/mock/baseRepository'; import { RecordEntity } from '../../services/storage/indexedDb'; import { ViewDefinition } from '../../services/contracts/metadata'; import { DynamicTable } from './DynamicTable'; import { DynamicFormRenderer } from './DynamicFormRenderer'; import { DynamicCardRenderer } from './DynamicCardRenderer'; import { Button } from '../../shared/ui/Button'; import { Badge } from '../../shared/ui/Badge'; import { Modal } from '../../shared/ui/Modal'; import { Drawer } from '../../shared/ui/Drawer'; import { useToast } from '../../shared/ui/useToast'; import { Layers, Plus, Table, CreditCard, Sliders, Layout, Sparkles, Lock, } from 'lucide-react'; import { BaseEntity } from '../../entities/types'; export const CustomEntityPage: React.FC = () => { const { entityCode, recordId } = useParams<{ entityCode: string; recordId?: string }>(); const navigate = useNavigate(); const { currentEntity, views, layouts, loading } = useMetadata(entityCode); const { showToast } = useToast(); const [records, setRecords] = useState([]); const [isLoadingRecords, setIsLoadingRecords] = useState(true); // Active View const [activeViewId, setActiveViewId] = useState(''); // Modals & Drawers const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingRecord, setEditingRecord] = useState(null); const [viewingRecord, setViewingRecord] = useState(null); const [recordToDelete, setRecordToDelete] = useState(null); // Create repository instance for this entity const repository = useMemo(() => { if (!entityCode) return null; return new BaseMockRepository(entityCode); }, [entityCode]); // Fetch records const loadRecords = useCallback(async () => { if (!repository) return; try { setIsLoadingRecords(true); const res = await repository.list({ pageSize: 100 }); setRecords(res.items); } catch (err) { showToast({ type: 'error', title: 'Ошибка загрузки записей', message: err instanceof Error ? err.message : String(err), }); } finally { setIsLoadingRecords(false); } }, [repository, showToast]); useEffect(() => { void Promise.resolve().then(() => { loadRecords(); }); }, [loadRecords]); // Set default view useEffect(() => { void Promise.resolve().then(() => { if (views && views.length > 0 && !activeViewId) { const def = views.find((v) => v.isDefault) || views[0]; setActiveViewId(def.id); } }); }, [views, activeViewId]); // Handle direct link to recordId useEffect(() => { if (recordId && repository) { repository.getById(recordId).then((rec) => { if (rec) setViewingRecord(rec); }); } }, [recordId, repository]); const activeView = useMemo(() => { return views.find((v) => v.id === activeViewId) || null; }, [views, activeViewId]); // Form layout (if exists) const formLayout = useMemo(() => { return layouts.find((l) => l.type === 'form' || l.isDefault) || null; }, [layouts]); // Card layout (if exists) const cardLayout = useMemo(() => { return layouts.find((l) => l.type === 'card' || l.cardBlocks) || null; }, [layouts]); // Save new record const handleCreateRecord = async (values: Record) => { if (!repository || !currentEntity) return; // Handle autonumbering if enabled let recordValues = { ...values }; if (currentEntity.autonumberingEnabled && !recordValues.number) { const year = new Date().getFullYear(); const count = records.length + 1; const formattedNumber = `DOC-${year}-${String(count).padStart(4, '0')}`; recordValues = { ...recordValues, number: formattedNumber }; } await repository.create(recordValues as Omit); showToast({ type: 'success', title: 'Запись успешно создана' }); setIsCreateModalOpen(false); await loadRecords(); }; // Update record const handleUpdateRecord = async (values: Record) => { if (!repository || !editingRecord) return; await repository.update(editingRecord.id, values); showToast({ type: 'success', title: 'Запись обновлена' }); setEditingRecord(null); if (viewingRecord?.id === editingRecord.id) { const updated = await repository.getById(editingRecord.id); setViewingRecord(updated); } await loadRecords(); }; // Delete record const handleDeleteRecord = async () => { if (!repository || !recordToDelete) return; try { await repository.delete(recordToDelete.id); showToast({ type: 'success', title: 'Запись удалена' }); setRecordToDelete(null); if (viewingRecord?.id === recordToDelete.id) { setViewingRecord(null); } await loadRecords(); } catch (err) { showToast({ type: 'error', title: 'Ошибка удаления', message: err instanceof Error ? err.message : String(err), }); } }; if (!currentEntity && !loading) { return (

Сущность «{entityCode}» не найдена

Возможно, она была удалена или переименована в каталоге сущностей.

); } return (
{/* Top Banner */}

{currentEntity?.pluralName || currentEntity?.name || entityCode}

{currentEntity?.isSystem ? ( System ) : ( Custom )}

{currentEntity?.description || `Управление записями сущности ${entityCode}`}

{/* Settings Shortcuts */}