Files
edr-platform/apps/edr-freight-web/backoffice/src/components/overview/OverviewRecentBookingsTable.tsx
ghost2023 214f96dbae feat(freight-backoffice): support DJF in booking, contract and warehouse screens
Currency dropdowns/pickers (AdditionalPaymentsTab, ClearanceChargesTab,
PhasedClearanceActionPanel, AdviseDutyCard, ContractRequestsPage,
ruleEngine/resources, WarehouseRulesPage, VehicleDetailPage,
FeePreviewModal) offer DJF alongside ETB/USD; GlCreateBookingForm's
currency selector gets allowDjf next to allowUsd. Narrow 'ETB'|'USD'
type unions widened to include 'DJF' across the warehouse
billingCurrency plumbing (useWarehouses, warehouse.service, api.ts)
and the customer/invoice types.

Ad-hoc money() formatters (BookingTrucksPanel, AccrualDashboard,
ImportTrucksPage, EmptyReturnRequestsPage) and formatMoney call sites
that hardcoded 2 decimals (wagon-cancellation cards, BookingRequestDetailPage,
WagonCancellationsPage, PaymentsPage, WarehouseInvoicesPage) now use
currencyDecimals() from @edr/ui-common so DJF renders with 0 decimals
instead of forced cents. The 3 duplicate overview formatCurrency/
formatAmount helpers (typed 'ETB'|'USD') widen to accept any currency.

Two correctness fixes: OverviewRecentBookingsTable's currency==='USD'
? 'USD' : 'ETB' was mislabeling every non-USD currency as ETB; and
WarehouseInvoicesPage's gateway-method default now routes any
non-ETB currency (not just USD) to WAAFI, so DJF invoices get a
working default instead of TELEBIRR (ETB-only).

Claude-Session: https://claude.ai/code/session_01CZy77vCWhka3pnmVF9NDkL
2026-09-04 11:53:56 +03:00

114 lines
3.6 KiB
TypeScript

import { History } from "lucide-react";
import { Link, useNavigate } from "react-router-dom";
import { Table, Text } from "@mantine/core";
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
import type { IOverviewRecentBooking } from "@/types/overview";
import { SummaryCard } from "./summary/SummaryCard";
function formatAmount(amount: number | null, currency: string | null) {
if (amount == null) return "—";
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency || "ETB",
maximumFractionDigits: 0,
}).format(amount);
}
export function OverviewRecentBookingsTable({
bookings,
}: {
bookings: IOverviewRecentBooking[];
}) {
const navigate = useNavigate();
return (
<SummaryCard
icon={History}
accent="gray"
title="Recent bookings"
subtitle="Latest submissions, newest first"
minHeight={260}
action={
<Text
component={Link}
to="/dashboard/booking-requests"
size="xs"
fw={600}
c="edr-green"
style={{ whiteSpace: "nowrap", textDecoration: "none" }}
>
View all
</Text>
}
>
{bookings.length === 0 ? (
<Text size="sm" c="dimmed" ta="center" py="lg">
No recent bookings
</Text>
) : (
<Table highlightOnHover verticalSpacing="sm" horizontalSpacing="sm">
<Table.Thead>
<Table.Tr>
{["Reference", "Customer", "Status", "Priority", "Amount", "Created"].map(
(header) => (
<Table.Th
key={header}
style={{
fontSize: 11,
fontWeight: 700,
textTransform: "uppercase",
letterSpacing: 0.3,
color: "var(--mantine-color-edr-muted-6)",
borderBottom: "1px solid var(--mantine-color-gray-2)",
}}
>
{header}
</Table.Th>
),
)}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{bookings.map((booking) => (
<Table.Tr
key={booking.id}
style={{ cursor: "pointer" }}
onClick={() => navigate(`/dashboard/booking-requests/${booking.id}`)}
>
<Table.Td>
<Text fw={600} size="sm">
{booking.reference}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm" truncate style={{ maxWidth: 180 }}>
{booking.customerLabel}
</Text>
</Table.Td>
<Table.Td>
<BookingStatusBadge status={booking.status} />
</Table.Td>
<Table.Td>
<BookingPriorityBadge score={booking.priorityScore} />
</Table.Td>
<Table.Td>
<Text size="sm" style={{ fontVariantNumeric: "tabular-nums" }}>
{formatAmount(booking.totalAmount, booking.paymentCurrency)}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed">
{new Date(booking.createdAt).toLocaleDateString()}
</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</SummaryCard>
);
}