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}
|
* Endpoint: DELETE /api/mandates/{mandateId}
|
||||||
*/
|
*/
|
||||||
export async function deleteMandate(
|
export async function deleteMandate(
|
||||||
|
|
@ -134,3 +134,22 @@ export async function deleteMandate(
|
||||||
method: 'delete'
|
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,
|
createMandate as createMandateApi,
|
||||||
updateMandate as updateMandateApi,
|
updateMandate as updateMandateApi,
|
||||||
deleteMandate as deleteMandateApi,
|
deleteMandate as deleteMandateApi,
|
||||||
|
hardDeleteMandate as hardDeleteMandateApi,
|
||||||
type Mandate,
|
type Mandate,
|
||||||
type MandateUpdateData,
|
type MandateUpdateData,
|
||||||
type PaginationParams
|
type PaginationParams
|
||||||
|
|
@ -203,6 +204,19 @@ export function useAdminMandates() {
|
||||||
}
|
}
|
||||||
}, [request, fetchMandates]);
|
}, [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
|
// Inline update
|
||||||
const handleInlineUpdate = useCallback(async (
|
const handleInlineUpdate = useCallback(async (
|
||||||
mandateId: string,
|
mandateId: string,
|
||||||
|
|
@ -231,6 +245,7 @@ export function useAdminMandates() {
|
||||||
handleCreate,
|
handleCreate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
|
handleHardDelete,
|
||||||
handleInlineUpdate,
|
handleInlineUpdate,
|
||||||
updateOptimistically,
|
updateOptimistically,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { useToast } from '../../contexts/ToastContext';
|
||||||
import { usePrompt } from '../../hooks/usePrompt';
|
import { usePrompt } from '../../hooks/usePrompt';
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
|
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';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
export const AdminMandatesPage: React.FC = () => {
|
export const AdminMandatesPage: React.FC = () => {
|
||||||
|
|
@ -37,6 +37,7 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
handleCreate,
|
handleCreate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
|
handleHardDelete,
|
||||||
handleInlineUpdate,
|
handleInlineUpdate,
|
||||||
updateOptimistically,
|
updateOptimistically,
|
||||||
} = useAdminMandates();
|
} = useAdminMandates();
|
||||||
|
|
@ -118,17 +119,37 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const entered = await prompt(
|
const entered = await prompt(
|
||||||
`Um den Mandanten "${mandate.name}" unwiderruflich zu löschen, geben Sie den Namen ein:`,
|
`Um den Mandanten "${mandate.name}" zu deaktivieren (Soft-Delete), geben Sie den Namen ein:`,
|
||||||
{ title: 'Mandant löschen', confirmLabel: 'Löschen', variant: 'danger', placeholder: mandate.name },
|
{ title: 'Mandant deaktivieren', confirmLabel: 'Deaktivieren', variant: 'danger', placeholder: mandate.name },
|
||||||
);
|
);
|
||||||
if (entered === null) return;
|
if (entered === null) return;
|
||||||
if (entered !== mandate.name) {
|
if (entered !== mandate.name) {
|
||||||
showWarning('Löschung abgebrochen', 'Der eingegebene Name stimmt nicht überein.');
|
showWarning('Abgebrochen', 'Der eingegebene Name stimmt nicht überein.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handleDelete(mandate.id);
|
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) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
|
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
|
||||||
|
|
@ -218,12 +239,21 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
}] : []),
|
}] : []),
|
||||||
...(canDelete ? [{
|
...(canDelete ? [{
|
||||||
type: 'delete' as const,
|
type: 'delete' as const,
|
||||||
title: 'Löschen',
|
title: 'Deaktivieren (Soft-Delete)',
|
||||||
disabled: (row: Mandate) => row.isSystem
|
disabled: (row: Mandate) => row.isSystem
|
||||||
? { disabled: true, message: 'System-Mandanten können nicht gelöscht werden' }
|
? { disabled: true, message: 'System-Mandanten können nicht gelöscht werden' }
|
||||||
: false
|
: 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}
|
onDelete={handleDeleteMandate}
|
||||||
hookData={{
|
hookData={{
|
||||||
refetch,
|
refetch,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue