streamline booking detail components and enhance journey visualization

- Removed the BookingClearanceWorkflowBanner from ClearanceCard as the clearance progress is now integrated into the unified journey wizard.
- Eliminated the MilestoneTimeline component from DocumentsTab, consolidating customs progress into the JourneyWizard.
- Updated PageHeader to include a ContractReferenceLink for better navigation to contract details.
- Simplified ShipmentTrackingCard to focus on duty/tax payment slip upload, removing unnecessary milestone display.
- Integrated JourneyWizard component to visualize the booking journey, replacing the previous progress tracker.
- Enhanced ContractDetailPage to better categorize documents and improve user experience with clearer sections for profile, business license, clearance, and other documents.
- Introduced CSS for contracts table to manage column sizing and sticky headers effectively.
- Added ContractReferenceLink component for backoffice to link to contract details, ensuring consistent navigation across applications.
This commit is contained in:
Marshal
2026-07-19 07:06:58 +00:00
parent 0dead281ce
commit 17dc505d50
18 changed files with 548 additions and 422 deletions

View File

@@ -0,0 +1,43 @@
import { Link } from "react-router-dom";
/**
* The parent contract's reference, linking to that contract's detail page.
*
* Backoffice-local on purpose: the contract detail route differs per app
* (`/dashboard/contract-requests/:id` here vs `/contracts/:id` in the portal),
* so the portal keeps its own copy in `pages/bookings/booking-display.tsx`
* rather than the two sharing a component that would have to take the route as
* a prop at every call site.
*
* Renders nothing when either field is missing: `contractId` is nullable on the
* booking, and only the bookings list/detail endpoints join `contractReference`
* — other endpoints (warehouse, fleet, payments) return booking rows without it,
* and a link with no id would be a dead one.
*
* `stopPropagation` matters: booking rows are click-to-navigate, so without it a
* click here would race the row handler and land on the booking instead.
*/
export function ContractReferenceLink({
contractId,
contractReference,
className,
}: {
contractId?: string | null;
contractReference?: string | null;
className?: string;
}) {
if (!contractId || !contractReference) return null;
return (
<Link
to={`/dashboard/contract-requests/${contractId}`}
onClick={(e) => e.stopPropagation()}
className={
className ??
"block truncate font-mono text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
}
>
{contractReference}
</Link>
);
}

View File

@@ -24,6 +24,7 @@ import type { LucideIcon } from "lucide-react";
import type { BookingDetail } from "@/types/booking";
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
import { ContractReferenceLink } from "@/components/bookings/ContractReferenceLink";
import { SchedulingStatusBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
import { NextStepBanner } from "@/components/bookings/NextStepBanner";
@@ -94,9 +95,15 @@ export function BookingRequestHero({
Booking reference
</Text>
<Group gap="sm" align="center" wrap="wrap">
<Title order={2} fw={700} style={{ letterSpacing: "-0.4px" }}>
{booking.reference}
</Title>
<Stack gap={2} miw={0}>
<Title order={2} fw={700} style={{ letterSpacing: "-0.4px" }}>
{booking.reference}
</Title>
<ContractReferenceLink
contractId={booking.contractId}
contractReference={booking.contractReference}
/>
</Stack>
<BookingStatusBadge status={booking.status} />
<BookingPriorityBadge score={booking.priorityScore} />
{booking.schedulingStatus ? (

View File

@@ -19,6 +19,7 @@ export function toBookingListRow(booking: BookingDetail): BookingListRow {
id: booking.id,
reference: booking.reference,
contractReference: booking.contractReference ?? null,
contractId: booking.contractId ?? null,
approvalSteps: booking.approvalSteps,
customerLabel: booking.isGovernment
? (booking.governmentInstitution ?? "Government")

View File

@@ -27,7 +27,8 @@ export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
staleTime: 30_000,
// staleTime: 30_000,
staleTime:0,
// Data freshness is driven by mutation invalidation (MutationCache above),
// socket pushes, and explicit polling — not by tab focus. Focus refetch
// just re-fires every mounted query each time the window is refocused.

View File

@@ -48,6 +48,7 @@ import {
useBookingList,
useBookingListSummary,
} from "@/hooks/bookings/useBookings";
import { ContractReferenceLink } from "@/components/bookings/ContractReferenceLink";
import { api } from "@/services/api";
import type { BookingListFilter } from "@/services/bookings.service";
import type { BookingListRow } from "@/types/booking";
@@ -308,7 +309,17 @@ export default function BookingRequestsPage() {
return (
<div className="py-1">
{ref ? (
<span className="truncate font-mono text-xs text-foreground">{ref}</span>
// Fall back to plain text when the id is missing — the reference is
// still worth showing, it just has nowhere to link to.
(row.original.contractId ? (
<ContractReferenceLink
contractId={row.original.contractId}
contractReference={ref}
className="truncate font-mono text-xs text-foreground underline underline-offset-2 hover:text-primary"
/>
) : (
<span className="truncate font-mono text-xs text-foreground">{ref}</span>
))
) : (
<span className="text-xs text-muted-foreground"></span>
)}

View File

@@ -231,6 +231,8 @@ export interface BookingListRow {
id: string;
reference: string;
contractReference?: string | null;
/** Needed to link the reference to the contract's detail page. */
contractId?: string | null;
customerLabel: string;
approvalSteps?: BookingApprovalStep[];
status: BookingStatus;