QuickNav.jsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 {
  9. buildNextUrlForBranchSwitch,
  10. readRouteBranchFromPathname,
  11. safeReadLocalStorageBranch,
  12. safeWriteLocalStorageBranch,
  13. } from "@/lib/frontend/quickNav/branchSwitch";
  14. import { Button } from "@/components/ui/button";
  15. import {
  16. DropdownMenu,
  17. DropdownMenuContent,
  18. DropdownMenuLabel,
  19. DropdownMenuRadioGroup,
  20. DropdownMenuRadioItem,
  21. DropdownMenuSeparator,
  22. DropdownMenuTrigger,
  23. } from "@/components/ui/dropdown-menu";
  24. const STORAGE_KEY_LAST_BRANCH = "rhl_last_branch";
  25. const BRANCH_LIST_STATE = Object.freeze({
  26. IDLE: "idle",
  27. LOADING: "loading",
  28. READY: "ready",
  29. ERROR: "error",
  30. });
  31. export default function QuickNav() {
  32. const { status, user } = useAuth();
  33. const isAuthenticated = status === "authenticated" && user;
  34. const isAdminDev =
  35. isAuthenticated && (user.role === "admin" || user.role === "dev");
  36. const isBranchUser = isAuthenticated && user.role === "branch";
  37. const [selectedBranch, setSelectedBranch] = React.useState(null);
  38. const [branchList, setBranchList] = React.useState({
  39. status: BRANCH_LIST_STATE.IDLE,
  40. branches: null,
  41. });
  42. React.useEffect(() => {
  43. if (!isAuthenticated) return;
  44. if (isBranchUser) {
  45. const own = user.branchId;
  46. setSelectedBranch(own && isValidBranchParam(own) ? own : null);
  47. return;
  48. }
  49. const fromRoute = readRouteBranchFromPathname(window.location.pathname);
  50. const fromStorage = safeReadLocalStorageBranch(STORAGE_KEY_LAST_BRANCH);
  51. const initial = fromRoute || fromStorage || null;
  52. if (initial) setSelectedBranch(initial);
  53. }, [isAuthenticated, isBranchUser, user?.branchId]);
  54. React.useEffect(() => {
  55. // B.4 fix:
  56. // - Fetch the branch list once for admin/dev users (or when the user changes),
  57. // not on every selectedBranch change.
  58. if (!isAdminDev) return;
  59. let cancelled = false;
  60. setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null });
  61. (async () => {
  62. try {
  63. const res = await getBranches();
  64. if (cancelled) return;
  65. const branches = Array.isArray(res?.branches) ? res.branches : [];
  66. setBranchList({ status: BRANCH_LIST_STATE.READY, branches });
  67. } catch (err) {
  68. if (cancelled) return;
  69. console.error("[QuickNav] getBranches failed:", err);
  70. setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
  71. }
  72. })();
  73. return () => {
  74. cancelled = true;
  75. };
  76. }, [isAdminDev, user?.userId]);
  77. React.useEffect(() => {
  78. // After we have the branch list, ensure selectedBranch is valid and known.
  79. // This effect does NOT trigger any refetches (only local state).
  80. if (!isAdminDev) return;
  81. if (branchList.status !== BRANCH_LIST_STATE.READY) return;
  82. const branches = Array.isArray(branchList.branches)
  83. ? branchList.branches
  84. : [];
  85. if (branches.length === 0) return;
  86. // If no selection yet (or selection is no longer in the list), choose a stable default.
  87. if (!selectedBranch || !branches.includes(selectedBranch)) {
  88. const next = branches[0];
  89. setSelectedBranch(next);
  90. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, next);
  91. }
  92. }, [isAdminDev, branchList.status, branchList.branches, selectedBranch]);
  93. React.useEffect(() => {
  94. if (!isAdminDev) return;
  95. if (!selectedBranch) return;
  96. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, selectedBranch);
  97. }, [isAdminDev, selectedBranch]);
  98. if (!isAuthenticated) return null;
  99. const effectiveBranch = isBranchUser ? user.branchId : selectedBranch;
  100. const canNavigate = Boolean(
  101. effectiveBranch && isValidBranchParam(effectiveBranch)
  102. );
  103. function navigateToBranchKeepingContext(nextBranch) {
  104. if (typeof window === "undefined") return;
  105. if (!isValidBranchParam(nextBranch)) return;
  106. const nextUrl = buildNextUrlForBranchSwitch({
  107. pathname: window.location.pathname || "/",
  108. search: window.location.search || "",
  109. nextBranch,
  110. });
  111. if (!nextUrl) return;
  112. window.location.assign(nextUrl);
  113. }
  114. return (
  115. <div className="hidden items-center gap-2 md:flex">
  116. {isAdminDev ? (
  117. <DropdownMenu>
  118. <DropdownMenuTrigger asChild>
  119. <Button
  120. variant="outline"
  121. size="sm"
  122. type="button"
  123. title="Niederlassung auswählen"
  124. >
  125. {canNavigate ? effectiveBranch : "Niederlassung wählen"}
  126. </Button>
  127. </DropdownMenuTrigger>
  128. <DropdownMenuContent align="end" className="min-w-56">
  129. <DropdownMenuLabel>Niederlassung</DropdownMenuLabel>
  130. <DropdownMenuSeparator />
  131. {branchList.status === BRANCH_LIST_STATE.ERROR ? (
  132. <div className="px-2 py-2 text-xs text-muted-foreground">
  133. Konnte nicht geladen werden.
  134. </div>
  135. ) : (
  136. <DropdownMenuRadioGroup
  137. value={canNavigate ? effectiveBranch : ""}
  138. onValueChange={(value) => {
  139. if (!value) return;
  140. if (!isValidBranchParam(value)) return;
  141. setSelectedBranch(value);
  142. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, value);
  143. navigateToBranchKeepingContext(value);
  144. }}
  145. >
  146. {(Array.isArray(branchList.branches)
  147. ? branchList.branches
  148. : []
  149. ).map((b) => (
  150. <DropdownMenuRadioItem key={b} value={b}>
  151. {b}
  152. </DropdownMenuRadioItem>
  153. ))}
  154. </DropdownMenuRadioGroup>
  155. )}
  156. </DropdownMenuContent>
  157. </DropdownMenu>
  158. ) : null}
  159. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  160. <Link
  161. href={canNavigate ? branchPath(effectiveBranch) : "#"}
  162. title="Explorer öffnen"
  163. >
  164. Explorer
  165. </Link>
  166. </Button>
  167. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  168. <Link
  169. href={canNavigate ? searchPath(effectiveBranch) : "#"}
  170. title="Suche öffnen"
  171. >
  172. Suche
  173. </Link>
  174. </Button>
  175. </div>
  176. );
  177. }