diff --git a/apps/edr-freight-web/backoffice/src/pages/trainScheduling/BatchScheduleDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/trainScheduling/BatchScheduleDetailPage.tsx index afbad003e..d3f753057 100644 --- a/apps/edr-freight-web/backoffice/src/pages/trainScheduling/BatchScheduleDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/trainScheduling/BatchScheduleDetailPage.tsx @@ -1,8 +1,9 @@ -import { useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { ReactNode } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Accordion, + ActionIcon, Alert, Badge, Box, @@ -25,6 +26,8 @@ import { Boxes, CalendarDays, CheckCircle2, + ChevronLeft, + ChevronRight, Clock, FileSignature, Hourglass, @@ -551,22 +554,35 @@ export default function BatchScheduleDetailPage() { [data], ); - // Open today's day, any day with bookings, and pending-contract; else the last day. - const openDays = useMemo(() => { - if (!dayGroups.length && !data?.pendingContract.bookings.length) return []; - const todayEat = new Intl.DateTimeFormat("en-CA", { - timeZone: "Africa/Addis_Ababa", - year: "numeric", - month: "2-digit", - day: "2-digit", - }).format(new Date()); - const open = dayGroups - .filter((d) => d.totalBookings > 0 || d.date === todayEat) - .map((d) => d.date); - if (!open.length && dayGroups.length) open.push(dayGroups[dayGroups.length - 1].date); - if (data?.pendingContract.bookings.length) open.push("pending-contract"); - return open; - }, [dayGroups, data]); + const todayEat = useMemo( + () => + new Intl.DateTimeFormat("en-CA", { + timeZone: "Africa/Addis_Ababa", + year: "numeric", + month: "2-digit", + day: "2-digit", + }).format(new Date()), + [], + ); + + // Date-stepper: which day is currently shown. Default to today, else the first + // day with bookings, else the first day. Keep the selection if still valid. + const [selectedDate, setSelectedDate] = useState(null); + useEffect(() => { + if (!dayGroups.length) return; + if (selectedDate && dayGroups.some((d) => d.date === selectedDate)) return; + const preferred = + dayGroups.find((d) => d.date === todayEat) ?? + dayGroups.find((d) => d.totalBookings > 0) ?? + dayGroups[0]; + setSelectedDate(preferred.date); + }, [dayGroups, selectedDate, todayEat]); + + const selectedIndex = Math.max( + 0, + dayGroups.findIndex((d) => d.date === selectedDate), + ); + const selectedDay = dayGroups[selectedIndex]; const handleRunAllocation = () => { runAllocation @@ -815,79 +831,119 @@ export default function BatchScheduleDetailPage() { - - {dayGroups.map((day) => ( - - - - - - - - - - {day.dateLabel} - - - {day.totalBookings - ? `${day.totalBookings} booking${day.totalBookings === 1 ? "" : "s"} · ${day.windows.length} windows` - : `${day.windows.length} windows · no bookings`} - - - - - {day.hasIssues ? ( - } - > - Issues - - ) : null} - - + {dayGroups.length && selectedDay ? ( + <> + {/* Date stepper — page back/forward through each day in the range */} + + setSelectedDate(dayGroups[selectedIndex - 1]?.date ?? null)} + > + + + + + + + + {selectedDay.dateLabel} + + {selectedDay.date === todayEat ? ( + + Today + + ) : null} - - - - {day.windows.map((window) => ( - - ))} - - - - ))} - {data.pendingContract.bookings.length ? ( + + {selectedDay.totalBookings + ? `${selectedDay.totalBookings} booking${selectedDay.totalBookings === 1 ? "" : "s"} · ${selectedDay.windows.length} windows` + : `${selectedDay.windows.length} windows · no bookings`} + + + + = dayGroups.length - 1} + onClick={() => setSelectedDate(dayGroups[selectedIndex + 1]?.date ?? null)} + > + + + + + + + Day {selectedIndex + 1} of {dayGroups.length} + + + {selectedDay.hasIssues ? ( + } + > + Issues + + ) : null} + + + + + + {selectedDay.windows.map((window) => ( + + ))} + + + ) : ( + + No batch windows for this schedule. + + )} + + {data.pendingContract.bookings.length ? ( + @@ -927,8 +983,8 @@ export default function BatchScheduleDetailPage() { - ) : null} - + + ) : null} {/* Train composition */}