Ledger Table
Double-entry lines with a running balance. Debits and credits get their own columns and are totalled separately, because a ledger where the two do not agree is the finding.
src/components/ui/ledger-table.tsxAbout
From the component's own source, where it was written.
A double-entry ledger with a running balance.
The component checks that debits equal credits and says so when they do not. That is the entire point of double-entry — an unbalanced ledger is a bug, and a table that renders one without comment is worse than useless because it looks authoritative.
Amounts are integer minor units (cents), not floats. 0.1 + 0.2 is a genuinely wrong number in a ledger, and running balances accumulate that error line by line until the total is visibly off.
The running balance is computed here rather than taken per row, so it cannot disagree with the entries above it.
Install
npx astralyx-ui add ledger-table
Usage
import { LedgerTable } from '@/components/ui/ledger-table'
<LedgerTable entries={entries} openingBalance={0} />Composer
Change the props and the source below follows.
| Date | Description | Debit | Credit | Balance | |
|---|---|---|---|---|---|
| Invoice #2261 — NorthwindINV-2261 | US$4,800.00 | — | US$17,300.00 | ||
| Payment receivedINV-2261 | — | US$4,800.00 | US$12,500.00 | ||
| Cloud hostingAWS-0824 | US$964.12 | — | US$13,464.12 | ||
| Affiliate payout — batch 41PO-0041 | US$2,120.50 | — | US$15,584.62 | ||
| Refund — order 88213RF-88213 | — | US$49.90 | US$15,534.72 | ||
| Totals | US$7,884.62 | US$4,849.90 | US$15,534.72 | ||
Debits and credits do not balance — out by US$3,034.72.
<LedgerTable entries={entries} openingBalance={1250000} />Examples
A running balance
<LedgerTable entries={entries} currency="GBP" />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
LedgerTable
Extends ComponentProps<'div'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| entries | LedgerEntry[] | required | { id, date, description, account?, reference?, debit?, credit? }. Exactly one of debit or credit per line. |
| currency | string | 'USD' | — |
| locale | string | 'en-GB' | — |
| openingBalance | number | 0 | Minor units. The running balance starts here rather than at zero. |
| showBalance | boolean | true | — |
| columnLabels | { date?: ReactNode description?: ReactNode account?: ReactNode debit?: ReactNode credit?: ReactNode balance?: ReactNode } | { date: 'Date', description: 'Description', account: 'Account', debit: 'Debit', credit: 'Credit', balance: 'Balance', } | Column headings. |
| totalsLabel | ReactNode | 'Totals' | — |
Types
LedgerEntry
A double-entry ledger with a running balance.
| Field | Type | Description |
|---|---|---|
| id | string | — |
| date | Date | — |
| description | ReactNode | — |
| account? | ReactNode | — |
| reference? | ReactNode | — |
| debit? | number | Minor units. Exactly one of debit or credit per entry. |
| credit? | number | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| amounts | minor units | Integers throughout. Running balances accumulate, and a float error accumulates with them. |
| totals | computed | Debit and credit columns are summed independently and shown in the footer, so an unbalanced set is visible rather than hidden in a net figure. |