/** * MandateNavigation * * Hierarchische Navigation für das Multi-Tenant-System. * Verwendet TreeNavigation für flexible Baumstruktur. * * Navigation wird vollständig vom Backend geladen (/api/navigation). * Backend liefert Blocks-Struktur mit Static und Dynamic Blocks. * UI mappt uiComponent zu Icons via pageRegistry. * * TREE STRUCTURE (alles collapsible): * ▼ Meine Sicht * - Übersicht, Einstellungen, Prompts, Dateien, Verbindungen, Billing * ───────────── * ▼ Mandant 1 * - 🎯 Instanz 1 (Feature-Icon + Instanz-Name) * - 💼 Instanz 2 (Feature-Icon + Instanz-Name) * ───────────── * ▶ Administration * - Users, Mandates, Roles, ... */ import React, { useMemo, useCallback } from 'react'; import { useNavigation } from '../../hooks/useNavigation'; import type { DynamicBlock, NavigationItem, NavSubgroup, NavigationMandate, FeatureInstance, FeatureView } from '../../hooks/useNavigation'; import { getPageIcon } from '../../config/pageRegistry'; import { FaSpinner, FaPen } from 'react-icons/fa'; import { TreeNavigation, type TreeItem, type TreeNodeItem } from './TreeNavigation'; import api from '../../api'; import styles from './MandateNavigation.module.css'; // ============================================================================= // HELPER FUNCTIONS - Convert API blocks to TreeItems // ============================================================================= /** * Convert a NavigationItem (from static block) to TreeNodeItem */ function navigationItemToTreeNode(item: NavigationItem): TreeNodeItem { return { id: item.objectKey, label: item.uiLabel, icon: getPageIcon(item.uiComponent), path: item.uiPath, }; } /** * Convert a list of NavigationItems into a collapsible TreeNodeItem container. * Used for grouping static items under "Meine Sicht" and "Administration". */ function _staticItemsToTreeNode( id: string, label: string, items: NavigationItem[], defaultExpanded: boolean = true, ): TreeNodeItem { return { id, label, children: items.map(navigationItemToTreeNode), defaultExpanded, }; } /** * Convert a FeatureView to TreeNodeItem */ function featureViewToTreeNode(view: FeatureView): TreeNodeItem { return { id: view.objectKey, label: view.uiLabel, path: view.uiPath, }; } /** * Convert a FeatureInstance to TreeNodeItem (with feature icon) * Instance node gets path to first view so clicking the instance name navigates to dashboard. * Shows the feature icon next to the instance name for visual distinction. * If user is instance admin, a rename icon appears on hover. */ function featureInstanceToTreeNode( instance: FeatureInstance, featureUiComponent: string, onRename?: (instanceId: string, currentLabel: string) => void, ): TreeNodeItem { const children = instance.views.map(featureViewToTreeNode); const renameAction = instance.isAdmin && onRename ? ( ) : undefined; return { id: instance.id, label: instance.uiLabel, icon: getPageIcon(featureUiComponent), path: instance.views.length > 0 ? instance.views[0].uiPath : undefined, children, defaultExpanded: false, actions: renameAction, }; } /** * Convert a NavigationMandate to TreeNodeItem * * FLAT STRUCTURE: Instances are listed directly under mandate (no feature grouping). * Each instance shows the feature's icon for visual distinction. * * Before: Mandate → Feature → Instance → Views * Now: Mandate → Instance (with feature icon) → Views */ function navigationMandateToTreeNode( mandate: NavigationMandate, onRename?: (instanceId: string, currentLabel: string) => void, ): TreeNodeItem | null { if (mandate.features.length === 0) { return null; } const instanceNodes: TreeNodeItem[] = []; for (const feature of mandate.features) { for (const instance of feature.instances) { instanceNodes.push(featureInstanceToTreeNode(instance, feature.uiComponent, onRename)); } } if (instanceNodes.length === 0) { return null; } return { id: mandate.id, label: mandate.uiLabel, children: instanceNodes, defaultExpanded: true, }; } /** * Convert a DynamicBlock to array of TreeNodeItems (mandate nodes) */ function dynamicBlockToTreeNodes( block: DynamicBlock, onRename?: (instanceId: string, currentLabel: string) => void, ): TreeNodeItem[] { return block.mandates .map((m) => navigationMandateToTreeNode(m, onRename)) .filter((node): node is TreeNodeItem => node !== null); } // ============================================================================= // LOADING STATE // ============================================================================= const LoadingState: React.FC = () => (
Keine Feature-Instanzen verfügbar.
Kontaktiere einen Administrator, um Zugriff zu erhalten.