import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useRoles, useCreateRole, useUpdateRole, useDeleteRole, usePermissions } from '@/api/hooks'; import type { PermissionItem } from '@/api/hooks'; import { Input } from '@/components/ui/Input'; import { Button } from '@/components/ui/Button'; import { Card } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; import { EmptyState } from '@/components/ui/EmptyState'; import { Skeleton } from '@/components/ui/Skeleton'; import { useToast } from '@/components/ui/Toast'; import { Modal } from '@/components/ui/Modal'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; interface Role { id: string; name: string; permissions: Record; field_permissions?: Record; } function isPermissionGranted(permissions: Record, permKey: string): boolean { if (!permissions) return false; if (typeof permissions === 'boolean') return permissions; if (Array.isArray(permissions)) return permissions.includes(permKey); if (typeof permissions === 'object') { if (permKey in permissions) { const val = permissions[permKey]; return val === true || val === 'true' || val === 1; } if ('all' in permissions && permissions.all === true) return true; } return false; } function togglePermission( permissions: Record, permKey: string ): Record { const current = isPermissionGranted(permissions, permKey); return { ...permissions, [permKey]: !current }; } function getGrantedPermissionKeys(permissions: Record): string[] { if (!permissions) return []; if (Array.isArray(permissions)) return permissions as string[]; if (typeof permissions === 'object') { return Object.keys(permissions).filter( (k) => k !== 'all' && permissions[k] === true ); } return []; } export function SettingsRolesPage() { const { t } = useTranslation(); const toast = useToast(); const { data, isLoading, isError, error, refetch } = useRoles(); const { data: permissionsData, isLoading: permissionsLoading } = usePermissions(); const createRoleMutation = useCreateRole(); const updateRoleMutation = useUpdateRole(); const deleteRoleMutation = useDeleteRole(); const [createOpen, setCreateOpen] = useState(false); const [newRoleName, setNewRoleName] = useState(''); const [newRolePermissions, setNewRolePermissions] = useState>({}); const [editingRole, setEditingRole] = useState(null); const [confirmDelete, setConfirmDelete] = useState(null); const roles: Role[] = data?.items ?? []; const allPermissions: PermissionItem[] = useMemo(() => { return permissionsData?.all ?? []; }, [permissionsData]); const permissionLabelMap = useMemo(() => { const map = new Map(); for (const perm of allPermissions) { map.set(perm.key, perm.label); } return map; }, [allPermissions]); const groupedPermissions = useMemo(() => { const groups: Record = {}; for (const perm of allPermissions) { if (!groups[perm.category]) { groups[perm.category] = []; } groups[perm.category].push(perm); } return groups; }, [allPermissions]); const handleCreateRole = async () => { if (!newRoleName.trim()) { toast.error(t('validation.required')); return; } try { await createRoleMutation.mutateAsync({ name: newRoleName.trim(), permissions: newRolePermissions, field_permissions: {}, }); toast.success(t('settings.roleCreated')); setNewRoleName(''); setNewRolePermissions({}); setCreateOpen(false); } catch (err: any) { toast.error(err.message || t('common.error')); } }; const handleTogglePermission = (permKey: string, target: 'new' | 'edit') => { if (target === 'new') { setNewRolePermissions((prev) => togglePermission(prev, permKey)); } else if (editingRole) { setEditingRole({ ...editingRole, permissions: togglePermission(editingRole.permissions, permKey), }); } }; const handleSaveRole = async () => { if (!editingRole) return; try { await updateRoleMutation.mutateAsync({ id: editingRole.id, data: { name: editingRole.name, permissions: editingRole.permissions, field_permissions: editingRole.field_permissions || {}, }, }); toast.success(t('settings.roleUpdated')); setEditingRole(null); } catch (err: any) { toast.error(err.message || t('common.error')); } }; const handleDeleteRole = async () => { if (!confirmDelete) return; try { await deleteRoleMutation.mutateAsync(confirmDelete.id); toast.success(t('settings.roleDeleted')); setConfirmDelete(null); } catch (err: any) { toast.error(err.message || t('common.error')); } }; if (isLoading || permissionsLoading) { return (
); } if (isError) { return (

{t('settings.roles')}

refetch()}>{t('common.reset')}} >

{error?.message || t('common.error')}

); } return (

{t('settings.roles')}

{roles.length === 0 ? ( setCreateOpen(true)}>{t('settings.createRole')}} /> ) : (
{roles.map((role) => { const grantedKeys = getGrantedPermissionKeys(role.permissions); return (

{role.name}

{grantedKeys.length === 0 ? ( ) : ( grantedKeys.map((perm) => { const label = permissionLabelMap.get(perm) || perm; return {label}; }) )}
); })}
)} setCreateOpen(false)} title={t('settings.createRole')}>
setNewRoleName(e.target.value)} placeholder={t('settings.roleName')} data-testid="new-role-name" />
{allPermissions.length === 0 && (

{t('settings.noPermissions')}

)} {Object.entries(groupedPermissions).map(([category, perms]) => (

{category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}

{perms.map((perm) => ( ))}
))}
setEditingRole(null)} title={t('settings.roles') + ' — ' + (editingRole?.name || '')}> {editingRole && (
setEditingRole({ ...editingRole, name: e.target.value })} />
{allPermissions.length === 0 && (

{t('settings.noPermissions')}

)} {Object.entries(groupedPermissions).map(([category, perms]) => (

{category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}

{perms.map((perm) => ( ))}
))}
)}
setConfirmDelete(null)} />
); }