/** * useMandates Hook * * Hook für die Verwaltung von Mandanten (Mandates) im Admin-Bereich. * Folgt dem gleichen Pattern wie useOrgUsers. */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useApiRequest } from './useApi'; import api from '../api'; import { usePermissions, type UserPermissions } from './usePermissions'; import { fetchMandates as fetchMandatesApi, fetchMandateById as fetchMandateByIdApi, createMandate as createMandateApi, updateMandate as updateMandateApi, deleteMandate as deleteMandateApi, hardDeleteMandate as hardDeleteMandateApi, type Mandate, type MandateUpdateData, type PaginationParams } from '../api/mandateApi'; import type { AttributeDefinition as FormGenAttr } from '../components/FormGenerator/FormGeneratorForm'; import { getMandateBillingFormAttributes } from '../utils/mandateBillingFormMerge'; // Re-export types export type { Mandate, MandateUpdateData, PaginationParams }; export interface AttributeDefinition { name: string; type: string; label: string; description?: string; required?: boolean; default?: any; options?: Array<{ value: string | number; label: string }> | string; sortable?: boolean; filterable?: boolean; searchable?: boolean; width?: number; minWidth?: number; maxWidth?: number; readonly?: boolean; editable?: boolean; } /** * Hook for managing mandates in admin panel */ export function useAdminMandates() { const [mandates, setMandates] = useState([]); const [attributes, setAttributes] = useState([]); const [permissions, setPermissions] = useState(null); const [pagination, setPagination] = useState<{ currentPage: number; pageSize: number; totalItems: number; totalPages: number; } | null>(null); const { request, isLoading: loading, error } = useApiRequest(); const { checkPermission } = usePermissions(); // Fetch attributes from backend const fetchAttributes = useCallback(async () => { try { const response = await api.get('/api/attributes/Mandate'); let attrs: AttributeDefinition[] = []; if (response.data?.attributes && Array.isArray(response.data.attributes)) { attrs = response.data.attributes; } else if (Array.isArray(response.data)) { attrs = response.data; } else if (response.data && typeof response.data === 'object') { const keys = Object.keys(response.data); for (const key of keys) { if (Array.isArray(response.data[key])) { attrs = response.data[key]; break; } } } setAttributes(attrs); return attrs; } catch (error: any) { if (error.response?.status === 429) { console.warn('Rate limit exceeded while fetching mandate attributes.'); } else if (error.response?.status !== 401) { console.error('Error fetching mandate attributes:', error); } setAttributes([]); return []; } }, []); // Fetch permissions const fetchPermissions = useCallback(async () => { try { const perms = await checkPermission('DATA', 'Mandate'); setPermissions(perms); return perms; } catch (error: any) { console.error('Error fetching mandate permissions:', error); const defaultPerms: UserPermissions = { view: false, read: 'n', create: 'n', update: 'n', delete: 'n', }; setPermissions(defaultPerms); return defaultPerms; } }, [checkPermission]); // Fetch mandates const fetchMandates = useCallback(async (params?: PaginationParams) => { try { const data = await fetchMandatesApi(request, params); if (data && typeof data === 'object' && 'items' in data) { const items = Array.isArray(data.items) ? data.items : []; setMandates(items); if (data.pagination) { setPagination(data.pagination); } } else { const items = Array.isArray(data) ? data : []; setMandates(items); setPagination(null); } } catch (error: any) { setMandates([]); setPagination(null); } }, [request]); // Optimistic updates const removeOptimistically = (mandateId: string) => { setMandates(prev => prev.filter(m => m.id !== mandateId)); }; const updateOptimistically = (mandateId: string, updateData: Partial) => { setMandates(prev => prev.map(m => m.id === mandateId ? { ...m, ...updateData } : m) ); }; // Fetch single mandate const fetchMandateById = useCallback(async (mandateId: string): Promise => { return await fetchMandateByIdApi(request, mandateId); }, [request]); // Generate columns from attributes (including fkSource/fkDisplayField for FK resolution) const columns = attributes.map(attr => ({ key: attr.name, label: attr.label || attr.name, type: attr.type as any, sortable: attr.sortable !== false, filterable: attr.filterable !== false, searchable: attr.searchable !== false, width: attr.width || 150, minWidth: attr.minWidth || 100, maxWidth: attr.maxWidth || 400, fkSource: (attr as any).fkSource, // API endpoint for FK data fkDisplayField: (attr as any).fkDisplayField, // Which field of FK target to display })); // Create mandate const handleCreate = useCallback(async (mandateData: Partial): Promise => { try { const created = await createMandateApi(request, mandateData); await fetchMandates(); return created ?? null; } catch (error: any) { console.error('Error creating mandate:', error); return null; } }, [request, fetchMandates]); // Update mandate const handleUpdate = useCallback(async (mandateId: string, updateData: MandateUpdateData): Promise => { try { updateOptimistically(mandateId, updateData); await updateMandateApi(request, mandateId, updateData); return true; } catch (error: any) { console.error('Error updating mandate:', error); await fetchMandates(); return false; } }, [request, fetchMandates]); // Delete mandate const handleDelete = useCallback(async (mandateId: string): Promise => { try { removeOptimistically(mandateId); await deleteMandateApi(request, mandateId); return true; } catch (error: any) { console.error('Error deleting mandate:', error); await fetchMandates(); return false; } }, [request, fetchMandates]); // Hard-delete mandate (irreversible) const handleHardDelete = useCallback(async (mandateId: string, confirmName: string): Promise => { try { removeOptimistically(mandateId); await hardDeleteMandateApi(request, mandateId, confirmName); return true; } catch (error: any) { console.error('Error hard-deleting mandate:', error); await fetchMandates(); return false; } }, [request, fetchMandates]); // Inline update const handleInlineUpdate = useCallback(async ( mandateId: string, updateData: Partial ): Promise => { await handleUpdate(mandateId, updateData); }, [handleUpdate]); // Load data on mount useEffect(() => { fetchAttributes(); fetchPermissions(); fetchMandates(); }, []); return { mandates, attributes, columns, permissions, pagination, loading, error, refetch: fetchMandates, fetchMandateById, handleCreate, handleUpdate, handleDelete, handleHardDelete, handleInlineUpdate, updateOptimistically, }; } export default useAdminMandates; /** * Mandate model attributes for FormGenerator (create/edit) — shared by Admin page and wizard. */ export function useMandateFormAttributes() { const [attributes, setAttributes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const response = await api.get('/api/attributes/Mandate'); let attrs: AttributeDefinition[] = []; const data = response.data; if (data?.attributes && Array.isArray(data.attributes)) { attrs = data.attributes; } else if (Array.isArray(data)) { attrs = data; } else if (data && typeof data === 'object') { for (const key of Object.keys(data)) { if (Array.isArray((data as Record)[key])) { attrs = (data as Record)[key]; break; } } } setAttributes(attrs); } catch (e: unknown) { const msg = e instanceof Error ? e.message : 'Attribute-Laden fehlgeschlagen'; setError(msg); setAttributes([]); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const formAttributes: FormGenAttr[] = useMemo(() => { return attributes .filter(attr => attr.name !== 'id') .map(attr => ({ ...attr, type: attr.type })) as FormGenAttr[]; }, [attributes]); const createFormAttributes: FormGenAttr[] = useMemo( () => formAttributes.filter(attr => attr.name !== 'isSystem'), [formAttributes] ); const billingFormAttributes: FormGenAttr[] = useMemo(() => getMandateBillingFormAttributes(), []); /** Mandate attributes + billing (Abrechnung) for SysAdmin create flows */ const createFormAttributesWithBilling: FormGenAttr[] = useMemo( () => [...createFormAttributes, ...billingFormAttributes], [createFormAttributes, billingFormAttributes] ); /** Mandate attributes + billing for SysAdmin edit flows */ const formAttributesWithBilling: FormGenAttr[] = useMemo( () => [...formAttributes, ...billingFormAttributes], [formAttributes, billingFormAttributes] ); return { formAttributes, createFormAttributes, formAttributesWithBilling, createFormAttributesWithBilling, loading, error, refetch: load, }; }