/** * useNavigation Hook * * Fetches the navigation structure from the Navigation API. * Static nav items and feature view labels are resolved server-side for the * request language (resolveText). User-defined mandate/instance names are raw. * * API: GET /api/navigation */ import { useState, useEffect, useCallback } from 'react'; import api from '../api'; // ============================================================================= // TYPES - New Navigation API Structure // ============================================================================= /** Static block item (system, admin pages) */ export interface NavigationItem { uiComponent: string; uiLabel: string; uiPath: string; order: number; objectKey: string; } /** Subgroup within a static block (for nested navigation) */ export interface NavSubgroup { id: string; title: string; order: number; items: NavigationItem[]; } /** Static navigation block */ export interface StaticBlock { type: 'static'; id: string; title: string; order: number; items: NavigationItem[]; subgroups?: NavSubgroup[]; } /** View within a feature instance */ export interface FeatureView { uiComponent: string; uiLabel: string; uiPath: string; order: number; objectKey: string; } /** Feature instance within a mandate */ export interface FeatureInstance { id: string; uiLabel: string; /** Feature type code, e.g. trustee, workspace (for display: Label (code)) */ featureCode?: string; order: number; views: FeatureView[]; isAdmin?: boolean; } /** Feature within a mandate */ export interface MandateFeature { uiComponent: string; uiLabel: string; order: number; instances: FeatureInstance[]; } /** Mandate in the dynamic block */ export interface NavigationMandate { id: string; uiLabel: string; order: number; features: MandateFeature[]; } /** Dynamic navigation block (features) */ export interface DynamicBlock { type: 'dynamic'; id: string; title: string; order: number; mandates: NavigationMandate[]; } /** Union type for all block types */ export type NavigationBlock = StaticBlock | DynamicBlock; /** API Response structure */ export interface NavigationResponse { blocks: NavigationBlock[]; } /** Hook return type */ interface UseNavigationReturn { /** All navigation blocks from API */ blocks: NavigationBlock[]; /** Static blocks only (for convenience) */ staticBlocks: StaticBlock[]; /** Dynamic block (features) if present */ dynamicBlock: DynamicBlock | null; /** Loading state */ loading: boolean; /** Error message if any */ error: string | null; /** Refresh navigation data */ refresh: () => Promise; } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function isStaticBlock(block: NavigationBlock): block is StaticBlock { return block.type === 'static'; } function isDynamicBlock(block: NavigationBlock): block is DynamicBlock { return block.type === 'dynamic'; } // ============================================================================= // HOOK // ============================================================================= export function useNavigation(): UseNavigationReturn { const [blocks, setBlocks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchNavigation = useCallback(async () => { setLoading(true); setError(null); try { const response = await api.get('/api/navigation'); setBlocks(response.data.blocks || []); } catch (err: unknown) { const errorMsg = err instanceof Error ? err.message : (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail || 'Fehler beim Laden der Navigation'; setError(errorMsg); setBlocks([]); } finally { setLoading(false); } }, []); useEffect(() => { fetchNavigation(); }, [fetchNavigation]); useEffect(() => { const onFeaturesChanged = () => { fetchNavigation(); }; window.addEventListener('features-changed', onFeaturesChanged); window.addEventListener('userInfoUpdated', onFeaturesChanged); return () => { window.removeEventListener('features-changed', onFeaturesChanged); window.removeEventListener('userInfoUpdated', onFeaturesChanged); }; }, [fetchNavigation]); // Derive static and dynamic blocks const staticBlocks = blocks.filter(isStaticBlock); const dynamicBlock = blocks.find(isDynamicBlock) || null; return { blocks, staticBlocks, dynamicBlock, loading, error, refresh: fetchNavigation, }; } export default useNavigation;