# Ledger Table

> Bookkeeping: LedgerTable (bookings with project and agent suggestions), PnLStatement and ReceiptSuggestion.

Component · Data · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/LedgerTable/)

Bookkeeping: `LedgerTable` (bookings with project and agent suggestions), `PnLStatement` and `ReceiptSuggestion`.

LedgerTable: `rows` ({id, date, ref, text, category, project, projectTint, net, vat, gross, suggested, status, statusLabel}), `categories` (makes the category editable in place) + `onEdit(row, change)`, `currency`, `selectable`, `bulkActions`, `toolbar`, `maxHeight`.
PnLStatement: `columns` (months), `rows` ({kind: "section" | "subtotal" | "total", label, values}), `currency`, `total` (false hides it).
ReceiptSuggestion: `file`, `source`, `fields` ({label, value, mono, confidence 0–1}), `suggestion`, `onConfirm`, `onDiscard`, `confirmLabel`, `discardLabel`, children.

- Agent suggestions carry the signal diamond until a person confirms them.
- Negative amounts are blush; amounts use the locale's currency format.

## Example

```tsx
import * as React from "react";
import { LedgerTable, PnLStatement, ReceiptSuggestion } from "@twentyfirstmedia/pro";
import type { LedgerRow } from "@twentyfirstmedia/pro";

const rows: LedgerRow[] = [
  {
    id: 1,
    date: "Apr 2",
    ref: "RE-0412",
    text: "Hetzner Online GmbH",
    category: "Hosting",
    project: "Northwind",
    projectTint: "sky",
    net: 84.03,
    vat: 15.97,
    gross: 100.0,
    status: "ok",
    statusLabel: "Booked",
  },
  {
    id: 2,
    date: "Apr 3",
    ref: "ST-2231",
    text: "Stripe payout",
    category: "Revenue",
    project: "Chatteo",
    projectTint: "mint",
    net: 4210.0,
    vat: 0,
    gross: 4210.0,
    status: "ok",
    statusLabel: "Booked",
  },
  {
    id: 3,
    date: "Apr 4",
    ref: "MAIL-88",
    text: "Google Ireland Ltd.",
    category: "Marketing",
    project: "Northwind",
    projectTint: "sky",
    net: -320.0,
    vat: 0,
    gross: -320.0,
    suggested: "Ledger",
    status: "signal",
    statusLabel: "Suggested",
  },
];
export default function Preview() {
  return (
    <div
      className="tf-col"
      style={{
        gap: 16,
      }}
    >
      <div className="tf-panel tf-panel-flush">
        <LedgerTable
          rows={rows}
          selectable
          categories={["Hosting", "Revenue", "Marketing", "Software"]}
          bulkActions={[
            {
              label: "Confirm",
              icon: "check",
              onClick: (r, c) => {
                c();
              },
            },
          ]}
        />
      </div>
      <div
        className="tf-grid"
        style={
          {
            "--cols": "1.4fr 1fr",
            gap: 16,
          } as React.CSSProperties
        }
      >
        <div className="tf-panel">
          <PnLStatement
            columns={["Jan", "Feb", "Mar"]}
            rows={[
              {
                kind: "section",
                label: "Revenue",
              },
              {
                label: "Subscriptions",
                values: [18200, 19400, 21100],
              },
              {
                label: "One-off",
                values: [2100, 1800, 2600],
              },
              {
                kind: "section",
                label: "Costs",
              },
              {
                label: "Hosting",
                values: [-820, -840, -910],
              },
              {
                label: "Marketing",
                values: [-4200, -3900, -5100],
              },
              {
                kind: "total",
                label: "Result",
                values: [15280, 16460, 17690],
              },
            ]}
          />
        </div>
        <ReceiptSuggestion
          file="Rechnung_2026-0412.pdf"
          source="From billing@hetzner.com · Apr 2"
          fields={[
            {
              label: "Vendor",
              value: "Hetzner Online GmbH",
              confidence: 0.99,
            },
            {
              label: "Date",
              value: "Apr 2, 2026",
              mono: true,
              confidence: 0.98,
            },
            {
              label: "Gross",
              value: "€100.00",
              mono: true,
              confidence: 0.97,
            },
            {
              label: "VAT",
              value: "19%",
              confidence: 0.72,
            },
          ]}
          suggestion="Hosting · Northwind · as every month"
          onConfirm={() => {}}
          onDiscard={() => {}}
        />
      </div>
    </div>
  );
}
```

## Props

### LedgerTable

| Prop | Type | Notes |
| --- | --- | --- |
| `rows` | `LedgerRow[]` | Required |
| `categories?` | `string[]` |  |
| `onEdit?` | `(row: LedgerRow, change: Partial<LedgerRow>) => void` |  |
| `currency?` | `string` |  |
| `selectable?` | `boolean` |  |
| `bulkActions?` | `BulkAction<LedgerRow>[]` |  |
| `toolbar?` | `ReactNode` |  |
| `maxHeight?` | `number` |  |
| `columnToggle?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### PnLStatement

| Prop | Type | Notes |
| --- | --- | --- |
| `columns` | `string[]` | Required |
| `rows` | `{ kind?: "section" \| "subtotal" \| "total"; label: string; values?: number[] }[]` | Required |
| `currency?` | `string` |  |
| `total?` | `boolean` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ReceiptSuggestion

| Prop | Type | Notes |
| --- | --- | --- |
| `file` | `string` | Required |
| `source?` | `ReactNode` |  |
| `fields` | `{ label: string; value: ReactNode; mono?: boolean; confidence?: number }[]` | Required |
| `suggestion?` | `ReactNode` |  |
| `onConfirm?` | `() => void` |  |
| `onDiscard?` | `() => void` |  |
| `confirmLabel?` | `string` |  |
| `discardLabel?` | `string` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
