QuickNav.jsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. "use client";
  2. import React from "react";
  3. import Link from "next/link";
  4. import { useAuth } from "@/components/auth/authContext";
  5. import { getBranches } from "@/lib/frontend/apiClient";
  6. import { branchPath, searchPath } from "@/lib/frontend/routes";
  7. import { isValidBranchParam } from "@/lib/frontend/params";
  8. import { Button } from "@/components/ui/button";
  9. import {
  10. DropdownMenu,
  11. DropdownMenuContent,
  12. DropdownMenuLabel,
  13. DropdownMenuRadioGroup,
  14. DropdownMenuRadioItem,
  15. DropdownMenuSeparator,
  16. DropdownMenuTrigger,
  17. } from "@/components/ui/dropdown-menu";
  18. const STORAGE_KEY_LAST_BRANCH = "rhl_last_branch";
  19. const BRANCH_LIST_STATE = Object.freeze({
  20. IDLE: "idle",
  21. LOADING: "loading",
  22. READY: "ready",
  23. ERROR: "error",
  24. });
  25. function readRouteBranchFromPathname(pathname) {
  26. if (typeof pathname !== "string" || !pathname.startsWith("/")) return null;
  27. const seg = pathname.split("/").filter(Boolean)[0] || null;
  28. return seg && isValidBranchParam(seg) ? seg : null;
  29. }
  30. function safeReadLocalStorageBranch() {
  31. if (typeof window === "undefined") return null;
  32. try {
  33. const raw = window.localStorage.getItem(STORAGE_KEY_LAST_BRANCH);
  34. return raw && isValidBranchParam(raw) ? raw : null;
  35. } catch {
  36. return null;
  37. }
  38. }
  39. function safeWriteLocalStorageBranch(branch) {
  40. if (typeof window === "undefined") return;
  41. try {
  42. window.localStorage.setItem(STORAGE_KEY_LAST_BRANCH, String(branch));
  43. } catch {
  44. // ignore
  45. }
  46. }
  47. export default function QuickNav() {
  48. const { status, user } = useAuth();
  49. const isAuthenticated = status === "authenticated" && user;
  50. const isAdminDev =
  51. isAuthenticated && (user.role === "admin" || user.role === "dev");
  52. const isBranchUser = isAuthenticated && user.role === "branch";
  53. const [selectedBranch, setSelectedBranch] = React.useState(null);
  54. const [branchList, setBranchList] = React.useState({
  55. status: BRANCH_LIST_STATE.IDLE,
  56. branches: null,
  57. });
  58. // Determine a good default branch:
  59. // - branch users: always their own
  60. // - admin/dev: prefer current route branch, else last stored branch, else first fetched branch
  61. React.useEffect(() => {
  62. if (!isAuthenticated) return;
  63. if (isBranchUser) {
  64. const own = user.branchId;
  65. setSelectedBranch(own && isValidBranchParam(own) ? own : null);
  66. return;
  67. }
  68. // admin/dev
  69. const fromRoute = readRouteBranchFromPathname(window.location.pathname);
  70. const fromStorage = safeReadLocalStorageBranch();
  71. const initial = fromRoute || fromStorage || null;
  72. if (initial) setSelectedBranch(initial);
  73. }, [isAuthenticated, isBranchUser, user?.branchId]);
  74. // Fetch branches for admin/dev dropdown (fail-open; used only for convenience).
  75. React.useEffect(() => {
  76. if (!isAdminDev) return;
  77. let cancelled = false;
  78. setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null });
  79. (async () => {
  80. try {
  81. const res = await getBranches();
  82. if (cancelled) return;
  83. const branches = Array.isArray(res?.branches) ? res.branches : [];
  84. setBranchList({ status: BRANCH_LIST_STATE.READY, branches });
  85. // If nothing selected yet, pick the first available branch as a convenience default.
  86. if (!selectedBranch && branches.length > 0) {
  87. setSelectedBranch(branches[0]);
  88. safeWriteLocalStorageBranch(branches[0]);
  89. }
  90. } catch (err) {
  91. if (cancelled) return;
  92. console.error("[QuickNav] getBranches failed:", err);
  93. setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
  94. }
  95. })();
  96. return () => {
  97. cancelled = true;
  98. };
  99. }, [isAdminDev, selectedBranch]);
  100. React.useEffect(() => {
  101. // Persist selection for admin/dev convenience.
  102. if (!isAdminDev) return;
  103. if (!selectedBranch) return;
  104. safeWriteLocalStorageBranch(selectedBranch);
  105. }, [isAdminDev, selectedBranch]);
  106. if (!isAuthenticated) return null;
  107. const effectiveBranch = isBranchUser ? user.branchId : selectedBranch;
  108. const canNavigate = Boolean(
  109. effectiveBranch && isValidBranchParam(effectiveBranch)
  110. );
  111. // Keep TopNav clean on small screens.
  112. return (
  113. <div className="hidden items-center gap-2 md:flex">
  114. {isAdminDev ? (
  115. <DropdownMenu>
  116. <DropdownMenuTrigger asChild>
  117. <Button
  118. variant="outline"
  119. size="sm"
  120. type="button"
  121. title="Niederlassung auswählen"
  122. >
  123. {canNavigate ? effectiveBranch : "Niederlassung wählen"}
  124. </Button>
  125. </DropdownMenuTrigger>
  126. <DropdownMenuContent align="end" className="min-w-[14rem]">
  127. <DropdownMenuLabel>Niederlassung</DropdownMenuLabel>
  128. <DropdownMenuSeparator />
  129. {branchList.status === BRANCH_LIST_STATE.ERROR ? (
  130. <div className="px-2 py-2 text-xs text-muted-foreground">
  131. Konnte nicht geladen werden.
  132. </div>
  133. ) : (
  134. <DropdownMenuRadioGroup
  135. value={canNavigate ? effectiveBranch : ""}
  136. onValueChange={(value) => {
  137. if (!value) return;
  138. setSelectedBranch(value);
  139. }}
  140. >
  141. {(Array.isArray(branchList.branches)
  142. ? branchList.branches
  143. : []
  144. ).map((b) => (
  145. <DropdownMenuRadioItem key={b} value={b}>
  146. {b}
  147. </DropdownMenuRadioItem>
  148. ))}
  149. </DropdownMenuRadioGroup>
  150. )}
  151. </DropdownMenuContent>
  152. </DropdownMenu>
  153. ) : null}
  154. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  155. <Link
  156. href={canNavigate ? branchPath(effectiveBranch) : "#"}
  157. title="Explorer öffnen"
  158. >
  159. Explorer
  160. </Link>
  161. </Button>
  162. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  163. <Link
  164. href={canNavigate ? searchPath(effectiveBranch) : "#"}
  165. title="Suche öffnen"
  166. >
  167. Suche
  168. </Link>
  169. </Button>
  170. </div>
  171. );
  172. }