|
|
@@ -0,0 +1,208 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import React from "react";
|
|
|
+import Link from "next/link";
|
|
|
+
|
|
|
+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 { 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",
|
|
|
+});
|
|
|
+
|
|
|
+function readRouteBranchFromPathname(pathname) {
|
|
|
+ if (typeof pathname !== "string" || !pathname.startsWith("/")) return null;
|
|
|
+
|
|
|
+ const seg = pathname.split("/").filter(Boolean)[0] || null;
|
|
|
+ return seg && isValidBranchParam(seg) ? seg : null;
|
|
|
+}
|
|
|
+
|
|
|
+function safeReadLocalStorageBranch() {
|
|
|
+ if (typeof window === "undefined") return null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const raw = window.localStorage.getItem(STORAGE_KEY_LAST_BRANCH);
|
|
|
+ return raw && isValidBranchParam(raw) ? raw : null;
|
|
|
+ } catch {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function safeWriteLocalStorageBranch(branch) {
|
|
|
+ if (typeof window === "undefined") return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ window.localStorage.setItem(STORAGE_KEY_LAST_BRANCH, String(branch));
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default function QuickNav() {
|
|
|
+ const { status, user } = useAuth();
|
|
|
+
|
|
|
+ const isAuthenticated = status === "authenticated" && user;
|
|
|
+ const isAdminDev =
|
|
|
+ isAuthenticated && (user.role === "admin" || user.role === "dev");
|
|
|
+ const isBranchUser = isAuthenticated && user.role === "branch";
|
|
|
+
|
|
|
+ const [selectedBranch, setSelectedBranch] = React.useState(null);
|
|
|
+
|
|
|
+ const [branchList, setBranchList] = React.useState({
|
|
|
+ status: BRANCH_LIST_STATE.IDLE,
|
|
|
+ branches: null,
|
|
|
+ });
|
|
|
+
|
|
|
+ // Determine a good default branch:
|
|
|
+ // - branch users: always their own
|
|
|
+ // - admin/dev: prefer current route branch, else last stored branch, else first fetched branch
|
|
|
+ React.useEffect(() => {
|
|
|
+ if (!isAuthenticated) return;
|
|
|
+
|
|
|
+ if (isBranchUser) {
|
|
|
+ const own = user.branchId;
|
|
|
+ setSelectedBranch(own && isValidBranchParam(own) ? own : null);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // admin/dev
|
|
|
+ const fromRoute = readRouteBranchFromPathname(window.location.pathname);
|
|
|
+ const fromStorage = safeReadLocalStorageBranch();
|
|
|
+
|
|
|
+ const initial = fromRoute || fromStorage || null;
|
|
|
+ if (initial) setSelectedBranch(initial);
|
|
|
+ }, [isAuthenticated, isBranchUser, user?.branchId]);
|
|
|
+
|
|
|
+ // Fetch branches for admin/dev dropdown (fail-open; used only for convenience).
|
|
|
+ React.useEffect(() => {
|
|
|
+ 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 });
|
|
|
+
|
|
|
+ // If nothing selected yet, pick the first available branch as a convenience default.
|
|
|
+ if (!selectedBranch && branches.length > 0) {
|
|
|
+ setSelectedBranch(branches[0]);
|
|
|
+ safeWriteLocalStorageBranch(branches[0]);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ if (cancelled) return;
|
|
|
+
|
|
|
+ console.error("[QuickNav] getBranches failed:", err);
|
|
|
+ setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ cancelled = true;
|
|
|
+ };
|
|
|
+ }, [isAdminDev, selectedBranch]);
|
|
|
+
|
|
|
+ React.useEffect(() => {
|
|
|
+ // Persist selection for admin/dev convenience.
|
|
|
+ if (!isAdminDev) return;
|
|
|
+ if (!selectedBranch) return;
|
|
|
+
|
|
|
+ safeWriteLocalStorageBranch(selectedBranch);
|
|
|
+ }, [isAdminDev, selectedBranch]);
|
|
|
+
|
|
|
+ if (!isAuthenticated) return null;
|
|
|
+
|
|
|
+ const effectiveBranch = isBranchUser ? user.branchId : selectedBranch;
|
|
|
+ const canNavigate = Boolean(
|
|
|
+ effectiveBranch && isValidBranchParam(effectiveBranch)
|
|
|
+ );
|
|
|
+
|
|
|
+ // Keep TopNav clean on small screens.
|
|
|
+ return (
|
|
|
+ <div className="hidden items-center gap-2 md:flex">
|
|
|
+ {isAdminDev ? (
|
|
|
+ <DropdownMenu>
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ type="button"
|
|
|
+ title="Niederlassung auswählen"
|
|
|
+ >
|
|
|
+ {canNavigate ? effectiveBranch : "Niederlassung wählen"}
|
|
|
+ </Button>
|
|
|
+ </DropdownMenuTrigger>
|
|
|
+
|
|
|
+ <DropdownMenuContent align="end" className="min-w-[14rem]">
|
|
|
+ <DropdownMenuLabel>Niederlassung</DropdownMenuLabel>
|
|
|
+ <DropdownMenuSeparator />
|
|
|
+
|
|
|
+ {branchList.status === BRANCH_LIST_STATE.ERROR ? (
|
|
|
+ <div className="px-2 py-2 text-xs text-muted-foreground">
|
|
|
+ Konnte nicht geladen werden.
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <DropdownMenuRadioGroup
|
|
|
+ value={canNavigate ? effectiveBranch : ""}
|
|
|
+ onValueChange={(value) => {
|
|
|
+ if (!value) return;
|
|
|
+ setSelectedBranch(value);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {(Array.isArray(branchList.branches)
|
|
|
+ ? branchList.branches
|
|
|
+ : []
|
|
|
+ ).map((b) => (
|
|
|
+ <DropdownMenuRadioItem key={b} value={b}>
|
|
|
+ {b}
|
|
|
+ </DropdownMenuRadioItem>
|
|
|
+ ))}
|
|
|
+ </DropdownMenuRadioGroup>
|
|
|
+ )}
|
|
|
+ </DropdownMenuContent>
|
|
|
+ </DropdownMenu>
|
|
|
+ ) : null}
|
|
|
+
|
|
|
+ <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
|
|
|
+ <Link
|
|
|
+ href={canNavigate ? branchPath(effectiveBranch) : "#"}
|
|
|
+ title="Explorer öffnen"
|
|
|
+ >
|
|
|
+ Explorer
|
|
|
+ </Link>
|
|
|
+ </Button>
|
|
|
+
|
|
|
+ <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
|
|
|
+ <Link
|
|
|
+ href={canNavigate ? searchPath(effectiveBranch) : "#"}
|
|
|
+ title="Suche öffnen"
|
|
|
+ >
|
|
|
+ Suche
|
|
|
+ </Link>
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|