From 58c49d34b2a0e73f838ed48459b7fc6979b56d4d Mon Sep 17 00:00:00 2001 From: natib21 Date: Sat, 4 Jul 2026 01:31:08 +0000 Subject: [PATCH] fix --- .../src/pages/operations/FirstMilePage.tsx | 33 ++++++++++++----- .../src/pages/operations/LastMilePage.tsx | 36 +++++++++++++------ .../src/services/first-mile.service.ts | 1 + .../src/services/last-mile.service.ts | 1 + 4 files changed, 51 insertions(+), 20 deletions(-) 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 2da6e5bb8..f7a4b82bc 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/FirstMilePage.tsx @@ -52,8 +52,8 @@ import { vehiclesService } from "@/services/vehicles.service"; import { ratesService } from "@/services/rates.service"; import type { BookingDetail } from "@/types/booking"; -const formatPrice = (amount: number) => - `ETB ${amount.toLocaleString("en-US", { +const formatPrice = (amount: number | string | null | undefined, currency = "ETB") => + `${currency || "ETB"} ${(Number(amount) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })}`; @@ -111,8 +111,17 @@ const vehicleLabel = (record: FirstMileRecord) => { const isAssigned = (record: FirstMileRecord) => Boolean(record.vehicleId); +// Paid = record flag set OR its invoice reached PAID. +const isPaidRecord = (r: FirstMileRecord) => + Boolean((r as { paid?: boolean }).paid) || + (r.invoice?.status ?? "").toUpperCase() === "PAID"; +// Post payment pending = a post payment is owed but not yet paid. +const isPostPaymentPending = (r: FirstMileRecord) => + Number(r.remainingPayment) > 0 && !isPaidRecord(r); + // Map API record → display fields used in modals and trip slip const bookingRef = (r: FirstMileRecord) => r.booking?.reference ?? r.bookingId; +const currencyOf = (r: FirstMileRecord) => r.booking?.paymentCurrency ?? "ETB"; const customerName = (r: FirstMileRecord) => r.booking?.company?.name ?? "—"; const pickupLocation = (r: FirstMileRecord) => r.booking?.firstMilePickupAddress ?? "—"; const cargoDesc = (r: FirstMileRecord) => { @@ -169,8 +178,8 @@ const BookingInfo = ({ record }: { record: FirstMileRecord }) => { {hasPickupAddress && } - - + + @@ -189,8 +198,8 @@ const tripSlipRows = (record: FirstMileRecord): [string, string][] => [ ["Pickup location", pickupLocation(record)], ["Destination yard", destinationYardName(record)], ["Cargo", cargoDesc(record)], - ["Advanced Payment", formatPrice(record.advancedPayment)], - ["Post Payment", formatPrice(record.remainingPayment)], + ["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)], @@ -589,6 +598,7 @@ const FirstMilePage = () => { }; const matchesFilter = (r: FirstMileRecord) => { + if (filterPostPaymentPending && !isPostPaymentPending(r)) return false; switch (statusFilter) { case "ALL": return true; case "ASSIGNED": return isAssigned(r); @@ -615,6 +625,11 @@ const FirstMilePage = () => { return counts; }, [records]); + const postPaymentPendingCount = useMemo( + () => records.filter(isPostPaymentPending).length, + [records], + ); + const filteredRecords = useMemo(() => { const term = search.trim().toLowerCase(); return records.filter((r) => { @@ -751,7 +766,7 @@ const FirstMilePage = () => { id: "postPayment", header: "Post Payment", meta: { headerClassName, cellClassName }, - cell: ({ row }) => formatPrice(row.original.remainingPayment), + cell: ({ row }) => formatPrice(row.original.remainingPayment, currencyOf(row.original)), }, { id: "vehicle", @@ -853,7 +868,7 @@ const FirstMilePage = () => { } - disabled={!assigned} + disabled={!assigned || Boolean(row.original.invoice)} onClick={() => openAssign(row.original.id)} > Reassign @@ -975,7 +990,7 @@ const FirstMilePage = () => { setPagination((p) => ({ ...p, pageIndex: 0 })); }} > - Post Payment Pending + Post Payment Pending ({postPaymentPendingCount}) diff --git a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx index 31b4a6cff..d7545fd7b 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx @@ -58,8 +58,8 @@ import { ratesService } from "@/services/rates.service"; import { ReleaseOrderModal, type ReleaseOrderTruckPrefill } from "@/components/warehouses/ReleaseOrderModal"; import { LastMileStepper, type LastMileStepState } from "@/components/operations/LastMileSteps"; -const formatPrice = (amount: number) => - `ETB ${amount.toLocaleString("en-US", { +const formatPrice = (amount: number | string | null | undefined, currency = "ETB") => + `${currency || "ETB"} ${(Number(amount) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })}`; @@ -156,6 +156,14 @@ const containerLabels = (record: LastMileRecord): string[] => { const isAssigned = (record: LastMileRecord) => Boolean(record.vehicleId); +// Paid = record flag set OR its invoice reached PAID. +const isPaidRecord = (r: LastMileRecord) => + Boolean((r as { paid?: boolean }).paid) || + (r.invoice?.status ?? "").toUpperCase() === "PAID"; +// Post payment pending = a post payment is owed but not yet paid. +const isPostPaymentPending = (r: LastMileRecord) => + Number(r.remainingPayment) > 0 && !isPaidRecord(r); + const fmtStamp = (iso?: string | null) => { if (!iso) return null; const d = new Date(iso); @@ -209,6 +217,7 @@ const computeLastMileSteps = ( }; const bookingRef = (r: LastMileRecord) => r.booking?.reference ?? r.bookingId; +const currencyOf = (r: LastMileRecord) => r.booking?.paymentCurrency ?? "ETB"; const customerName = (r: LastMileRecord) => r.booking?.company?.name ?? "—"; const deliveryLocation = (r: LastMileRecord) => r.booking?.lastMileDeliveryAddress ?? "—"; const cargoDesc = (r: LastMileRecord) => { @@ -311,8 +320,8 @@ const BookingInfo = ({ record }: { record: LastMileRecord }) => { {hasDeliveryAddress && } - - + + @@ -358,7 +367,7 @@ const tripSlipRows = ( ["Pickup (origin yard)", originYardName(record)], ["Destination", deliveryLocation(record)], ["Cargo", cargoDesc(record)], - ["Post Payment", formatPrice(record.remainingPayment)], + ["Post Payment", formatPrice(record.remainingPayment, currencyOf(record))], ...vehicleRows, ["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`], ["Requested date", requestedDate(record)], @@ -870,11 +879,16 @@ const LastMilePage = () => { return counts; }, [records]); + const postPaymentPendingCount = useMemo( + () => records.filter(isPostPaymentPending).length, + [records], + ); + const filteredRecords = useMemo(() => { const term = search.trim().toLowerCase(); return records.filter((r) => { if (!matchesFilter(r)) return false; - if (filterPostPaymentPending && !(r.remainingPayment > 0)) return false; + if (filterPostPaymentPending && !isPostPaymentPending(r)) return false; if (!term) return true; return [bookingRef(r), customerName(r), deliveryLocation(r), cargoDesc(r)] .join(" ") @@ -1089,7 +1103,7 @@ const LastMilePage = () => { id: "postPayment", header: "Post Payment", meta: { headerClassName, cellClassName }, - cell: ({ row }) => formatPrice(row.original.remainingPayment), + cell: ({ row }) => formatPrice(row.original.remainingPayment, currencyOf(row.original)), }, { id: "vehicle", @@ -1242,7 +1256,7 @@ const LastMilePage = () => { } - disabled={!assigned || delivered} + disabled={!assigned || delivered || Boolean(row.original.invoice)} onClick={() => openAssign(row.original.id)} > Reassign @@ -1250,7 +1264,7 @@ const LastMilePage = () => { } - disabled={!assigned || delivered} + disabled={!assigned || delivered || Boolean(row.original.invoice)} onClick={() => setVehiclesMutation.mutate( { id: row.original.id, vehicles: [] }, @@ -1387,7 +1401,7 @@ const LastMilePage = () => { setPagination((p) => ({ ...p, pageIndex: 0 })); }} > - Post Payment Pending + Post Payment Pending ({postPaymentPendingCount}) @@ -1928,7 +1942,7 @@ const LastMilePage = () => { Invoice amount - {formatPrice(invoiceConfirm.remainingPayment)} + {formatPrice(invoiceConfirm.remainingPayment, currencyOf(invoiceConfirm))} This creates the delivery-fee invoice. Confirm the distances and amount are correct. diff --git a/apps/edr-freight-web/backoffice/src/services/first-mile.service.ts b/apps/edr-freight-web/backoffice/src/services/first-mile.service.ts index 1418cb636..a1c2ec8e2 100644 --- a/apps/edr-freight-web/backoffice/src/services/first-mile.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/first-mile.service.ts @@ -16,6 +16,7 @@ export interface FirstMileBooking { cargoFreeText?: string | null; cargoTotalWeightVgm: number; totalAmount: number; + paymentCurrency?: string | null; scheduledDate?: string | null; company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null; serviceType?: { id: string; label?: string } | null; diff --git a/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts b/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts index c68c5a9a2..2f8a1cec5 100644 --- a/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts @@ -16,6 +16,7 @@ export interface LastMileBooking { cargoFreeText?: string | null; cargoTotalWeightVgm: number; totalAmount: number; + paymentCurrency?: string | null; scheduledDate?: string | null; company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null; serviceType?: { id: string; name?: string; label?: string } | null;