mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
add first /last mile summery on booking detail
This commit is contained in:
@@ -222,7 +222,7 @@ export function ReadonlyBookingView({
|
||||
|
||||
<ActivityCard booking={booking} />
|
||||
|
||||
<MileSummaryCard bookingId={booking.id} />
|
||||
<MileSummaryCard booking={booking} />
|
||||
</>
|
||||
}
|
||||
right={
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user