Parcourir la source

RHL-025 feat(date-range): add SearchDateFilterChip and SearchDateRangePicker components for date selection

Code_Uwe il y a 2 semaines
Parent
commit
8e68e1b4b8

+ 40 - 0
components/search/form/SearchDateFilterChip.jsx

@@ -0,0 +1,40 @@
+"use client";
+
+import React from "react";
+import { X } from "lucide-react";
+
+import { formatIsoDateRangeLabelDe } from "@/lib/frontend/search/dateRange";
+
+import { Badge } from "@/components/ui/badge";
+
+export default function SearchDateFilterChip({
+	from,
+	to,
+	onClear,
+	isSubmitting,
+}) {
+	const label = formatIsoDateRangeLabelDe({ from, to });
+	if (!label) return null;
+
+	const canClear = typeof onClear === "function" && !isSubmitting;
+
+	return (
+		<Badge className="gap-1">
+			<span>{label}</span>
+
+			<button
+				type="button"
+				className="ml-1 inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-muted/60 hover:text-gray-100 disabled:opacity-60 cursor-pointer"
+				onClick={() => {
+					if (!canClear) return;
+					onClear();
+				}}
+				disabled={!canClear}
+				aria-label="Zeitraum entfernen"
+				title="Zeitraum entfernen"
+			>
+				<X className="h-3 w-3" />
+			</button>
+		</Badge>
+	);
+}

+ 119 - 0
components/search/form/SearchDateRangePicker.jsx

@@ -0,0 +1,119 @@
+"use client";
+
+import React from "react";
+import { CalendarRange } from "lucide-react";
+
+import { formatIsoDateRangeLabelDe } from "@/lib/frontend/search/dateRange";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+	Popover,
+	PopoverContent,
+	PopoverTrigger,
+} from "@/components/ui/popover";
+
+function toNullableYmd(value) {
+	if (typeof value !== "string") return null;
+	const s = value.trim();
+	return s ? s : null;
+}
+
+export default function SearchDateRangePicker({
+	from,
+	to,
+	onDateRangeChange,
+	isSubmitting,
+}) {
+	const [open, setOpen] = React.useState(false);
+
+	const label = formatIsoDateRangeLabelDe({ from, to }) || "Zeitraum";
+
+	const fromId = React.useId();
+	const toId = React.useId();
+
+	const canClear = Boolean((from || to) && !isSubmitting);
+
+	return (
+		<div className="grid gap-2">
+			<Label>Zeitraum</Label>
+
+			<Popover open={open} onOpenChange={setOpen}>
+				<PopoverTrigger asChild>
+					<Button
+						type="button"
+						variant="outline"
+						disabled={isSubmitting}
+						title="Zeitraum auswählen"
+						className="justify-between"
+					>
+						<span className="truncate">{label}</span>
+						<CalendarRange className="h-4 w-4 opacity-70" />
+					</Button>
+				</PopoverTrigger>
+
+				<PopoverContent align="start" className="w-80">
+					<div className="space-y-4">
+						<div className="grid gap-2">
+							<Label htmlFor={fromId}>Von</Label>
+							<Input
+								id={fromId}
+								type="date"
+								value={from || ""}
+								disabled={isSubmitting}
+								onChange={(e) => {
+									if (typeof onDateRangeChange !== "function") return;
+									onDateRangeChange({
+										from: toNullableYmd(e.target.value),
+										to: to ?? null,
+									});
+								}}
+							/>
+						</div>
+
+						<div className="grid gap-2">
+							<Label htmlFor={toId}>Bis</Label>
+							<Input
+								id={toId}
+								type="date"
+								value={to || ""}
+								disabled={isSubmitting}
+								onChange={(e) => {
+									if (typeof onDateRangeChange !== "function") return;
+									onDateRangeChange({
+										from: from ?? null,
+										to: toNullableYmd(e.target.value),
+									});
+								}}
+							/>
+						</div>
+
+						<div className="flex justify-end gap-2">
+							<Button
+								type="button"
+								variant="outline"
+								size="sm"
+								disabled={!canClear}
+								onClick={() => {
+									if (!canClear) return;
+									if (typeof onDateRangeChange !== "function") return;
+
+									onDateRangeChange({ from: null, to: null });
+								}}
+								title="Zeitraum entfernen"
+							>
+								Zurücksetzen
+							</Button>
+						</div>
+
+						<div className="text-xs text-muted-foreground">
+							Tipp: Für einen einzelnen Tag setzen Sie <strong>Von</strong> und{" "}
+							<strong>Bis</strong> auf dasselbe Datum.
+						</div>
+					</div>
+				</PopoverContent>
+			</Popover>
+		</div>
+	);
+}