mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 13:35:03 +00:00
The booking's freight invoice is looked up through the existing invoice list endpoint (source=booking, sourceId matched by search), newest first so a re-issue supersedes the old number.
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import { Banknote, Receipt } from "lucide-react";
|
|
import { Divider, Group, Paper, Stack, Text } from "@mantine/core";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
|
|
import { api } from "@/services/api";
|
|
import type { BookingDetail } from "@/types/booking";
|
|
|
|
import { SectionCard } from "./detail/SectionCard";
|
|
import { detailStyles } from "./detail/booking-detail.styles";
|
|
|
|
export function BookingPricingSummary({ booking }: { booking: BookingDetail }) {
|
|
// The booking's own freight invoice: source `booking`, sourceId = booking id
|
|
// (which `search` matches). Newest first — a re-issue supersedes the old one.
|
|
const invoiceQuery = useQuery(
|
|
api.invoices.list.queryOptions({
|
|
input: {
|
|
filter: {
|
|
page: 1,
|
|
pageSize: 1,
|
|
sources: "booking",
|
|
search: booking.id,
|
|
sortBy: "createdAt",
|
|
sortOrder: "DESC",
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const invoiceNumber = invoiceQuery.data?.items[0]?.invoiceNumber ?? null;
|
|
|
|
const computed = Number(booking.totalAmount);
|
|
// The booking price is computed from the contract and is NOT staff-editable.
|
|
// A historical `adjustedTotalAmount` (from before adjustments were removed)
|
|
// is still shown read-only so old records render correctly.
|
|
const isAdjusted =
|
|
booking.adjustedTotalAmount !== null &&
|
|
booking.adjustedTotalAmount !== undefined;
|
|
const effective = isAdjusted ? Number(booking.adjustedTotalAmount) : computed;
|
|
|
|
const lineItems = booking.pricingBreakdown?.lineItems ?? [];
|
|
|
|
const fmt = (n: number) =>
|
|
`${booking.paymentCurrency} ${n.toLocaleString(undefined, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
})}`;
|
|
|
|
return (
|
|
<SectionCard icon={Banknote} title="Pricing & payment">
|
|
<Stack gap="md">
|
|
<Paper radius="md" withBorder p="md" style={detailStyles.highlightCard}>
|
|
<Text size="xs" c="dimmed" fw={500} tt="uppercase" lts="0.04em">
|
|
{isAdjusted ? "Adjusted total" : "Total amount"}
|
|
</Text>
|
|
<Text
|
|
size="xl"
|
|
fw={700}
|
|
c="edr-green.9"
|
|
mt={4}
|
|
style={{ fontVariantNumeric: "tabular-nums", letterSpacing: "-0.5px" }}
|
|
>
|
|
{fmt(effective)}
|
|
</Text>
|
|
{isAdjusted && (
|
|
<Text size="xs" c="dimmed" mt={2}>
|
|
Computed: {fmt(computed)}
|
|
{booking.adjustmentReason ? ` · ${booking.adjustmentReason}` : ""}
|
|
</Text>
|
|
)}
|
|
</Paper>
|
|
|
|
<Row label="Payment status" value={booking.paymentStatus} />
|
|
{invoiceNumber && <Row label="Invoice number" value={invoiceNumber} mono />}
|
|
{booking.pnrCode && <Row label="PNR code" value={booking.pnrCode} mono />}
|
|
|
|
{lineItems.length > 0 && (
|
|
<>
|
|
<Divider color="var(--mantine-color-gray-2)" />
|
|
<Group gap={6}>
|
|
<Receipt size={13} color="var(--mantine-color-gray-5)" />
|
|
<Text size="xs" c="dimmed" fw={500} tt="uppercase" lts="0.04em">
|
|
Price breakdown
|
|
</Text>
|
|
</Group>
|
|
<Stack gap="xs">
|
|
{lineItems.map((li, i) => (
|
|
<Group
|
|
key={`${li.code}-${i}`}
|
|
justify="space-between"
|
|
px="sm"
|
|
py={6}
|
|
style={{
|
|
borderRadius: 8,
|
|
border: "1px solid var(--mantine-color-gray-2)",
|
|
background: "var(--mantine-color-gray-0)",
|
|
}}
|
|
>
|
|
<Text size="sm" c="dimmed">
|
|
{li.description}
|
|
</Text>
|
|
<Text size="sm" fw={600} style={{ fontVariantNumeric: "tabular-nums" }}>
|
|
{Number(li.amount).toLocaleString(undefined, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
})}{" "}
|
|
{li.currency}
|
|
</Text>
|
|
</Group>
|
|
))}
|
|
</Stack>
|
|
</>
|
|
)}
|
|
</Stack>
|
|
</SectionCard>
|
|
);
|
|
}
|
|
|
|
function Row({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
|
|
return (
|
|
<Group
|
|
justify="space-between"
|
|
px="sm"
|
|
py="xs"
|
|
style={{
|
|
borderRadius: 8,
|
|
border: "1px solid var(--mantine-color-gray-2)",
|
|
background: "var(--mantine-color-gray-0)",
|
|
}}
|
|
>
|
|
<Text size="sm" c="dimmed">
|
|
{label}
|
|
</Text>
|
|
<Text size="sm" fw={600} ff={mono ? "monospace" : undefined}>
|
|
{value}
|
|
</Text>
|
|
</Group>
|
|
);
|
|
}
|