import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useRoles, useCreateRole, useUpdateRole, useDeleteRole, usePermissions } from '@/api/hooks'; import type { PermissionItem, FieldDefinition } 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; denied_permissions?: string[]; 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 []; } function toggleDeniedPermission(denied: string[], permKey: string): string[] { if (denied.includes(permKey)) { return denied.filter((k) => k !== permKey); } return [...denied, permKey]; } 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 [newRoleDenied, setNewRoleDenied] = useState([]); const [editingRole, setEditingRole] = useState(null); const [confirmDelete, setConfirmDelete] = useState(null); const [editTab, setEditTab] = useState<'permissions' | 'denied' | 'fields'>('permissions'); 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 fieldDefinitions: FieldDefinition[] = useMemo(() => { return permissionsData?.field_definitions ?? []; }, [permissionsData]); const groupedFieldDefinitions = useMemo(() => { const groups: Record = {}; for (const fd of fieldDefinitions) { if (!groups[fd.module]) { groups[fd.module] = []; } groups[fd.module].push(fd); } return groups; }, [fieldDefinitions]); const handleCreateRole = async () => { if (!newRoleName.trim()) { toast.error(t('validation.required')); return; } try { await createRoleMutation.mutateAsync({ name: newRoleName.trim(), permissions: newRolePermissions, denied_permissions: newRoleDenied, field_permissions: {}, } as any); toast.success(t('settings.roleCreated')); setNewRoleName(''); setNewRolePermissions({}); setNewRoleDenied([]); 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 handleToggleDenied = (permKey: string, target: 'new' | 'edit') => { if (target === 'new') { setNewRoleDenied((prev) => toggleDeniedPermission(prev, permKey)); } else if (editingRole) { setEditingRole({ ...editingRole, denied_permissions: toggleDeniedPermission(editingRole.denied_permissions || [], permKey), }); } }; const handleFieldPermChange = (module: string, field: string, value: 'hidden' | 'readonly' | 'read' | 'default') => { if (!editingRole) return; const fieldPerms = { ...(editingRole.field_permissions || {}) }; if (!fieldPerms[module]) { fieldPerms[module] = {}; } if (value === 'default') { delete fieldPerms[module][field]; if (Object.keys(fieldPerms[module]).length === 0) { delete fieldPerms[module]; } } else { fieldPerms[module][field] = value; } setEditingRole({ ...editingRole, field_permissions: fieldPerms }); }; const handleSaveRole = async () => { if (!editingRole) return; try { await updateRoleMutation.mutateAsync({ id: editingRole.id, data: { name: editingRole.name, permissions: editingRole.permissions, denied_permissions: editingRole.denied_permissions || [], field_permissions: editingRole.field_permissions || {}, } as any, }); 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')}} />
); } 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) => ( ))}
))}
{allPermissions.length === 0 && (

{t('settings.noPermissions')}

)} {allPermissions.map((perm) => ( ))}
setEditingRole(null)} title={t('settings.roles') + ' — ' + (editingRole?.name || '')} size="xl"> {editingRole && (
setEditingRole({ ...editingRole, name: e.target.value })} /> {/* Tab Navigation */}
{/* Permissions Tab */} {editTab === 'permissions' && (
{allPermissions.length === 0 && (

{t('settings.noPermissions')}

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

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

{perms.map((perm) => ( ))}
))}
)} {/* Denied Permissions Tab */} {editTab === 'denied' && (

{t('settings.deniedPermissionsHint', 'Diese Berechtigungen werden explizit verweigert.')}

{allPermissions.length === 0 && (

{t('settings.noPermissions')}

)} {allPermissions.map((perm) => ( ))}
)} {/* Field Permissions Tab */} {editTab === 'fields' && (
{fieldDefinitions.length === 0 ? (

{t('settings.noFieldDefinitions', 'Keine Feld-Definitionen verfügbar')}

) : (
{Object.entries(groupedFieldDefinitions).map(([moduleName, fields]) => (

{moduleName}

{fields.map((fd) => { const currentVal = (editingRole.field_permissions || {})[moduleName]?.[fd.field] || 'default'; return (
{fd.label} ({fd.sensitivity})
); })}
))}
)}
)}
)}
setConfirmDelete(null)} />
); }