# Invoices table

> The organization's invoices with their status, amount and a link to the PDF.

Source: https://bettersupabase.com/docs/registry/ui-recipes/invoices-table

{/* Generated by scripts/build-recipes.ts from the example apps. Edit the examples or the script, then run `pnpm recipes`. */}

It loads its data on the server through the block's client; the [invoices block](https://bettersupabase.com/blocks/invoices) must be installed (`npx better-supabase add invoices`). Its page on bettersupabase.com is [/recipes/invoices-table](https://bettersupabase.com/recipes/invoices-table).

Web, with shadcn components:

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

```tsx title="apps/examples/nextjs/src/features/billing/components/invoices-table.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 organization's invoices with their status,
 * total and what's still owed. Renders nothing without `invoices.read`, so the
 * billing page still shows the plan.
 */
export async function InvoicesTable() {
  const session = await getSession();

  if (!can(session, "invoices.read")) {
    return null;
  }

  const [list, t, format, locale] = await Promise.all([
    getInvoices(),
    getExtracted("billing"),
    getFormatter(),
    getLocale(),
  ]);

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

  const canWrite = can(session, "invoices.write");

  const status = (value: InvoiceStatus, overdue: boolean) => {
    if (overdue) {
      return <Badge variant="destructive-light">{t("Overdue")}</Badge>;
    }

    switch (value) {
      case "draft": {
        return <Badge variant="secondary">{t("Draft")}</Badge>;
      }

      case "issued": {
        return <Badge variant="info-light">{t("Issued")}</Badge>;
      }

      case "partially_paid": {
        return <Badge variant="warning-light">{t("Partly paid")}</Badge>;
      }

      case "paid": {
        return <Badge variant="success-light">{t("Paid")}</Badge>;
      }

      case "void": {
        return <Badge variant="outline">{t("Void")}</Badge>;
      }

      case "uncollectible": {
        return <Badge variant="destructive-light">{t("Uncollectible")}</Badge>;
      }

      default: {
        const unknown: never = value;

        return unknown;
      }
    }
  };

  const hasInvoices = list.kind === "ok" && list.invoices.length > 0;

  return (
    <Card data-recipe="invoices-table">
      <CardHeader>
        <CardTitle>{t("Invoices")}</CardTitle>
        <CardDescription>
          {t("Every invoice for this organization, newest first.")}
        </CardDescription>
        {hasInvoices && canWrite ? (
          <CardAction>
            <CreateDraftInvoiceDialog variant="outline" />
          </CardAction>
        ) : null}
      </CardHeader>
      <CardContent>
        {list.kind === "error" ? (
          <Alert variant="destructive">
            <TriangleAlertIcon />
            <AlertTitle>{t("The invoices 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>
        ) : list.invoices.length === 0 ? (
          <Empty className="border">
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <FileTextIcon />
              </EmptyMedia>
              <EmptyTitle>{t("No invoices yet")}</EmptyTitle>
              <EmptyDescription>
                {t("Invoices show up here once they're created.")}
              </EmptyDescription>
            </EmptyHeader>
            {canWrite ? (
              <EmptyContent>
                <CreateDraftInvoiceDialog />
              </EmptyContent>
            ) : null}
          </Empty>
        ) : (
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>{t("Invoice")}</TableHead>
                <TableHead>{t("Status")}</TableHead>
                <TableHead>{t("Due")}</TableHead>
                <TableHead className="text-right">{t("Total")}</TableHead>
                <TableHead className="text-right">{t("Owed")}</TableHead>
                {canWrite ? (
                  <TableHead>
                    <span className="sr-only">{t("Actions")}</span>
                  </TableHead>
                ) : null}
              </TableRow>
            </TableHeader>
            <TableBody>
              {list.invoices.map((invoice) => (
                <TableRow key={invoice.id}>
                  <TableCell>
                    <div className="flex flex-col gap-1">
                      <span className="text-sm">{invoice.title}</span>
                      <span className="font-mono text-xs text-muted-foreground">
                        {invoice.identifier ?? t("No number yet")}
                      </span>
                    </div>
                  </TableCell>
                  <TableCell>
                    {status(invoice.status, invoice.overdue)}
                  </TableCell>
                  <TableCell className="text-muted-foreground">
                    {invoice.dueOn
                      ? format.dateTime(new Date(invoice.dueOn), {
                          dateStyle: "medium",
                          timeZone: "UTC",
                        })
                      : t("Not issued")}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">
                    {formatMoney(invoice.total, locale)}
                  </TableCell>
                  <TableCell className="text-right tabular-nums">
                    {formatMoney(invoice.balance, locale)}
                  </TableCell>
                  {canWrite ? (
                    <TableCell className="text-right">
                      {invoice.status === "draft" ? (
                        <IssueInvoiceButton invoiceId={invoice.id} />
                      ) : null}
                    </TableCell>
                  ) : null}
                </TableRow>
              ))}
            </TableBody>
          </Table>
        )}
      </CardContent>
    </Card>
  );
}
```