add first /last mile summery on booking detail

This commit is contained in:
natib21
2026-07-09 03:19:57 +00:00
parent 3263cf4262
commit 13adc02e13
2 changed files with 86 additions and 28 deletions

View File

@@ -222,7 +222,7 @@ export function ReadonlyBookingView({
<ActivityCard booking={booking} />
<MileSummaryCard bookingId={booking.id} />
<MileSummaryCard booking={booking} />
</>
}
right={

View File

@@ -1,6 +1,8 @@
import { Box, Group, Stack, Text } from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import type { Freight } from "@edr/types";
import { bookingsService } from "@/services/bookings.service";
import type {
MileLegSummary,
@@ -81,11 +83,19 @@ function VehicleRow({ v }: { v: MileVehicleSummary }) {
);
}
function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) {
const fmtMoney = (n: number | null) =>
function LegBlock({
title,
leg,
address,
}: {
title: string;
leg: MileLegSummary | null;
address?: string | null;
}) {
const fmtMoney = (n: number | null, currency: string) =>
n == null
? null
: `${leg.currency} ${Number(n).toLocaleString(undefined, {
: `${currency} ${Number(n).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}`;
@@ -96,10 +106,25 @@ function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) {
<Text fz="13px" fw={700} c="#10202F">
{title}
</Text>
<StatusPill status={leg.status} />
{leg ? (
<StatusPill status={leg.status} />
) : (
<StatusPill status="AWAITING_ASSIGNMENT" />
)}
</Group>
{leg.vehicles.length > 0 ? (
{address && (
<Text fz="12px" c="#6B7C8E" pb={4}>
{title.startsWith("First") ? "Pickup" : "Delivery"}:{" "}
<b style={{ color: "#10202F" }}>{address}</b>
</Text>
)}
{!leg ? (
<Text fz="12px" c="#9AA8B5" fs="italic" py={6}>
Requested a vehicle and driver will be assigned soon.
</Text>
) : leg.vehicles.length > 0 ? (
<Box>
{leg.vehicles.map((v, i) => (
<VehicleRow key={`${v.plate ?? v.code ?? "v"}-${i}`} v={v} />
@@ -111,36 +136,57 @@ function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) {
</Text>
)}
<Group justify="space-between" align="center" pt={8} mt={4} style={{ borderTop: "1px solid #F2F5F8" }}>
<Group gap={16}>
{leg.exactKm != null && (
<Text fz="12px" c="#6B7C8E">
Total distance <b style={{ color: "#10202F" }}>{leg.exactKm} km</b>
</Text>
)}
{leg.remainingPayment != null && leg.remainingPayment > 0 && (
<Text fz="12px" c="#6B7C8E">
Balance <b style={{ color: "#10202F" }}>{fmtMoney(leg.remainingPayment)}</b>
{leg && (
<Group
justify="space-between"
align="center"
pt={8}
mt={4}
style={{ borderTop: "1px solid #F2F5F8" }}
>
<Group gap={16}>
{leg.exactKm != null && (
<Text fz="12px" c="#6B7C8E">
Total distance{" "}
<b style={{ color: "#10202F" }}>{leg.exactKm} km</b>
</Text>
)}
{leg.remainingPayment != null && leg.remainingPayment > 0 && (
<Text fz="12px" c="#6B7C8E">
Balance{" "}
<b style={{ color: "#10202F" }}>
{fmtMoney(leg.remainingPayment, leg.currency)}
</b>
</Text>
)}
</Group>
{leg.invoiced && (
<Text fz="11px" fw={700} c="#0A6F4D">
Invoiced
</Text>
)}
</Group>
{leg.invoiced && (
<Text fz="11px" fw={700} c="#0A6F4D">
Invoiced
</Text>
)}
</Group>
)}
</Box>
);
}
export function MileSummaryCard({ bookingId }: { bookingId: string }) {
export function MileSummaryCard({ booking }: { booking: Freight.IBooking }) {
const { data } = useQuery({
queryKey: ["booking-mile-summary", bookingId],
queryFn: () => bookingsService.mileSummary(bookingId),
queryKey: ["booking-mile-summary", booking.id],
queryFn: () => bookingsService.mileSummary(booking.id),
});
if (!data || (!data.firstMile && !data.lastMile)) return null;
const firstLeg = data?.firstMile ?? null;
const lastLeg = data?.lastMile ?? null;
// The backend doesn't persist an "enabled" flag — the presence of a
// pickup/delivery address is the request signal. Also show a leg once its
// record exists, regardless of address.
const showFirst = !!booking.firstMilePickupAddress || !!firstLeg;
const showLast = !!booking.lastMileDeliveryAddress || !!lastLeg;
if (!showFirst && !showLast) return null;
return (
<SectionCard p={22}>
@@ -148,8 +194,20 @@ export function MileSummaryCard({ bookingId }: { bookingId: string }) {
<CardTitle>First & Last Mile</CardTitle>
</Box>
<Stack gap={20}>
{data.firstMile && <LegBlock title="First mile" leg={data.firstMile} />}
{data.lastMile && <LegBlock title="Last mile" leg={data.lastMile} />}
{showFirst && (
<LegBlock
title="First mile"
leg={firstLeg}
address={booking.firstMilePickupAddress}
/>
)}
{showLast && (
<LegBlock
title="Last mile"
leg={lastLeg}
address={booking.lastMileDeliveryAddress}
/>
)}
</Stack>
</SectionCard>
);