mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 03:05:42 +00:00
fix
This commit is contained in:
@@ -52,8 +52,8 @@ import { vehiclesService } from "@/services/vehicles.service";
|
|||||||
import { ratesService } from "@/services/rates.service";
|
import { ratesService } from "@/services/rates.service";
|
||||||
import type { BookingDetail } from "@/types/booking";
|
import type { BookingDetail } from "@/types/booking";
|
||||||
|
|
||||||
const formatPrice = (amount: number) =>
|
const formatPrice = (amount: number | string | null | undefined, currency = "ETB") =>
|
||||||
`ETB ${amount.toLocaleString("en-US", {
|
`${currency || "ETB"} ${(Number(amount) || 0).toLocaleString("en-US", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}`;
|
})}`;
|
||||||
@@ -111,8 +111,17 @@ const vehicleLabel = (record: FirstMileRecord) => {
|
|||||||
|
|
||||||
const isAssigned = (record: FirstMileRecord) => Boolean(record.vehicleId);
|
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
|
// Map API record → display fields used in modals and trip slip
|
||||||
const bookingRef = (r: FirstMileRecord) => r.booking?.reference ?? r.bookingId;
|
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 customerName = (r: FirstMileRecord) => r.booking?.company?.name ?? "—";
|
||||||
const pickupLocation = (r: FirstMileRecord) => r.booking?.firstMilePickupAddress ?? "—";
|
const pickupLocation = (r: FirstMileRecord) => r.booking?.firstMilePickupAddress ?? "—";
|
||||||
const cargoDesc = (r: FirstMileRecord) => {
|
const cargoDesc = (r: FirstMileRecord) => {
|
||||||
@@ -169,8 +178,8 @@ const BookingInfo = ({ record }: { record: FirstMileRecord }) => {
|
|||||||
{hasPickupAddress && <InfoRow label="Pickup location" value={pickupLocation(record)} />}
|
{hasPickupAddress && <InfoRow label="Pickup location" value={pickupLocation(record)} />}
|
||||||
<InfoRow label="Destination (origin yard)" value={destinationYardName(record)} />
|
<InfoRow label="Destination (origin yard)" value={destinationYardName(record)} />
|
||||||
<InfoRow label="Cargo" value={cargoDesc(record)} />
|
<InfoRow label="Cargo" value={cargoDesc(record)} />
|
||||||
<InfoRow label="Advanced Payment" value={formatPrice(record.advancedPayment)} />
|
<InfoRow label="Advanced Payment" value={formatPrice(record.advancedPayment, currencyOf(record))} />
|
||||||
<InfoRow label="Post Payment" value={formatPrice(record.remainingPayment)} />
|
<InfoRow label="Post Payment" value={formatPrice(record.remainingPayment, currencyOf(record))} />
|
||||||
<InfoRow label="Contact" value={contactPersonName(record)} />
|
<InfoRow label="Contact" value={contactPersonName(record)} />
|
||||||
<InfoRow label="Phone" value={contactPhone(record)} />
|
<InfoRow label="Phone" value={contactPhone(record)} />
|
||||||
<InfoRow label="Requested date" value={requestedDate(record)} />
|
<InfoRow label="Requested date" value={requestedDate(record)} />
|
||||||
@@ -189,8 +198,8 @@ const tripSlipRows = (record: FirstMileRecord): [string, string][] => [
|
|||||||
["Pickup location", pickupLocation(record)],
|
["Pickup location", pickupLocation(record)],
|
||||||
["Destination yard", destinationYardName(record)],
|
["Destination yard", destinationYardName(record)],
|
||||||
["Cargo", cargoDesc(record)],
|
["Cargo", cargoDesc(record)],
|
||||||
["Advanced Payment", formatPrice(record.advancedPayment)],
|
["Advanced Payment", formatPrice(record.advancedPayment, currencyOf(record))],
|
||||||
["Post Payment", formatPrice(record.remainingPayment)],
|
["Post Payment", formatPrice(record.remainingPayment, currencyOf(record))],
|
||||||
["Vehicle", vehicleLabel(record) ?? "Unassigned"],
|
["Vehicle", vehicleLabel(record) ?? "Unassigned"],
|
||||||
["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`],
|
["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`],
|
||||||
["Requested date", requestedDate(record)],
|
["Requested date", requestedDate(record)],
|
||||||
@@ -589,6 +598,7 @@ const FirstMilePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const matchesFilter = (r: FirstMileRecord) => {
|
const matchesFilter = (r: FirstMileRecord) => {
|
||||||
|
if (filterPostPaymentPending && !isPostPaymentPending(r)) return false;
|
||||||
switch (statusFilter) {
|
switch (statusFilter) {
|
||||||
case "ALL": return true;
|
case "ALL": return true;
|
||||||
case "ASSIGNED": return isAssigned(r);
|
case "ASSIGNED": return isAssigned(r);
|
||||||
@@ -615,6 +625,11 @@ const FirstMilePage = () => {
|
|||||||
return counts;
|
return counts;
|
||||||
}, [records]);
|
}, [records]);
|
||||||
|
|
||||||
|
const postPaymentPendingCount = useMemo(
|
||||||
|
() => records.filter(isPostPaymentPending).length,
|
||||||
|
[records],
|
||||||
|
);
|
||||||
|
|
||||||
const filteredRecords = useMemo(() => {
|
const filteredRecords = useMemo(() => {
|
||||||
const term = search.trim().toLowerCase();
|
const term = search.trim().toLowerCase();
|
||||||
return records.filter((r) => {
|
return records.filter((r) => {
|
||||||
@@ -751,7 +766,7 @@ const FirstMilePage = () => {
|
|||||||
id: "postPayment",
|
id: "postPayment",
|
||||||
header: "Post Payment",
|
header: "Post Payment",
|
||||||
meta: { headerClassName, cellClassName },
|
meta: { headerClassName, cellClassName },
|
||||||
cell: ({ row }) => formatPrice(row.original.remainingPayment),
|
cell: ({ row }) => formatPrice(row.original.remainingPayment, currencyOf(row.original)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "vehicle",
|
id: "vehicle",
|
||||||
@@ -853,7 +868,7 @@ const FirstMilePage = () => {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<RefreshCw size={15} />}
|
leftSection={<RefreshCw size={15} />}
|
||||||
disabled={!assigned}
|
disabled={!assigned || Boolean(row.original.invoice)}
|
||||||
onClick={() => openAssign(row.original.id)}
|
onClick={() => openAssign(row.original.id)}
|
||||||
>
|
>
|
||||||
Reassign
|
Reassign
|
||||||
@@ -975,7 +990,7 @@ const FirstMilePage = () => {
|
|||||||
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Post Payment Pending
|
Post Payment Pending ({postPaymentPendingCount})
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ import { ratesService } from "@/services/rates.service";
|
|||||||
import { ReleaseOrderModal, type ReleaseOrderTruckPrefill } from "@/components/warehouses/ReleaseOrderModal";
|
import { ReleaseOrderModal, type ReleaseOrderTruckPrefill } from "@/components/warehouses/ReleaseOrderModal";
|
||||||
import { LastMileStepper, type LastMileStepState } from "@/components/operations/LastMileSteps";
|
import { LastMileStepper, type LastMileStepState } from "@/components/operations/LastMileSteps";
|
||||||
|
|
||||||
const formatPrice = (amount: number) =>
|
const formatPrice = (amount: number | string | null | undefined, currency = "ETB") =>
|
||||||
`ETB ${amount.toLocaleString("en-US", {
|
`${currency || "ETB"} ${(Number(amount) || 0).toLocaleString("en-US", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
})}`;
|
})}`;
|
||||||
@@ -156,6 +156,14 @@ const containerLabels = (record: LastMileRecord): string[] => {
|
|||||||
|
|
||||||
const isAssigned = (record: LastMileRecord) => Boolean(record.vehicleId);
|
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) => {
|
const fmtStamp = (iso?: string | null) => {
|
||||||
if (!iso) return null;
|
if (!iso) return null;
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
@@ -209,6 +217,7 @@ const computeLastMileSteps = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bookingRef = (r: LastMileRecord) => r.booking?.reference ?? r.bookingId;
|
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 customerName = (r: LastMileRecord) => r.booking?.company?.name ?? "—";
|
||||||
const deliveryLocation = (r: LastMileRecord) => r.booking?.lastMileDeliveryAddress ?? "—";
|
const deliveryLocation = (r: LastMileRecord) => r.booking?.lastMileDeliveryAddress ?? "—";
|
||||||
const cargoDesc = (r: LastMileRecord) => {
|
const cargoDesc = (r: LastMileRecord) => {
|
||||||
@@ -311,8 +320,8 @@ const BookingInfo = ({ record }: { record: LastMileRecord }) => {
|
|||||||
<InfoRow label="Pickup (origin yard)" value={originYardName(record)} />
|
<InfoRow label="Pickup (origin yard)" value={originYardName(record)} />
|
||||||
{hasDeliveryAddress && <InfoRow label="Destination" value={deliveryLocation(record)} />}
|
{hasDeliveryAddress && <InfoRow label="Destination" value={deliveryLocation(record)} />}
|
||||||
<InfoRow label="Cargo" value={cargoDesc(record)} />
|
<InfoRow label="Cargo" value={cargoDesc(record)} />
|
||||||
<InfoRow label="Advanced Payment" value={formatPrice(record.advancedPayment)} />
|
<InfoRow label="Advanced Payment" value={formatPrice(record.advancedPayment, currencyOf(record))} />
|
||||||
<InfoRow label="Post Payment" value={formatPrice(record.remainingPayment)} />
|
<InfoRow label="Post Payment" value={formatPrice(record.remainingPayment, currencyOf(record))} />
|
||||||
<InfoRow label="Contact" value={contactPersonName(record)} />
|
<InfoRow label="Contact" value={contactPersonName(record)} />
|
||||||
<InfoRow label="Phone" value={contactPhone(record)} />
|
<InfoRow label="Phone" value={contactPhone(record)} />
|
||||||
<InfoRow label="Requested date" value={requestedDate(record)} />
|
<InfoRow label="Requested date" value={requestedDate(record)} />
|
||||||
@@ -358,7 +367,7 @@ const tripSlipRows = (
|
|||||||
["Pickup (origin yard)", originYardName(record)],
|
["Pickup (origin yard)", originYardName(record)],
|
||||||
["Destination", deliveryLocation(record)],
|
["Destination", deliveryLocation(record)],
|
||||||
["Cargo", cargoDesc(record)],
|
["Cargo", cargoDesc(record)],
|
||||||
["Post Payment", formatPrice(record.remainingPayment)],
|
["Post Payment", formatPrice(record.remainingPayment, currencyOf(record))],
|
||||||
...vehicleRows,
|
...vehicleRows,
|
||||||
["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`],
|
["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`],
|
||||||
["Requested date", requestedDate(record)],
|
["Requested date", requestedDate(record)],
|
||||||
@@ -870,11 +879,16 @@ const LastMilePage = () => {
|
|||||||
return counts;
|
return counts;
|
||||||
}, [records]);
|
}, [records]);
|
||||||
|
|
||||||
|
const postPaymentPendingCount = useMemo(
|
||||||
|
() => records.filter(isPostPaymentPending).length,
|
||||||
|
[records],
|
||||||
|
);
|
||||||
|
|
||||||
const filteredRecords = useMemo(() => {
|
const filteredRecords = useMemo(() => {
|
||||||
const term = search.trim().toLowerCase();
|
const term = search.trim().toLowerCase();
|
||||||
return records.filter((r) => {
|
return records.filter((r) => {
|
||||||
if (!matchesFilter(r)) return false;
|
if (!matchesFilter(r)) return false;
|
||||||
if (filterPostPaymentPending && !(r.remainingPayment > 0)) return false;
|
if (filterPostPaymentPending && !isPostPaymentPending(r)) return false;
|
||||||
if (!term) return true;
|
if (!term) return true;
|
||||||
return [bookingRef(r), customerName(r), deliveryLocation(r), cargoDesc(r)]
|
return [bookingRef(r), customerName(r), deliveryLocation(r), cargoDesc(r)]
|
||||||
.join(" ")
|
.join(" ")
|
||||||
@@ -1089,7 +1103,7 @@ const LastMilePage = () => {
|
|||||||
id: "postPayment",
|
id: "postPayment",
|
||||||
header: "Post Payment",
|
header: "Post Payment",
|
||||||
meta: { headerClassName, cellClassName },
|
meta: { headerClassName, cellClassName },
|
||||||
cell: ({ row }) => formatPrice(row.original.remainingPayment),
|
cell: ({ row }) => formatPrice(row.original.remainingPayment, currencyOf(row.original)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "vehicle",
|
id: "vehicle",
|
||||||
@@ -1242,7 +1256,7 @@ const LastMilePage = () => {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<RefreshCw size={15} />}
|
leftSection={<RefreshCw size={15} />}
|
||||||
disabled={!assigned || delivered}
|
disabled={!assigned || delivered || Boolean(row.original.invoice)}
|
||||||
onClick={() => openAssign(row.original.id)}
|
onClick={() => openAssign(row.original.id)}
|
||||||
>
|
>
|
||||||
Reassign
|
Reassign
|
||||||
@@ -1250,7 +1264,7 @@ const LastMilePage = () => {
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
color="red"
|
color="red"
|
||||||
leftSection={<X size={15} />}
|
leftSection={<X size={15} />}
|
||||||
disabled={!assigned || delivered}
|
disabled={!assigned || delivered || Boolean(row.original.invoice)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setVehiclesMutation.mutate(
|
setVehiclesMutation.mutate(
|
||||||
{ id: row.original.id, vehicles: [] },
|
{ id: row.original.id, vehicles: [] },
|
||||||
@@ -1387,7 +1401,7 @@ const LastMilePage = () => {
|
|||||||
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Post Payment Pending
|
Post Payment Pending ({postPaymentPendingCount})
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1928,7 +1942,7 @@ const LastMilePage = () => {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text fw={600}>Invoice amount</Text>
|
<Text fw={600}>Invoice amount</Text>
|
||||||
<Text fw={700}>{formatPrice(invoiceConfirm.remainingPayment)}</Text>
|
<Text fw={700}>{formatPrice(invoiceConfirm.remainingPayment, currencyOf(invoiceConfirm))}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
This creates the delivery-fee invoice. Confirm the distances and amount are correct.
|
This creates the delivery-fee invoice. Confirm the distances and amount are correct.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface FirstMileBooking {
|
|||||||
cargoFreeText?: string | null;
|
cargoFreeText?: string | null;
|
||||||
cargoTotalWeightVgm: number;
|
cargoTotalWeightVgm: number;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
paymentCurrency?: string | null;
|
||||||
scheduledDate?: string | null;
|
scheduledDate?: string | null;
|
||||||
company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null;
|
company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null;
|
||||||
serviceType?: { id: string; label?: string } | null;
|
serviceType?: { id: string; label?: string } | null;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface LastMileBooking {
|
|||||||
cargoFreeText?: string | null;
|
cargoFreeText?: string | null;
|
||||||
cargoTotalWeightVgm: number;
|
cargoTotalWeightVgm: number;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
|
paymentCurrency?: string | null;
|
||||||
scheduledDate?: string | null;
|
scheduledDate?: string | null;
|
||||||
company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null;
|
company?: { id: string; name?: string; phone?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null } | null;
|
||||||
serviceType?: { id: string; name?: string; label?: string } | null;
|
serviceType?: { id: string; name?: string; label?: string } | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user