331 lines
10 KiB
TypeScript
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,
|
|
};
|
|
}
|