Problem 3/4: Replace hardcoded ALL_PERMISSIONS with usePermissions API call, group by category
This commit is contained in:
@@ -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,13 +242,21 @@ 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 && (
|
||||||
|
<p className="text-sm text-secondary-500">{t('settings.noPermissions')}</p>
|
||||||
|
)}
|
||||||
|
{Object.entries(groupedPermissions).map(([category, perms]) => (
|
||||||
|
<div key={category} className="mb-3">
|
||||||
|
<p className="text-xs font-semibold text-secondary-500 uppercase mb-1">
|
||||||
|
{category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}
|
||||||
|
</p>
|
||||||
|
{perms.map((perm) => (
|
||||||
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
|
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -246,6 +268,8 @@ export function SettingsRolesPage() {
|
|||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
<Button variant="secondary" onClick={() => setCreateOpen(false)}>{t('common.cancel')}</Button>
|
<Button variant="secondary" onClick={() => setCreateOpen(false)}>{t('common.cancel')}</Button>
|
||||||
@@ -271,7 +295,15 @@ 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 && (
|
||||||
|
<p className="text-sm text-secondary-500">{t('settings.noPermissions')}</p>
|
||||||
|
)}
|
||||||
|
{Object.entries(groupedPermissions).map(([category, perms]) => (
|
||||||
|
<div key={category} className="mb-3">
|
||||||
|
<p className="text-xs font-semibold text-secondary-500 uppercase mb-1">
|
||||||
|
{category === 'system' ? t('settings.systemPermissions') : t('settings.pluginPermissions')}
|
||||||
|
</p>
|
||||||
|
{perms.map((perm) => (
|
||||||
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
|
<label key={perm.key} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer min-h-touch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -283,6 +315,8 @@ export function SettingsRolesPage() {
|
|||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
<Button variant="secondary" onClick={() => setEditingRole(null)}>{t('common.cancel')}</Button>
|
<Button variant="secondary" onClick={() => setEditingRole(null)}>{t('common.cancel')}</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user