|
@@ -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>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|