"use client"; import React from "react"; import { CalendarRange, X } from "lucide-react"; import { formatIsoDateDe, formatIsoDateRangeLabelDe, toDateFromIsoDateYmd, toIsoDateYmdFromDate, compareIsoDatesYmd, normalizeIsoDateYmdOrNull, } from "@/lib/frontend/search/dateRange"; import { buildDatePresets } from "@/lib/frontend/search/datePresets"; import { Calendar } from "@/components/ui/calendar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; const FIELD = Object.freeze({ FROM: "from", TO: "to", }); function pickInitialActiveField(from, to) { if (!from) return FIELD.FROM; if (from && !to) return FIELD.TO; return FIELD.FROM; } function focusById(id) { if (typeof document === "undefined") return; const el = document.getElementById(id); if (el && typeof el.focus === "function") el.focus(); } function buildDisplayedRange(from, to) { const fIso = normalizeIsoDateYmdOrNull(from); const tIso = normalizeIsoDateYmdOrNull(to); const f = toDateFromIsoDateYmd(fIso); const t = toDateFromIsoDateYmd(tIso); if (f && t && compareIsoDatesYmd(fIso, tIso) <= 0) { return { from: f, to: t }; } if (f) return { from: f, to: undefined }; if (t) return { from: t, to: undefined }; return undefined; } export default function SearchDateRangePicker({ from, to, onDateRangeChange, isSubmitting, }) { const [open, setOpen] = React.useState(false); const [activeField, setActiveField] = React.useState(() => pickInitialActiveField(from, to) ); const fromBtnId = React.useId(); const toBtnId = React.useId(); const fromDate = React.useMemo(() => toDateFromIsoDateYmd(from), [from]); const toDate = React.useMemo(() => toDateFromIsoDateYmd(to), [to]); const now = React.useMemo(() => new Date(), []); const presets = React.useMemo(() => buildDatePresets({ now }), [now]); // Calendar month anchor (controlled) so we can keep UX stable when switching fields. const [month, setMonth] = React.useState(() => fromDate || toDate || now); // When popover opens: pick a sensible active field and month anchor. React.useEffect(() => { if (!open) return; const nextActive = pickInitialActiveField(from, to); setActiveField(nextActive); const nextMonth = nextActive === FIELD.FROM ? fromDate || toDate || now : toDate || fromDate || now; setMonth(nextMonth); queueMicrotask(() => { focusById(nextActive === FIELD.FROM ? fromBtnId : toBtnId); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); const label = formatIsoDateRangeLabelDe({ from, to }) || "Zeitraum"; const fromLabel = formatIsoDateDe(from) || "Startdatum"; const toLabel = formatIsoDateDe(to) || "Enddatum"; const canClearBoth = Boolean((from || to) && !isSubmitting); const canClearFrom = Boolean(from && !isSubmitting); const canClearTo = Boolean(to && !isSubmitting); const activeBlue = "border-blue-600 bg-blue-50 dark:border-blue-900 dark:bg-blue-950"; const fromYear = 2000; const toYear = now.getFullYear() + 1; // Show the selected range visually (works for presets too). const displayedRange = React.useMemo(() => { return buildDisplayedRange(from, to); }, [from, to]); function commit(nextFrom, nextTo) { if (typeof onDateRangeChange !== "function") return; onDateRangeChange({ from: nextFrom, to: nextTo }); } function setActive(field) { setActiveField(field); const nextMonth = field === FIELD.FROM ? fromDate || month : toDate || month; if (nextMonth) setMonth(nextMonth); queueMicrotask(() => { focusById(field === FIELD.FROM ? fromBtnId : toBtnId); }); } function clearFrom() { if (!canClearFrom) return; commit(null, to ?? null); setActiveField(FIELD.FROM); setMonth(toDate || now); queueMicrotask(() => focusById(fromBtnId)); } function clearTo() { if (!canClearTo) return; commit(from ?? null, null); setActiveField(FIELD.TO); setMonth(fromDate || now); queueMicrotask(() => focusById(toBtnId)); } function clearBoth() { if (!canClearBoth) return; commit(null, null); setActiveField(FIELD.FROM); setMonth(now); queueMicrotask(() => focusById(fromBtnId)); } function applyPreset(preset) { if (!preset?.from || !preset?.to) return; commit(preset.from, preset.to); // Keep popover open so the user immediately sees the range highlighted. setActiveField(FIELD.TO); const nextMonth = toDateFromIsoDateYmd(preset.from) || now; setMonth(nextMonth); queueMicrotask(() => focusById(toBtnId)); } function handleDayClick(day) { if (!(day instanceof Date) || Number.isNaN(day.getTime())) return; const iso = toIsoDateYmdFromDate(day); if (!iso) return; if (activeField === FIELD.FROM) { commit(iso, to ?? null); // After selecting FROM, auto-switch to TO (fast range building). setActiveField(FIELD.TO); setMonth(day); queueMicrotask(() => focusById(toBtnId)); return; } // TO commit(from ?? null, iso); setMonth(day); // Keep popover open; closing is user-controlled (click outside). } return (
Schnellwahl