frontend_nyla/src/hooks/useMandates.ts
2026-04-11 19:44:52 +02:00

331 lines
10 KiB
TypeScript

/**
* useMandates Hook
*
* Hook für die Verwaltung von Mandanten (Mandates) im Admin-Bereich.
* Folgt dem gleichen Pattern wie useOrgUsers.
*/
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useApiRequest } from './useApi';
import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions';
import {
fetchMandates as fetchMandatesApi,
fetchMandateById as fetchMandateByIdApi,
createMandate as createMandateApi,
updateMandate as updateMandateApi,
deleteMandate as deleteMandateApi,
hardDeleteMandate as hardDeleteMandateApi,
type Mandate,
type MandateUpdateData,
type PaginationParams
} from '../api/mandateApi';
import type { AttributeDefinition as FormGenAttr } from '../components/FormGenerator/FormGeneratorForm';
import { getMandateBillingFormAttributes } from '../utils/mandateBillingFormMerge';
// Re-export types
export type { Mandate, MandateUpdateData, PaginationParams };
export interface AttributeDefinition {
name: string;
type: string;
label: string;
description?: string;
required?: boolean;
default?: any;
options?: Array<{ value: string | number; label: string }> | string;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
readonly?: boolean;
editable?: boolean;
}
/**
* Hook for managing mandates in admin panel
*/
export function useAdminMandates() {
const [mandates, setMandates] = useState<Mandate[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
const [pagination, setPagination] = useState<{
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
} | null>(null);
const { request, isLoading: loading, error } = useApiRequest<null, Mandate[]>();
const { checkPermission } = usePermissions();
// Fetch attributes from backend
const fetchAttributes = useCallback(async () => {
try {
const response = await api.get('/api/attributes/Mandate');
let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) {
attrs = response.data.attributes;
} else if (Array.isArray(response.data)) {
attrs = response.data;
} else if (response.data && typeof response.data === 'object') {
const keys = Object.keys(response.data);
for (const key of keys) {
if (Array.isArray(response.data[key])) {
attrs = response.data[key];
break;
}
}
}
setAttributes(attrs);
return attrs;
} catch (error: any) {
if (error.response?.status === 429) {
console.warn('Rate limit exceeded while fetching mandate attributes.');
} else if (error.response?.status !== 401) {
console.error('Error fetching mandate attributes:', error);
}
setAttributes([]);
return [];
}
}, []);
// Fetch permissions
const fetchPermissions = useCallback(async () => {
try {
const perms = await checkPermission('DATA', 'Mandate');
setPermissions(perms);
return perms;
} catch (error: any) {
console.error('Error fetching mandate permissions:', error);
const defaultPerms: UserPermissions = {
view: false,
read: 'n',
create: 'n',
update: 'n',
delete: 'n',
};
setPermissions(defaultPerms);
return defaultPerms;
}
}, [checkPermission]);
// Fetch mandates
const fetchMandates = useCallback(async (params?: PaginationParams) => {
try {
const data = await fetchMandatesApi(request, params);
if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : [];
setMandates(items);
if (data.pagination) {
setPagination(data.pagination);
}
} else {
const items = Array.isArray(data) ? data : [];
setMandates(items);
setPagination(null);
}
} catch (error: any) {
setMandates([]);
setPagination(null);
}
}, [request]);
// Optimistic updates
const removeOptimistically = (mandateId: string) => {
setMandates(prev => prev.filter(m => m.id !== mandateId));
};
const updateOptimistically = (mandateId: string, updateData: Partial<Mandate>) => {
setMandates(prev =>
prev.map(m => m.id === mandateId ? { ...m, ...updateData } : m)
);
};
// Fetch single mandate
const fetchMandateById = useCallback(async (mandateId: string): Promise<Mandate | null> => {
return await fetchMandateByIdApi(request, mandateId);
}, [request]);
// Generate columns from attributes (including fkSource/fkDisplayField for FK resolution)
const columns = attributes.map(attr => ({
key: attr.name,
label: attr.label || attr.name,
type: attr.type as any,
sortable: attr.sortable !== false,
filterable: attr.filterable !== false,
searchable: attr.searchable !== false,
width: attr.width || 150,
minWidth: attr.minWidth || 100,
maxWidth: attr.maxWidth || 400,
fkSource: (attr as any).fkSource, // API endpoint for FK data
fkDisplayField: (attr as any).fkDisplayField, // Which field of FK target to display
}));
// Create mandate
const handleCreate = useCallback(async (mandateData: Partial<Mandate>): Promise<Mandate | null> => {
try {
const created = await createMandateApi(request, mandateData);
await fetchMandates();
return created ?? null;
} catch (error: any) {
console.error('Error creating mandate:', error);
return null;
}
}, [request, fetchMandates]);
// Update mandate
const handleUpdate = useCallback(async (mandateId: string, updateData: MandateUpdateData): Promise<boolean> => {
try {
updateOptimistically(mandateId, updateData);
await updateMandateApi(request, mandateId, updateData);
return true;
} catch (error: any) {
console.error('Error updating mandate:', error);
await fetchMandates();
return false;
}
}, [request, fetchMandates]);
// Delete mandate
const handleDelete = useCallback(async (mandateId: string): Promise<boolean> => {
try {
removeOptimistically(mandateId);
await deleteMandateApi(request, mandateId);
return true;
} catch (error: any) {
console.error('Error deleting mandate:', error);
await fetchMandates();
return false;
}
}, [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,
updateData: Partial<Mandate>
): Promise<void> => {
await handleUpdate(mandateId, updateData);
}, [handleUpdate]);
// Load data on mount
useEffect(() => {
fetchAttributes();
fetchPermissions();
fetchMandates();
}, []);
return {
mandates,
attributes,
columns,
permissions,
pagination,
loading,
error,
refetch: fetchMandates,
fetchMandateById,
handleCreate,
handleUpdate,
handleDelete,
handleHardDelete,
handleInlineUpdate,
updateOptimistically,
};
}
export default useAdminMandates;
/**
* Mandate model attributes for FormGenerator (create/edit) — shared by Admin page and wizard.
*/
export function useMandateFormAttributes() {
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await api.get('/api/attributes/Mandate');
let attrs: AttributeDefinition[] = [];
const data = response.data;
if (data?.attributes && Array.isArray(data.attributes)) {
attrs = data.attributes;
} else if (Array.isArray(data)) {
attrs = data;
} else if (data && typeof data === 'object') {
for (const key of Object.keys(data)) {
if (Array.isArray((data as Record<string, unknown>)[key])) {
attrs = (data as Record<string, AttributeDefinition[]>)[key];
break;
}
}
}
setAttributes(attrs);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : 'Attribute-Laden fehlgeschlagen';
setError(msg);
setAttributes([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
const formAttributes: FormGenAttr[] = useMemo(() => {
return attributes
.filter(attr => attr.name !== 'id')
.map(attr => ({ ...attr, type: attr.type })) as FormGenAttr[];
}, [attributes]);
const createFormAttributes: FormGenAttr[] = useMemo(
() => formAttributes.filter(attr => attr.name !== 'isSystem'),
[formAttributes]
);
const billingFormAttributes: FormGenAttr[] = useMemo(() => getMandateBillingFormAttributes(), []);
/** Mandate attributes + billing (Abrechnung) for SysAdmin create flows */
const createFormAttributesWithBilling: FormGenAttr[] = useMemo(
() => [...createFormAttributes, ...billingFormAttributes],
[createFormAttributes, billingFormAttributes]
);
/** Mandate attributes + billing for SysAdmin edit flows */
const formAttributesWithBilling: FormGenAttr[] = useMemo(
() => [...formAttributes, ...billingFormAttributes],
[formAttributes, billingFormAttributes]
);
return {
formAttributes,
createFormAttributes,
formAttributesWithBilling,
createFormAttributesWithBilling,
loading,
error,
refetch: load,
};
}