ui-nyla/src/hooks/useRoles.ts
2026-01-21 00:32:52 +01:00

237 lines
6.7 KiB
TypeScript

/**
* useRoles Hook
*
* Hook für die Verwaltung von globalen RBAC-Rollen im Admin-Bereich.
* Folgt dem gleichen Pattern wie useOrgUsers.
*/
import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi';
import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions';
import {
fetchRoles as fetchRolesApi,
fetchRoleById as fetchRoleByIdApi,
createRole as createRoleApi,
updateRole as updateRoleApi,
deleteRole as deleteRoleApi,
type Role,
type RoleUpdateData,
type PaginationParams
} from '../api/roleApi';
// Re-export types
export type { Role, RoleUpdateData, PaginationParams };
export interface AttributeDefinition {
name: string;
type: string;
label: string;
description?: string;
required?: boolean;
default?: any;
options?: Array<{ value: string | number; label: string | { [key: string]: string } }> | string;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
readonly?: boolean;
editable?: boolean;
}
/**
* Hook for managing RBAC roles in admin panel
*/
export function useAdminRoles() {
const [roles, setRoles] = useState<Role[]>([]);
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, Role[]>();
const { checkPermission } = usePermissions();
// Fetch attributes from backend
const fetchAttributes = useCallback(async () => {
try {
const response = await api.get('/api/attributes/Role');
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 role attributes.');
} else if (error.response?.status !== 401) {
console.error('Error fetching role attributes:', error);
}
setAttributes([]);
return [];
}
}, []);
// Fetch permissions
const fetchPermissions = useCallback(async () => {
try {
const perms = await checkPermission('DATA', 'Role');
setPermissions(perms);
return perms;
} catch (error: any) {
console.error('Error fetching role permissions:', error);
const defaultPerms: UserPermissions = {
view: false,
read: 'n',
create: 'n',
update: 'n',
delete: 'n',
};
setPermissions(defaultPerms);
return defaultPerms;
}
}, [checkPermission]);
// Fetch roles
const fetchRoles = useCallback(async (params?: PaginationParams) => {
try {
const data = await fetchRolesApi(request, params);
if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : [];
setRoles(items);
if (data.pagination) {
setPagination(data.pagination);
}
} else {
const items = Array.isArray(data) ? data : [];
setRoles(items);
setPagination(null);
}
} catch (error: any) {
setRoles([]);
setPagination(null);
}
}, [request]);
// Optimistic updates
const removeOptimistically = (roleId: string) => {
setRoles(prev => prev.filter(r => r.id !== roleId));
};
const updateOptimistically = (roleId: string, updateData: Partial<Role>) => {
setRoles(prev =>
prev.map(r => r.id === roleId ? { ...r, ...updateData } : r)
);
};
// Fetch single role
const fetchRoleById = useCallback(async (roleId: string): Promise<Role | null> => {
return await fetchRoleByIdApi(request, roleId);
}, [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 role
const handleCreate = useCallback(async (roleData: Partial<Role>): Promise<boolean> => {
try {
await createRoleApi(request, roleData);
await fetchRoles();
return true;
} catch (error: any) {
console.error('Error creating role:', error);
return false;
}
}, [request, fetchRoles]);
// Update role
const handleUpdate = useCallback(async (roleId: string, updateData: RoleUpdateData): Promise<boolean> => {
try {
updateOptimistically(roleId, updateData);
await updateRoleApi(request, roleId, updateData);
return true;
} catch (error: any) {
console.error('Error updating role:', error);
await fetchRoles();
return false;
}
}, [request, fetchRoles]);
// Delete role
const handleDelete = useCallback(async (roleId: string): Promise<boolean> => {
try {
removeOptimistically(roleId);
await deleteRoleApi(request, roleId);
return true;
} catch (error: any) {
console.error('Error deleting role:', error);
await fetchRoles();
return false;
}
}, [request, fetchRoles]);
// Inline update
const handleInlineUpdate = useCallback(async (
roleId: string,
updateData: Partial<Role>
): Promise<void> => {
await handleUpdate(roleId, updateData);
}, [handleUpdate]);
// Load data on mount
useEffect(() => {
fetchAttributes();
fetchPermissions();
fetchRoles();
}, []);
return {
roles,
attributes,
columns,
permissions,
pagination,
loading,
error,
refetch: fetchRoles,
fetchRoleById,
handleCreate,
handleUpdate,
handleDelete,
handleInlineUpdate,
updateOptimistically,
};
}
export default useAdminRoles;