/** * 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. * * Struktur (gemäss Navigation-API-Konzept): * - SYSTEM (static block, order: 10) * - MEINE FEATURES (dynamic block, order: 15) * - Mandant 1 * - Feature A * - Instanz 1 (mit Views) * - WORKFLOWS (static block, order: 20) * - BASISDATEN (static block, order: 30) * - MIGRATE TO FEATURES (static block, order: 40) * - ADMINISTRATION (static block, order: 200) */ import React, { useMemo } from 'react'; import { useNavigation } from '../../hooks/useNavigation'; import type { StaticBlock, DynamicBlock, NavigationItem, NavigationMandate, MandateFeature, FeatureInstance, FeatureView } from '../../hooks/useNavigation'; import { getPageIcon } from '../../config/pageRegistry'; import { FaSpinner } from 'react-icons/fa'; import { TreeNavigation, type TreeItem, type TreeNodeItem } from './TreeNavigation'; 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 StaticBlock to TreeItem (section) */ function staticBlockToTreeItem(block: StaticBlock): TreeItem { return { type: 'section', title: block.title, children: block.items.map(navigationItemToTreeNode), }; } /** * 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 */ function featureInstanceToTreeNode(instance: FeatureInstance): TreeNodeItem { return { id: instance.id, label: instance.uiLabel, path: instance.views.length > 0 ? instance.views[0].uiPath : undefined, children: instance.views.map(featureViewToTreeNode), defaultExpanded: false, }; } /** * Convert a MandateFeature to TreeNodeItem */ function mandateFeatureToTreeNode(feature: MandateFeature): TreeNodeItem | null { if (feature.instances.length === 0) { return null; } return { id: feature.uiComponent, label: feature.uiLabel, icon: getPageIcon(feature.uiComponent), badge: feature.instances.length, path: feature.instances.length > 0 && feature.instances[0].views.length > 0 ? feature.instances[0].views[0].uiPath : undefined, children: feature.instances.map(featureInstanceToTreeNode), defaultExpanded: false, }; } /** * Convert a NavigationMandate to TreeNodeItem */ function navigationMandateToTreeNode(mandate: NavigationMandate): TreeNodeItem | null { if (mandate.features.length === 0) { return null; } const children = mandate.features .map(mandateFeatureToTreeNode) .filter((node): node is TreeNodeItem => node !== null); if (children.length === 0) { return null; } return { id: mandate.id, label: mandate.uiLabel, children, defaultExpanded: true, }; } /** * Convert a DynamicBlock to array of TreeNodeItems (mandate nodes) */ function dynamicBlockToTreeNodes(block: DynamicBlock): TreeNodeItem[] { return block.mandates .map(navigationMandateToTreeNode) .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.