Files
edr-platform/apps/edr-freight-web/backoffice/src/pages/bookings/BookingDetailPage.tsx
natib21 6fa315db0f fix
2026-06-29 14:45:22 +00:00

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;