ui-nyla/src/pages/views/trustee/TrusteeContractsView.tsx
2026-01-23 21:05:36 +01:00

209 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;