mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
Merge branch 'freight/feat/booking-schedule' into freight/develop
This commit is contained in:
@@ -13,6 +13,8 @@ const statusColorMap: Record<string, string> = {
|
||||
FULLY_EXECUTED: "indigo",
|
||||
PNR_GENERATED: "violet",
|
||||
PAYMENT_VERIFICATION_IN_PROGRESS: "yellow",
|
||||
SELECTED_FOR_BATCH: "orange",
|
||||
EXPIRED: "red",
|
||||
PAID: "green",
|
||||
IN_TRANSIT: "cyan",
|
||||
COMPLETED: "indigo",
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { Timer } from "lucide-react";
|
||||
|
||||
import { SectionCard } from "./SectionCard";
|
||||
|
||||
export interface BookingPaymentCountdownCardProps {
|
||||
/** ISO timestamp marking the end of the pay window. */
|
||||
paymentDeadline: string;
|
||||
}
|
||||
|
||||
interface Remaining {
|
||||
days: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
expired: boolean;
|
||||
}
|
||||
|
||||
function getRemaining(deadlineMs: number): Remaining {
|
||||
const diff = deadlineMs - Date.now();
|
||||
if (diff <= 0) {
|
||||
return { days: 0, hours: 0, minutes: 0, seconds: 0, expired: true };
|
||||
}
|
||||
const totalSeconds = Math.floor(diff / 1000);
|
||||
return {
|
||||
days: Math.floor(totalSeconds / 86400),
|
||||
hours: Math.floor((totalSeconds % 86400) / 3600),
|
||||
minutes: Math.floor((totalSeconds % 3600) / 60),
|
||||
seconds: totalSeconds % 60,
|
||||
expired: false,
|
||||
};
|
||||
}
|
||||
|
||||
function Segment({ value, label }: { value: number; label: string }) {
|
||||
return (
|
||||
<Stack gap={2} align="center" style={{ minWidth: 56 }}>
|
||||
<Text fw={700} size="2rem" style={{ lineHeight: 1, fontVariantNumeric: "tabular-nums" }}>
|
||||
{String(value).padStart(2, "0")}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" lts="0.06em">
|
||||
{label}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
/** Live countdown to the payment deadline. Ticks every second; shows an expired state past the deadline. */
|
||||
export function BookingPaymentCountdownCard({ paymentDeadline }: BookingPaymentCountdownCardProps) {
|
||||
const deadlineMs = new Date(paymentDeadline).getTime();
|
||||
const [remaining, setRemaining] = useState<Remaining>(() => getRemaining(deadlineMs));
|
||||
|
||||
useEffect(() => {
|
||||
setRemaining(getRemaining(deadlineMs));
|
||||
const interval = setInterval(() => {
|
||||
const next = getRemaining(deadlineMs);
|
||||
setRemaining(next);
|
||||
if (next.expired) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [deadlineMs]);
|
||||
|
||||
const accent = remaining.expired ? "red" : "orange";
|
||||
|
||||
return (
|
||||
<SectionCard
|
||||
icon={Timer}
|
||||
title="Payment Deadline"
|
||||
subtitle={
|
||||
remaining.expired
|
||||
? "The pay window has closed"
|
||||
: "Time remaining to complete payment"
|
||||
}
|
||||
accent={accent}
|
||||
>
|
||||
{remaining.expired ? (
|
||||
<Text fw={600} c="red.7">
|
||||
Expired
|
||||
</Text>
|
||||
) : (
|
||||
<Group justify="center" gap="lg" wrap="nowrap">
|
||||
<Segment value={remaining.days} label="Days" />
|
||||
<Segment value={remaining.hours} label="Hours" />
|
||||
<Segment value={remaining.minutes} label="Mins" />
|
||||
<Segment value={remaining.seconds} label="Secs" />
|
||||
</Group>
|
||||
)}
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
@@ -137,6 +137,8 @@ export interface BookingDetailView {
|
||||
priorityScore: number;
|
||||
cargoTotalWeightVgm: number;
|
||||
pnrCode?: string | null;
|
||||
/** End of the pay window once the booking is SELECTED_FOR_BATCH. */
|
||||
paymentDeadline?: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
company?: BookingNamedRefView;
|
||||
|
||||
@@ -9,6 +9,7 @@ export * from "./BookingContainersCard";
|
||||
export * from "./BookingApprovalCard";
|
||||
export * from "./BookingReviewNotesCard";
|
||||
export * from "./BookingPaymentCard";
|
||||
export * from "./BookingPaymentCountdownCard";
|
||||
export * from "./BookingFactsCard";
|
||||
export * from "./BookingDocumentsCard";
|
||||
export * from "./BookingRequestHero";
|
||||
|
||||
@@ -50,6 +50,14 @@ export const BOOKING_STATUS_STYLES: Record<string, StatusStyle> = {
|
||||
label: "Payment Verification",
|
||||
color: "bg-amber-50 text-amber-800 border-amber-200",
|
||||
},
|
||||
SELECTED_FOR_BATCH: {
|
||||
label: "Selected for Batch",
|
||||
color: "bg-orange-50 text-orange-700 border-orange-200",
|
||||
},
|
||||
EXPIRED: {
|
||||
label: "Expired",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
},
|
||||
PAID: {
|
||||
label: "Paid",
|
||||
color: "bg-[color:var(--freight-brand-muted)] text-[color:var(--freight-brand)] border-[color:var(--freight-brand-border)]",
|
||||
@@ -241,6 +249,8 @@ export const BOOKING_LIST_TABS = [
|
||||
"FULLY_EXECUTED",
|
||||
"PNR_GENERATED",
|
||||
"PAYMENT_VERIFICATION_IN_PROGRESS",
|
||||
"SELECTED_FOR_BATCH",
|
||||
"EXPIRED",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -270,6 +280,8 @@ export const WORKFLOW_STAGES = [
|
||||
"FULLY_EXECUTED",
|
||||
"PNR_GENERATED",
|
||||
"PAYMENT_VERIFICATION_IN_PROGRESS",
|
||||
"SELECTED_FOR_BATCH",
|
||||
"EXPIRED",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { Container, Stack, Grid } from "@mantine/core";
|
||||
import { Container, Grid, Stack } from "@mantine/core";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
||||
import {
|
||||
detailStyles,
|
||||
type BookingDetailView,
|
||||
BookingDetailToolbar,
|
||||
BookingDetailHeader,
|
||||
BookingLifecycleStepper,
|
||||
BookingRouteCard,
|
||||
BookingContainersCard,
|
||||
BookingApprovalCard,
|
||||
BookingReviewNotesCard,
|
||||
BookingPaymentCard,
|
||||
BookingFactsCard,
|
||||
BookingContainersCard,
|
||||
BookingDetailToolbar,
|
||||
BookingDocumentsCard,
|
||||
BookingFactsCard,
|
||||
BookingLifecycleStepper,
|
||||
BookingPaymentCard,
|
||||
BookingPaymentCountdownCard,
|
||||
BookingReviewNotesCard,
|
||||
BookingRouteCard,
|
||||
detailStyles,
|
||||
type BookingDetailView
|
||||
} from "@/components/bookings/detail";
|
||||
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
||||
|
||||
const BookingDetailPage = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -25,8 +25,9 @@ const BookingDetailPage = () => {
|
||||
const booking: BookingDetailView = {
|
||||
id: id || "a61955b7-af21-4664-84d3-7e4e66293b6f",
|
||||
reference: "BKG-2026-001456",
|
||||
status: "IN_TRANSIT",
|
||||
status: "SELECTED_FOR_BATCH",
|
||||
scheduledDate: "2026-06-15",
|
||||
paymentDeadline: "2026-06-18T17:00:00Z",
|
||||
totalAmount: 15750.5,
|
||||
paymentCurrency: "USD",
|
||||
paymentStatus: "PAID",
|
||||
@@ -138,6 +139,9 @@ const BookingDetailPage = () => {
|
||||
{/* RIGHT — summary sidebar */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Stack gap="lg">
|
||||
{booking.status === "SELECTED_FOR_BATCH" && booking.paymentDeadline && (
|
||||
<BookingPaymentCountdownCard paymentDeadline={booking.paymentDeadline} />
|
||||
)}
|
||||
<BookingPaymentCard
|
||||
totalAmount={booking.totalAmount}
|
||||
currency={booking.paymentCurrency}
|
||||
|
||||
Reference in New Issue
Block a user