286 lines
8.2 KiB
TypeScript
286 lines
8.2 KiB
TypeScript
/**
|
|
* 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<string, any>;
|
|
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<MandateUser[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [pagination, setPagination] = useState<PaginationMetadata | null>(null);
|
|
|
|
// Store current mandate for refetch
|
|
const currentMandateIdRef = useRef<string>('');
|
|
|
|
/**
|
|
* Fetch all users in a specific mandate with optional pagination
|
|
*/
|
|
const fetchMandateUsers = useCallback(async (
|
|
mandateIdOrPagination?: string | PaginationParams
|
|
): Promise<MandateUser[]> => {
|
|
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<Mandate[]> => {
|
|
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<Role[]> => {
|
|
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<Array<{id: string; username: string; email?: string; fullName?: string}>> => {
|
|
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;
|