import { Box, Button, Group, Text } from "@mantine/core"; import { Calendar as CalendarIcon, Check, ChevronLeft, ChevronRight, } from "lucide-react"; import { useMemo, useState } from "react"; export interface OperationDatePickerProps { /** Selectable days as `yyyy-MM-dd` strings. */ availableDays: string[]; /** Show the loading state instead of the grid. */ isLoading?: boolean; /** Currently selected day as `yyyy-MM-dd`, or "" when none. */ value: string; /** Called with the picked `yyyy-MM-dd` day. */ onChange: (date: string) => void; /** Stretch to the full width of the parent container. */ fullWidth?: boolean; } /** `yyyy-MM-dd` for a local date. */ function fmtDay(d: Date): string { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } const MONTH_NAMES = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; const WEEKDAY_LABELS = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]; /** * Presentational month calendar for picking a binding shipment day. Only the * `availableDays` (passed in by the caller, which owns the query) are * selectable; every other day is disabled. Framework-light: no data fetching, * no date library — both the portal and backoffice feed it their own * availability results so the picker renders identically in each app. */ export function OperationDatePicker({ availableDays, isLoading = false, value, onChange, fullWidth = false, }: OperationDatePickerProps) { const [month, setMonth] = useState(() => { const now = new Date(); return new Date(now.getFullYear(), now.getMonth(), 1); }); const departureDays = useMemo( () => new Set(availableDays ?? []), [availableDays], ); const cells = useMemo(() => { const first = new Date(month.getFullYear(), month.getMonth(), 1); const lead = (first.getDay() + 6) % 7; const start = new Date(first); start.setDate(first.getDate() - lead); const today = new Date(); const todayStr = fmtDay(today); return Array.from({ length: 42 }, (_, i) => { const date = new Date(start); date.setDate(start.getDate() + i); const dateString = fmtDay(date); return { dateString, day: date.getDate(), inMonth: date.getMonth() === month.getMonth(), today: dateString === todayStr, selected: value === dateString, hasDeparture: departureDays.has(dateString), }; }); }, [month, departureDays, value]); const availableInMonth = useMemo( () => cells.filter((c) => c.inMonth && c.hasDeparture).length, [cells], ); const shiftMonth = (delta: number) => setMonth((m) => new Date(m.getFullYear(), m.getMonth() + delta, 1)); return ( {isLoading ? ( Loading available days… ) : ( <> {WEEKDAY_LABELS.map((d) => ( {d} ))} {cells.map((c) => { const clickable = c.hasDeparture && c.inMonth; return ( ); })} {value && ( Selected:{" "} {new Date(value + "T00:00:00").toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", year: "numeric", })} )} {departureDays.size === 0 && ( No scheduled departures found for this route yet. )} )} ); } function StackedMonthHeader({ month, availableCount, }: { month: Date; availableCount: number; }) { return ( {MONTH_NAMES[month.getMonth()]} {month.getFullYear()} {availableCount} available day{availableCount === 1 ? "" : "s"} ); } export default OperationDatePicker;