/** * TrusteeDataTablesView * * Consolidated "Daten-Tabellen" page that exposes every Trustee table in * its own tab using `FormGeneratorTable` (pagination, sort, filter, search). * * Architecture: * - One generic body component (`TrusteeDataTab`) is reused for the simple * CRUD tables (Organisation, Rolle, Zugriff, Vertrag) and the read-only * sync tables (TrusteeData*, TrusteeAccounting*). * - For "Position" and "Dokument" tabs we embed the existing specialised * views (`TrusteePositionsView`, `TrusteeDocumentsView`) directly, because * they already implement the full action set: * - Positionen: edit / delete / sync-to-accounting / Beleg-Download (1-2) * - Dokumente: edit / delete / Download * That way RBAC, optimistic updates, batch sync and download stay in one * place. * - Each tab is a thin wrapper that calls its own hook so React hook rules * are respected and inactive tabs perform zero data fetching (lazy-mount). * - Tab state lives in `?tab=` so deep links from QuickActions / * notifications / docs stay stable. * * Layout / sizing: see `wiki/b-reference/frontend-nyla/formgenerator.md` * ("Page Layout Chain"). Outer is `adminPage + adminPageFill`, active tab * sits inside `tableContainer`, which provides the bounded height chain * `FormGeneratorTable` requires. */ import React, { useCallback, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useLanguage } from '../../../providers/language/LanguageContext'; import { useTrusteeOrganisations, useTrusteeOrganisationOperations, useTrusteeRoles, useTrusteeRoleOperations, useTrusteeAccess, useTrusteeAccessOperations, useTrusteeContracts, useTrusteeContractOperations, useTrusteeDataAccounts, useTrusteeDataJournalEntries, useTrusteeDataJournalLines, useTrusteeDataContacts, useTrusteeDataAccountBalances, useTrusteeAccountingConfigs, useTrusteeAccountingSyncs, } from '../../../hooks/useTrustee'; import { useInstanceId } from '../../../hooks/useCurrentInstance'; import { TrusteeDataTab } from './dataTables/TrusteeDataTab'; import { TrusteePositionsView } from './TrusteePositionsView'; import { TrusteeDocumentsView } from './TrusteeDocumentsView'; import adminStyles from '../../admin/Admin.module.css'; // --------------------------------------------------------------------------- // Tab definitions // --------------------------------------------------------------------------- interface TabDef { id: string; entityName: string; label: string; icon: string; color: string; readOnly: boolean; Wrapper: React.FC<{ instanceId: string }>; } interface TabGroupDef { id: string; label: string; color: string; tabs: TabDef[]; } function _buildApiEndpoint(instanceId: string, suffix: string): string { return `/api/trustee/${instanceId}/${suffix}`; } // --------------------------------------------------------------------------- // Wrappers – per-tab so inactive tabs do not fetch. // --------------------------------------------------------------------------- // Generic CRUD wrapper: data hook + operations hook → edit/delete actions. function _makeCrudWrapper( useDataHook: () => any, useOpsHook: () => any, suffix: string, entityLabel: string, ): React.FC<{ instanceId: string }> { const Wrapper: React.FC<{ instanceId: string }> = ({ instanceId }) => { const data = useDataHook(); const ops = useOpsHook(); return ( ); }; Wrapper.displayName = `TrusteeDataTabCrud(${suffix})`; return Wrapper; } // Read-only wrapper: data hook only, no actions. function _makeReadOnlyWrapper( useDataHook: () => any, suffix: string, ): React.FC<{ instanceId: string }> { const Wrapper: React.FC<{ instanceId: string }> = ({ instanceId }) => { const data = useDataHook(); return ( ); }; Wrapper.displayName = `TrusteeDataTabRO(${suffix})`; return Wrapper; } // Specialised wrappers: reuse existing CRUD views with full action set. const _PositionsWrapper: React.FC<{ instanceId: string }> = () => ; _PositionsWrapper.displayName = 'TrusteeDataTabPositions'; const _DocumentsWrapper: React.FC<{ instanceId: string }> = () => ; _DocumentsWrapper.displayName = 'TrusteeDataTabDocuments'; const _OrganisationsWrapper = _makeCrudWrapper(useTrusteeOrganisations, useTrusteeOrganisationOperations, 'organisations', 'Organisation'); const _RolesWrapper = _makeCrudWrapper(useTrusteeRoles, useTrusteeRoleOperations, 'roles', 'Rolle'); const _AccessWrapper = _makeCrudWrapper(useTrusteeAccess, useTrusteeAccessOperations, 'access', 'Zugriff'); const _ContractsWrapper = _makeCrudWrapper(useTrusteeContracts, useTrusteeContractOperations, 'contracts', 'Vertrag'); const _DataAccountsWrapper = _makeReadOnlyWrapper(useTrusteeDataAccounts, 'data/accounts'); const _DataJournalEntriesWrapper = _makeReadOnlyWrapper(useTrusteeDataJournalEntries, 'data/journal-entries'); const _DataJournalLinesWrapper = _makeReadOnlyWrapper(useTrusteeDataJournalLines, 'data/journal-lines'); const _DataContactsWrapper = _makeReadOnlyWrapper(useTrusteeDataContacts, 'data/contacts'); const _DataAccountBalancesWrapper = _makeReadOnlyWrapper(useTrusteeDataAccountBalances, 'data/account-balances'); const _AccountingConfigsWrapper = _makeReadOnlyWrapper(useTrusteeAccountingConfigs, 'accounting/configs'); const _AccountingSyncsWrapper = _makeReadOnlyWrapper(useTrusteeAccountingSyncs, 'accounting/syncs'); // Group structure mirrors `DATA_OBJECTS` in `gateway/modules/features/trustee/mainTrustee.py` // (UDB folders): Stammdaten · Lokale Daten · Konfiguration · Daten aus Buchhaltungssystem. // "Stammdaten" is page-only (Organisation/Rolle/Zugriff/Vertrag are admin tables that // don't appear in the UDB because the feature instance IS the organisation). function _buildTabGroups(t: (k: string) => string): TabGroupDef[] { return [ { id: 'master', label: t('Stammdaten'), color: '#1976d2', tabs: [ { id: 'organisations', entityName: 'TrusteeOrganisation', label: t('Organisation'), icon: '\uD83C\uDFE2', color: '#1976d2', readOnly: false, Wrapper: _OrganisationsWrapper }, { id: 'roles', entityName: 'TrusteeRole', label: t('Rolle'), icon: '\uD83D\uDC65', color: '#0277bd', readOnly: false, Wrapper: _RolesWrapper }, { id: 'access', entityName: 'TrusteeAccess', label: t('Zugriff'), icon: '\uD83D\uDD11', color: '#0288d1', readOnly: false, Wrapper: _AccessWrapper }, { id: 'contracts', entityName: 'TrusteeContract', label: t('Vertrag'), icon: '\uD83D\uDCDC', color: '#00796b', readOnly: false, Wrapper: _ContractsWrapper }, ], }, { id: 'localData', label: t('Lokale Daten'), color: '#388e3c', tabs: [ { id: 'documents', entityName: 'TrusteeDocument', label: t('Dokument'), icon: '\uD83D\uDCC4', color: '#388e3c', readOnly: false, Wrapper: _DocumentsWrapper }, { id: 'positions', entityName: 'TrusteePosition', label: t('Position'), icon: '\uD83D\uDCCA', color: '#43a047', readOnly: false, Wrapper: _PositionsWrapper }, ], }, { id: 'config', label: t('Konfiguration'), color: '#5e35b1', tabs: [ { id: 'accounting-configs', entityName: 'TrusteeAccountingConfig', label: t('Buchhaltungs-Verbindung'), icon: '\u2699\uFE0F', color: '#5e35b1', readOnly: true, Wrapper: _AccountingConfigsWrapper }, { id: 'accounting-syncs', entityName: 'TrusteeAccountingSync', label: t('Sync-Protokoll'), icon: '\uD83D\uDD04', color: '#3949ab', readOnly: true, Wrapper: _AccountingSyncsWrapper }, ], }, { id: 'accountingData', label: t('Daten aus Buchhaltungssystem'), color: '#ef6c00', tabs: [ { id: 'accounts', entityName: 'TrusteeDataAccount', label: t('Kontenplan'), icon: '\uD83D\uDCD2', color: '#f57c00', readOnly: true, Wrapper: _DataAccountsWrapper }, { id: 'journal-entries', entityName: 'TrusteeDataJournalEntry', label: t('Buchungen'), icon: '\uD83D\uDCDD', color: '#ef6c00', readOnly: true, Wrapper: _DataJournalEntriesWrapper }, { id: 'journal-lines', entityName: 'TrusteeDataJournalLine', label: t('Buchungszeilen'), icon: '\uD83D\uDCC3', color: '#e65100', readOnly: true, Wrapper: _DataJournalLinesWrapper }, { id: 'contacts', entityName: 'TrusteeDataContact', label: t('Kontakte'), icon: '\uD83D\uDC64', color: '#c2185b', readOnly: true, Wrapper: _DataContactsWrapper }, { id: 'account-balances', entityName: 'TrusteeDataAccountBalance', label: t('Kontosalden'), icon: '\uD83D\uDCB0', color: '#ad1457', readOnly: true, Wrapper: _DataAccountBalancesWrapper }, ], }, ]; } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export const TrusteeDataTablesView: React.FC = () => { const { t } = useLanguage(); const instanceId = useInstanceId(); const [searchParams, setSearchParams] = useSearchParams(); const tabGroups = useMemo(() => _buildTabGroups(t), [t]); const visibleTabs = useMemo(() => tabGroups.flatMap((g) => g.tabs), [tabGroups]); const requestedTab = searchParams.get('tab'); const activeTab = useMemo(() => { if (requestedTab && visibleTabs.some((tab) => tab.id === requestedTab)) { return requestedTab; } return visibleTabs[0]?.id || ''; }, [requestedTab, visibleTabs]); const _setActiveTab = useCallback((tabId: string) => { setSearchParams({ tab: tabId }, { replace: true }); }, [setSearchParams]); const currentTab = visibleTabs.find((tab) => tab.id === activeTab) || visibleTabs[0]; if (!instanceId) { return (

{t('Instanz wird geladen…')}

); } if (!currentTab) { return (

{t('Daten-Tabellen')}

{t('Du hast keine Berechtigung für')}

); } const ActiveWrapper = currentTab.Wrapper; return (

{t('Daten-Tabellen')}

{t('Alle Datenbanktabellen dieser Trustee-Instanz auf einen Blick.')}

{tabGroups.map((group) => (
{group.label}
{group.tabs.map((tab) => { const isActive = activeTab === tab.id; return ( ); })}
))}
); }; export default TrusteeDataTablesView;