Problem 3/4: Replace hardcoded ALL_PERMISSIONS with usePermissions API call, group by category

This commit is contained in:
2026-07-03 20:07:53 +00:00
parent 68b5153139
commit 493d99aff2
+70 -36
View File
@@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useRoles, useCreateRole, useUpdateRole, useDeleteRole } from '@/api/hooks'; import { useRoles, useCreateRole, useUpdateRole, useDeleteRole, usePermissions } from '@/api/hooks';
import type { PermissionItem } from '@/api/hooks';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
@@ -18,17 +19,6 @@ interface Role {
field_permissions?: Record<string, any>; field_permissions?: Record<string, any>;
} }
const ALL_PERMISSIONS = [
{ key: 'companies:read', label: 'Firmen lesen' },
{ key: 'companies:write', label: 'Firmen bearbeiten' },
{ key: 'contacts:read', label: 'Kontakte lesen' },
{ key: 'contacts:write', label: 'Kontakte bearbeiten' },
{ key: 'users:read', label: 'Benutzer lesen' },
{ key: 'users:write', label: 'Benutzer bearbeiten' },
{ key: 'audit:read', label: 'Audit-Log lesen' },
{ key: 'settings:write', label: 'Einstellungen bearbeiten' },
];
function isPermissionGranted(permissions: Record<string, any>, permKey: string): boolean { function isPermissionGranted(permissions: Record<string, any>, permKey: string): boolean {
if (!permissions) return false; if (!permissions) return false;
if (typeof permissions === 'boolean') return permissions; if (typeof permissions === 'boolean') return permissions;
@@ -66,6 +56,7 @@ export function SettingsRolesPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const toast = useToast(); const toast = useToast();
const { data, isLoading, isError, error, refetch } = useRoles(); const { data, isLoading, isError, error, refetch } = useRoles();
const { data: permissionsData, isLoading: permissionsLoading } = usePermissions();
const createRoleMutation = useCreateRole(); const createRoleMutation = useCreateRole();
const updateRoleMutation = useUpdateRole(); const updateRoleMutation = useUpdateRole();
const deleteRoleMutation = useDeleteRole(); const deleteRoleMutation = useDeleteRole();
@@ -78,6 +69,29 @@ export function SettingsRolesPage() {
const roles: Role[] = data?.items ?? []; const roles: Role[] = data?.items ?? [];
const allPermissions: PermissionItem[] = useMemo(() => {
return permissionsData?.all ?? [];
}, [permissionsData]);
const permissionLabelMap = useMemo(() => {
const map = new Map<string, string>();
for (const perm of allPermissions) {
map.set(perm.key, perm.label);
}
return map;
}, [allPermissions]);
const groupedPermissions = useMemo(() => {
const groups: Record<string, PermissionItem[]> = {};
for (const perm of allPermissions) {
if (!groups[perm.category]) {
groups[perm.category] = [];
}
groups[perm.category].push(perm);
}
return groups;
}, [allPermissions]);
const handleCreateRole = async () => { const handleCreateRole = async () => {
if (!newRoleName.trim()) { if (!newRoleName.trim()) {
toast.error(t('validation.required')); toast.error(t('validation.required'));
@@ -138,7 +152,7 @@ export function SettingsRolesPage() {
} }
}; };
if (isLoading) { if (isLoading || permissionsLoading) {
return ( return (
<div className="p-6 max-w-4xl mx-auto" data-testid="settings-roles-page"> <div className="p-6 max-w-4xl mx-auto" data-testid="settings-roles-page">
<Skeleton className="h-8 w-48 mb-6" /> <Skeleton className="h-8 w-48 mb-6" />
@@ -211,7 +225,7 @@ export function SettingsRolesPage() {
<span className="text-sm text-secondary-500"></span> <span className="text-sm text-secondary-500"></span>
) : ( ) : (
grantedKeys.map((perm) => { grantedKeys.map((perm) => {
const label = ALL_PERMISSIONS.find((p) => p.key === perm)?.label || perm; const label = permissionLabelMap.get(perm) || perm;
return <Badge key={perm} variant="primary">{label}</Badge>; return <Badge key={perm} variant="primary">{label}</Badge>;
}) })
)} )}
@@ -228,22 +242,32 @@ export function SettingsRolesPage() {
label={t('settings.roleName')} label={t('settings.roleName')}
value={newRoleName} value={newRoleName}
onChange={(e) => setNewRoleName(e.target.value)} onChange={(e) => setNewRoleName(e.target.value)}
placeholder="z.B. Vertrieb" placeholder={t('settings.roleName')}
data-testid="new-role-name" data-testid="new-role-name"
/> />
<div> <div>
<label className="block text-sm font-medium text-secondary-700 mb-2">{t('settings.permissions')}</label> <label className="block text-sm font-medium text-secondary-700 mb-2">{t('settings.permissions')}</label>
<div className="space-y-2"> <div className="space-y-2">
{ALL_PERMISSIONS.map((perm) => ( {allPermissions.length === 0 && (
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch"> <p className="text-sm text-secondary-500">{t('settings.noPermissions')}</p>
<input )}
type="checkbox" {Object.entries(groupedPermissions).map(([category, perms]) => (
checked={isPermissionGranted(newRolePermissions, perm.key)} <div key={category} className="mb-3">
onChange={() => handleTogglePermission(perm.key, 'new')} <p className="text-xs font-semibold text-secondary-500 uppercase mb-1">
className="w-4 h-4 rounded border-secondary-300 text-primary-600 focus:ring-primary-500" {category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}
/> </p>
<span className="text-sm text-secondary-900">{perm.label}</span> {perms.map((perm) => (
</label> <label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
<input
type="checkbox"
checked={isPermissionGranted(newRolePermissions, perm.key)}
onChange={() => handleTogglePermission(perm.key, 'new')}
className="w-4 h-4 rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-secondary-900">{perm.label}</span>
</label>
))}
</div>
))} ))}
</div> </div>
</div> </div>
@@ -271,16 +295,26 @@ export function SettingsRolesPage() {
<div> <div>
<label className="block text-sm font-medium text-secondary-700 mb-2">{t('settings.permissions')}</label> <label className="block text-sm font-medium text-secondary-700 mb-2">{t('settings.permissions')}</label>
<div className="space-y-2"> <div className="space-y-2">
{ALL_PERMISSIONS.map((perm) => ( {allPermissions.length === 0 && (
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch"> <p className="text-sm text-secondary-500">{t('settings.noPermissions')}</p>
<input )}
type="checkbox" {Object.entries(groupedPermissions).map(([category, perms]) => (
checked={isPermissionGranted(editingRole.permissions, perm.key)} <div key={category} className="mb-3">
onChange={() => handleTogglePermission(perm.key, 'edit')} <p className="text-xs font-semibold text-secondary-500 uppercase mb-1">
className="w-4 h-4 rounded border-secondary-300 text-primary-600 focus:ring-primary-500" {category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}
/> </p>
<span className="text-sm text-secondary-900">{perm.label}</span> {perms.map((perm) => (
</label> <label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
<input
type="checkbox"
checked={isPermissionGranted(editingRole.permissions, perm.key)}
onChange={() => handleTogglePermission(perm.key, 'edit')}
className="w-4 h-4 rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
/>
<span className="text-sm text-secondary-900">{perm.label}</span>
</label>
))}
</div>
))} ))}
</div> </div>
</div> </div>