181 lines
5.9 KiB
TypeScript
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;
|