import type { Asset, BookedLine, Invoice } from "@/lib/data"; import { ZAR2 } from "@/lib/utils"; import { AlertTriangle, CheckCircle2 } from "lucide-react"; import { MachineCombobox } from "@/components/MachineCombobox"; import { ScrollFade } from "@/components/ScrollFade"; import { SiteBookingSelect, SiteBookingLegend } from "@/components/SiteBookingSelect"; import { InvoiceUploader, AttachedDocument } from "@/components/InvoiceUploader"; export function InvoiceSummary({ invoice, lines, assets, embedded, }: { invoice: Invoice; lines: BookedLine[]; assets: Asset[]; // Inside the expandable invoice row the surrounding card already shows the number and // site, so the header block is dropped to avoid saying the same thing twice. embedded?: boolean; }) { const unbooked = lines.filter((l) => !l.isBooked); // Two different numbers, both worth showing. // ex-VAT = what actually lands in the P&L as income for the machine. // incl-VAT= what the debtor actually pays (ex-VAT + SARS VAT). // Booking is done ex-VAT: VAT is collected for SARS, never income. const period = `${invoice.period_from.slice(0, 10)} → ${invoice.period_to.slice(0, 10)}`; const periodMonth = new Date(invoice.period_to).toLocaleDateString("en-ZA", { month: "long", year: "numeric", timeZone: "UTC", }); return (
{invoice.description}
{unbooked.length} line{unbooked.length > 1 ? "s" : ""} not yet booked to a machine — choose a plant item below.
All {lines.length} lines booked to {periodMonth} income.
| Machine | Description | Qty | Unit | Rate | Hire ex VAT | VAT | Total incl | Booked to |
|---|---|---|---|---|---|---|---|---|
|
{l.line_no}
{l.machine_code}
|
{l.description} | {l.qty} | {l.unit} | {ZAR2(l.rate)} | {ZAR2(l.amount_excl)} | {ZAR2(l.vat)} | {ZAR2(l.total_incl)} |
{/* Stacked, not side by side. At 768px the Booked to column is only
~166px, so two 200px pickers could never fit across — as flex
children they were compressed into each other by ~12px, and once
forced not to shrink they spilled PAST the edge of the table. The
column now declares a min-width, the controls stack inside it, and
the table scrolls inside its own box instead of overflowing. */}
Pick a machine above to save it. No match in the register? Book it to the site instead — it posts no income, because no machine carries it. )} |