import { useEffect, useState } from "react"; import { Alert, Box, Button, Group, Modal, Stack, Text, TextInput, ThemeIcon, } from "@mantine/core"; import { isAxiosError } from "axios"; import { CalendarClock, Info } from "lucide-react"; import { useMutation } from "@tanstack/react-query"; import { api } from "@/services/api"; import { useToast } from "@/hooks/use-toast"; function parseError(error: unknown, fallback: string): string { if (isAxiosError(error)) { const message = error.response?.data?.message; if (Array.isArray(message)) return message.join(", "); if (typeof message === "string") return message; } return fallback; } /** ISO → the `YYYY-MM-DDTHH:mm` value a datetime-local input expects (local time). */ function toLocalInputValue(iso: string | null | undefined): string { if (!iso) return ""; const date = new Date(iso); if (Number.isNaN(date.getTime())) return ""; const pad = (n: number) => String(n).padStart(2, "0"); return ( `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` + `T${pad(date.getHours())}:${pad(date.getMinutes())}` ); } export interface EditScheduleDateModalProps { scheduleId: string | null; currentDate: string | null; routeName?: string | null; opened: boolean; onClose: () => void; /** Called after a successful save (e.g. to refetch a list). */ onSaved?: () => void; } /** * Reschedule a train's departure date. Only shown for schedules whose booking * window has not opened yet; the API rejects a date inside the booking lead * window (import/intercity lead in days, export in hours). */ export default function EditScheduleDateModal({ scheduleId, currentDate, routeName, opened, onClose, onSaved, }: EditScheduleDateModalProps) { const { toast } = useToast(); const save = useMutation( api.trainScheduling.updateScheduleDate.mutationOptions(), ); const [value, setValue] = useState(""); // Earliest selectable departure, refreshed each time the modal opens. const [minValue, setMinValue] = useState(""); useEffect(() => { if (!opened) return; setValue(toLocalInputValue(currentDate)); setMinValue(toLocalInputValue(new Date().toISOString())); }, [opened, currentDate]); const handleSave = async () => { if (!scheduleId || !value) { toast({ title: "Pick a departure date", variant: "destructive" }); return; } if (new Date(value).getTime() < Date.now()) { toast({ title: "Departure date must be in the future", variant: "destructive", }); return; } try { await save.mutateAsync({ id: scheduleId, scheduleDate: new Date(value).toISOString(), }); toast({ title: "Departure date updated" }); onSaved?.(); onClose(); } catch (err) { toast({ title: "Update failed", description: parseError(err, "Could not update departure date"), variant: "destructive", }); } }; return ( Edit departure date {routeName ?? "This schedule only"} } > }> The date can only be changed before the booking window opens, and must still leave room for the booking lead window before departure. setValue(e.currentTarget.value)} /> ); }