# Medication chart

> A record's medication orders for today, with the status of each planned dose.

- Block: [medications](https://bettersupabase.com/blocks/medications.md)
- Kind: screen
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/medication-chart

## Install the block

```bash
npx better-supabase add medications
```

## Web (Next.js)

Add the shadcn components:

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

From `apps/examples/nextjs/src/features/medications/components/medication-chart.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/medications/components/medication-chart.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();
}

const DOSE_VARIANT = {
  planned: "secondary",
  given: "success-light",
  refused: "warning-light",
  skipped: "outline",
  missed: "destructive-light",
} as const satisfies Record<MedicationAdministrationStatus, string>;

/**
 * Render inside `<Suspense>`: today's medication chart of a record, an order
 * per row with its dose and route and each of today's doses with its status.
 * Members with `medications.read` and read access to the record see it.
 */
export async function MedicationChart() {
  await connection();

  const [state, t, format] = await Promise.all([
    getCustomerChart(),
    getExtracted("medications"),
    getFormatter(),
  ]);

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

  if (state.kind === "error") {
    return (
      <Alert variant="destructive" data-recipe="medication-chart">
        <TriangleAlertIcon />
        <AlertTitle>{t("The medication chart 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>
    );
  }

  const chart = state.data;

  if (chart === "no-customer" || chart.orders.length === 0) {
    return (
      <Empty className="border" data-recipe="medication-chart">
        <EmptyHeader>
          <EmptyMedia variant="icon">
            <PillIcon />
          </EmptyMedia>
          <EmptyTitle>{t("No medication today")}</EmptyTitle>
          <EmptyDescription>
            {t(
              "Add a medication to the list, then prescribe it for a customer with a dose and a schedule.",
            )}
          </EmptyDescription>
        </EmptyHeader>
        <EmptyContent>
          <a
            className="text-sm underline underline-offset-4"
            href="https://bettersupabase.com/docs/blocks/medications"
          >
            {t("Read the medications guide")}
          </a>
        </EmptyContent>
      </Empty>
    );
  }

  const label = (status: MedicationAdministrationStatus) => {
    switch (status) {
      case "planned": {
        return t("Planned");
      }

      case "given": {
        return t("Given");
      }

      case "refused": {
        return t("Refused");
      }

      case "skipped": {
        return t("Skipped");
      }

      case "missed": {
        return t("Missed");
      }

      default: {
        const unknown: never = status;

        return unknown;
      }
    }
  };

  const time = (instant: string) =>
    format.dateTime(new Date(instant), { timeStyle: "short", timeZone: "UTC" });

  return (
    <Card data-recipe="medication-chart">
      <CardHeader>
        <CardTitle>
          {t("Medication for {customer}", { customer: chart.customer })}
        </CardTitle>
        <CardDescription>
          {t("Today's orders and the status of each dose.")}
        </CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-4">
        {chart.orders.map((order) => (
          <div
            key={order.id}
            className="flex flex-col gap-2 border-b pb-4 last:border-b-0 last:pb-0"
          >
            <div className="flex flex-wrap items-center gap-2">
              <span className="text-sm font-medium">{order.medication}</span>
              <span className="text-sm text-muted-foreground">
                {order.route ? `${order.dose}, ${order.route}` : order.dose}
              </span>
              {order.asNeeded ? (
                <Badge variant="info-light">{t("As needed")}</Badge>
              ) : null}
              {order.highRisk ? (
                <Badge variant="warning-light">{t("Double check")}</Badge>
              ) : null}
              {order.paused ? (
                <Badge variant="secondary">{t("Paused")}</Badge>
              ) : null}
            </div>
            {order.doses.length > 0 ? (
              <div className="flex flex-wrap gap-1">
                {order.doses.map((dose) => (
                  <Badge key={dose.id} variant={DOSE_VARIANT[dose.status]}>
                    {dose.at ? `${time(dose.at)} ` : ""}
                    {label(dose.status)}
                  </Badge>
                ))}
              </div>
            ) : (
              <span className="text-xs text-muted-foreground">
                {t("No doses today")}
              </span>
            )}
          </div>
        ))}
      </CardContent>
    </Card>
  );
}

export function MedicationChartSkeleton() {
  return <Skeleton className="h-64" aria-busy="true" />;
}
```

## Prompt

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

### Web

```text
Build "Medication chart" in a React web app that uses shadcn/ui components. A record's medication orders for today, with the status of each planned dose.

It uses the better-supabase `medications` block (Medications). 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 medications` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add alert badge button card empty skeleton`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the screen. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/medications/components/medication-chart.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/medication-chart.md
- Block docs: https://bettersupabase.com/docs/blocks/medications
```
