209 lines
6.4 KiB
TypeScript
209 lines
6.4 KiB
TypeScript
/**
|
||
* TrusteeContractsView
|
||
*
|
||
* Vertrags-Verwaltung für eine Trustee-Instanz.
|
||
* Zeigt Kundenverträge mit Organisation-Zuordnung.
|
||
*/
|
||
|
||
import React, { useState, useMemo } from 'react';
|
||
import { useTrusteeContracts, useTrusteeContractOperations, TrusteeContract } from '../../../hooks/useTrustee';
|
||
import { useTrusteeOptions } from '../../../hooks/useTrusteeOptions';
|
||
import { useTablePermission } from '../../../hooks/useInstancePermissions';
|
||
import { Popup } from '../../../components/UiComponents/Popup/Popup';
|
||
import { TrusteeEditForm, FieldConfig } from './components';
|
||
import styles from './TrusteeViews.module.css';
|
||
|
||
export const TrusteeContractsView: React.FC = () => {
|
||
const { items: contracts, loading, error, refetch } = useTrusteeContracts();
|
||
const { handleDelete, handleCreate, handleUpdate, deletingItems, creatingItem } = useTrusteeContractOperations();
|
||
const { canCreate, canUpdate, canDelete } = useTablePermission('TrusteeContract');
|
||
|
||
// Options für Label-Auflösung
|
||
const { getLabelFast, loading: optionsLoading } = useTrusteeOptions(['organisations']);
|
||
|
||
// Modal State
|
||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||
const [editingContract, setEditingContract] = useState<TrusteeContract | null>(null);
|
||
const [formError, setFormError] = useState<string | null>(null);
|
||
|
||
// Feld-Konfiguration für das Formular
|
||
const fields: FieldConfig[] = useMemo(() => [
|
||
{
|
||
key: 'organisationId',
|
||
label: 'Organisation',
|
||
type: 'enum',
|
||
required: true,
|
||
optionsReference: 'organisations',
|
||
editable: !editingContract, // Nicht änderbar nach Erstellung
|
||
helpText: editingContract ? 'Organisation kann nicht geändert werden' : undefined,
|
||
},
|
||
{
|
||
key: 'label',
|
||
label: 'Bezeichnung',
|
||
type: 'string',
|
||
required: true,
|
||
placeholder: 'z.B. Kunde AG 2026',
|
||
},
|
||
{
|
||
key: 'enabled',
|
||
label: 'Status',
|
||
type: 'boolean',
|
||
helpText: 'Vertrag ist aktiv',
|
||
},
|
||
], [editingContract]);
|
||
|
||
if (loading || optionsLoading) {
|
||
return <div className={styles.loading}>Lade Verträge...</div>;
|
||
}
|
||
|
||
if (error) {
|
||
return <div className={styles.error}>Fehler: {error}</div>;
|
||
}
|
||
|
||
const onDelete = async (contractId: string) => {
|
||
if (window.confirm('Vertrag wirklich löschen?')) {
|
||
const success = await handleDelete(contractId);
|
||
if (success) {
|
||
refetch();
|
||
}
|
||
}
|
||
};
|
||
|
||
const onEdit = (contract: TrusteeContract) => {
|
||
setEditingContract(contract);
|
||
setFormError(null);
|
||
setIsModalOpen(true);
|
||
};
|
||
|
||
const onCreate = () => {
|
||
setEditingContract(null);
|
||
setFormError(null);
|
||
setIsModalOpen(true);
|
||
};
|
||
|
||
const onCloseModal = () => {
|
||
setIsModalOpen(false);
|
||
setEditingContract(null);
|
||
setFormError(null);
|
||
};
|
||
|
||
const onSave = async (data: Partial<TrusteeContract>) => {
|
||
setFormError(null);
|
||
|
||
try {
|
||
if (editingContract) {
|
||
// Bei Update: organisationId nicht mitsenden (ist immutable)
|
||
const { organisationId, ...updateData } = data;
|
||
const result = await handleUpdate(editingContract.id, updateData);
|
||
if (!result.success) {
|
||
setFormError(result.error || 'Fehler beim Aktualisieren');
|
||
return;
|
||
}
|
||
} else {
|
||
const result = await handleCreate(data);
|
||
if (!result.success) {
|
||
setFormError(result.error || 'Fehler beim Erstellen');
|
||
return;
|
||
}
|
||
}
|
||
|
||
onCloseModal();
|
||
refetch();
|
||
} catch (err: any) {
|
||
setFormError(err.message || 'Ein Fehler ist aufgetreten');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className={styles.listView}>
|
||
{/* Toolbar */}
|
||
<div className={styles.toolbar}>
|
||
{canCreate && (
|
||
<button className={styles.primaryButton} onClick={onCreate}>
|
||
+ Neuer Vertrag
|
||
</button>
|
||
)}
|
||
<button className={styles.secondaryButton} onClick={() => refetch()}>
|
||
Aktualisieren
|
||
</button>
|
||
</div>
|
||
|
||
{/* Tabelle */}
|
||
{contracts.length === 0 ? (
|
||
<div className={styles.emptyState}>
|
||
<p>Keine Verträge vorhanden.</p>
|
||
</div>
|
||
) : (
|
||
<table className={styles.dataTable}>
|
||
<thead>
|
||
<tr>
|
||
<th>Bezeichnung</th>
|
||
<th>Organisation</th>
|
||
<th>Status</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{contracts.map((contract) => (
|
||
<tr key={contract.id}>
|
||
<td>{contract.label}</td>
|
||
<td>{getLabelFast('organisations', contract.organisationId)}</td>
|
||
<td>
|
||
<span className={`${styles.badge} ${contract.enabled ? styles.badgeSuccess : styles.badgeWarning}`}>
|
||
{contract.enabled ? 'Aktiv' : 'Inaktiv'}
|
||
</span>
|
||
</td>
|
||
<td className={styles.actions}>
|
||
{canUpdate && (
|
||
<button
|
||
className={styles.iconButton}
|
||
title="Bearbeiten"
|
||
onClick={() => onEdit(contract)}
|
||
>
|
||
✏️
|
||
</button>
|
||
)}
|
||
{canDelete && (
|
||
<button
|
||
className={styles.iconButton}
|
||
title="Löschen"
|
||
onClick={() => onDelete(contract.id)}
|
||
disabled={deletingItems.has(contract.id)}
|
||
>
|
||
{deletingItems.has(contract.id) ? '...' : '🗑️'}
|
||
</button>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
|
||
{/* Create/Edit Modal */}
|
||
<Popup
|
||
isOpen={isModalOpen}
|
||
title={editingContract ? 'Vertrag bearbeiten' : 'Neuer Vertrag'}
|
||
onClose={onCloseModal}
|
||
size="medium"
|
||
>
|
||
{formError && (
|
||
<div className={styles.formError} style={{ marginBottom: '1rem' }}>
|
||
{formError}
|
||
</div>
|
||
)}
|
||
<TrusteeEditForm<TrusteeContract>
|
||
initialData={editingContract || { enabled: true }}
|
||
fields={fields}
|
||
onSave={onSave}
|
||
onCancel={onCloseModal}
|
||
isSaving={creatingItem}
|
||
isEdit={!!editingContract}
|
||
saveLabel={editingContract ? 'Aktualisieren' : 'Erstellen'}
|
||
/>
|
||
</Popup>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default TrusteeContractsView;
|