/** * AdminFeatureRolesPage * * Admin page for managing FEATURE TEMPLATE ROLES. * These are roles that are copied to new feature instances. * * According to admin_ui_concept.md: * - Filter: featureCode!=null AND mandateId=null AND featureInstanceId=null * - View: Per feature (dropdown selection) * - Actions: Create feature role, edit description, manage AccessRules, delete role */ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; import { AccessRulesEditor } from '../../components/AccessRules'; import { FaPlus, FaSync, FaUserShield, FaCube, FaShieldAlt } from 'react-icons/fa'; import { useToast } from '../../contexts/ToastContext'; import api from '../../api'; import styles from './Admin.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; interface Feature { id?: string; code: string; featureCode?: string; label: string; name?: string; description?: string; icon?: string; } interface FeatureRole { id: string; roleLabel: string; description?: string; featureCode: string; mandateId?: string | null; featureInstanceId?: string | null; isSystemRole?: boolean; } export const AdminFeatureRolesPage: React.FC = () => { const { t } = useLanguage(); const { showError } = useToast(); // State const [features, setFeatures] = useState([]); const [selectedFeatureCode, setSelectedFeatureCode] = useState(''); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); const [editingRole, setEditingRole] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [permissionsRole, setPermissionsRole] = useState(null); // Load features on mount useEffect(() => { const loadFeatures = async () => { try { const response = await api.get('/api/features/'); const featureList = response.data?.items || response.data || []; setFeatures(Array.isArray(featureList) ? featureList : []); // Auto-select first feature if available if (featureList.length > 0 && !selectedFeatureCode) { setSelectedFeatureCode(featureList[0].code || featureList[0].featureCode || ''); } } catch (err: any) { console.error('Error loading features:', err); setError(t('Fehler beim Laden der Features')); } }; loadFeatures(); }, []); const [pagination, setPagination] = useState(null); // Load roles when feature changes const fetchRoles = useCallback(async (params?: any) => { if (!selectedFeatureCode) { setRoles([]); return; } setLoading(true); setError(null); try { const requestParams: Record = { featureCode: selectedFeatureCode }; if (params && typeof params === 'object') { const paginationObj: any = {}; if (params.page !== undefined) paginationObj.page = params.page; if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize; if (params.sort) paginationObj.sort = params.sort; if (params.filters) paginationObj.filters = params.filters; if (params.search) paginationObj.search = params.search; if (Object.keys(paginationObj).length > 0) { requestParams.pagination = JSON.stringify(paginationObj); } } const response = await api.get(`/api/features/templates/roles`, { params: requestParams }); const data = response.data; if (data && typeof data === 'object' && 'items' in data) { setRoles(Array.isArray(data.items) ? data.items : []); if (data.pagination) setPagination(data.pagination); } else { setRoles(Array.isArray(data) ? data : []); setPagination(null); } } catch (err: any) { console.error('Error loading feature roles:', err); setError(t('Fehler beim Laden der Feature-Rollen')); setRoles([]); setPagination(null); } finally { setLoading(false); } }, [selectedFeatureCode, t]); useEffect(() => { fetchRoles(); }, [fetchRoles]); const getTextValue = (value: any): string => { if (!value) return '-'; if (typeof value === 'string') return value; return String(value); }; // Table columns const columns = useMemo(() => [ { key: 'roleLabel', label: t('Rollen-Label'), type: 'string' as const, sortable: true, filterable: true, searchable: true, width: 180 }, { key: 'description', label: t('Beschreibung'), type: 'string' as const, sortable: false, width: 300, formatter: (value: string) => getTextValue(value) }, { key: 'featureCode', label: t('Feature'), type: 'string' as const, sortable: true, filterable: true, width: 120, formatter: (value: string) => ( {value} ) }, ], [t]); // Form attributes for create const createFields: AttributeDefinition[] = useMemo(() => { const fields: AttributeDefinition[] = [ { name: 'roleLabel', label: t('Rollen-Label'), type: 'string', required: true, description: t('Rollen-Label Beschreibung') }, { name: 'description', label: t('Beschreibung'), type: 'textarea', required: false, description: t('Beschreibung der Rolle') } ]; return fields; }, [t]); // Form attributes for edit const editFields: AttributeDefinition[] = useMemo(() => { return [ { name: 'roleLabel', label: t('Rollen-Label'), type: 'string', required: true, readonly: true, description: t('Rollen-Label (nur lesen)') }, { name: 'description', label: t('Beschreibung'), type: 'textarea', required: false, description: t('Beschreibung der Rolle') } ]; }, [t]); // Handle create role const handleCreateRole = async (data: { roleLabel: string; description?: string }) => { if (!selectedFeatureCode) return; setIsSubmitting(true); try { const params = new URLSearchParams(); params.append('roleLabel', data.roleLabel); params.append('featureCode', selectedFeatureCode); await api.post(`/api/features/templates/roles?${params.toString()}`, data.description ?? ''); setShowCreateModal(false); await fetchRoles(); } catch (err: any) { console.error('Error creating role:', err); showError(t('Fehler'), err.response?.data?.detail || t('Fehler beim Erstellen der Rolle')); } finally { setIsSubmitting(false); } }; // Handle edit role const handleEditRole = async (data: { roleLabel: string; description?: string }) => { if (!editingRole) return; setIsSubmitting(true); try { await api.put(`/api/rbac/roles/${editingRole.id}`, { description: data.description }); setEditingRole(null); await fetchRoles(); } catch (err: any) { console.error('Error updating role:', err); showError(t('Fehler'), err.response?.data?.detail || t('Fehler beim Aktualisieren der Rolle')); } finally { setIsSubmitting(false); } }; // Handle delete role (confirmation handled by DeleteActionButton) const handleDeleteRole = async (role: FeatureRole) => { try { await api.delete(`/api/rbac/roles/${role.id}`); await fetchRoles(); } catch (err: any) { console.error('Error deleting role:', err); showError(t('Fehler'), err.response?.data?.detail || t('Fehler beim Löschen der Rolle')); } }; // Handle edit click const handleEditClick = (role: FeatureRole) => { setEditingRole(role); }; const getFeatureName = (feature: Feature) => { return feature.label || feature.name || '-'; }; if (error && !selectedFeatureCode) { return (
⚠️

{error}

); } return (

{t('Feature-Rollen-Rechte')}

{t('Template-Rollen und deren Berechtigungen für')}

{/* Feature Selector */}
{selectedFeatureCode && (
)}
{/* Info Box */} {selectedFeatureCode && (
{t('Feature-Template-Rollen')}{' '} {t('werden bei der Erstellung neuer Feature-Instanzen automatisch kopiert. Änderungen an Template-Rollen wirken sich nicht auf bestehende Instanzen aus.')}
)} {/* Content */} {!selectedFeatureCode ? (

{t('Kein Feature ausgewählt')}

{t('Wählen Sie ein Feature aus, um dessen Template-Rollen zu verwalten.')}

) : (
, onClick: (role: FeatureRole) => setPermissionsRole(role), title: t('Berechtigungen verwalten'), } ]} onDelete={handleDeleteRole} hookData={{ refetch: fetchRoles, pagination, handleDelete: handleDeleteRole, }} emptyMessage={t('Keine Feature-Rollen gefunden')} />
)} {/* Create Role Modal */} {showCreateModal && (
setShowCreateModal(false)}>
e.stopPropagation()}>

{t('Neue Feature-Rolle erstellen')}

{t('Feature')}: {selectedFeatureCode}
setShowCreateModal(false)} submitButtonText={isSubmitting ? t('Erstelle…') : t('Rolle erstellen')} cancelButtonText={t('Abbrechen')} />
)} {/* Edit Role Modal */} {editingRole && (
setEditingRole(null)}>
e.stopPropagation()}>

{t('Feature-Rolle bearbeiten')}

{t('Feature:')} {editingRole.featureCode}
setEditingRole(null)} submitButtonText={isSubmitting ? t('Speichern') : t('Speichern')} cancelButtonText={t('Abbrechen')} />
)} {/* Permissions Modal */} {permissionsRole && (
setPermissionsRole(null)}>
e.stopPropagation()}>

{t('Berechtigungen')}: {permissionsRole.roleLabel}

{t('Feature')}: {permissionsRole.featureCode} {t('Template-Rolle global')}
setPermissionsRole(null)} featureCode={permissionsRole.featureCode} />
)}
); }; export default AdminFeatureRolesPage;