/** * AdminMandatesPage * * Admin page for managing Mandates (tenants) using FormGeneratorTable. */ import React, { useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAdminMandates, useMandateFormAttributes, type Mandate } from '../../hooks/useMandates'; import { useApiRequest } from '../../hooks/useApi'; import { fetchSettingsAdmin, updateSettingsAdmin } from '../../api/billingApi'; import { mergeBillingIntoMandateFormData, splitMandateAndBillingFromForm, } from '../../utils/mandateBillingFormMerge'; import { useToast } from '../../contexts/ToastContext'; import { usePrompt } from '../../hooks/usePrompt'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; import { FaPlus, FaSync, FaUsers, FaLock, FaSkullCrossbones } from 'react-icons/fa'; import { getUserDataCache } from '../../utils/userCache'; import styles from './Admin.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; export const AdminMandatesPage: React.FC = () => { const { t } = useLanguage(); const navigate = useNavigate(); const { request } = useApiRequest(); const { showWarning, showSuccess } = useToast(); const { prompt, PromptDialog } = usePrompt(); const { mandates, columns, permissions, pagination, loading, error, refetch, fetchMandateById, handleCreate, handleUpdate, handleDelete, handleHardDelete, handleInlineUpdate, updateOptimistically, } = useAdminMandates(); const { formAttributes, createFormAttributes, formAttributesWithBilling, createFormAttributesWithBilling, loading: mandateAttrsLoading, } = useMandateFormAttributes(); const [showCreateModal, setShowCreateModal] = useState(false); /** Mandate row merged with billing fields for FormGenerator */ const [editingFormData, setEditingFormData] = useState | null>(null); const [editingBillingWarning, setEditingBillingWarning] = useState(null); const isPlatformAdmin = getUserDataCache()?.isPlatformAdmin === true; // MandateAdmin: only label + billing fields editable; rest readonly const _MANDATE_ADMIN_EDITABLE = new Set(['label', 'warningThresholdPercent', 'notifyOnWarning', 'notifyEmails']); const editFormAttrs: AttributeDefinition[] = useMemo(() => { if (isPlatformAdmin) return formAttributesWithBilling; return formAttributesWithBilling.map(attr => _MANDATE_ADMIN_EDITABLE.has(attr.name) ? attr : { ...attr, editable: false, readonly: true } ); }, [formAttributesWithBilling, isPlatformAdmin]); // Check if user can create const canCreate = permissions?.create !== 'n'; const canUpdate = permissions?.update !== 'n'; const canDelete = permissions?.delete !== 'n'; // Handle edit click — load mandate + billing settings (separate persistence) const handleEditClick = async (mandate: Mandate) => { setEditingBillingWarning(null); const fullMandate = await fetchMandateById(mandate.id); if (!fullMandate) return; try { const settings = await fetchSettingsAdmin(request, fullMandate.id); setEditingFormData( mergeBillingIntoMandateFormData(fullMandate as Record, settings) ); } catch { setEditingFormData(mergeBillingIntoMandateFormData(fullMandate as Record, null)); setEditingBillingWarning( t('Abrechnungseinstellungen konnten nicht geladen werden. Nur Mandantendaten sind sicher bearbeitbar.') ); } }; // Handle create submit — POST mandate, then billing settings const handleCreateSubmit = async (data: Record) => { const { mandatePayload, billingUpdate } = splitMandateAndBillingFromForm(data); const created = await handleCreate(mandatePayload as Partial); if (!created?.id) return; try { await updateSettingsAdmin(request, created.id, billingUpdate); showSuccess(t('Erstellt'), t('Mandant inkl. Abrechnungseinstellungen gespeichert.')); } catch (e: unknown) { console.error(e); showWarning( t('Mandant erstellt'), t('Abrechnungseinstellungen konnten nicht gespeichert werden. Bitte unter Administration → Abrechnung nachpflegen.') ); } setShowCreateModal(false); }; // Handle edit submit — PUT mandate + POST billing settings const handleEditSubmit = async (data: Record) => { if (!editingFormData?.id) return; const mandateId = String(editingFormData.id); const { mandatePayload, billingUpdate } = splitMandateAndBillingFromForm(data); const mandateOk = await handleUpdate(mandateId, mandatePayload as Partial); if (!mandateOk) { showWarning(t('Fehler'), t('Mandant konnte nicht gespeichert werden. Fehlende Berechtigung oder Serverfehler.')); return; } try { await updateSettingsAdmin(request, mandateId, billingUpdate); showSuccess(t('Gespeichert'), t('Mandant und Abrechnung aktualisiert.')); } catch (e: unknown) { console.error(e); showWarning(t('Teilweise gespeichert'), t('Mandant gespeichert, Abrechnung konnte nicht aktualisiert werden.')); } setEditingFormData(null); setEditingBillingWarning(null); }; const handleDeleteMandate = async (mandate: Mandate) => { if (mandate.isSystem) { return; } const entered = await prompt( t( 'Um den Mandanten zu deaktivieren (Soft-Delete), geben Sie das Kurzzeichen «{slug}» exakt ein (Anzeigename: «{label}»).', { slug: mandate.name, label: mandate.label || mandate.name } ), { title: t('Mandat deaktivieren'), confirmLabel: t('Deaktivieren'), variant: 'danger', placeholder: mandate.name }, ); if (entered === null) return; if (entered !== mandate.name) { showWarning(t('Abgebrochen'), t('Das eingegebene Kurzzeichen stimmt nicht überein.')); return; } await handleDelete(mandate.id); }; const handleHardDeleteMandate = async (mandate: Mandate) => { if (mandate.isSystem) { showWarning(t('Nicht erlaubt'), t('System-Mandanten können nicht gelöscht werden.')); return; } const entered = await prompt( t( 'ACHTUNG: Dies löscht den Mandanten unwiderruflich inkl. aller Subscriptions, Features, Benutzer-Zuweisungen und Daten. Geben Sie das Kurzzeichen «{slug}» exakt ein (Anzeigename: «{label}»).', { slug: mandate.name, label: mandate.label || mandate.name } ), { title: t('Unwiderrufliches Löschen'), confirmLabel: t('Dauerhaft löschen'), variant: 'danger', placeholder: mandate.name }, ); if (entered === null) return; if (entered !== mandate.name) { showWarning(t('Abgebrochen'), t('Das eingegebene Kurzzeichen stimmt nicht überein.')); return; } const ok = await handleHardDelete(mandate.id, entered); if (ok) { showSuccess( t('Gelöscht'), t('Mandant «{name}» wurde endgültig gelöscht.', { name: mandate.label || mandate.name }) ); } }; if (error) { return (
⚠️

{t('Fehler beim Laden der Mandanten')}: {error}

); } return (

{t('Mandanten')}

{t('Verwalten Sie alle Mandanten im')} {' '} {t( 'Der Volle Name erscheint in der Oberfläche; das Kurzzeichen ist systemweit eindeutig und dient Referenzierung und Bestätigungsabfragen.' )}

{canCreate && ( )}
row.isSystem ? { disabled: true, message: t('System-Mandanten können nicht gelöscht werden') } : false }] : []), ]} customActions={canDelete ? [{ id: 'hard-delete', icon: , onClick: handleHardDeleteMandate, title: t('Unwiderrufliches Löschen'), disabled: (row: Mandate) => row.isSystem ? { disabled: true, message: t('System-Mandanten können nicht gelöscht werden') } : false, }] : []} onDelete={handleDeleteMandate} hookData={{ refetch, permissions, pagination, handleDelete, handleInlineUpdate, updateOptimistically, }} emptyMessage={t('Keine Mandanten gefunden')} />
{/* Create Modal */} {showCreateModal && (

{t('Neuer Mandant')}

{t('Stammdaten kommen aus dem Modell')} Mandate {t('(API). Abrechnung wird in')}{' '} BillingSettings {t('pro Mandant gespeichert.')}

{mandateAttrsLoading || createFormAttributes.length === 0 ? (
{t('Lade Formular')}
) : ( setShowCreateModal(false)} submitButtonText={t('Erstellen')} cancelButtonText={t('Abbrechen')} /> )}
)} {/* Edit Modal */} {editingFormData && (

{t('Mandant bearbeiten')}

{Boolean(editingFormData.isSystem) && (
{t('Dies ist ein')} {t('System-Mandant')}.{' '} {t( 'Er kann nicht gelöscht werden. Das Kurzzeichen (technischer Identifier) soll nicht geändert werden; der Volle Name kann bei Bedarf angepasst werden.' )}
)} {editingBillingWarning && (
{editingBillingWarning}
)} {formAttributes.length === 0 ? (
{t('Lade Formular')}
) : ( { setEditingFormData(null); setEditingBillingWarning(null); }} submitButtonText={t('Speichern')} cancelButtonText={t('Abbrechen')} /> )}
)}
); }; export default AdminMandatesPage;