YearsExplorer.jsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. "use client";
  2. import React from "react";
  3. import Link from "next/link";
  4. import { CalendarDays, RefreshCw } from "lucide-react";
  5. import { getYears } from "@/lib/frontend/apiClient";
  6. import { branchPath, yearPath } from "@/lib/frontend/routes";
  7. import { sortNumericStringsDesc } from "@/lib/frontend/explorer/sorters";
  8. import { mapExplorerError } from "@/lib/frontend/explorer/errorMapping";
  9. import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
  10. import { useExplorerQuery } from "@/lib/frontend/hooks/useExplorerQuery";
  11. import { useDebouncedVisibility } from "@/lib/frontend/hooks/useDebouncedVisibility";
  12. import { LOADING_UI_DELAY_MS } from "@/lib/frontend/ui/uxTimings";
  13. import ExplorerBreadcrumbs from "@/components/explorer/breadcrumbs/ExplorerBreadcrumbs";
  14. import ExplorerPageShell from "@/components/explorer/ExplorerPageShell";
  15. import ExplorerSectionCard from "@/components/explorer/ExplorerSectionCard";
  16. import ExplorerLoading from "@/components/explorer/states/ExplorerLoading";
  17. import ExplorerEmpty from "@/components/explorer/states/ExplorerEmpty";
  18. import ExplorerError from "@/components/explorer/states/ExplorerError";
  19. import ExplorerNotFound from "@/components/explorer/states/ExplorerNotFound";
  20. import ForbiddenView from "@/components/system/ForbiddenView";
  21. import { Button } from "@/components/ui/button";
  22. export default function YearsExplorer({ branch }) {
  23. const loadFn = React.useCallback(() => getYears(branch), [branch]);
  24. const { status, data, error, retry } = useExplorerQuery(loadFn, [loadFn]);
  25. const mapped = React.useMemo(() => mapExplorerError(error), [error]);
  26. const showLoadingUi = useDebouncedVisibility(status === "loading", {
  27. delayMs: LOADING_UI_DELAY_MS,
  28. minVisibleMs: 0,
  29. });
  30. React.useEffect(() => {
  31. if (mapped?.kind !== "unauthenticated") return;
  32. const next =
  33. typeof window !== "undefined"
  34. ? `${window.location.pathname}${window.location.search}`
  35. : branchPath(branch);
  36. window.location.replace(
  37. buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }),
  38. );
  39. }, [mapped?.kind, branch]);
  40. const breadcrumbsNode = <ExplorerBreadcrumbs branch={branch} />;
  41. const actions = (
  42. <Button variant="outline" size="sm" onClick={retry} title="Aktualisieren">
  43. <RefreshCw className="h-4 w-4" />
  44. Aktualisieren
  45. </Button>
  46. );
  47. if (showLoadingUi) {
  48. return (
  49. <ExplorerPageShell
  50. title="Jahre"
  51. description="Wählen Sie ein Jahr, um die Lieferscheine anzuzeigen."
  52. breadcrumbs={breadcrumbsNode}
  53. actions={actions}
  54. >
  55. <ExplorerSectionCard title="Verfügbare Jahre" description="Lade Daten…">
  56. <ExplorerLoading variant="grid" count={8} />
  57. </ExplorerSectionCard>
  58. </ExplorerPageShell>
  59. );
  60. }
  61. if (status === "loading") {
  62. return (
  63. <ExplorerPageShell
  64. title="Jahre"
  65. description="Wählen Sie ein Jahr, um die Lieferscheine anzuzeigen."
  66. breadcrumbs={breadcrumbsNode}
  67. actions={actions}
  68. >
  69. <div className="h-16" aria-hidden="true" />
  70. </ExplorerPageShell>
  71. );
  72. }
  73. if (status === "error" && mapped) {
  74. if (mapped.kind === "forbidden") {
  75. return <ForbiddenView attemptedBranch={branch} />;
  76. }
  77. if (mapped.kind === "notfound") {
  78. return (
  79. <ExplorerPageShell
  80. title="Jahre"
  81. description="Wählen Sie ein Jahr, um die Lieferscheine anzuzeigen."
  82. breadcrumbs={breadcrumbsNode}
  83. actions={actions}
  84. >
  85. <ExplorerNotFound branchRootHref={branchPath(branch)} />
  86. </ExplorerPageShell>
  87. );
  88. }
  89. if (mapped.kind === "unauthenticated") {
  90. return (
  91. <ExplorerPageShell
  92. title="Jahre"
  93. description="Sitzung abgelaufen — Weiterleitung zum Login…"
  94. breadcrumbs={breadcrumbsNode}
  95. >
  96. <div className="h-16" aria-hidden="true" />
  97. </ExplorerPageShell>
  98. );
  99. }
  100. return (
  101. <ExplorerPageShell
  102. title="Jahre"
  103. description="Wählen Sie ein Jahr, um die Lieferscheine anzuzeigen."
  104. breadcrumbs={breadcrumbsNode}
  105. actions={actions}
  106. >
  107. <ExplorerSectionCard title="Verfügbare Jahre" description="Fehler">
  108. <ExplorerError
  109. title={mapped.title}
  110. description={mapped.description}
  111. onRetry={retry}
  112. />
  113. </ExplorerSectionCard>
  114. </ExplorerPageShell>
  115. );
  116. }
  117. const years = Array.isArray(data?.years) ? data.years : [];
  118. const sorted = sortNumericStringsDesc(years);
  119. return (
  120. <ExplorerPageShell
  121. title="Jahre"
  122. description="Wählen Sie ein Jahr, um die Lieferscheine anzuzeigen."
  123. breadcrumbs={breadcrumbsNode}
  124. actions={actions}
  125. >
  126. <ExplorerSectionCard
  127. title="Verfügbare Jahre"
  128. description={`Niederlassung ${branch}`}
  129. headerRight={
  130. <span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
  131. {sorted.length} Jahr{sorted.length === 1 ? "" : "e"}
  132. </span>
  133. }
  134. >
  135. {sorted.length === 0 ? (
  136. <ExplorerEmpty
  137. title="Keine Jahre gefunden"
  138. description="Für diese Niederlassung wurden keine Jahre gefunden."
  139. upHref={null}
  140. />
  141. ) : (
  142. <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
  143. {sorted.map((y) => (
  144. <Button
  145. key={y}
  146. variant="outline"
  147. className="w-full justify-start"
  148. asChild
  149. >
  150. <Link href={yearPath(branch, y)}>
  151. <CalendarDays className="h-4 w-4" />
  152. {y}
  153. </Link>
  154. </Button>
  155. ))}
  156. </div>
  157. )}
  158. </ExplorerSectionCard>
  159. </ExplorerPageShell>
  160. );
  161. }