/** * MandateInfoPanel — inline-editable mandate + billing summary (left expand panel). */ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import type { Mandate } from '../../hooks/useMandates'; import { useMandateFormAttributes } from '../../hooks/useMandates'; import { useApiRequest } from '../../hooks/useApi'; import { fetchSettingsAdmin, updateSettingsAdmin, type BillingSettingsUpdate } from '../../api/billingApi'; import { mergeBillingIntoMandateFormData, getMandateBillingFormAttributes, MANDATE_INVOICE_FIELD_NAMES, formatMandateInvoiceAddress, isMandateBillingField, mandateBillingFieldNames, } from '../../utils/mandateBillingFormMerge'; import { InlineEditableField, type InlineFieldType } from './InlineEditableField'; import { useToast } from '../../contexts/ToastContext'; import styles from './MandateInfoPanel.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; import { mandateDisplayLabel } from '../../utils/mandateDisplayUtils'; import { getUserDataCache } from '../../utils/userCache'; const STAMMDATEN_FIELDS = ['name', 'label', 'enabled', 'isSystem', 'mfaRequired'] as const; const FIELD_TYPES: Record = { name: 'text', label: 'text', enabled: 'boolean', isSystem: 'boolean', mfaRequired: 'boolean', warningThresholdPercent: 'number', notifyOnWarning: 'boolean', notifyEmails: 'textarea', invoiceEmail: 'email', }; const MANDATE_ADMIN_EDITABLE = new Set(['label', 'warningThresholdPercent', 'notifyOnWarning', 'notifyEmails']); export interface MandateInfoPanelProps { mandateId: string; mandateLabel?: string; canUpdate: boolean; fetchMandateById: (id: string) => Promise; handleUpdate: (mandateId: string, updateData: Partial) => Promise; onMandateUpdated?: () => void; refreshKey?: number; } function isFieldEditable( fieldName: string, canUpdate: boolean, isPlatformAdmin: boolean, isSystemMandate: boolean, ): boolean { if (!canUpdate) return false; if (fieldName === 'isSystem') return false; if (isSystemMandate && fieldName === 'name') return false; if (!isPlatformAdmin) { return MANDATE_ADMIN_EDITABLE.has(fieldName); } return fieldName !== 'isSystem' && fieldName !== 'id' && fieldName !== 'deletedAt'; } function InfoSection({ title, children, }: { title: string; children: React.ReactNode; }) { return (

{title}

{children}
); } export const MandateInfoPanel: React.FC = ({ mandateId, mandateLabel, canUpdate, fetchMandateById, handleUpdate, onMandateUpdated, refreshKey = 0, }) => { const { t } = useLanguage(); const { request } = useApiRequest(); const { showError, showWarning } = useToast(); const { formAttributes } = useMandateFormAttributes(); const isPlatformAdmin = getUserDataCache()?.isPlatformAdmin === true; const [data, setData] = useState | null>(null); const [loading, setLoading] = useState(true); const [billingWarning, setBillingWarning] = useState(null); const [savingField, setSavingField] = useState(null); const [editingAddress, setEditingAddress] = useState(false); const labelMap = useMemo(() => { const map = new Map(); for (const attr of formAttributes) { map.set(attr.name, attr.label || attr.name); } for (const attr of getMandateBillingFormAttributes()) { map.set(attr.name, attr.label || attr.name); } return map; }, [formAttributes]); const loadDetail = useCallback(async () => { setLoading(true); setBillingWarning(null); try { const fullMandate = await fetchMandateById(mandateId); if (!fullMandate) { setData(null); return; } try { const settings = await fetchSettingsAdmin(request, mandateId); setData(mergeBillingIntoMandateFormData(fullMandate as Record, settings)); } catch { setData(mergeBillingIntoMandateFormData(fullMandate as Record, null)); setBillingWarning(t('Abrechnungseinstellungen konnten nicht geladen werden.')); } } finally { setLoading(false); } }, [mandateId, fetchMandateById, request, t]); useEffect(() => { void loadDetail(); }, [loadDetail, refreshKey]); const isSystemMandate = Boolean(data?.isSystem); const canEditInvoice = isPlatformAdmin && canUpdate; const saveField = useCallback( async (fieldName: string, newValue: unknown) => { setSavingField(fieldName); try { if (isMandateBillingField(fieldName)) { const billingUpdate: BillingSettingsUpdate = {}; if (fieldName === 'warningThresholdPercent') { const n = Number(newValue); if (Number.isNaN(n)) { showError(t('Fehler'), t('Ungültiger Wert')); return; } billingUpdate.warningThresholdPercent = n; } else if (fieldName === 'notifyOnWarning') { billingUpdate.notifyOnWarning = Boolean(newValue); } else if (fieldName === 'notifyEmails') { const emails = typeof newValue === 'string' ? newValue.split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean) : []; billingUpdate.notifyEmails = emails; } await updateSettingsAdmin(request, mandateId, billingUpdate); } else { const ok = await handleUpdate(mandateId, { [fieldName]: newValue } as Partial); if (!ok) { showWarning(t('Fehler'), t('Speichern fehlgeschlagen.')); await loadDetail(); return; } } setData((prev) => (prev ? { ...prev, [fieldName]: newValue } : prev)); onMandateUpdated?.(); } catch (e: unknown) { console.error(e); showError(t('Fehler'), t('Speichern fehlgeschlagen.')); await loadDetail(); } finally { setSavingField(null); } }, [mandateId, request, handleUpdate, loadDetail, onMandateUpdated, showError, showWarning, t], ); const displayTitle = mandateLabel || (data ? mandateDisplayLabel({ label: data.label as string | undefined, name: data.name as string | undefined, id: mandateId, }) : mandateId); const formattedAddress = data ? formatMandateInvoiceAddress(data) : null; if (loading) { return (
{t('Laden…')}
); } if (!data) { return (
{t('Mandant konnte nicht geladen werden.')}
); } return (

{t('Mandant')}

{displayTitle}

{billingWarning &&
{billingWarning}
} {Boolean(data.isSystem) && (
{t('System-Mandant')} — {t('Kurzzeichen ist schreibgeschützt.')}
)}
{STAMMDATEN_FIELDS.map((fieldName) => ( ))}
{editingAddress && canEditInvoice ? (
{MANDATE_INVOICE_FIELD_NAMES.map((fieldName) => ( ))}
) : (
{ e.stopPropagation(); if (canEditInvoice) setEditingAddress(true); }} onKeyDown={(e) => { if (canEditInvoice && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); setEditingAddress(true); } }} title={canEditInvoice ? t('Klicken zum Bearbeiten') : undefined} > {formattedAddress ? (
{formattedAddress}
) : ( {canEditInvoice ? t('Klicken, um Adresse zu erfassen') : '—'} )}
)}
{mandateBillingFieldNames.map((fieldName) => ( ))}
); }; export default MandateInfoPanel;