add hard delete for mandates (SysAdmin only)
Made-with: Cursor
This commit is contained in:
parent
ca019ae28d
commit
8fcad7de45
3 changed files with 70 additions and 6 deletions
|
|
@ -122,7 +122,7 @@ export async function createMandate(
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete a mandate
|
||||
* Soft-delete a mandate (sets enabled=false, 30-day retention)
|
||||
* Endpoint: DELETE /api/mandates/{mandateId}
|
||||
*/
|
||||
export async function deleteMandate(
|
||||
|
|
@ -134,3 +134,22 @@ export async function deleteMandate(
|
|||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-delete a mandate with full cascade (irreversible)
|
||||
* Endpoint: DELETE /api/mandates/{mandateId}?force=true
|
||||
*/
|
||||
export async function hardDeleteMandate(
|
||||
request: ApiRequestFunction,
|
||||
mandateId: string,
|
||||
confirmName: string
|
||||
): Promise<void> {
|
||||
await request({
|
||||
url: `/api/mandates/${mandateId}`,
|
||||
method: 'delete',
|
||||
params: { force: true },
|
||||
additionalConfig: {
|
||||
headers: { 'X-Confirm-Name': confirmName }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
createMandate as createMandateApi,
|
||||
updateMandate as updateMandateApi,
|
||||
deleteMandate as deleteMandateApi,
|
||||
hardDeleteMandate as hardDeleteMandateApi,
|
||||
type Mandate,
|
||||
type MandateUpdateData,
|
||||
type PaginationParams
|
||||
|
|
@ -203,6 +204,19 @@ export function useAdminMandates() {
|
|||
}
|
||||
}, [request, fetchMandates]);
|
||||
|
||||
// Hard-delete mandate (irreversible)
|
||||
const handleHardDelete = useCallback(async (mandateId: string, confirmName: string): Promise<boolean> => {
|
||||
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,
|
||||
|
|
@ -231,6 +245,7 @@ export function useAdminMandates() {
|
|||
handleCreate,
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
handleHardDelete,
|
||||
handleInlineUpdate,
|
||||
updateOptimistically,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { useToast } from '../../contexts/ToastContext';
|
|||
import { usePrompt } from '../../hooks/usePrompt';
|
||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
|
||||
import { FaPlus, FaSync, FaBuilding, FaUsers, FaLock } from 'react-icons/fa';
|
||||
import { FaPlus, FaSync, FaBuilding, FaUsers, FaLock, FaSkullCrossbones } from 'react-icons/fa';
|
||||
import styles from './Admin.module.css';
|
||||
|
||||
export const AdminMandatesPage: React.FC = () => {
|
||||
|
|
@ -37,6 +37,7 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
handleCreate,
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
handleHardDelete,
|
||||
handleInlineUpdate,
|
||||
updateOptimistically,
|
||||
} = useAdminMandates();
|
||||
|
|
@ -118,17 +119,37 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
const entered = await prompt(
|
||||
`Um den Mandanten "${mandate.name}" unwiderruflich zu löschen, geben Sie den Namen ein:`,
|
||||
{ title: 'Mandant löschen', confirmLabel: 'Löschen', variant: 'danger', placeholder: mandate.name },
|
||||
`Um den Mandanten "${mandate.name}" zu deaktivieren (Soft-Delete), geben Sie den Namen ein:`,
|
||||
{ title: 'Mandant deaktivieren', confirmLabel: 'Deaktivieren', variant: 'danger', placeholder: mandate.name },
|
||||
);
|
||||
if (entered === null) return;
|
||||
if (entered !== mandate.name) {
|
||||
showWarning('Löschung abgebrochen', 'Der eingegebene Name stimmt nicht überein.');
|
||||
showWarning('Abgebrochen', 'Der eingegebene Name stimmt nicht überein.');
|
||||
return;
|
||||
}
|
||||
await handleDelete(mandate.id);
|
||||
};
|
||||
|
||||
const handleHardDeleteMandate = async (mandate: Mandate) => {
|
||||
if (mandate.isSystem) {
|
||||
showWarning('Nicht erlaubt', 'System-Mandanten können nicht gelöscht werden.');
|
||||
return;
|
||||
}
|
||||
const entered = await prompt(
|
||||
`ACHTUNG: Dies löscht den Mandanten "${mandate.name}" unwiderruflich inkl. aller Subscriptions, Features, Benutzer-Zuweisungen und Daten. Geben Sie den exakten Namen ein:`,
|
||||
{ title: 'Hard Delete (irreversibel)', confirmLabel: 'Endgültig löschen', variant: 'danger', placeholder: mandate.name },
|
||||
);
|
||||
if (entered === null) return;
|
||||
if (entered !== mandate.name) {
|
||||
showWarning('Abgebrochen', 'Der eingegebene Name stimmt nicht überein.');
|
||||
return;
|
||||
}
|
||||
const ok = await handleHardDelete(mandate.id, entered);
|
||||
if (ok) {
|
||||
showSuccess('Gelöscht', `Mandant "${mandate.name}" wurde endgültig gelöscht.`);
|
||||
}
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
|
||||
|
|
@ -218,12 +239,21 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
}] : []),
|
||||
...(canDelete ? [{
|
||||
type: 'delete' as const,
|
||||
title: 'Löschen',
|
||||
title: 'Deaktivieren (Soft-Delete)',
|
||||
disabled: (row: Mandate) => row.isSystem
|
||||
? { disabled: true, message: 'System-Mandanten können nicht gelöscht werden' }
|
||||
: false
|
||||
}] : []),
|
||||
]}
|
||||
customActions={canDelete ? [{
|
||||
id: 'hard-delete',
|
||||
icon: <FaSkullCrossbones style={{ color: 'var(--error-color, #e53e3e)' }} />,
|
||||
onClick: handleHardDeleteMandate,
|
||||
title: 'Hard Delete (irreversibel)',
|
||||
disabled: (row: Mandate) => row.isSystem
|
||||
? { disabled: true, message: 'System-Mandanten können nicht gelöscht werden' }
|
||||
: false,
|
||||
}] : []}
|
||||
onDelete={handleDeleteMandate}
|
||||
hookData={{
|
||||
refetch,
|
||||
|
|
|
|||
Loading…
Reference in a new issue