Merge branch 'alpha' of github.com:Tria-plc/edr-platform into alpha

This commit is contained in:
Abubeker Yasin
2026-07-15 23:14:19 +03:00

View File

@@ -412,6 +412,103 @@ function BookingDetailContent() {
return displayTotal / etbTotal;
})();
// Flight-style origin → train → destination timeline for a single leg's schedule.
// Shared by both the pending-payment "Trip Summary" card and the confirmed booking's
// "Journey Details" card so a round trip's outbound and return legs render identically —
// each of those cards calls this once per leg instead of hardcoding booking.schedule only.
const renderJourneyTimeline = (schedule: any) => (
<div className="flex">
<div className="flex flex-col items-center w-8 flex-shrink-0">
<div className="w-4 h-4 rounded-full border-4 border-primary bg-white dark:bg-gray-900 z-10" />
<div className="w-0.5 flex-1 bg-gradient-to-b from-primary via-gray-300 dark:via-gray-700 to-gray-300 dark:to-gray-700 my-2" />
<div className="w-4 h-4 rounded-full border-4 border-gray-400 dark:border-gray-600 bg-white dark:bg-gray-900 z-10" />
</div>
<div className="flex-1 flex flex-col">
<div className="pb-8">
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{schedule?.departureAt ? formatTime(schedule.departureAt) : "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{schedule?.departureAt
? `${format(toZonedDate(new Date(schedule.departureAt)), "EEE, MMM d")} · ${getTimePeriod(schedule.departureAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{schedule?.origin?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{schedule?.origin?.city}
</div>
</div>
<div className="pb-8">
<div className="flex items-center gap-6 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1.5">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
<span className="font-medium">Train {schedule?.trainNumber}</span>
</div>
</div>
</div>
<div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{schedule?.arrivalAt ? formatTime(schedule.arrivalAt) : "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{schedule?.arrivalAt
? `${format(toZonedDate(new Date(schedule.arrivalAt)), "EEE, MMM d")} · ${getTimePeriod(schedule.arrivalAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{schedule?.destination?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{schedule?.destination?.city}
</div>
</div>
</div>
</div>
);
// Renders one or both legs' timelines with an "Outbound Journey"/"Return Journey"
// heading pair when the booking has a return leg, or a single "Your Journey" heading
// for one-way bookings — used by both the pending-payment and confirmed views.
const renderJourneyLegs = () => (
<>
<div className="flex items-center gap-2 mb-4">
<div className="w-2 h-2 bg-primary rounded-full" />
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
{isRoundTripBooking ? "Outbound Journey" : "Your Journey"}
</span>
{booking.passengers?.[0]?.seat?.seatClass && (
<span className="ml-auto text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full font-medium">
{booking.passengers[0].seat.seatClass}
</span>
)}
</div>
{renderJourneyTimeline(booking.schedule)}
{isRoundTripBooking && booking.returnSchedule && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2 mb-4">
<div className="w-2 h-2 bg-primary rounded-full" />
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Return Journey
</span>
</div>
{renderJourneyTimeline(booking.returnSchedule)}
</div>
)}
</>
);
// Order summary card — mirrors /booking/payment's OrderSummary: fare breakdown per
// passenger, Total with a loading spinner while a currency conversion is in flight, and
// a note confirming what will actually be charged once a payment method is selected.
@@ -586,137 +683,62 @@ function BookingDetailContent() {
Trip Summary
</h2>
<div className="flex items-center gap-2 mb-4">
<div className="w-2 h-2 bg-primary rounded-full" />
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Your Journey
</span>
{booking.passengers?.[0]?.seat?.seatClass && (
<span className="ml-auto text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full font-medium">
{booking.passengers[0].seat.seatClass}
</span>
)}
</div>
{/* Flight-style timeline */}
<div className="flex">
{/* Left column: Timeline with dots and line */}
<div className="flex flex-col items-center w-8 flex-shrink-0">
{/* Origin dot */}
<div className="w-4 h-4 rounded-full border-4 border-primary bg-white dark:bg-gray-900 z-10" />
{/* Vertical line */}
<div className="w-0.5 flex-1 bg-gradient-to-b from-primary via-gray-300 dark:via-gray-700 to-gray-300 dark:to-gray-700 my-2" />
{/* Destination dot */}
<div className="w-4 h-4 rounded-full border-4 border-gray-400 dark:border-gray-600 bg-white dark:bg-gray-900 z-10" />
</div>
{/* Right column: Content */}
<div className="flex-1 flex flex-col">
{/* Origin */}
<div className="pb-8">
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{booking.schedule?.departureAt
? formatTime(booking.schedule.departureAt)
: "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{booking.schedule?.departureAt
? `${format(toZonedDate(new Date(booking.schedule.departureAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{booking.schedule?.origin?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{booking.schedule?.origin?.city}
</div>
</div>
{/* Journey Info */}
<div className="pb-8">
<div className="flex items-center gap-6 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1.5">
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
<span className="font-medium">
Train {booking.schedule?.trainNumber}
</span>
</div>
{booking.schedule?.trainName && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{booking.schedule.trainName}
</span>
)}
</div>
</div>
{/* Destination */}
<div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{booking.schedule?.arrivalAt
? formatTime(booking.schedule.arrivalAt)
: "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{booking.schedule?.arrivalAt
? `${format(toZonedDate(new Date(booking.schedule.arrivalAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{booking.schedule?.destination?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{booking.schedule?.destination?.city}
</div>
</div>
</div>
</div>
{renderJourneyLegs()}
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
<div className="flex items-center gap-2 mb-3">
<Users className="w-4 h-4 text-gray-500" />
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
{booking.passengers?.length || 0} Passenger(s)
{groupedPassengers.length} Passenger(s)
</span>
</div>
<div className="space-y-2">
{booking.passengers?.map(
(passenger: any, idx: number) => (
<div
key={idx}
className="flex items-center justify-between text-sm py-2 px-3 bg-gray-50 dark:bg-gray-900 rounded-lg"
>
<div>
<div className="text-gray-900 dark:text-white font-medium">
{passenger.fullName}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{passenger.category} Coach{" "}
{passenger.seat?.coach}
</div>
{groupedPassengers.map((passenger: any, idx: number) => (
<div
key={idx}
className="text-sm py-2 px-3 bg-gray-50 dark:bg-gray-900 rounded-lg"
>
<div className="flex items-center justify-between">
<div className="text-gray-900 dark:text-white font-medium">
{passenger.fullName}
</div>
<div className="text-right">
<div className="font-semibold text-gray-900 dark:text-white">
Seat {passenger.seat?.number}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{passenger.seat?.seatClass}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{passenger.category}
</div>
</div>
),
)}
{isRoundTripBooking ? (
<div className="mt-1.5 grid grid-cols-2 gap-2">
{(
[
{ legLabel: "Outbound", seat: passenger.outboundSeat },
{ legLabel: "Return", seat: passenger.returnSeat },
] as const
).map(({ legLabel, seat }) => (
<div key={legLabel}>
<div className="text-xs text-gray-400 dark:text-gray-500">
{legLabel}
</div>
<div className="font-semibold text-gray-900 dark:text-white">
Seat {seat?.number ?? "--"}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{seat?.seatClass}
</div>
</div>
))}
</div>
) : (
<div className="mt-1.5 text-right">
<div className="font-semibold text-gray-900 dark:text-white">
Seat {passenger.outboundSeat?.number}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{passenger.outboundSeat?.seatClass}
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
@@ -994,102 +1016,7 @@ function BookingDetailContent() {
Journey Details
</h2>
<div className="flex items-center gap-2 mb-4">
<div className="w-2 h-2 bg-primary rounded-full" />
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Your Journey
</span>
{booking.passengers?.[0]?.seat?.seatClass && (
<span className="ml-auto text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full font-medium">
{booking.passengers[0].seat.seatClass}
</span>
)}
</div>
{/* Flight-style timeline */}
<div className="flex">
{/* Left column: Timeline with dots and line */}
<div className="flex flex-col items-center w-8 flex-shrink-0">
{/* Origin dot */}
<div className="w-4 h-4 rounded-full border-4 border-primary bg-white dark:bg-gray-900 z-10" />
{/* Vertical line */}
<div className="w-0.5 flex-1 bg-gradient-to-b from-primary via-gray-300 dark:via-gray-700 to-gray-300 dark:to-gray-700 my-2" />
{/* Destination dot */}
<div className="w-4 h-4 rounded-full border-4 border-gray-400 dark:border-gray-600 bg-white dark:bg-gray-900 z-10" />
</div>
{/* Right column: Content */}
<div className="flex-1 flex flex-col">
{/* Origin */}
<div className="pb-8">
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{booking.schedule?.departureAt
? formatTime(booking.schedule.departureAt)
: "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{booking.schedule?.departureAt
? `${format(toZonedDate(new Date(booking.schedule.departureAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{booking.schedule?.origin?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{booking.schedule?.origin?.city}
</div>
</div>
{/* Journey Info */}
<div className="pb-8">
<div className="flex items-center gap-6 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1.5">
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
<span className="font-medium">
Train {booking.schedule?.trainNumber}
</span>
</div>
{booking.schedule?.trainName && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{booking.schedule.trainName}
</span>
)}
</div>
</div>
{/* Destination */}
<div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{booking.schedule?.arrivalAt
? formatTime(booking.schedule.arrivalAt)
: "--:--"}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
{booking.schedule?.arrivalAt
? `${format(toZonedDate(new Date(booking.schedule.arrivalAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
: "N/A"}
</div>
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
{booking.schedule?.destination?.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{booking.schedule?.destination?.city}
</div>
</div>
</div>
</div>
{renderJourneyLegs()}
</div>
<div className="bg-white dark:bg-gray-800 rounded-2xl p-6 border border-gray-200 dark:border-gray-700">