diff --git a/src/components/date-time-picker.tsx b/src/components/date-time-picker.tsx index 323d64a..f2631e4 100644 --- a/src/components/date-time-picker.tsx +++ b/src/components/date-time-picker.tsx @@ -18,13 +18,6 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; import { cn } from "@/lib/utils"; interface DateTimePickerProps { @@ -35,6 +28,14 @@ interface DateTimePickerProps { className?: string; } +type QuickShortcut = "Today" | "Next week" | "Next month"; + +interface QuickShortcutResult { + keepOpen: true; + nextMonth: Date; + nextValue: string; +} + /** Parse the incoming ISO / date string into a Date object, or return undefined. */ function parseValue(value: string): Date | undefined { if (!value) return undefined; @@ -65,8 +66,55 @@ function buildValue( return `${format(date, "yyyy-MM-dd")}T${hh}:${mm}:00`; } -const HOURS = Array.from({ length: 24 }, (_, i) => i); -const MINUTES = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]; +function resolveQuickShortcutDate(shortcut: QuickShortcut, now: Date): Date { + const today = startOfDay(now); + + if (shortcut === "Today") return today; + if (shortcut === "Next week") return startOfDay(addWeeks(today, 1)); + + return startOfDay(addMonths(today, 1)); +} + +function getTimeParts(value: string, allDay: boolean) { + const parsed = parseValue(value); + + if (!parsed || allDay) { + return { hour: 0, minute: 0 }; + } + + return { + hour: parsed.getHours(), + minute: parsed.getMinutes(), + }; +} + +export function getCalendarMonthForValue( + value: string, + fallbackDate: Date, +): Date { + return parseValue(value) ?? fallbackDate; +} + +export function applyQuickDateShortcut({ + shortcut, + value, + allDay, + now, +}: { + shortcut: QuickShortcut; + value: string; + allDay: boolean; + now: Date; +}): QuickShortcutResult { + const nextMonth = resolveQuickShortcutDate(shortcut, now); + const { hour, minute } = getTimeParts(value, allDay); + + return { + keepOpen: true, + nextMonth, + nextValue: buildValue(nextMonth, hour, minute, allDay), + }; +} export function DateTimePicker({ value, @@ -76,46 +124,41 @@ export function DateTimePicker({ className, }: DateTimePickerProps) { const parsed = parseValue(value); - - // Derive hour/minute from the current value (fallback to 0:00) - const currentHour = parsed && !allDay ? parsed.getHours() : 0; - const currentMinute = parsed && !allDay ? parsed.getMinutes() : 0; - - // Snap current minute to nearest MINUTES bucket for the select - const snappedMinute = MINUTES.reduce((prev, curr) => - Math.abs(curr - currentMinute) < Math.abs(prev - currentMinute) - ? curr - : prev, + const { hour: currentHour, minute: currentMinute } = getTimeParts( + value, + allDay, ); const [open, setOpen] = React.useState(false); + const [visibleMonth, setVisibleMonth] = React.useState(() => + getCalendarMonthForValue(value, new Date()), + ); + + React.useEffect(() => { + setVisibleMonth(getCalendarMonthForValue(value, new Date())); + }, [value]); const handleDaySelect = (day: Date | undefined) => { if (!day) return; - onChange(buildValue(day, currentHour, snappedMinute, allDay)); + onChange(buildValue(day, currentHour, currentMinute, allDay)); + setVisibleMonth(day); setOpen(false); }; - const handleHourChange = (h: string) => { - const base = parsed ?? new Date(); - onChange(buildValue(base, Number(h), snappedMinute, allDay)); + const handleQuickSelect = (shortcut: QuickShortcut) => { + const result = applyQuickDateShortcut({ + shortcut, + value, + allDay, + now: new Date(), + }); + + onChange(result.nextValue); + setVisibleMonth(result.nextMonth); + setOpen(result.keepOpen); }; - const handleMinuteChange = (m: string) => { - const base = parsed ?? new Date(); - onChange(buildValue(base, currentHour, Number(m), allDay)); - }; - - const handleQuickSelect = (day: Date) => { - onChange(buildValue(day, currentHour, snappedMinute, allDay)); - setOpen(false); - }; - - const quickOptions: { label: string; date: Date }[] = [ - { label: "Today", date: startOfDay(new Date()) }, - { label: "Next week", date: startOfDay(addWeeks(new Date(), 1)) }, - { label: "Next month", date: startOfDay(addMonths(new Date(), 1)) }, - ]; + const quickOptions: QuickShortcut[] = ["Today", "Next week", "Next month"]; const displayLabel = parsed ? allDay @@ -124,7 +167,7 @@ export function DateTimePicker({ : null; return ( -