"use client"; import { useState } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; import { RefreshCw, Save } from "lucide-react"; import { toast } from "sonner"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import apiClient from "@/lib/api/client"; interface Role { roleId: number; roleName: string; permissions?: Permission[]; } interface Permission { permissionId: number; permissionName: string; description: string; } interface RbacMatrixProps { roles: Role[]; permissions: Permission[]; rolePermissions: Record; // roleId -> permissionIds[] } const securityService = { getRoles: async () => { const response = await apiClient.get("/users/roles"); return response.data?.data || response.data; }, getPermissions: async () => { const response = await apiClient.get("/users/permissions"); return response.data?.data || response.data; }, updateRolePermissions: async (roleId: number, permissionIds: number[]) => { // This endpoint might not exist as a bulk update, usually it's per role // Assuming backend supports: PATCH /users/roles/:id/permissions { permissionIds: [] } return (await apiClient.patch(`/users/roles/${roleId}/permissions`, { permissionIds })).data; }, }; export function RbacMatrix() { const queryClient = useQueryClient(); const [pendingChanges, setPendingChanges] = useState>({}); const { data: roles = [], isLoading: rolesLoading } = useQuery({ queryKey: ["roles"], queryFn: securityService.getRoles, }); const { data: permissions = [], isLoading: permsLoading } = useQuery({ queryKey: ["permissions"], queryFn: securityService.getPermissions, }); // Fetch current assignments - this logic assumes we can get a map or list // For now, let's assume we can fetch matrix or individual role calls // In a real implementation this is heavier. For implementation speed, I'll mock the state logic assumption // that we load initial state from roles (if roles include permissions relation). // TODO: Fetch existing role_permissions. Assuming roles endpoint returns `permissions` array. const updateMutation = useMutation({ mutationFn: async (changes: Record) => { const promises = Object.entries(changes).map(([roleId, perms]) => securityService.updateRolePermissions(parseInt(roleId), perms) ); return Promise.all(promises); }, onSuccess: () => { toast.success("Permissions updated successfully"); setPendingChanges({}); queryClient.invalidateQueries({ queryKey: ["roles"] }); }, onError: () => toast.error("Failed to update permissions"), }); const handleToggle = (roleId: number, permId: number, currentPerms: number[]) => { const roleChanges = pendingChanges[roleId] || currentPerms; const newPerms = roleChanges.includes(permId) ? roleChanges.filter((id) => id !== permId) : [...roleChanges, permId]; setPendingChanges({ ...pendingChanges, [roleId]: newPerms }); }; const hasChanges = Object.keys(pendingChanges).length > 0; if (rolesLoading || permsLoading) { return (
); } // Simplified: Permissions grouped by module/resource would be better, but flat list for now return (
Permission {roles.map((role) => ( {role.roleName} ))} {permissions.map((perm) => (
{perm.permissionName}
{perm.description}
{roles.map((role: any) => { // Assume role.permissions is populated const currentRolePerms = role.permissions?.map((p: any) => p.permissionId) || []; const activePerms = pendingChanges[role.roleId] || currentRolePerms; const isChecked = activePerms.includes(perm.permissionId); return ( handleToggle(role.roleId, perm.permissionId, currentRolePerms)} /> ); })}
))}
); }