"use client"; import * as React from "react"; import { Calendar as CalendarIcon, X, ChevronDown } from "lucide-react"; import { addMinutes } from "date-fns"; import { Button } from "@/src/components/ui/button"; import { Calendar } from "@/src/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/src/components/ui/popover"; import { cn } from "@/src/utils/tailwind"; import { type DateRange as RDPDateRange } from "react-day-picker"; import { format } from "date-fns"; import { useEffect, useState, useCallback } from "react"; import { setBeginningOfDay, setEndOfDay } from "@/src/utils/dates"; import { TimePicker } from "@/src/components/ui/time-picker"; import { DashboardDateRangeDropdown } from "@/src/components/date-range-dropdowns"; import { DASHBOARD_AGGREGATION_PLACEHOLDER, type DashboardDateRangeOptions, type DashboardDateRange, TIME_RANGES, formatDateRange, type TimeRange, } from "@/src/utils/date-range-utils"; import { combineDateAndTime } from "@/src/components/ui/time-picker-utils"; export function DatePicker({ date, onChange, clearable = false, className, disabled, includeTimePicker, }: { date?: Date | undefined; onChange: (date: Date | undefined) => void; clearable?: boolean; className?: string; disabled?: boolean; includeTimePicker?: boolean; }) { return (
onChange(d)} autoFocus /> {includeTimePicker && ( onChange(d)} /> )} {date && clearable && ( )}
); } export type DatePickerWithRangeProps = { dateRange?: DashboardDateRange; className?: string; selectedOption: DashboardDateRangeOptions; disabled?: React.ComponentProps["disabled"]; setDateRangeAndOption: ( option: DashboardDateRangeOptions, date?: DashboardDateRange, ) => void; }; export function DatePickerWithRange({ className, dateRange, selectedOption, setDateRangeAndOption, disabled, }: DatePickerWithRangeProps) { const [internalDateRange, setInternalDateRange] = useState< RDPDateRange | undefined >(dateRange); useEffect(() => { setInternalDateRange(dateRange); }, [dateRange]); const setNewDateRange = ( internalDateRange: RDPDateRange | undefined, newFromDate: Date | undefined, newToDate: Date | undefined, ): RDPDateRange | undefined => { return internalDateRange ? { from: newFromDate ?? internalDateRange.from, to: newToDate ?? internalDateRange.to, } : undefined; }; const updateDashboardDateRange = ( newRange: RDPDateRange | undefined, setDateRangeAndOption: ( option: DashboardDateRangeOptions, date?: DashboardDateRange, ) => void, ) => { if (newRange && newRange.from && newRange.to) { const dashboardDateRange: DashboardDateRange = { from: newRange.from, to: newRange.to, }; setDateRangeAndOption( DASHBOARD_AGGREGATION_PLACEHOLDER, dashboardDateRange, ); } }; const onCalendarSelection = (range?: RDPDateRange) => { const newRange = range ? { from: range.from ? setBeginningOfDay(range.from) : undefined, to: range.to ? setEndOfDay(range.to) : undefined, } : undefined; setInternalDateRange(newRange); updateDashboardDateRange(newRange, setDateRangeAndOption); }; const onStartTimeSelection = (date: Date | undefined) => { const newDateTime = combineDateAndTime(internalDateRange?.from, date); const newRange = setNewDateRange( internalDateRange, newDateTime, internalDateRange?.to, ); setInternalDateRange(newRange); updateDashboardDateRange(newRange, setDateRangeAndOption); }; const onEndTimeSelection = (date: Date | undefined) => { const newDateTime = combineDateAndTime(internalDateRange?.to, date); const newRange = setNewDateRange( internalDateRange, internalDateRange?.from, newDateTime, ); setInternalDateRange(newRange); updateDashboardDateRange(newRange, setDateRangeAndOption); }; return (

Start time

End time

); } export type TimeRangePickerProps = { timeRange?: TimeRange; onTimeRangeChange: (timeRange: TimeRange) => void; timeRangePresets: readonly string[]; className?: string; disabled?: boolean | { before?: Date; after?: Date } | Date | Date[]; }; export function TimeRangePicker({ className, timeRange, timeRangePresets, onTimeRangeChange, disabled, }: TimeRangePickerProps) { // Determine the range type const rangeType: "named" | "custom" | null = timeRange ? "from" in timeRange ? "custom" : "named" : null; // Disable future dates by default, plus any additional disabled prop const calendarDisabled = React.useMemo(() => { const futureDisabled = { after: new Date() }; if (!disabled) return futureDisabled; if (typeof disabled === "boolean") return disabled; // Always return an array when combining with additional restrictions const disabledArray = Array.isArray(disabled) ? disabled : [disabled]; return [...disabledArray, futureDisabled] as React.ComponentProps< typeof Calendar >["disabled"]; }, [disabled]); const namedRangeValue = rangeType === "named" && timeRange && "range" in timeRange ? timeRange.range : null; // Convert TimeRange to DateRange for internal use const dateRange = timeRange && "from" in timeRange ? timeRange : undefined; const [internalDateRange, setInternalDateRange] = useState< RDPDateRange | undefined >(dateRange); // Update internal date range when timeRange changes useEffect(() => { if (rangeType === "custom") { // Custom range - use as is setInternalDateRange(dateRange); } else if (rangeType === "named" && timeRange && "range" in timeRange) { // Preset range - look up in generic time ranges const setting = TIME_RANGES[timeRange.range as keyof typeof TIME_RANGES]; if (setting && setting.minutes) { const now = new Date(); setInternalDateRange({ from: addMinutes(now, -setting.minutes), to: now, }); } else { setInternalDateRange(undefined); } } else { setInternalDateRange(undefined); } }, [timeRange, dateRange, rangeType]); const setNewDateRange = ( internalDateRange: RDPDateRange | undefined, newFromDate: Date | undefined, newToDate: Date | undefined, ): RDPDateRange | undefined => { return internalDateRange ? { from: newFromDate ?? internalDateRange.from, to: newToDate ?? internalDateRange.to, } : undefined; }; const updateDateRange = (newRange: RDPDateRange | undefined) => { if (newRange && newRange.from && newRange.to) { onTimeRangeChange({ from: newRange.from, to: newRange.to, }); } }; const onCalendarSelection = (range?: RDPDateRange) => { const newRange = range ? { from: range.from ? setBeginningOfDay(range.from) : undefined, to: range.to ? setEndOfDay(range.to) : undefined, } : undefined; setInternalDateRange(newRange); updateDateRange(newRange); }; const onStartTimeSelection = (date: Date | undefined) => { const newDateTime = combineDateAndTime(internalDateRange?.from, date); const newRange = setNewDateRange( internalDateRange, newDateTime, internalDateRange?.to, ); setInternalDateRange(newRange); updateDateRange(newRange); }; const onEndTimeSelection = (date: Date | undefined) => { const newDateTime = combineDateAndTime(internalDateRange?.to, date); const newRange = setNewDateRange( internalDateRange, internalDateRange?.from, newDateTime, ); setInternalDateRange(newRange); updateDateRange(newRange); }; const onPresetSelection = (value: string) => { if (timeRangePresets.includes(value as keyof typeof TIME_RANGES)) { onTimeRangeChange({ range: value }); } }; const [isOpen, setIsOpen] = useState(false); const [tab, setTab] = useState<"presets" | "calendar">("presets"); const handleOpenChange = useCallback((open: boolean) => { setIsOpen(open); if (open) { setTab("presets"); } }, []); const getDisplayContent = () => { if (rangeType === "custom") { // Custom range - show calendar icon and date range return (
{dateRange ? formatDateRange(dateRange.from, dateRange.to) : "Select from calendar"}
); } else if (rangeType === "named") { // Preset range - show badge with abbreviation and label const setting = TIME_RANGES[namedRangeValue as keyof typeof TIME_RANGES]; return (
{setting?.abbreviation || namedRangeValue} {setting?.label || namedRangeValue}
); } else { // No time range selected return (
Select time range
); } }; return (
{tab === "calendar" ? ( /* Show calendar picker when in calendar mode */ <>

Start time

End time

) : ( /* Always show preset options dropdown */
{timeRangePresets.map((presetKey) => { const setting = TIME_RANGES[presetKey as keyof typeof TIME_RANGES]; return (
{ onPresetSelection(presetKey); setIsOpen(false); }} > {setting.abbreviation} {setting.label}
); })}
{ setTab("calendar"); }} > Select from calendar
)}
); }