# Invoice Page

> A React invoice page to edit and view Romanian B2B invoices: client, series, VAT lines, totals, an A4 preview, sending and e-Factura.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add invoice-page`
- Docs: https://wingo-ui.com/components/invoice-page

## Interaction

Switch between Edit and Preview in the header (arrow keys move the segmented control). In edit mode pick the client in the search field (name, CUI or city; a full-height sheet on phones), or type a new name and choose New client to open a dialog where Look up in ANAF (or Enter in the CUI field) fills the company from its CUI; Change swaps the client. Pick the series (the number jumps to the next free one), the issue date (the due date keeps the same term), the due date with the +15, +30 and +45 days presets, and the currency: EUR converts every price at the BNR rate and shows the RON equivalent under the total. Edit the lines like a spreadsheet (product search, quantity, price, VAT rate, discount, drag or Move up / Move down to reorder, remove with undo; cards with an edit sheet on phones) and type the notes. The aside keeps the summary with VAT per rate and the total, the IBAN with a copy button, and the e-Factura switch in view while you scroll (on a laptop with a sidebar the lines take the full width under them instead). A draft saves itself 800 ms after the last change (Saving…, then Saved a few seconds ago); Ctrl or Cmd + S saves at once. Issue invoice checks the client, its CUI, the number (a taken one fails), the dates and the lines: the problems are listed in an alert whose links move focus to each field, and the lines get their own errors. Issuing uploads to SPV when the switch is on and turns to the preview: an A4 sheet in paper colours in both themes, scaled to the width on tablets and phones (pinch to zoom). Send opens the send dialog (a full-height sheet on phones): recipients as chips, CC, subject, message, the PDF attached and an SPV switch; Ctrl or Cmd + Enter sends. Download PDF opens the print dialog, which prints only the document. The ⋯ menu holds Duplicate (a new draft with the next number), Mark as paid (the badge pops), Issue credit note and Void invoice (undo in a toast for 5 seconds). Overdue invoices offer Send reminder; paid and cancelled ones are read-only. On phones a bar at the bottom shows the total with Issue, Send or Download.

## Installation

```bash
npx wingo-ui@latest add invoice-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/invoice-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/invoice-page.tsx`
- `components/blocks/invoice-page-data.ts`

npm dependencies: `npm i motion lucide-react react-hook-form zod`

Wingo UI dependencies (installed automatically):

- [Alert](https://wingo-ui.com/components/alert) ([Markdown](https://wingo-ui.com/components/alert.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Copy Button](https://wingo-ui.com/components/copy-button) ([Markdown](https://wingo-ui.com/components/copy-button.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))
- [Description List](https://wingo-ui.com/components/description-list) ([Markdown](https://wingo-ui.com/components/description-list.md))
- [Dialog](https://wingo-ui.com/components/dialog) ([Markdown](https://wingo-ui.com/components/dialog.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Invoice line items](https://wingo-ui.com/components/invoice-line-items) ([Markdown](https://wingo-ui.com/components/invoice-line-items.md))
- [Kbd](https://wingo-ui.com/components/kbd) ([Markdown](https://wingo-ui.com/components/kbd.md))
- [Page Header](https://wingo-ui.com/components/page-header) ([Markdown](https://wingo-ui.com/components/page-header.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) ([Markdown](https://wingo-ui.com/components/sticky-cta-bar.md))
- [Switch](https://wingo-ui.com/components/switch) ([Markdown](https://wingo-ui.com/components/switch.md))
- [Tag Input](https://wingo-ui.com/components/tag-input) ([Markdown](https://wingo-ui.com/components/tag-input.md))
- [Textarea](https://wingo-ui.com/components/textarea) ([Markdown](https://wingo-ui.com/components/textarea.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [useShortcut](https://wingo-ui.com/components/use-shortcut) ([Markdown](https://wingo-ui.com/components/use-shortcut.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Date utils](https://wingo-ui.com/components/date-utils) ([Markdown](https://wingo-ui.com/components/date-utils.md))
- [Form validation](https://wingo-ui.com/components/form-validation) ([Markdown](https://wingo-ui.com/components/form-validation.md))
- [Invoicing](https://wingo-ui.com/components/invoicing) ([Markdown](https://wingo-ui.com/components/invoicing.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(admin)/invoices/[id]/page.tsx (the shell lives in app/(admin)/layout.tsx: <AdminShell>{children}</AdminShell>)
"use client"

import { InvoicePage, type InvoiceDocument } from "@/components/blocks/invoice-page"

export default function InvoiceRoute() {
  return (
    <InvoicePage
      onSave={async (invoice: InvoiceDocument) => {
        await fetch(`/api/invoices/${invoice.id}`, { method: "PUT", body: JSON.stringify(invoice) })
      }}
      onIssue={async (invoice) => {
        await fetch(`/api/invoices/${invoice.id}/issue`, { method: "POST" })
      }}
      onSend={async (invoice, payload) => {
        await fetch(`/api/invoices/${invoice.id}/send`, { method: "POST", body: JSON.stringify(payload) })
      }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `invoice?` | `InvoiceDocument` |  | The document, controlled ({ id, series, number, status, issueDate, dueDate, currency, client, supplier, lines, notes, paymentTerms, documentDiscount, sentToSpv, paidAt, issuedBy }); pair it with onInvoiceChange. |
| `defaultInvoice?` | `InvoiceDocument` |  | The document while uncontrolled; defaults to INVOICE_PAGE_SAMPLE (draft CRP #2026-0412 to Tech Solutions SRL). createInvoiceDraft(now) gives an empty one. |
| `onInvoiceChange?` | `(invoice: InvoiceDocument) => void` |  | Called with the whole document after every edit and status change. |
| `mode?` | `"edit" \| "preview"` |  | The view, controlled; pair it with onModeChange. |
| `defaultMode?` | `"edit" \| "preview"` | `"edit"` | The first view while uncontrolled; when left out, edit for drafts and preview for every other status (the sample is a draft). |
| `onModeChange?` | `(mode: InvoiceMode) => void` |  | Called when the segmented control, issuing or duplicating switches the view. |
| `status?` | `"draft" \| "issued" \| "sent" \| "paid" \| "overdue" \| "cancelled"` | `"draft"` | Seeds the document's status on mount (a playground shortcut; the sample is a draft). Overdue moves the dates 3 days past due, paid sets today as the payment date. |
| `clients?` | `InvoiceParty[]` |  | The client picker's options ({ id, name, cui, regCom, address, city, county, email, vatPayer, individual }); defaults to the 12 Northwind CRM clients (INVOICE_PAGE_CLIENTS). |
| `products?` | `InvoiceProduct[]` |  | The line product search; defaults to the 7 Northwind CRM products and services (INVOICE_PAGE_PRODUCTS). |
| `productCurrency?` | `string` | `"RON"` | The currency of the product prices; they are converted to the document's currency through exchangeRate. |
| `supplier?` | `InvoiceParty` |  | The issuer printed on the document and shown in Payment; defaults to Northwind Soft SRL (RO40528170, Banca Transilvania). |
| `series?` | `string[]` |  | The document series; defaults to ["CRP", "CRPX"] (labels.seriesHints describes each). |
| `takenNumbers?` | `Record<string, string[]>` |  | Numbers already used per series: the next free one is suggested and a taken one fails validation; defaults to INVOICE_PAGE_TAKEN_NUMBERS. |
| `exchangeRate?` | `{ currency: string; rate: number; date: Date; source?: string }` |  | The foreign currency the Currency field offers and its RON rate; defaults to BNR 1 EUR = 4.9713 RON on 2026-09-24. |
| `userName?` | `string` | `"Emma Carter"` | Who signs: Prepared by on the document and the sender of the email, unless the invoice has issuedBy. |
| `pricesIncludeVat?` | `boolean` | `false` | The price column holds prices with VAT (B2C); totals are computed backwards. |
| `eFactura?` | `boolean` |  | The Send to SPV automatically on issue switch, controlled. |
| `defaultEFactura?` | `boolean` | `true` | The SPV switch while uncontrolled: issuing uploads the invoice to e-Factura and marks it Sent to SPV. |
| `onEFacturaChange?` | `(on: boolean) => void` |  | Called when the SPV switch flips. |
| `spvConnected?` | `boolean` | `true` | An ANAF certificate is linked; false disables the SPV switches and says Neconectat. |
| `onSave?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Saves the draft, 800 ms after the last change and on Ctrl or Cmd + S; simulated by default. A rejection shows Nu am putut salva. |
| `onIssue?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Issues a valid draft (blank lines removed); simulated by default. Then the status becomes Issued, or Sent to SPV with e-Factura on. |
| `onSend?` | `(invoice: InvoiceDocument, payload: { to; cc; subject; message; spv }) => (void \| Promise<unknown>)` |  | Sends from the dialog (a draft is issued first); simulated by default. A rejection shows its message in the dialog. |
| `onDownload?` | `(invoice: InvoiceDocument) => void` |  | Download PDF; default window.print(), where Save as PDF gives a real PDF with only the document on it. |
| `onMarkPaid?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Mark as paid; simulated by default, then the status is Paid with today's date. |
| `onDuplicate?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Duplicate; simulated by default, then the page opens a new draft with the next number and today's dates. |
| `onStorno?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Emite storno for issued, sent, paid and overdue invoices; simulated by default with a toast. |
| `onCancel?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Void invoice: the status changes at once with an undo toast; a rejection rolls it back. |
| `onRemind?` | `(invoice: InvoiceDocument) => (void \| Promise<unknown>)` |  | Send reminder on overdue invoices; simulated by default with a toast. |
| `onCreateClient?` | `(client: InvoiceParty) => (void \| InvoiceParty \| Promise<void \| InvoiceParty>)` |  | Saves a client from the Client nou dialog; a returned party replaces the typed one. Simulated by default; the new client is selected. |
| `linesLayout?` | `"auto" \| "table" \| "cards"` | `"auto"` | The lines editor: auto shows the spreadsheet table wherever it gets 936px (from a page about 936px wide the lines span the full row under the client and the summary, from 1352px they sit beside the sticky summary), cards with an edit sheet below. |
| `stickyBar?` | `boolean` | `true` | The phone bar (below 768px) with the total and Issue, Send or Download; hide the shell's tab bar while it shows. |
| `defaultSendOpen?` | `boolean` | `false` | Opens the send dialog on mount (examples). |
| `defaultShowErrors?` | `boolean` | `false` | Starts with the issue checks shown, as after pressing Emite factura (examples). |
| `saveShortcut?` | `string` | `"mod+s"` | Saves the draft; "" turns it off. |
| `sendShortcut?` | `string` | `"mod+enter"` | Sends from the open send dialog, also while typing the message; "" turns it off. |
| `invoicesHref?` | `string` | `"/invoices"` | Where the breadcrumb's Facturi goes. |
| `labels?` | `Partial<InvoicePageLabels>` |  | Every visible string, English by default (DEFAULT_INVOICE_PAGE_LABELS; INVOICE_PAGE_LABELS_EN is the same set under its older name). Nested keys pass strings to the composed items (lineItems, validation, datePicker, tagInput, stickyBar). |
| `locale?` | `string` | `"en-US"` | Intl locale for money, numbers and dates. |
| `currency?` | `string` | `"RON"` | Seeds the document's currency on mount (the sample is in RON); EUR converts the prices at exchangeRate. |
| `now?` | `Date \| number` |  | The reference time for today, overdue days, payments and duplicates; default the live clock. |
| `tone?` | `Tone` | `"neutral"` | The accent of the primary actions, the mode switch, the date pickers and the switches. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | The panels' radius: 12, 16 or 24px. |
| `loading?` | `boolean` | `false` | The header, panels and aside as skeletons in the real layout. |
| `animateIn?` | `boolean` | `true` | The header and sections rise in once on mount; off in thumbnails and under reduced motion. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for the breadcrumb; default an <a> (or the LinkProvider's). |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<InvoicePageSlot, string>>` |  | Classes for header, alerts, edit, main, client, details, lines, notes, aside, summary, payment, efactura, preview, document, send and bar. |

## Parts

### DEFAULT_INVOICE_PAGE_LABELS (constant)

The English strings for every visible label, the default of labels. Type: InvoicePageLabels.

### INVOICE_PAGE_LABELS_EN (constant)

The same English strings under their older name, kept for existing imports.

### INVOICE_PAGE_SAMPLE (constant)

The demo invoice: draft CRP 2026-0412 from Northwind Soft SRL in RON, four lines at 21% and 11% VAT.

### INVOICE_PAGE_SAMPLE_EN (constant)

The same invoice in EUR with converted prices and an English payment note.

### INVOICE_PAGE_SUPPLIER (constant)

The sample supplier (Northwind Soft SRL) with its CUI, registry number, address and bank account.

### INVOICE_PAGE_CLIENTS (constant)

The sample clients offered by the client picker.

### INVOICE_PAGE_ANAF_DIRECTORY (constant)

The companies the demo's CUI lookup finds, standing in for the ANAF registry.

### INVOICE_PAGE_PRODUCTS (constant)

The product catalog for the line picker, priced in RON with their VAT rates and SKUs.

### INVOICE_PAGE_PRODUCTS_EN (constant)

The same catalog priced in EUR.

### INVOICE_PAGE_SERIES (constant)

The invoice series of the sample supplier: ["CRP", "CRPX"].

### INVOICE_PAGE_TAKEN_NUMBERS (constant)

Numbers already used per series; the next free one is suggested and a taken one fails validation.

### INVOICE_PAGE_EXCHANGE_RATE (constant)

The sample BNR rate for EUR (4.9713 RON on Sep 24, 2026), shown on foreign currency invoices.

### INVOICE_PAGE_NOW (constant)

The demo clock (Sep 24, 2026, 4:40 PM), so due dates and overdue days stay stable.

### createInvoiceDraft (function)

A new draft: (now?, { series, number, supplier, currency, issuedBy }?) => InvoiceDocument with no client and one empty line.

### invoiceWithStatus (function)

Moves an invoice to a status with matching dates: (invoice, status, now?) => InvoiceDocument (overdue, paid, sent).

### InvoicePageProps (type)

Props of InvoicePage, including the div attributes of the root.

### InvoiceDocument (type)

The invoice: series, number, status, dates, currency, client, supplier, lines, notes and discount.

### InvoiceSendPayload (type)

What onSend receives with the invoice: { to, cc, subject, message, spv }, spv sending it to e-Factura.

## Examples

- **Issued, previewed as the A4 document**: status="issued", tone="brand": the printable sheet, scaled to fit on phones.
- **Send dialog open**: defaultSendOpen: recipients, subject and message prefilled; Ctrl or Cmd + Enter sends.
- **A new draft, Issue pressed**: No client, one empty line and a taken number: every error listed and linked to its field.
- **Paid**: status="paid" in edit mode: the success alert and read-only lines.
- **Overdue**: status="overdue": due 3 days ago, with Send reminder.
- **Loading**: loading: header, panels and aside as skeletons in the real layout.
- **Export invoice in euro**: locale="en-GB", an EUR invoice priced from the euro catalogue, with the RON equivalent at the BNR rate.

## See also

- [Invoice line items](https://wingo-ui.com/components/invoice-line-items) ([Markdown](https://wingo-ui.com/components/invoice-line-items.md))
- [Invoicing](https://wingo-ui.com/components/invoicing) ([Markdown](https://wingo-ui.com/components/invoicing.md))
- [Admin Shell](https://wingo-ui.com/components/admin-shell) ([Markdown](https://wingo-ui.com/components/admin-shell.md))
- [Page Header](https://wingo-ui.com/components/page-header) ([Markdown](https://wingo-ui.com/components/page-header.md))
- [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) ([Markdown](https://wingo-ui.com/components/sticky-cta-bar.md))

---

Canonical: https://wingo-ui.com/components/invoice-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
