import type { ReactNode } from 'react' import { cn } from '@/lib/utils' import type { InvoiceItemResponse, InvoiceResponse, PaymentResponse } from '@/types/api' import { fmtDate, fmtMoney, fmtQty, invoiceNo } from './invoice-format' export type InvoicePaper = 'A4' | '80mm' | '58mm' export interface InvoiceCustomer { address?: string | null phone?: string | null } interface InvoiceTemplateProps { invoice: InvoiceResponse payments?: PaymentResponse[] customer?: InvoiceCustomer paper?: InvoicePaper // Draft-editor hooks (A4 only): inputs in the qty / price cells, a per-row action, and an extra table row. renderQuantity?: (item: InvoiceItemResponse) => ReactNode renderUnitPrice?: (item: InvoiceItemResponse) => ReactNode renderActions?: (item: InvoiceItemResponse) => ReactNode footerRow?: ReactNode // Blank lines for the agent's handwritten edits on the paper copy. showAgentEdits?: boolean className?: string } // Soft slate palette: light rules, muted section headings. const RULE = '#cbd5e1' const HEADING = '#64748b' const statusLabel = (invoice: InvoiceResponse) => invoice.status === 'Finalized' ? null : invoice.status.toUpperCase() // Removed lines stay on the document (struck through) but are excluded from every total. function totals(invoice: InvoiceResponse, payments: PaymentResponse[]) { const subtotal = invoice.items.filter((i) => !i.isRemoved).reduce((s, i) => s + i.quantity * i.unitPrice, 0) const discount = Math.min(invoice.discountAmount ?? 0, subtotal) const total = subtotal - discount const paid = payments.reduce((s, p) => s + p.amount, 0) return { subtotal, discount, total, paid, balance: total - paid } } export function InvoiceTemplate({ paper = 'A4', ...props }: InvoiceTemplateProps) { return paper === 'A4' ? : } // ─── A4 ─────────────────────────────────────────────────────────────────────── function A4Sheet({ invoice, payments = [], customer, renderQuantity, renderUnitPrice, renderActions, footerRow, showAgentEdits, className, }: Omit) { const { subtotal, discount, total, paid, balance } = totals(invoice, payments) const status = statusLabel(invoice) const hasRemoved = invoice.items.some((i) => i.isRemoved) const hasFree = invoice.items.some((i) => i.isFree) // Free issues are listed after every paid line. const rows = [...invoice.items].sort((a, b) => Number(a.isFree) - Number(b.isFree)) return (
{/* Header */}

INVOICE

Invoice No.
{invoiceNo(invoice.id)}
Invoice Date
{fmtDate(invoice.createdAt)}
PO Ref.
{invoice.shopPOId.substring(0, 8).toUpperCase()}
{status && ( <>
Status
{status}
)}
{/* Bill to */}

Bill To

{invoice.storeName}

{customer?.address &&

{customer.address}

} {customer?.phone &&

{customer.phone}

}
{/* Items */}

Item Details

{hasFree && } {renderActions && {invoice.items.length === 0 && !footerRow ? ( ) : ( rows.map((item) => ( {hasFree && ( )} {renderActions && } )) )} {footerRow}
Item QtyFree IssueUnit Price Amount}
No items.
{item.itemName} {item.isFree && item.schemeName && ( Free issue · {item.schemeName} )} {item.isFree ? '-' : renderQuantity && !item.isRemoved ? renderQuantity(item) : fmtQty(item.quantity)} {item.isFree ? (renderQuantity && !item.isRemoved ? renderQuantity(item) : fmtQty(item.quantity)) : '-'} {renderUnitPrice && !item.isRemoved ? renderUnitPrice(item) : fmtMoney(item.unitPrice)} {fmtMoney(item.quantity * item.unitPrice)}{renderActions(item)}
{hasRemoved && (

Struck-through lines were removed and are not charged.

)}
{/* Notes + totals */}
{showAgentEdits && ( <>

Notes / Agent Edits

{[0, 1, 2, 3].map((n) => (
))} )}
Subtotal
{fmtMoney(subtotal)}
{discount > 0 && (
Scheme discount
-{fmtMoney(discount)}
)}
Total
{fmtMoney(total)}
{paid > 0 && ( <>
Paid
{fmtMoney(paid)}
Balance Due
{fmtMoney(balance)}
)}
{/* Payments */}

Payments

{payments.length === 0 ? (

No payment recorded yet.

) : ( {payments.map((p) => ( ))}
{fmtDate(p.collectedAt)} {p.method} {p.chequeNumber && · Cheque {p.chequeNumber}} {fmtMoney(p.amount)}
)}
{/* Signatures */}
Authorised by
Received by (customer signature)
) } // ─── 80mm / 58mm thermal receipt ────────────────────────────────────────────── function ReceiptSheet({ invoice, payments = [], customer, paper, showAgentEdits, className, }: Omit & { paper: Exclude }) { const { subtotal, discount, total, paid, balance } = totals(invoice, payments) const status = statusLabel(invoice) const narrow = paper === '58mm' const rule =
const num = (n: number) => n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) return (

INVOICE

{status &&

*** {status} ***

}
{rule}

No: {invoiceNo(invoice.id)}

Date: {fmtDate(invoice.createdAt)}

Customer: {invoice.storeName}

{customer?.phone &&

Phone: {customer.phone}

}
{rule}
{[...invoice.items].sort((a, b) => Number(a.isFree) - Number(b.isFree)).map((i) => (

{i.itemName}

{i.isFree ? ( <> {fmtQty(i.quantity)} FREE{i.schemeName ? ` (${i.schemeName})` : ''} {num(0)} ) : ( <> {fmtQty(i.quantity)} x {num(i.unitPrice)} {num(i.quantity * i.unitPrice)} )}

))}
{rule}

Subtotal {fmtMoney(subtotal)}

{discount > 0 && (

Scheme discount -{fmtMoney(discount)}

)}

TOTAL {fmtMoney(total)}

{paid > 0 && ( <>

Paid{fmtMoney(paid)}

Balance{fmtMoney(balance)}

)} {payments.length > 0 && ( <> {rule} {payments.map((p) => (

{fmtDate(p.collectedAt)} {p.method} {num(p.amount)}

))} )} {showAgentEdits && ( <> {rule}

Notes:

{[0, 1, 2].map((n) =>
)} )} {rule}

Thank you!

) }