/** * useNavigation Hook * * Fetches the navigation structure from the new Navigation API. * The backend provides a blocks-based structure with static and dynamic blocks. * * API: GET /api/navigation?language=de * * Response structure (gemäss Navigation-API-Konzept): * { * "language": "de", * "blocks": [ * { "type": "static", "id": "system", "title": "SYSTEM", "order": 10, "items": [...] }, * { "type": "dynamic", "id": "features", "title": "MEINE FEATURES", "order": 15, "mandates": [...] }, * ... * ] * } */ 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; order: number; views: FeatureView[]; } /** 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 { language: string; 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(language: string = 'de'): UseNavigationReturn { const [blocks, setBlocks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchNavigation = useCallback(async () => { setLoading(true); setError(null); try { // New API endpoint: /api/navigation (without /system prefix) const response = await api.get( `/api/navigation?language=${language}` ); // Blocks are already sorted by order from backend 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); } }, [language]); useEffect(() => { fetchNavigation(); }, [fetchNavigation]); useEffect(() => { const onFeaturesChanged = () => { fetchNavigation(); }; window.addEventListener('features-changed', onFeaturesChanged); return () => window.removeEventListener('features-changed', 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;