"use client"; import React from "react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { FolderOpen, Search as SearchIcon } from "lucide-react"; import { useAuth } from "@/components/auth/authContext"; import { getBranches } from "@/lib/frontend/apiClient"; import { branchPath, searchPath } from "@/lib/frontend/routes"; import { isValidBranchParam } from "@/lib/frontend/params"; import { buildNextUrlForBranchSwitch, readRouteBranchFromPathname, safeReadLocalStorageBranch, safeWriteLocalStorageBranch, } from "@/lib/frontend/quickNav/branchSwitch"; import { getPrimaryNavFromPathname, PRIMARY_NAV, } from "@/lib/frontend/nav/activeRoute"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; const STORAGE_KEY_LAST_BRANCH = "rhl_last_branch"; const BRANCH_LIST_STATE = Object.freeze({ IDLE: "idle", LOADING: "loading", READY: "ready", ERROR: "error", }); export default function QuickNav() { const router = useRouter(); const pathname = usePathname() || "/"; const { status, user, retry } = useAuth(); const isAuthenticated = status === "authenticated" && user; const isAdminDev = isAuthenticated && (user.role === "admin" || user.role === "dev"); const isBranchUser = isAuthenticated && user.role === "branch"; const canRevalidate = typeof retry === "function"; // Persisted selection for admin/dev: // - Used as the "safe fallback" when the route branch is invalid/non-existent. const [selectedBranch, setSelectedBranch] = React.useState(null); const [branchList, setBranchList] = React.useState({ status: BRANCH_LIST_STATE.IDLE, branches: null, }); const activePrimaryNav = React.useMemo(() => { return getPrimaryNavFromPathname(pathname); }, [pathname]); const isExplorerActive = activePrimaryNav?.active === PRIMARY_NAV.EXPLORER; const isSearchActive = activePrimaryNav?.active === PRIMARY_NAV.SEARCH; const routeBranch = React.useMemo(() => { return readRouteBranchFromPathname(pathname); }, [pathname]); const knownBranches = branchList.status === BRANCH_LIST_STATE.READY && Array.isArray(branchList.branches) ? branchList.branches : null; const isKnownRouteBranch = React.useMemo(() => { if (!routeBranch) return false; if (!knownBranches) return false; // do not "trust" route until we know the branch list return knownBranches.includes(routeBranch); }, [routeBranch, knownBranches]); React.useEffect(() => { if (!isAuthenticated) return; // Branch users: fixed branch, no persisted selection needed. if (isBranchUser) { const own = user.branchId; setSelectedBranch(own && isValidBranchParam(own) ? own : null); return; } // Admin/dev: initialize from localStorage only. // IMPORTANT: // We do NOT initialize from the route branch, because invalid-but-syntactically-valid // branches (e.g. NL200) would pollute state and cause thrashing. const fromStorage = safeReadLocalStorageBranch(STORAGE_KEY_LAST_BRANCH); if (fromStorage && fromStorage !== selectedBranch) { setSelectedBranch(fromStorage); } }, [isAuthenticated, isBranchUser, user?.branchId, selectedBranch]); React.useEffect(() => { // Fetch the branch list once for admin/dev users (or when the user changes). if (!isAdminDev) return; let cancelled = false; setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null }); (async () => { try { const res = await getBranches(); if (cancelled) return; const branches = Array.isArray(res?.branches) ? res.branches : []; setBranchList({ status: BRANCH_LIST_STATE.READY, branches }); } catch (err) { if (cancelled) return; // Fail open: do not block navigation if validation fails. console.error("[QuickNav] getBranches failed:", err); setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null }); } })(); return () => { cancelled = true; }; }, [isAdminDev, user?.userId]); React.useEffect(() => { // Once we know the branch list, keep selectedBranch valid and stable. if (!isAdminDev) return; if (!knownBranches || knownBranches.length === 0) return; if (!selectedBranch || !knownBranches.includes(selectedBranch)) { const next = knownBranches[0]; setSelectedBranch(next); safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, next); } }, [isAdminDev, knownBranches, selectedBranch]); React.useEffect(() => { // Sync selectedBranch to the current route branch ONLY when that route branch is known to exist. // This prevents the "NL200 thrash" while still keeping the dropdown in sync for valid routes. if (!isAdminDev) return; if (!isKnownRouteBranch) return; if (!routeBranch) return; if (routeBranch !== selectedBranch) { setSelectedBranch(routeBranch); safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, routeBranch); } }, [isAdminDev, isKnownRouteBranch, routeBranch, selectedBranch]); React.useEffect(() => { if (!isAdminDev) return; if (!selectedBranch) return; // Keep localStorage in sync (defense-in-depth). safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, selectedBranch); }, [isAdminDev, selectedBranch]); if (!isAuthenticated) return null; // Effective branch for navigation: // - Branch users: always their own branch // - Admin/dev: always the persisted/validated selection (NOT the route branch) // This guarantees that nav buttons still work even when the user is on /NL200. const effectiveBranch = isBranchUser ? user.branchId : selectedBranch; const canNavigate = Boolean( effectiveBranch && isValidBranchParam(effectiveBranch), ); function navigateToBranchKeepingContext(nextBranch) { if (!isValidBranchParam(nextBranch)) return; const currentPathname = typeof window !== "undefined" ? window.location.pathname || pathname : pathname; const currentSearch = typeof window !== "undefined" ? window.location.search || "" : ""; const nextUrl = buildNextUrlForBranchSwitch({ pathname: currentPathname, search: currentSearch, nextBranch, }); if (!nextUrl) return; // Trigger a session revalidation without causing content flicker. if (canRevalidate) retry(); router.push(nextUrl); } const explorerVariant = isExplorerActive ? "secondary" : "ghost"; const searchVariant = isSearchActive ? "secondary" : "ghost"; return (
{isAdminDev ? ( Niederlassung {branchList.status === BRANCH_LIST_STATE.ERROR ? (
Konnte nicht geladen werden.
) : ( { if (!value) return; if (!isValidBranchParam(value)) return; setSelectedBranch(value); safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, value); navigateToBranchKeepingContext(value); }} > {(Array.isArray(branchList.branches) ? branchList.branches : [] ).map((b) => ( {b} ))} )}
) : null}
); }