frontend_nyla/src/pages/views/trustee/TrusteeInstanceRolesView.tsx
2026-01-25 03:01:07 +01:00

181 lines
5.9 KiB
TypeScript

/**
* TrusteeInstanceRolesView
*
* Verwaltung der instanz-spezifischen Rollen und deren Berechtigungen.
* Nur für Feature-Admins sichtbar (benötigt instance-roles.manage Permission).
*
* Diese View erlaubt das Anpassen der AccessRules für die Rollen dieser
* spezifischen Trustee-Instanz.
*/
import React, { useState, useEffect, useCallback } from 'react';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import { AccessRulesEditor } from '../../../components/AccessRules';
import { FaUserShield, FaShieldAlt, FaSync, FaChevronDown, FaChevronRight } from 'react-icons/fa';
import api from '../../../api';
import styles from './TrusteeViews.module.css';
interface InstanceRole {
id: string;
roleLabel: string;
description?: { [key: string]: string };
featureCode: string;
mandateId: string;
featureInstanceId: string;
isSystemRole?: boolean;
}
export const TrusteeInstanceRolesView: React.FC = () => {
const { instance } = useCurrentInstance();
const [roles, setRoles] = useState<InstanceRole[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [expandedRoleId, setExpandedRoleId] = useState<string | null>(null);
// Get display text from multilingual object
const getTextValue = (value: string | { [key: string]: string } | undefined): string => {
if (!value) return '';
if (typeof value === 'string') return value;
return value.de || value.en || Object.values(value)[0] || '';
};
// Load instance roles
const fetchRoles = useCallback(async () => {
if (!instance?.id || !instance?.mandateId) return;
setLoading(true);
setError(null);
try {
const response = await api.get(`/api/trustee/${instance.id}/instance-roles`, {
headers: { 'X-Mandate-Id': instance.mandateId }
});
const rolesList = response.data?.items || response.data || [];
setRoles(Array.isArray(rolesList) ? rolesList : []);
} catch (err: any) {
const errorMsg = err.response?.data?.detail || err.message || 'Fehler beim Laden der Rollen';
setError(errorMsg);
console.error('Error loading instance roles:', err);
} finally {
setLoading(false);
}
}, [instance?.id, instance?.mandateId]);
useEffect(() => {
fetchRoles();
}, [fetchRoles]);
// Toggle role expansion
const toggleRole = (roleId: string) => {
setExpandedRoleId(prev => prev === roleId ? null : roleId);
};
if (!instance) {
return (
<div className={styles.viewContainer}>
<div className={styles.error}>Keine Feature-Instanz ausgewählt</div>
</div>
);
}
if (loading) {
return (
<div className={styles.viewContainer}>
<div className={styles.loading}>Lade Instanz-Rollen...</div>
</div>
);
}
if (error) {
return (
<div className={styles.viewContainer}>
<div className={styles.error}>
<p>{error}</p>
<button onClick={fetchRoles} className={styles.retryButton}>
<FaSync /> Erneut versuchen
</button>
</div>
</div>
);
}
return (
<div className={styles.viewContainer}>
<div className={styles.viewHeader}>
<div className={styles.headerLeft}>
<h2 className={styles.viewTitle}>
<FaUserShield style={{ marginRight: '0.5rem' }} />
Instanz-Rollen & Berechtigungen
</h2>
<p className={styles.viewSubtitle}>
Verwalten Sie die Berechtigungen für die Rollen dieser Trustee-Instanz
</p>
</div>
<div className={styles.headerActions}>
<button onClick={fetchRoles} className={styles.secondaryButton}>
<FaSync /> Aktualisieren
</button>
</div>
</div>
<div className={styles.infoBox}>
<FaShieldAlt style={{ marginRight: '0.5rem' }} />
<span>
Diese Rollen wurden von den Feature-Templates kopiert.
Änderungen hier gelten nur für diese Instanz.
</span>
</div>
{roles.length === 0 ? (
<div className={styles.emptyState}>
<FaUserShield className={styles.emptyIcon} />
<p>Keine Instanz-Rollen gefunden</p>
<p className={styles.emptyHint}>
Instanz-Rollen werden automatisch erstellt, wenn Benutzer dieser Instanz zugewiesen werden.
</p>
</div>
) : (
<div className={styles.rolesList}>
{roles.map(role => (
<div key={role.id} className={styles.roleCard}>
<div
className={styles.roleHeader}
onClick={() => toggleRole(role.id)}
>
<div className={styles.roleInfo}>
<span className={styles.expandIcon}>
{expandedRoleId === role.id ? <FaChevronDown /> : <FaChevronRight />}
</span>
<span className={styles.roleLabel}>{role.roleLabel}</span>
<span className={styles.roleDescription}>
{getTextValue(role.description)}
</span>
</div>
<div className={styles.roleBadges}>
{role.isSystemRole && (
<span className={styles.systemBadge}>System</span>
)}
</div>
</div>
{expandedRoleId === role.id && (
<div className={styles.roleContent}>
<AccessRulesEditor
roleId={role.id}
roleName={role.roleLabel}
isTemplate={false}
apiBasePath={`/api/trustee/${instance.id}/instance-roles/${role.id}`}
mandateId={instance.mandateId}
featureCode="trustee"
/>
</div>
)}
</div>
))}
</div>
)}
</div>
);
};
export default TrusteeInstanceRolesView;