Explorar o código

refactor(search): clean up code by standardizing useMemo dependencies and removing unused props

Code_Uwe hai 1 semana
pai
achega
1d244ce9aa

+ 12 - 13
components/search/SearchPage.jsx

@@ -80,11 +80,11 @@ export default function SearchPage({ branch: routeBranch }) {
 
 	const mappedError = React.useMemo(
 		() => mapSearchError(query.error),
-		[query.error]
+		[query.error],
 	);
 	const mappedLoadMoreError = React.useMemo(
 		() => mapSearchError(query.loadMoreError),
-		[query.loadMoreError]
+		[query.loadMoreError],
 	);
 
 	const localDateValidationError = React.useMemo(() => {
@@ -102,8 +102,8 @@ export default function SearchPage({ branch: routeBranch }) {
 		mappedError?.kind === "validation"
 			? mappedError
 			: mappedLocalDateValidation?.kind === "validation"
-			? mappedLocalDateValidation
-			: null;
+				? mappedLocalDateValidation
+				: null;
 
 	React.useEffect(() => {
 		if (mappedError?.kind !== "unauthenticated") return;
@@ -114,7 +114,7 @@ export default function SearchPage({ branch: routeBranch }) {
 				: searchPath(routeBranch);
 
 		window.location.replace(
-			buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next })
+			buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }),
 		);
 	}, [mappedError?.kind, routeBranch]);
 
@@ -122,14 +122,14 @@ export default function SearchPage({ branch: routeBranch }) {
 		(nextState) => {
 			router.push(buildSearchHref({ routeBranch, state: nextState }));
 		},
-		[router, routeBranch]
+		[router, routeBranch],
 	);
 
 	const replaceStateToUrl = React.useCallback(
 		(nextState) => {
 			router.replace(buildSearchHref({ routeBranch, state: nextState }));
 		},
-		[router, routeBranch]
+		[router, routeBranch],
 	);
 
 	const handleSubmit = React.useCallback(() => {
@@ -141,7 +141,7 @@ export default function SearchPage({ branch: routeBranch }) {
 			if (!isAdminDev) return;
 			replaceStateToUrl(buildNextStateForScopeChange({ urlState, nextScope }));
 		},
-		[isAdminDev, urlState, replaceStateToUrl]
+		[isAdminDev, urlState, replaceStateToUrl],
 	);
 
 	const handleToggleBranch = React.useCallback(
@@ -149,7 +149,7 @@ export default function SearchPage({ branch: routeBranch }) {
 			if (!isAdminDev) return;
 			replaceStateToUrl(buildNextStateForToggleBranch({ urlState, branchId }));
 		},
-		[isAdminDev, urlState, replaceStateToUrl]
+		[isAdminDev, urlState, replaceStateToUrl],
 	);
 
 	const handleClearAllBranches = React.useCallback(() => {
@@ -161,7 +161,7 @@ export default function SearchPage({ branch: routeBranch }) {
 		(nextLimit) => {
 			replaceStateToUrl({ ...urlState, limit: nextLimit });
 		},
-		[urlState, replaceStateToUrl]
+		[urlState, replaceStateToUrl],
 	);
 
 	const handleSingleBranchChange = React.useCallback(
@@ -174,7 +174,7 @@ export default function SearchPage({ branch: routeBranch }) {
 
 			router.push(href);
 		},
-		[isAdminDev, urlState, router]
+		[isAdminDev, urlState, router],
 	);
 
 	const handleDateRangeChange = React.useCallback(
@@ -185,7 +185,7 @@ export default function SearchPage({ branch: routeBranch }) {
 				to: to ?? null,
 			});
 		},
-		[urlState, replaceStateToUrl]
+		[urlState, replaceStateToUrl],
 	);
 
 	if (mappedError?.kind === "forbidden") {
@@ -269,7 +269,6 @@ export default function SearchPage({ branch: routeBranch }) {
 			>
 				<SearchResults
 					branch={routeBranch}
-					scope={urlState.scope}
 					status={query.status}
 					items={query.items}
 					total={query.total}

+ 1 - 2
components/search/SearchResults.jsx

@@ -20,7 +20,6 @@ import SearchResultsTable from "@/components/search/SearchResultsTable";
 
 export default function SearchResults({
 	branch,
-	scope,
 	status,
 	items,
 	total,
@@ -105,7 +104,7 @@ export default function SearchResults({
 				onSortModeChange={setSortMode}
 			/>
 
-			<SearchResultsTable routeBranch={branch} scope={scope} items={list} />
+			<SearchResultsTable routeBranch={branch} items={list} />
 
 			{loadMoreError ? (
 				<Alert variant="destructive">

+ 1 - 1
components/search/SearchResultsTable.jsx

@@ -19,7 +19,7 @@ import {
 	TableRow,
 } from "@/components/ui/table";
 
-export default function SearchResultsTable({ routeBranch, scope, items }) {
+export default function SearchResultsTable({ routeBranch, items }) {
 	// NOTE:
 	// - We intentionally keep the column set stable:
 	//   Branch | Date | File | Path | Actions

+ 42 - 6
components/search/form/SearchDateRangePicker.jsx

@@ -1,11 +1,11 @@
 "use client";
 
 import * as React from "react";
+import dynamic from "next/dynamic";
 import { Calendar as CalendarIcon, X } from "lucide-react";
 
 import { cn } from "@/lib/utils";
 
-import { Calendar } from "@/components/ui/calendar";
 import { Badge } from "@/components/ui/badge";
 import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
@@ -15,9 +15,32 @@ import {
 	PopoverContent,
 	PopoverTrigger,
 } from "@/components/ui/popover";
+import { Skeleton } from "@/components/ui/skeleton";
 
 import { useSearchDateRangePicker } from "@/lib/frontend/search/useSearchDateRangePicker";
 
+function CalendarLoading() {
+	return (
+		<div className="space-y-3">
+			<div className="flex gap-4">
+				<Skeleton className="h-72 w-72" />
+				<Skeleton className="h-72 w-72" />
+			</div>
+			<p className="text-xs text-muted-foreground text-center">
+				Kalender lädt…
+			</p>
+		</div>
+	);
+}
+
+const Calendar = dynamic(
+	() => import("@/components/ui/calendar").then((m) => m.Calendar),
+	{
+		ssr: false,
+		loading: CalendarLoading,
+	},
+);
+
 export default function SearchDateRangePicker({
 	from,
 	to,
@@ -44,6 +67,7 @@ export default function SearchDateRangePicker({
 		calendarSelected,
 		calendarModifiers,
 		calendarModifiersClassNames,
+		isRangeInvalid,
 		handlePickDay,
 		handleClearFrom,
 		handleClearTo,
@@ -56,6 +80,9 @@ export default function SearchDateRangePicker({
 		isSubmitting,
 	});
 
+	const fromInputId = React.useId();
+	const toInputId = React.useId();
+
 	const activeInputClass =
 		"border-blue-600 bg-blue-50 dark:border-blue-900 dark:bg-blue-950";
 
@@ -71,7 +98,7 @@ export default function SearchDateRangePicker({
 						disabled={disabled}
 						className={cn(
 							"w-[240px] justify-between font-normal",
-							!from && !to ? "text-muted-foreground" : ""
+							!from && !to ? "text-muted-foreground" : "",
 						)}
 						title="Zeitraum auswählen"
 					>
@@ -84,9 +111,10 @@ export default function SearchDateRangePicker({
 					<div className="w-fit space-y-4 p-4">
 						<div className="grid grid-cols-2 gap-4">
 							<div className="space-y-1">
-								<Label>Von</Label>
+								<Label htmlFor={fromInputId}>Von</Label>
 								<div className="relative">
 									<Input
+										id={fromInputId}
 										ref={fromRef}
 										readOnly
 										disabled={disabled}
@@ -94,7 +122,7 @@ export default function SearchDateRangePicker({
 										placeholder="TT.MM.JJJJ"
 										className={cn(
 											"pr-8",
-											activeField === "from" ? activeInputClass : ""
+											activeField === "from" ? activeInputClass : "",
 										)}
 										onFocus={() => setActiveField("from")}
 										onClick={() => setActiveField("from")}
@@ -114,9 +142,10 @@ export default function SearchDateRangePicker({
 							</div>
 
 							<div className="space-y-1">
-								<Label>Bis</Label>
+								<Label htmlFor={toInputId}>Bis</Label>
 								<div className="relative">
 									<Input
+										id={toInputId}
 										ref={toRef}
 										readOnly
 										disabled={disabled}
@@ -124,7 +153,7 @@ export default function SearchDateRangePicker({
 										placeholder="TT.MM.JJJJ"
 										className={cn(
 											"pr-8",
-											activeField === "to" ? activeInputClass : ""
+											activeField === "to" ? activeInputClass : "",
 										)}
 										onFocus={() => setActiveField("to")}
 										onClick={() => setActiveField("to")}
@@ -157,6 +186,12 @@ export default function SearchDateRangePicker({
 							onDayClick={handlePickDay}
 						/>
 
+						{isRangeInvalid ? (
+							<p className="text-xs text-destructive text-center">
+								Das Startdatum darf nicht nach dem Enddatum liegen.
+							</p>
+						) : null}
+
 						<div className="space-y-2">
 							<div className="text-sm text-muted-foreground">Schnellwahl</div>
 
@@ -211,6 +246,7 @@ export default function SearchDateRangePicker({
 								Zurücksetzen
 							</Button>
 						</div>
+
 						<p className="text-xs text-muted-foreground text-center">
 							Tipp: Für einen einzelnen Tag setzen Sie <b>Von</b> und <b>Bis</b>{" "}
 							auf dasselbe Datum.