/** * InstanceDetailModal * * Modal for a feature instance: Benutzer (PermissionMatrix), Rollen (sync), Einstellungen. */ import React, { useState, useEffect, useMemo } from 'react'; import { useFeatureAccess, type FeatureInstance, type FeatureAccessUser } from '../../hooks/useFeatureAccess'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; import { Tabs } from '../../components/UiComponents/Tabs'; import { FaSync } from 'react-icons/fa'; import { useToast } from '../../contexts/ToastContext'; import api from '../../api'; import { PermissionMatrix } from './PermissionMatrix'; import styles from './Admin.module.css'; import modalStyles from './InstanceDetailModal.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; export interface InstanceDetailModalProps { instance: FeatureInstance; mandateId: string; mandateName: string; featureLabel: string; onClose: () => void; onSaved: () => void; } export const InstanceDetailModal: React.FC = ({ instance, mandateId, mandateName, featureLabel, onClose, onSaved, }) => { const { fetchInstanceUsers, fetchInstanceRoles, addUserToInstance, removeUserFromInstance, updateInstanceUserRoles, syncInstanceRoles, updateInstance, } = useFeatureAccess(); const { showSuccess, showError } = useToast(); const { t } = useLanguage(); const [users, setUsers] = useState([]); const [roles, setRoles] = useState>([]); const [allUsers, setAllUsers] = useState>([]); const [loading, setLoading] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [editingUser, setEditingUser] = useState(null); const [syncing, setSyncing] = useState(false); const [roleOptions, setRoleOptions] = useState([]); const loadData = () => { setLoading(true); Promise.all([ fetchInstanceUsers(mandateId, instance.id), fetchInstanceRoles(mandateId, instance.id), ]) .then(([userList, roleList]) => { setUsers(Array.isArray(userList) ? userList : []); setRoles(Array.isArray(roleList) ? roleList : []); setRoleOptions( (Array.isArray(roleList) ? roleList : []).map((r) => ({ value: r.id, label: r.roleLabel, })) ); }) .catch(() => { setUsers([]); setRoles([]); setRoleOptions([]); }) .finally(() => setLoading(false)); }; useEffect(() => { loadData(); }, [mandateId, instance.id]); useEffect(() => { api .get(`/api/mandates/${mandateId}/users`) .then((res) => { const data = res.data?.items || res.data || []; setAllUsers( Array.isArray(data) ? data.map((u: { userId: string; username: string; email?: string }) => ({ id: u.userId, username: u.username, email: u.email, })) : [] ); }) .catch(() => setAllUsers([])); }, [mandateId]); const availableUsers = useMemo(() => { const ids = new Set(users.map((u) => u.userId)); return allUsers.filter((u) => !ids.has(u.id)); }, [allUsers, users]); const handleAddUser = async (data: { userId: string; roleIds: string[] }) => { const result = await addUserToInstance(mandateId, instance.id, { userId: data.userId, roleIds: data.roleIds, }); if (result.success) { setShowAddModal(false); loadData(); onSaved(); showSuccess(t('Benutzer hinzugefügt'), t('Der Benutzer wurde der Instanz hinzugefügt.')); } else { showError(t('Fehler'), result.error || t('Fehler beim Hinzufügen')); } }; const handleRemoveUser = async (user: FeatureAccessUser) => { const result = await removeUserFromInstance(mandateId, instance.id, user.userId); if (result.success) { loadData(); onSaved(); showSuccess(t('Benutzer entfernt'), t('"{name}" wurde entfernt.', { name: user.username })); } else { showError(t('Fehler'), result.error || t('Fehler beim Entfernen')); } }; const handleEditUser = (user: FeatureAccessUser) => { setEditingUser(user); }; const handleUpdateRoles = async (data: { roleIds: string[]; enabled?: boolean }) => { if (!editingUser) return; const result = await updateInstanceUserRoles(mandateId, instance.id, editingUser.userId, { roleIds: data.roleIds, enabled: data.enabled, }); if (result.success) { setEditingUser(null); loadData(); onSaved(); showSuccess(t('Aktualisiert'), t('Rollen und Status wurden gespeichert.')); } else { showError(t('Fehler'), result.error || t('Fehler beim Speichern')); } }; const handleSyncRoles = async () => { setSyncing(true); try { const result = await syncInstanceRoles(mandateId, instance.id, true); if (result.success && result.data) { loadData(); onSaved(); showSuccess( t('Rollen synchronisiert'), t('Hinzugefügt: {added}, Entfernt: {removed}', { added: result.data.added, removed: result.data.removed }) ); } else { showError(t('Fehler'), result.error || t('Synchronisierung fehlgeschlagen')); } } finally { setSyncing(false); } }; const handleUpdateInstance = async (data: { label?: string; enabled?: boolean }) => { const result = await updateInstance(mandateId, instance.id, data); if (result.success) { onSaved(); showSuccess(t('Instanz aktualisiert'), t('Einstellungen wurden gespeichert.')); } else { showError(t('Fehler'), result.error || t('Fehler beim Speichern')); } }; const addUserFields: AttributeDefinition[] = useMemo( () => [ { name: 'userId', label: t('Benutzer'), type: 'enum' as const, required: true, options: availableUsers.map((u) => ({ value: u.id, label: `${u.username}${u.email ? ` (${u.email})` : ''}`, })), }, { name: 'roleIds', label: t('Rollen'), type: 'multiselect' as const, required: true, options: roleOptions as AttributeDefinition['options'], }, ], [availableUsers, roleOptions, t] ); const editRolesFields: AttributeDefinition[] = useMemo( () => [ { name: 'roleIds', label: t('Rollen'), type: 'multiselect' as const, required: true, options: roleOptions as AttributeDefinition['options'], }, { name: 'enabled', label: t('Aktiv'), type: 'checkbox' as const, required: false, }, ], [roleOptions, t] ); const tabs = [ { id: 'users', label: t('Benutzer'), content: (
{loading ? (
{t('Lade Benutzer')}
) : ( setShowAddModal(true)} /> )}
), }, { id: 'roles', label: t('Rollen'), content: (

{t('Rollen werden von der Feature-Vorlage übernommen. Mit „Synchronisieren“ können Sie fehlende Rollen nachziehen.')}

    {roles.map((r) => (
  • {r.roleLabel}
  • ))}
), }, { id: 'settings', label: t('Einstellungen'), content: (
{}} />
), }, ]; return (

{instance.label}

{mandateName} · {featureLabel}

{showAddModal && (

{t('Benutzer hinzufügen')}

{availableUsers.length === 0 ? (

{t('Alle Mandantenbenutzer haben bereits Zugriff')}

) : addUserFields.length < 2 || !roleOptions?.length ? (

{t('Laden')}

) : ( setShowAddModal(false)} submitButtonText={t('Hinzufügen')} cancelButtonText={t('Abbrechen')} /> )}
)} {editingUser && (

{t('Rollen')}: {editingUser.username}

setEditingUser(null)} submitButtonText={t('Speichern')} cancelButtonText={t('Abbrechen')} />
)}
); }; export default InstanceDetailModal;