diff --git a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx index 7a5602751..efc3af7ca 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx @@ -202,21 +202,48 @@ const BookingInfo = ({ record }: { record: FirstMileRecord }) => { ); }; -const tripSlipRows = (record: FirstMileRecord): [string, string][] => [ - ["Customer", customerName(record)], - ["Service", serviceTypeName(record)], - ["Pickup location", pickupLocation(record)], - ["Destination yard", destinationYardName(record)], - ["Cargo", cargoDesc(record)], - ["Advanced Payment", formatPrice(record.advancedPayment, currencyOf(record))], - ["Post Payment", formatPrice(record.remainingPayment, currencyOf(record))], - ["Vehicle", vehicleLabel(record) ?? "Unassigned"], - ["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`], - ["Requested date", requestedDate(record)], - ["Est. Distance (KM)", record.estimatedKm != null ? String(record.estimatedKm) : "—"], - ["Actual Distance (KM)", record.exactKm != null ? String(record.exactKm) : "—"], - ["Status", STATUS_META[record.status].label], -]; +type TripSlipVehicle = NonNullable[number]; + +const tripSlipRows = ( + record: FirstMileRecord, + vehicle?: TripSlipVehicle | null, +): [string, string][] => { + // Per-vehicle block when a specific truck is chosen (its own driver, container + // and distance); else fall back to the record-level vehicle summary. + const vehicleRows: [string, string][] = vehicle + ? [ + [ + "Vehicle", + vehicle.vehicle + ? [vehicle.vehicle.code, vehicle.vehicle.plateNumber].filter(Boolean).join(" · ") + : vehicle.vehicleId, + ], + ["Driver", vehicle.vehicle?.assignedDriverName || "—"], + [ + "Container(s)", + vehicle.containerNumber || bookingContainerNumbers(record).join(", ") || "—", + ], + ["Distance (KM)", vehicle.distanceKm != null ? String(vehicle.distanceKm) : "—"], + ] + : [ + ["Vehicle", vehicleLabel(record) ?? "Unassigned"], + ["Actual Distance (KM)", record.exactKm != null ? String(record.exactKm) : "—"], + ]; + return [ + ["Customer", customerName(record)], + ["Service", serviceTypeName(record)], + ["Pickup location", pickupLocation(record)], + ["Destination yard", destinationYardName(record)], + ["Cargo", cargoDesc(record)], + ["Advanced Payment", formatPrice(record.advancedPayment, currencyOf(record))], + ["Post Payment", formatPrice(record.remainingPayment, currencyOf(record))], + ...vehicleRows, + ["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`], + ["Requested date", requestedDate(record)], + ["Est. Distance (KM)", record.estimatedKm != null ? String(record.estimatedKm) : "—"], + ["Status", STATUS_META[record.status].label], + ]; +}; const SampleStamp = () => ( @@ -282,7 +309,13 @@ const SignatureBlock = ({ title, stamp }: { title: string; stamp?: ReactNode }) ); -const TripSlipDocument = ({ record }: { record: FirstMileRecord }) => ( +const TripSlipDocument = ({ + record, + vehicle, +}: { + record: FirstMileRecord; + vehicle?: TripSlipVehicle | null; +}) => ( EDR Freight @@ -294,7 +327,7 @@ const TripSlipDocument = ({ record }: { record: FirstMileRecord }) => ( - {tripSlipRows(record).map(([label, value]) => ( + {tripSlipRows(record, vehicle).map(([label, value]) => ( ))} @@ -309,8 +342,8 @@ const TripSlipDocument = ({ record }: { record: FirstMileRecord }) => ( const escapeHtml = (v: string) => v.replace(/&/g, "&").replace(//g, ">"); -const buildTripSlipHtml = (record: FirstMileRecord) => { - const rows = tripSlipRows(record) +const buildTripSlipHtml = (record: FirstMileRecord, vehicle?: TripSlipVehicle | null) => { + const rows = tripSlipRows(record, vehicle) .map(([l, v]) => `${escapeHtml(l)}${escapeHtml(v)}`) .join(""); const sig = (title: string, withStamp: boolean) => ` @@ -369,6 +402,9 @@ const FirstMilePage = () => { const [detailOpen, setDetailOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipRecord, setTripSlipRecord] = useState(null); + // Which vehicle the trip slip is for (per-truck), + the pre-print picker. + const [tripSlipVehicleId, setTripSlipVehicleId] = useState(null); + const [tripSlipSelectOpen, setTripSlipSelectOpen] = useState(false); const [activeId, setActiveId] = useState(null); // Multi-vehicle assign: one row per truck — vehicle + the container it carries. const [vehicleRows, setVehicleRows] = useState< @@ -801,10 +837,27 @@ const FirstMilePage = () => { }; const handlePrintTripSlip = (record: FirstMileRecord) => { + // Always open the picker so the operator chooses which truck to print. setTripSlipRecord(record); + setTripSlipVehicleId(null); + setTripSlipSelectOpen(true); + }; + + const printBookingSlip = () => { + setTripSlipVehicleId(null); + setTripSlipSelectOpen(false); setTripSlipOpen(true); }; + const chooseTripSlipVehicle = (vehicleId: string) => { + setTripSlipVehicleId(vehicleId); + setTripSlipSelectOpen(false); + setTripSlipOpen(true); + }; + + const tripSlipVehicle = + tripSlipRecord?.vehicleAssignments?.find((a) => a.vehicleId === tripSlipVehicleId) ?? null; + const printTripSlip = () => { if (!tripSlipRecord) return; const win = window.open("", "_blank", "width=820,height=920"); @@ -812,8 +865,17 @@ const FirstMilePage = () => { toast({ title: "Pop-up blocked", description: "Allow pop-ups to print the trip slip.", variant: "destructive" }); return; } - win.document.write(buildTripSlipHtml(tripSlipRecord)); + win.document.write(buildTripSlipHtml(tripSlipRecord, tripSlipVehicle)); win.document.close(); + win.focus(); + // Explicit print after the doc paints (onload can miss with document.write). + setTimeout(() => { + try { + win.print(); + } catch { + /* window may have been closed */ + } + }, 250); }; const columns = useMemo((): ColumnDef[] => { @@ -1264,6 +1326,61 @@ const FirstMilePage = () => { + {/* Trip slip — pick a vehicle (multi-truck) */} + setTripSlipSelectOpen(false)} + title={Print trip slip — select vehicle} + radius="lg" + centered + > + + + {tripSlipRecord ? bookingRef(tripSlipRecord) : ""} — pick a truck to print its slip. + + {(tripSlipRecord?.vehicleAssignments ?? []).map((a) => { + const v = a.vehicle; + const label = v ? [v.code, v.plateNumber].filter(Boolean).join(" · ") : a.vehicleId; + return ( + chooseTripSlipVehicle(a.vehicleId)} + style={{ cursor: "pointer" }} + className="hover:bg-gray-50" + > + + + + + {label} + + + Driver: {v?.assignedDriverName || "—"} + + + Container: {a.containerNumber || "—"} + + + + + + + + ); + })} + {(tripSlipRecord?.vehicleAssignments?.length ?? 0) === 0 && ( + No vehicles assigned yet. + )} + + + + + {/* Trip slip modal */} { centered > - {tripSlipRecord && } + {tripSlipRecord && }