/** * InstanceHierarchyView * * Visual hierarchy: Mandanten (expandable) → Feature → Instanz (expandable) → User. * Hover over a user shows tooltip with Berechtigungen (roleLabels), E-Mail, and Aktiv/Inaktiv. */ import React, { useState, useMemo } from 'react'; import { FaChevronDown, FaChevronRight, FaUsers } from 'react-icons/fa'; import type { Feature } from '../../hooks/useFeatureAccess'; import type { FeatureAccessUser } from '../../hooks/useFeatureAccess'; import type { InstanceWithStats } from './AccessManagementHub'; import type { Mandate } from '../../hooks/useUserMandates'; import hubStyles from './AccessManagementHub.module.css'; import hierarchyStyles from './InstanceHierarchyView.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; export interface InstanceHierarchyViewProps { mandates: Mandate[]; getMandateName: (mandate: Mandate) => string; instancesByMandate: Record; instanceUsersMap: Record; features: Feature[]; getFeatureLabel: (feature: Feature) => string; loading?: boolean; onOpenDetail: (instance: InstanceWithStats, mandateId: string) => void; } function getFeatureLabelSafe( features: Feature[], featureCode: string, getFeatureLabel: (f: Feature) => string ): string { const f = features.find((x) => x.code === featureCode); return f ? getFeatureLabel(f) : featureCode; } interface MandateContentProps { mandateId: string; mandateName: string; instances: InstanceWithStats[]; instanceUsersMap: Record; features: Feature[]; getFeatureLabel: (f: Feature) => string; onOpenDetail: (instance: InstanceWithStats, mandateId: string) => void; } function MandateContent({ mandateId, mandateName: _mandateName, instances, instanceUsersMap, features, getFeatureLabel, onOpenDetail, }: MandateContentProps) { const { t } = useLanguage(); const [expandedInstanceIds, setExpandedInstanceIds] = useState>(new Set()); const byFeature = useMemo(() => { const map: Record = {}; instances.forEach((inst) => { if (!map[inst.featureCode]) map[inst.featureCode] = []; map[inst.featureCode].push(inst); }); return map; }, [instances]); const toggleInstance = (instanceId: string) => { setExpandedInstanceIds((prev) => { const next = new Set(prev); if (next.has(instanceId)) next.delete(instanceId); else next.add(instanceId); return next; }); }; return (
{Object.entries(byFeature).map(([featureCode, featureInstances]) => { const featureUserCount = featureInstances.reduce( (sum, inst) => sum + (instanceUsersMap[inst.id]?.length ?? 0), 0 ); return (
{getFeatureLabelSafe(features, featureCode, getFeatureLabel)} {featureInstances.length}{' '} {featureInstances.length !== 1 ? t('Instanzen') : t('Instanz')} {featureUserCount > 0 && ` · ${featureUserCount} ${t('Benutzer')}`}
{featureInstances.map((inst) => { const isExpanded = expandedInstanceIds.has(inst.id); const users = instanceUsersMap[inst.id] ?? []; return (
{isExpanded && (
{users.length === 0 ? (
{t('Keine Benutzer zugeordnet.')}{' '}
) : ( users.map((u) => ) )}
)}
); })}
); })}
); } export const InstanceHierarchyView: React.FC = ({ mandates, getMandateName, instancesByMandate, instanceUsersMap, features, getFeatureLabel, loading, onOpenDetail, }) => { const { t } = useLanguage(); const [expandedMandateIds, setExpandedMandateIds] = useState>(new Set()); const toggleMandate = (mandateId: string) => { setExpandedMandateIds((prev) => { const next = new Set(prev); if (next.has(mandateId)) next.delete(mandateId); else next.add(mandateId); return next; }); }; if (loading) { return (
{t('Lade Hierarchie und Benutzer')}
); } if (mandates.length === 0) { return (

{t('Hierarchie')}

{t('Keine Mandanten vorhanden. Legen Sie unter „Mandanten verwalten“ einen Mandanten an.')}
); } return (

{t('Hierarchie')}

{mandates.map((mandate) => { const mandateId = mandate.id; const instances = instancesByMandate[mandateId] ?? []; const isExpanded = expandedMandateIds.has(mandateId); const mandateName = getMandateName(mandate); const byFeat: Record = {}; instances.forEach((inst) => { if (!byFeat[inst.featureCode]) byFeat[inst.featureCode] = []; byFeat[inst.featureCode].push(inst); }); const featureCount = Object.keys(byFeat).length; return (
{isExpanded && ( )}
); })}
); }; interface UserRowProps { user: FeatureAccessUser; } function UserRow({ user }: UserRowProps) { const { t } = useLanguage(); const displayName = user.fullName?.trim() || user.username || user.userId; const rolesText = user.roleLabels && user.roleLabels.length > 0 ? user.roleLabels.join(', ') : t('Keine Rollen'); const statusText = user.enabled ? t('Aktiv') : t('Inaktiv'); return (
{displayName} {rolesText} {statusText}
{t('Berechtigungen')}
{user.email && (
{user.email}
)}
{rolesText}
{t('Zugang')}: {statusText}
); } export default InstanceHierarchyView;