/** * useUserMandates Hook * * Hook for managing user-mandate memberships (which users belong to which mandates with which roles). * Uses the /api/mandates/{mandateId}/users endpoints. */ import { useState, useCallback, useRef } from 'react'; import api from '../api'; // Types export interface PaginationParams { page?: number; pageSize?: number; search?: string; filters?: Record; sort?: Array<{ field: string; direction: 'asc' | 'desc' }>; } export interface PaginationMetadata { currentPage: number; pageSize: number; totalItems: number; totalPages: number; } export interface MandateUser { id: string; // UserMandate ID as primary key userId: string; username: string; email: string | null; firstname: string | null; lastname: string | null; roleIds: string[]; enabled: boolean; } export interface UserMandateCreate { targetUserId: string; roleIds: string[]; } export interface UserMandateResponse { id: string; // UserMandate ID as primary key userId: string; mandateId: string; roleIds: string[]; enabled: boolean; } export interface Role { id: string; roleLabel: string; description?: string | { [key: string]: string }; mandateId?: string; featureInstanceId?: string; featureCode?: string; isSystemRole?: boolean; } export interface Mandate { id: string; name: string | { [key: string]: string }; label?: string; code?: string; language?: string; isSystem?: boolean; } /** * Hook for managing user-mandate memberships */ export function useUserMandates() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [pagination, setPagination] = useState(null); // Store current mandate for refetch const currentMandateIdRef = useRef(''); /** * Fetch all users in a specific mandate with optional pagination */ const fetchMandateUsers = useCallback(async ( mandateIdOrPagination?: string | PaginationParams ): Promise => { setLoading(true); setError(null); let mandateId: string; let paginationParams: PaginationParams = {}; // Handle backward compatibility if (typeof mandateIdOrPagination === 'string') { mandateId = mandateIdOrPagination; currentMandateIdRef.current = mandateId; } else if (mandateIdOrPagination && typeof mandateIdOrPagination === 'object') { paginationParams = mandateIdOrPagination; mandateId = currentMandateIdRef.current; } else { mandateId = currentMandateIdRef.current; } if (!mandateId) { setLoading(false); return []; } try { const params = new URLSearchParams(); if (Object.keys(paginationParams).length > 0) { params.append('pagination', JSON.stringify(paginationParams)); } const url = params.toString() ? `/api/mandates/${mandateId}/users?${params.toString()}` : `/api/mandates/${mandateId}/users`; const response = await api.get(url); let data: MandateUser[] = []; if (response.data?.items && Array.isArray(response.data.items)) { data = response.data.items; if (response.data.pagination) { setPagination(response.data.pagination); } } else { data = Array.isArray(response.data) ? response.data : []; } setUsers(data); return data; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to fetch mandate users'; setError(errorMessage); setUsers([]); setPagination(null); return []; } finally { setLoading(false); } }, []); /** * Add a user to a mandate with specified roles */ const addUserToMandate = useCallback(async ( mandateId: string, data: UserMandateCreate ): Promise<{ success: boolean; data?: UserMandateResponse; error?: string }> => { setLoading(true); setError(null); try { const response = await api.post(`/api/mandates/${mandateId}/users`, data); return { success: true, data: response.data }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to add user to mandate'; setError(errorMessage); return { success: false, error: errorMessage }; } finally { setLoading(false); } }, []); /** * Remove a user from a mandate */ const removeUserFromMandate = useCallback(async ( mandateId: string, userId: string ): Promise<{ success: boolean; error?: string }> => { setLoading(true); setError(null); try { await api.delete(`/api/mandates/${mandateId}/users/${userId}`); // Optimistically update the local state setUsers(prev => prev.filter(u => u.userId !== userId)); return { success: true }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to remove user from mandate'; setError(errorMessage); return { success: false, error: errorMessage }; } finally { setLoading(false); } }, []); /** * Update a user's roles within a mandate */ const updateUserRoles = useCallback(async ( mandateId: string, userId: string, roleIds: string[] ): Promise<{ success: boolean; data?: UserMandateResponse; error?: string }> => { setLoading(true); setError(null); try { const response = await api.put(`/api/mandates/${mandateId}/users/${userId}/roles`, roleIds); // Optimistically update the local state setUsers(prev => prev.map(u => u.userId === userId ? { ...u, roleIds } : u )); return { success: true, data: response.data }; } catch (err: any) { const errorMessage = err.response?.data?.detail || err.message || 'Failed to update user roles'; setError(errorMessage); return { success: false, error: errorMessage }; } finally { setLoading(false); } }, []); /** * Fetch all available mandates (for selection) */ const fetchMandates = useCallback(async (): Promise => { try { const response = await api.get('/api/mandates/'); if (response.data?.items && Array.isArray(response.data.items)) { return response.data.items; } return Array.isArray(response.data) ? response.data : []; } catch (err: any) { console.error('Error fetching mandates:', err); return []; } }, []); /** * Fetch available roles for a mandate (mandate-instance roles only). * Each mandate has its own instances of system roles (admin, user, viewer) * copied from templates during mandate creation. Only these mandate-bound * roles should be offered for user assignment - NOT global templates. */ const fetchRoles = useCallback(async (mandateId?: string): Promise => { try { // Fetch roles server-side filtered by mandate (or templates if no mandateId) const params = mandateId ? { mandateId } : {}; const response = await api.get('/api/rbac/roles', { params }); let roles: Role[] = []; if (response.data?.items && Array.isArray(response.data.items)) { roles = response.data.items; } else if (Array.isArray(response.data)) { roles = response.data; } return roles; } catch (err: any) { console.error('Error fetching roles:', err); return []; } }, []); /** * Fetch all users (for selection when adding to mandate) */ const fetchAllUsers = useCallback(async (): Promise> => { try { const response = await api.get('/api/users/'); if (response.data?.items && Array.isArray(response.data.items)) { return response.data.items; } return Array.isArray(response.data) ? response.data : []; } catch (err: any) { console.error('Error fetching users:', err); return []; } }, []); return { users, loading, error, pagination, fetchMandateUsers, addUserToMandate, removeUserFromMandate, updateUserRoles, fetchMandates, fetchRoles, fetchAllUsers, }; } export default useUserMandates;