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} /> <ActivityCard booking={booking} />
<MileSummaryCard bookingId={booking.id} /> <MileSummaryCard booking={booking} />
</> </>
} }
right={ right={

View File

@@ -1,6 +1,8 @@
import { Box, Group, Stack, Text } from "@mantine/core"; import { Box, Group, Stack, Text } from "@mantine/core";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import type { Freight } from "@edr/types";
import { bookingsService } from "@/services/bookings.service"; import { bookingsService } from "@/services/bookings.service";
import type { import type {
MileLegSummary, MileLegSummary,
@@ -81,11 +83,19 @@ function VehicleRow({ v }: { v: MileVehicleSummary }) {
); );
} }
function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) { function LegBlock({
const fmtMoney = (n: number | null) => title,
leg,
address,
}: {
title: string;
leg: MileLegSummary | null;
address?: string | null;
}) {
const fmtMoney = (n: number | null, currency: string) =>
n == null n == null
? null ? null
: `${leg.currency} ${Number(n).toLocaleString(undefined, { : `${currency} ${Number(n).toLocaleString(undefined, {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
})}`; })}`;
@@ -96,10 +106,25 @@ function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) {
<Text fz="13px" fw={700} c="#10202F"> <Text fz="13px" fw={700} c="#10202F">
{title} {title}
</Text> </Text>
<StatusPill status={leg.status} /> {leg ? (
<StatusPill status={leg.status} />
) : (
<StatusPill status="AWAITING_ASSIGNMENT" />
)}
</Group> </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> <Box>
{leg.vehicles.map((v, i) => ( {leg.vehicles.map((v, i) => (
<VehicleRow key={`${v.plate ?? v.code ?? "v"}-${i}`} v={v} /> <VehicleRow key={`${v.plate ?? v.code ?? "v"}-${i}`} v={v} />
@@ -111,36 +136,57 @@ function LegBlock({ title, leg }: { title: string; leg: MileLegSummary }) {
</Text> </Text>
)} )}
<Group justify="space-between" align="center" pt={8} mt={4} style={{ borderTop: "1px solid #F2F5F8" }}> {leg && (
<Group gap={16}> <Group
{leg.exactKm != null && ( justify="space-between"
<Text fz="12px" c="#6B7C8E"> align="center"
Total distance <b style={{ color: "#10202F" }}>{leg.exactKm} km</b> pt={8}
</Text> mt={4}
)} style={{ borderTop: "1px solid #F2F5F8" }}
{leg.remainingPayment != null && leg.remainingPayment > 0 && ( >
<Text fz="12px" c="#6B7C8E"> <Group gap={16}>
Balance <b style={{ color: "#10202F" }}>{fmtMoney(leg.remainingPayment)}</b> {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> </Text>
)} )}
</Group> </Group>
{leg.invoiced && ( )}
<Text fz="11px" fw={700} c="#0A6F4D">
Invoiced
</Text>
)}
</Group>
</Box> </Box>
); );
} }
export function MileSummaryCard({ bookingId }: { bookingId: string }) { export function MileSummaryCard({ booking }: { booking: Freight.IBooking }) {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ["booking-mile-summary", bookingId], queryKey: ["booking-mile-summary", booking.id],
queryFn: () => bookingsService.mileSummary(bookingId), 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 ( return (
<SectionCard p={22}> <SectionCard p={22}>
@@ -148,8 +194,20 @@ export function MileSummaryCard({ bookingId }: { bookingId: string }) {
<CardTitle>First & Last Mile</CardTitle> <CardTitle>First & Last Mile</CardTitle>
</Box> </Box>
<Stack gap={20}> <Stack gap={20}>
{data.firstMile && <LegBlock title="First mile" leg={data.firstMile} />} {showFirst && (
{data.lastMile && <LegBlock title="Last mile" leg={data.lastMile} />} <LegBlock
title="First mile"
leg={firstLeg}
address={booking.firstMilePickupAddress}
/>
)}
{showLast && (
<LegBlock
title="Last mile"
leg={lastLeg}
address={booking.lastMileDeliveryAddress}
/>
)}
</Stack> </Stack>
</SectionCard> </SectionCard>
); );