mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 03:05:42 +00:00
199 lines
6.1 KiB
TypeScript
199 lines
6.1 KiB
TypeScript
import { Container, Grid, Stack } from "@mantine/core";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import toast from "react-hot-toast";
|
|
|
|
import {
|
|
BookingApprovalCard,
|
|
BookingContainersCard,
|
|
BookingDetailToolbar,
|
|
BookingDocumentsCard,
|
|
BookingFactsCard,
|
|
BookingLifecycleStepper,
|
|
BookingPaymentCard,
|
|
BookingPaymentCountdownCard,
|
|
BookingReviewNotesCard,
|
|
BookingRouteCard,
|
|
detailStyles,
|
|
type BookingDetailView,
|
|
} from "@/components/bookings/detail";
|
|
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
|
import ContainerAllocationTable from "@/components/ContainerAllocationTable";
|
|
import { api } from "@/services/api";
|
|
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
|
|
|
const BookingDetailPage = () => {
|
|
const { id } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const qc = useQueryClient();
|
|
|
|
const allocateMutation = useMutation({
|
|
mutationFn: (data: any) =>
|
|
api.post(`/bookings/${id}/allocate-containers`, data),
|
|
onSuccess: () => {
|
|
toast.success("Containers allocated");
|
|
qc.invalidateQueries({ queryKey: QUERY_KEYS.BOOKINGS.byId(id ?? "") });
|
|
},
|
|
onError: () => {
|
|
toast.error("Failed to allocate containers");
|
|
},
|
|
});
|
|
|
|
// Mock data - replace with actual API call
|
|
const booking: BookingDetailView = {
|
|
id: id || "a61955b7-af21-4664-84d3-7e4e66293b6f",
|
|
reference: "BKG-2026-001456",
|
|
status: "SELECTED_FOR_BATCH",
|
|
scheduledDate: "2026-06-15",
|
|
paymentDeadline: "2026-06-18T17:00:00Z",
|
|
totalAmount: 15750.5,
|
|
paymentCurrency: "USD",
|
|
paymentStatus: "PAID",
|
|
tradeDirection: "IMPORT",
|
|
freightType: "CONTAINER",
|
|
priorityScore: 650,
|
|
company: {
|
|
id: "1",
|
|
companyName: "Global Logistics Inc.",
|
|
name: "Global Logistics Inc.",
|
|
},
|
|
originYard: { id: "1", label: "Port of Shanghai", code: "PVG" },
|
|
destinationYard: { id: "2", label: "Port of Addis Ababa", code: "AAA" },
|
|
serviceType: { id: "1", label: "Container Import Service", code: "CIS" },
|
|
cargoType: { id: "1", label: "Electronics", code: "ELEC" },
|
|
shippingLine: { id: "1", label: "Maersk Line", code: "MAE" },
|
|
cargoTotalWeightVgm: 22.5,
|
|
pnrCode: "PNR-2026-001456",
|
|
createdAt: "2026-06-05T10:30:00Z",
|
|
updatedAt: "2026-06-06T14:20:00Z",
|
|
bookingContainers: [
|
|
{
|
|
id: "1",
|
|
quantity: 2,
|
|
vgmPerUnitTons: 11.25,
|
|
containerType: { label: "20FT Standard", sizeFt: 20 },
|
|
},
|
|
],
|
|
approvalSteps: [
|
|
{
|
|
id: "1",
|
|
stepOrder: 1,
|
|
requiredRole: "LINE_STAFF",
|
|
status: "APPROVED",
|
|
actionedAt: "2026-06-05T11:00:00Z",
|
|
},
|
|
// {
|
|
// id: "2",
|
|
// stepOrder: 2,
|
|
// requiredRole: "DIRECTOR",
|
|
// status: "APPROVED",
|
|
// actionedAt: "2026-06-05T13:30:00Z",
|
|
// },
|
|
{
|
|
id: "3",
|
|
stepOrder: 3,
|
|
requiredRole: "CEO",
|
|
status: "APPROVED",
|
|
actionedAt: "2026-06-05T15:45:00Z",
|
|
},
|
|
],
|
|
reviewNotes: [
|
|
{
|
|
id: "1",
|
|
note: "Cargo declaration verified against shipping documents.",
|
|
type: "VERIFICATION",
|
|
createdAt: "2026-06-05T11:15:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
note: "VGM documentation received and processed.",
|
|
type: "COMPLIANCE",
|
|
createdAt: "2026-06-05T12:00:00Z",
|
|
},
|
|
],
|
|
files: [
|
|
{ id: "1", name: "Bill_of_Lading.pdf", mimeType: "application/pdf" },
|
|
{ id: "2", name: "VGM_Certificate.pdf", mimeType: "application/pdf" },
|
|
{ id: "3", name: "Commercial_Invoice.pdf", mimeType: "application/pdf" },
|
|
],
|
|
};
|
|
|
|
const approvalSteps = booking.approvalSteps ?? [];
|
|
const approvedCount = approvalSteps.filter(
|
|
(s) => s.status === "APPROVED",
|
|
).length;
|
|
|
|
return (
|
|
<div style={detailStyles.page}>
|
|
<Container size="xxl" py="lg">
|
|
<BookingDetailToolbar onBack={() => navigate(-1)} />
|
|
|
|
<Breadcrumbs
|
|
items={[
|
|
{ label: "Operations" },
|
|
{ label: "Booking requests", href: "/dashboard/booking-requests" },
|
|
{ label: booking.reference },
|
|
]}
|
|
/>
|
|
{/*
|
|
<BookingDetailHeader
|
|
booking={booking}
|
|
approvedCount={approvedCount}
|
|
totalSteps={totalSteps}
|
|
/> */}
|
|
|
|
<BookingLifecycleStepper status={booking.status} />
|
|
|
|
<Grid>
|
|
{/* LEFT — primary content */}
|
|
<Grid.Col span={{ base: 12, lg: 8 }}>
|
|
<Stack gap="lg">
|
|
<BookingRouteCard booking={booking} />
|
|
<BookingContainersCard
|
|
containers={booking.bookingContainers ?? []}
|
|
/>
|
|
<ContainerAllocationTable
|
|
bookingId={booking.id}
|
|
containers={(booking.bookingContainers ?? []).map((c) => ({
|
|
id: c.id,
|
|
type: c.containerType?.label ?? "Unknown",
|
|
qty: c.quantity,
|
|
}))}
|
|
onSave={(allocations) =>
|
|
allocateMutation.mutateAsync({ allocations })
|
|
}
|
|
/>
|
|
<BookingApprovalCard
|
|
steps={approvalSteps}
|
|
approvedCount={approvedCount}
|
|
/>
|
|
<BookingReviewNotesCard notes={booking.reviewNotes ?? []} />
|
|
</Stack>
|
|
</Grid.Col>
|
|
|
|
{/* 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}
|
|
paymentStatus={booking.paymentStatus}
|
|
/>
|
|
<BookingFactsCard booking={booking} />
|
|
<BookingDocumentsCard files={booking.files ?? []} />
|
|
</Stack>
|
|
</Grid.Col>
|
|
</Grid>
|
|
</Container>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BookingDetailPage;
|