@get-asset/react

Invoices

Styled, drop-in accounts receivable — invoice date, description, customer, due date, a status tag, the balance and what settled it, one row per invoice. The header carries a status filter and a “Create new invoice” button that reveals a form above the table; clicking a row expands a reconciliation of what it is worth, what has been matched and what that leaves, and the kebab offers the full view, a status change and delete. Covers loading, error, empty and success, and restacks onto a narrow layout via a container query.

Preview

Loading preview…

Required scopes

The access token used by AssetProvider must include these scopes for this primitive to fetch data successfully:

  • invoice:read
  • invoice:create
  • invoice:update
  • invoice:delete
  • customer:read
  • customer:create
  • ledger:read
  • transaction:read
  • external_account:read

API reference

Root props

PropTypeDefaultDescription
dateFromCalendarDateStringInclusive start (YYYY-MM-DD) of a fixed due-date window. Setting it detaches the list from the provider's shared range entirely; must be paired with dateTo (one without the other throws). Use periodDateRange to derive the pair from a month, quarter, or year.
dateToCalendarDateStringInclusive end (YYYY-MM-DD) of a fixed due-date window. Must be paired with dateFrom. Omit both to follow the provider's DatePicker-driven range.
pageSizenumber25Rows per request. Further pages load from the footer's Load more button.
defaultStatusFilterInvoiceStatus | "all""all"Which status the filter starts on. "all" shows every invoice. All six statuses are offered, including the two this component cannot set.
defaultSelectedIdstringExpand this invoice's row on mount, for deep-linking straight to an invoice instead of making the user find it.
defaultViewingIdstringOpen this invoice's document view on mount — the deep link for “show me this invoice”, where defaultSelectedIdis “open its row in the list”. See The document view.
defaultCreatingbooleanfalseOpen the new-invoice panel on mount.
hideCreatebooleanfalseHides the Create new invoice button. For tenants that raise invoices in another system and push them in through the API — the list is then a record, not a place to author one.
invoicesInvoiceOverride[]Override the fetched invoices (skips the list request). The status filter and period are not re-applied, so supply the rows you want shown.
customersCustomerOption[]Override the customer directory. Resolves each row's Customer cell and fills the form's picker — pass it alongside invoices to stay offline.
ledgersLedgerOption[]Override the categories offered on a line of the new-invoice form.
matchCandidatesMatchCandidateOverride[]Override the transactions the match picker offers, for previews and tests. Live, the panel queries them itself once a row with a balance is expanded — pass this alongside invoices to keep an offline story offline. An empty array is still an override, and shows the picker with nothing in it. See Matching.
intake{ onCreated?: (id: string | null) => void }Lets a invoice be raised from a document. Supplying it puts an Upload invoice control in the header; without it the header is create-only. See Ingestion.
state"loading" | "error" | "empty"Force a specific state for testing.
hasMorebooleanForce the Load more button whenever the list isn't being fetched (previews/tests).
classNamestringForwarded to the outer Card wrapper element.

States

Loading

Loading preview…

Success

Loading preview…

EmptyNo invoices yet.

Loading preview…

Error

Loading preview…

CreatingThe new-invoice form, revealed above the table.

Loading preview…

ExpandedA part-paid row, opened onto its reconciliation.

Loading preview…

DetailsThe document view, deep-linked onto the part-paid invoice.

Loading preview…

Read-onlyhideCreate, for tenants that raise invoices elsewhere.

Loading preview…

Behaviour

The invoice number leads neither the table nor its header: it is an internal reference that reads as noise next to a description and a customer. It remains on the full view. The date takes its place, because that is what the list is ordered by.

The header names the period beside the title, and the table holds the invoices falling due inside it — the range comes from the AssetProvider, as it does on the report cards, so the list moves with the rest of the dashboard. dateFrom and dateTopin a fixed window for this card alone, detaching it from the provider's range. See Period and status filter for the window it sends.

Clicking a row expands it — the row itself is the toggle, not just the kebab. What opens is a reconciliation rather than a second copy of the row: the invoice total, what has been matched to it, and what that leaves. The matched line only appears once something has been matched. When a bank transaction is larger than the balance, the API allocates what is due and records the excess as customer credit, so the invoice reads Settled in full. The line items live on the full view instead, which is the surface for reading the invoice whole.

The Transactionscolumn names what settled the row — the bank's own description, with +Nonce more than one transaction settles it, and a dash when nothing has. It is content, not a control: one click anywhere on the row (the kebab aside) opens it. Each cell's text carries a text cursor at its own width, and a drag that selects it never opens the row — the click handler stands down whenever a selection ends the click. See Matching.

The kebab carries the full view, the status changes and delete. Editing lives on the full view, and reaches the lines alone — the number, customer and dates are fixed at creation. Marking one Uncollectible writes off the outstanding balance rather than just relabelling it, and the two refunded statuses are shown but never set. See Statuses for why.

The new-invoice form takes a number, a description, a customer, two dates and any number of lines with an optional category each, and totals the lines as they are typed. A customer who is not in the directory yet can be added from inside the form, without losing the invoice being drafted. Opening the full view or the form hides the list rather than pushing it down.

Every value, control and gate is a slot on the headless compound. See the Invoices part inventoryto rearrange the layout, swap the primitives, or add the parts this layout leaves out — the per-payment list and a detached detail panel.

The document view

The full view reads the invoice as a document, on the layout every document source shares — a bill, a receipt or a statement opens into the same anatomy, with only the source-specific copy changing. A back link returns to the list; the header carries the customer's name, the document date, an Invoicetag and the status as a plain tag beside it — the same badge the table's Status column wears; a summary band states what the document claims and settles it; and the record's own card holds the dates, the line items and the amount due. Delete and Edit share the action bar at the foot.

Editing swaps the line rows — and only them — for inputs, each with its own remove control, with the amount due following the keystrokes; Save sends what changed and Cancel discards it. The dates stay read-only: they are fixed at creation. + Add Item under the lines is the same editor, opened with a fresh line ready to type into. See Editing: lines only.

The summary band leads with a sentence — who was invoiced, for how much, due when — and carries the matching surface under it: one chip per matched deposit, a Select Deposit picker, and a payment total that reads red when more is taken in than the invoice is worth. See Matching.

The line items sit behind a toggle that names what it hides (“Show 14 Items”). They start open — the lines are what the document is made of — and stay put across an edit; the toggle is there for the reader who wants the totals without the itemisation.

An invoice raised from an uploaded file opens as a split view: the details keep the left column, and the source document renders beside them — the file on a page card, a control to open the original in its own tab, and zoom for images (a PDF renders through the browser's viewer, which brings its own). The header also wears the document's processing status as a small dotted chip — the same vocabulary as the AssetDocuments list. The lookup behind all of this is the headless DocumentViewer.

An invoice with no source document — one raised here, or pushed in through the API — keeps the single column at full width, with no status chip and no empty pane beside it.

Ingestion

Pass intake and the header gains an Upload invoice control. Picking a document hands it to Asset, which reads it and raises the invoice from it — party, lines and journal entry included. A banner under the header narrates it: uploading, then reading, then the record it added. When it finishes the list refreshes, the new record's row opens, and onCreatedfires with its id.

Nothing is asked of the reader: the extraction is the platform's. A failure gets its own line and stays until it is dismissed — an upload that quietly gave up is worse than one that visibly did — and every failure line ends on nothing was added, which is what settles whether a half-made record is now in the books. Try again appears only where running the same file could end differently: a file that is too large, the wrong type, or already on record will be all of those next time too. Reading runs on the server, so a reload picks the progress back up rather than losing it.

Matching

Beside the reconciliation sits Payments: one chip per payment already matched to the invoice, and a Select Deposit picker to add one. It is AssetTransactionMatch wired to the open row; the document view carries the same one inside its summary band, on the inline layout.

The picker is a searchable multi-select over the bank transactions around the invoice's due date, closest amount first: the date, the description and the amount on three fixed tracks, so a dozen rows can be scanned by eye. Ticking one records the payment and unticking it takes the payment off again — each is its own request, so there is no save. Already-matched transactions come back ticked, and every chip carries its own remove control, which is the way back if the wrong one was attached. Transactions moving money the wrong way are never offered: Guardrails has why.

A settled invoice keeps the panel and the picker, disabled — you can still read and undo what paid it, and hiding the control would make a settled row look like a different component. It also costs nothing: nothing is outstanding, so no candidates are fetched. See Settled records.

In the table, the row itself is the way in — one click opens it at the matching panel, whatever cell the click lands on. matchCandidates is the override that keeps a preview or a test off the network, and TransactionMatch is the headless compound to compose against for a layout of your own.