# Trial balance

> Total assets, liabilities and equity and this year's net income, then each account's debit, credit and balance with the totals.

- Block: [accounting](https://bettersupabase.com/blocks/accounting.md)
- Kind: component
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/trial-balance

## Install the block

```bash
npx better-supabase add accounting
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add alert badge button card empty skeleton table
```

From `apps/examples/nextjs/src/features/accounting/components/trial-balance.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/accounting/components/trial-balance.tsx):

```tsx
// oxlint-disable-next-line typescript/require-await -- a Server Action must be async, and refresh() returns nothing to await
async function retry() {
  "use server";
  refresh();
}

/**
 * Render inside `<Suspense>`: the balance sheet totals and this year's net
 * income, then every account's debits, credits and closing balance.
 */
export async function TrialBalance() {
  const [state, t, format, locale] = await Promise.all([
    getBooks(),
    getExtracted("accounting"),
    getFormatter(),
    getLocale(),
  ]);

  if (state.kind === "no-organization") {
    return null;
  }

  if (state.kind === "error") {
    return (
      <Alert variant="destructive" data-recipe="trial-balance">
        <TriangleAlertIcon />
        <AlertTitle>{t("The books didn't load")}</AlertTitle>
        <AlertDescription>{t("Try again in a moment.")}</AlertDescription>
        <AlertAction>
          <form action={retry}>
            <Button size="sm" variant="outline" type="submit">
              {t("Retry")}
            </Button>
          </form>
        </AlertAction>
      </Alert>
    );
  }

  if (state.data === "not-set-up" || state.data.accounts.length === 0) {
    return (
      <Empty className="border" data-recipe="trial-balance">
        <EmptyHeader>
          <EmptyMedia variant="icon">
            <BookOpenIcon />
          </EmptyMedia>
          <EmptyTitle>{t("No entries in the books yet")}</EmptyTitle>
          <EmptyDescription>
            {t(
              "Set up the books with a chart of accounts, open a fiscal year, then post the first entry.",
            )}
          </EmptyDescription>
        </EmptyHeader>
        <EmptyContent>
          <a
            className="text-sm underline underline-offset-4"
            href="https://bettersupabase.com/docs/blocks/accounting"
          >
            {t("Read the accounting guide")}
          </a>
        </EmptyContent>
      </Empty>
    );
  }

  const books = state.data;

  const amount = (value: MoneyJson) =>
    formatMoney(moneyFromJson(value), locale);

  const asOf = format.dateTime(new Date(books.asOf), {
    dateStyle: "medium",
    timeZone: "UTC",
  });

  return (
    <div className="flex flex-col gap-6" data-recipe="trial-balance">
      <div className="grid gap-4 md:grid-cols-3">
        <Card>
          <CardHeader>
            <CardDescription>{t("Assets")}</CardDescription>
            <CardTitle>{amount(books.totalAssets)}</CardTitle>
          </CardHeader>
        </Card>
        <Card>
          <CardHeader>
            <CardDescription>{t("Liabilities and equity")}</CardDescription>
            <CardTitle>{amount(books.liabilitiesAndEquity)}</CardTitle>
          </CardHeader>
          <CardContent>
            {books.balanced ? (
              <Badge variant="success-light">{t("Balanced")}</Badge>
            ) : (
              <Badge variant="destructive-light">{t("Out of balance")}</Badge>
            )}
          </CardContent>
        </Card>
        <Card>
          <CardHeader>
            <CardDescription>{t("Net income this year")}</CardDescription>
            <CardTitle>{amount(books.netIncome)}</CardTitle>
          </CardHeader>
        </Card>
      </div>
      <Card>
        <CardHeader>
          <CardTitle>{t("Trial balance")}</CardTitle>
          <CardDescription>
            {t("Posted entries through {date}.", { date: asOf })}
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>{t("Code")}</TableHead>
                <TableHead>{t("Account")}</TableHead>
                <TableHead className="text-right">{t("Debit")}</TableHead>
                <TableHead className="text-right">{t("Credit")}</TableHead>
                <TableHead className="text-right">{t("Balance")}</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {books.accounts.map((row) => (
                <TableRow key={row.accountId}>
                  <TableCell className="font-mono text-xs">
                    {row.code}
                  </TableCell>
                  <TableCell className="font-medium">{row.name}</TableCell>
                  <TableCell className="text-right tabular-nums">
                    {amount(row.debit)}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">
                    {amount(row.credit)}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">
                    {amount(row.closing)}
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
            <TableFooter>
              <TableRow>
                <TableCell colSpan={2}>{t("Total")}</TableCell>
                <TableCell className="text-right tabular-nums">
                  {amount(books.totals.debit)}
                </TableCell>
                <TableCell className="text-right tabular-nums">
                  {amount(books.totals.credit)}
                </TableCell>
                <TableCell />
              </TableRow>
            </TableFooter>
          </Table>
        </CardContent>
      </Card>
    </div>
  );
}

export function TrialBalanceSkeleton() {
  return (
    <div className="flex flex-col gap-6" aria-busy="true">
      <div className="grid gap-4 md:grid-cols-3">
        <Skeleton className="h-28 rounded-xl" />
        <Skeleton className="h-28 rounded-xl" />
        <Skeleton className="h-28 rounded-xl" />
      </div>
      <Skeleton className="h-72 rounded-xl" />
    </div>
  );
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "Trial balance" in a React web app that uses shadcn/ui components. Total assets, liabilities and equity and this year's net income, then each account's debit, credit and balance with the totals.

It uses the better-supabase `accounting` block (Accounting). It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the block yet, add the SQL with `npx better-supabase add accounting` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add alert badge button card empty skeleton table`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the component. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/accounting/components/trial-balance.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/trial-balance.md
- Block docs: https://bettersupabase.com/docs/blocks/accounting
```
