"use client"; // Globale Navigation ueber alle Modul-Frontends (SHL-01 Akzeptanzkriterium 1). // Statische Registry statt Live-Abruf: die Modul-Frontends dieses Testhosts // nutzen unterschiedliche, noch nicht vereinheitlichte Anmeldemodelle // (Core-Sitzung vs. feste Demo-Kennung per Umgebungsvariable) -- ein // gemeinsamer Live-Abruf ueber /api/visible-modules wuerde fuer die Haelfte // der Module eine Core-Anmeldung voraussetzen, die sie bisher nicht haben. // Einzige Quelle fuer die Modul-Liste (SHL-01 P1-Review): web/portal // importiert MODULE_REGISTRY von hier statt einer separaten JSON-Kopie // (web/portal/lib/modules.ts). description/permission sind optional, weil // das statische Nav hier (ModuleNavShell) nur key/label/href braucht; // portal/lib/modules.ts nutzt die zusaetzlichen Felder fuer die // rechte-gefilterte visible-modules-API. import { useEffect, useState } from "react"; import type { ReactNode } from "react"; import { Shell } from "./Shell"; export interface ModuleRegistryEntry { key: string; label: string; href: string; description?: string; permission?: string; } export const MODULE_REGISTRY: ModuleRegistryEntry[] = [ { key: "dms", label: "Dokumente", href: "http://localhost:3101", description: "Dokumente durchsuchen und verwalten.", permission: "dms.document.read", }, { key: "dms_upload", label: "Dokumente hochladen", href: "http://localhost:3107", description: "Dokumente und Kassenbelege hochladen, inklusive mobiler Kamera-Erfassung.", permission: "dms.upload.read", }, { key: "mail", label: "Postfach", href: "http://localhost:3102", description: "E-Mails durchsuchen und verwalten.", permission: "mail.message.read", }, { key: "workflow", label: "Aufgaben & Freigaben", href: "http://localhost:3103", description: "Freigaben bearbeiten, Akten einsehen, Aufgaben verwalten.", permission: "workflow.approval.read", }, { key: "workflow_cases", label: "Akten & Vorgänge", href: "http://localhost:3108", description: "Akten, Vermerke und Wiedervorlagen einsehen.", permission: "workflow.case.read", }, { key: "routing_rules", label: "Routing-Regeln", href: "http://localhost:3109", description: "Regelbasiertes Routing anlegen und testen.", permission: "workflow.routingrule.read", }, { key: "ai", label: "KI-Funktionen", href: "http://localhost:3104", description: "Zusammenfassungen, Extraktion und semantische Suche.", permission: "ai.summary.read", }, { key: "ai_extraction", label: "Extraktions-Korrektur", href: "http://localhost:3110", description: "Von der KI extrahierte Werte prüfen und korrigieren.", permission: "ai.extraction.read", }, { key: "connect", label: "Externe Anbindungen", href: "http://localhost:3105", description: "Status externer Systemanbindungen (ERP, DATEV, CRM).", permission: "connect.status.read", }, { key: "connect_datev", label: "DATEV-Export", href: "http://localhost:3111", description: "DATEV-Exporte anstoßen und Übertragungsprotokoll einsehen.", permission: "connect.datev.read", }, { key: "tenant_admin", label: "Mandantenverwaltung", href: "http://localhost:3106", description: "Mandanten und Import-Verzeichnisse verwalten.", permission: "core.tenant.admin", }, { key: "rbac_admin", label: "Rollen & Rechte", href: "http://localhost:3112", description: "Rollen und Berechtigungen verwalten.", permission: "core.tenant.admin", }, { key: "kennzahlen_dashboard", label: "Kennzahlen", href: "http://localhost:3113", description: "Löschkennzahlen je Tenant und Modul einsehen.", permission: "kennzahlen.read", }, { key: "portal", label: "Übersicht", href: "http://localhost:3900" }, ]; export interface ModuleNavShellProps { activeKey: string; children: ReactNode; } // Ersetzt "localhost" im Modul-Href durch den tatsaechlichen Hostnamen, // auf dem die Seite ausgeliefert wird (Testhosts binden Module unter // derselben IP auf unterschiedlichen Ports). Einzige Quelle fuer diese // Umschreibung (SHL-01 P1-Review): vorher identisch dupliziert als // resolveModuleHref in web/portal/app/ModuleGrid.tsx. export function resolveModuleHref( href: string, currentHostname: string = typeof window !== "undefined" ? window.location.hostname : "localhost" ): string { try { const url = new URL(href); if (url.hostname === "localhost" && currentHostname !== "localhost") { url.hostname = currentHostname; } return url.toString(); } catch { return href; } } // Liefert serverseitig/beim ersten Client-Render immer den unveraenderten // Original-Href (identisch zu SSR), damit React 19 keine // Hydration-Mismatch-Warnung wirft. Der Hostname-Tausch passiert erst in // einem useEffect nach dem Mount, analog zum bestehenden themeInit-Pattern. function resolveHrefClientOnly(href: string): string { return resolveModuleHref(href); } function tenantLabel(): string { if (typeof process === "undefined") return ""; return process.env.NEXT_PUBLIC_TENANT_SLUG ?? process.env.NEXT_PUBLIC_MAIL_TENANT_SLUG ?? "demo"; } export function ModuleNavShell({ activeKey, children }: ModuleNavShellProps) { const [hrefs, setHrefs] = useState>({}); useEffect(() => { setHrefs(Object.fromEntries(MODULE_REGISTRY.map((m) => [m.key, resolveHrefClientOnly(m.href)]))); }, []); const modules = MODULE_REGISTRY.map((m) => ({ key: m.key, label: m.label, href: hrefs[m.key] ?? m.href, active: m.key === activeKey, })); return ( {children} ); }