From c743750ef6cd7bb37561d298792922cd84e79a1f Mon Sep 17 00:00:00 2001 From: marshalyordanos Date: Mon, 10 Aug 2026 22:14:04 +0300 Subject: [PATCH] feat(freight): surface scheduling clock and gate pay during drain --- .../detail/BookingSchedulingWindowCard.tsx | 45 ++++++++++++++++++- .../backoffice/src/types/booking.ts | 5 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingSchedulingWindowCard.tsx b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingSchedulingWindowCard.tsx index 84f0645a8..9b5cf91f2 100644 --- a/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingSchedulingWindowCard.tsx +++ b/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingSchedulingWindowCard.tsx @@ -44,6 +44,27 @@ function formatRelative(iso: string, nowMs: number): string { return past ? `${span} ago` : `in ${span}`; } +/** + * Length of a window as "1h 30m" / "45m". Null unless both ends are real and + * ordered — the pay window is configurable per schedule, so this is read off the + * actual stamps rather than assuming any fixed duration. + */ +function formatDuration( + from: string | null | undefined, + to: string | null | undefined, +): string | null { + if (!from || !to) return null; + const fromMs = new Date(from).getTime(); + const toMs = new Date(to).getTime(); + if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) return null; + const minutes = Math.round((toMs - fromMs) / 60_000); + if (minutes <= 0) return null; + const hours = Math.floor(minutes / 60); + const rest = minutes % 60; + if (!hours) return `${rest}m`; + return rest ? `${hours}h ${rest}m` : `${hours}h`; +} + function Row({ label, value, @@ -98,8 +119,18 @@ export function BookingSchedulingWindowCard({ return () => clearInterval(interval); }, []); + // How long the customer actually had to pay: start → the raw deadline, NOT the + // drain end (the drain is settlement grace, not payable time). + const windowDuration = formatDuration( + booking.selectedForBatchAt, + booking.paymentDeadline, + ); + const hasAnything = - Boolean(schedule) || Boolean(payWindowEndsAt) || Boolean(booking.holdExpiresAt); + Boolean(schedule) || + Boolean(payWindowEndsAt) || + Boolean(booking.selectedForBatchAt) || + Boolean(booking.holdExpiresAt); if (!hasAnything) return null; const payWindowClosed = payWindowEndsAt @@ -157,6 +188,18 @@ export function BookingSchedulingWindowCard({ ) : null} + {booking.selectedForBatchAt ? ( + + ) : null} + {payWindowEndsAt ? (