QuickNav.jsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. if (!isAdminDev) return;
  56. let cancelled = false;
  57. setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null });
  58. (async () => {
  59. try {
  60. const res = await getBranches();
  61. if (cancelled) return;
  62. const branches = Array.isArray(res?.branches) ? res.branches : [];
  63. setBranchList({ status: BRANCH_LIST_STATE.READY, branches });
  64. if (!selectedBranch && branches.length > 0) {
  65. setSelectedBranch(branches[0]);
  66. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, branches[0]);
  67. }
  68. } catch (err) {
  69. if (cancelled) return;
  70. console.error("[QuickNav] getBranches failed:", err);
  71. setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
  72. }
  73. })();
  74. return () => {
  75. cancelled = true;
  76. };
  77. }, [isAdminDev, selectedBranch]);
  78. React.useEffect(() => {
  79. if (!isAdminDev) return;
  80. if (!selectedBranch) return;
  81. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, selectedBranch);
  82. }, [isAdminDev, selectedBranch]);
  83. if (!isAuthenticated) return null;
  84. const effectiveBranch = isBranchUser ? user.branchId : selectedBranch;
  85. const canNavigate = Boolean(
  86. effectiveBranch && isValidBranchParam(effectiveBranch)
  87. );
  88. function navigateToBranchKeepingContext(nextBranch) {
  89. if (typeof window === "undefined") return;
  90. if (!isValidBranchParam(nextBranch)) return;
  91. const nextUrl = buildNextUrlForBranchSwitch({
  92. pathname: window.location.pathname || "/",
  93. search: window.location.search || "",
  94. nextBranch,
  95. });
  96. if (!nextUrl) return;
  97. window.location.assign(nextUrl);
  98. }
  99. return (
  100. <div className="hidden items-center gap-2 md:flex">
  101. {isAdminDev ? (
  102. <DropdownMenu>
  103. <DropdownMenuTrigger asChild>
  104. <Button
  105. variant="outline"
  106. size="sm"
  107. type="button"
  108. title="Niederlassung auswählen"
  109. >
  110. {canNavigate ? effectiveBranch : "Niederlassung wählen"}
  111. </Button>
  112. </DropdownMenuTrigger>
  113. <DropdownMenuContent align="end" className="min-w-[14rem]">
  114. <DropdownMenuLabel>Niederlassung</DropdownMenuLabel>
  115. <DropdownMenuSeparator />
  116. {branchList.status === BRANCH_LIST_STATE.ERROR ? (
  117. <div className="px-2 py-2 text-xs text-muted-foreground">
  118. Konnte nicht geladen werden.
  119. </div>
  120. ) : (
  121. <DropdownMenuRadioGroup
  122. value={canNavigate ? effectiveBranch : ""}
  123. onValueChange={(value) => {
  124. if (!value) return;
  125. if (!isValidBranchParam(value)) return;
  126. setSelectedBranch(value);
  127. safeWriteLocalStorageBranch(STORAGE_KEY_LAST_BRANCH, value);
  128. navigateToBranchKeepingContext(value);
  129. }}
  130. >
  131. {(Array.isArray(branchList.branches)
  132. ? branchList.branches
  133. : []
  134. ).map((b) => (
  135. <DropdownMenuRadioItem key={b} value={b}>
  136. {b}
  137. </DropdownMenuRadioItem>
  138. ))}
  139. </DropdownMenuRadioGroup>
  140. )}
  141. </DropdownMenuContent>
  142. </DropdownMenu>
  143. ) : null}
  144. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  145. <Link
  146. href={canNavigate ? branchPath(effectiveBranch) : "#"}
  147. title="Explorer öffnen"
  148. >
  149. Explorer
  150. </Link>
  151. </Button>
  152. <Button variant="outline" size="sm" asChild disabled={!canNavigate}>
  153. <Link
  154. href={canNavigate ? searchPath(effectiveBranch) : "#"}
  155. title="Suche öffnen"
  156. >
  157. Suche
  158. </Link>
  159. </Button>
  160. </div>
  161. );
  162. }