# Kitchen display

> Open tickets per kitchen station, each line moved from queued to served, refreshed over Realtime.

Source: https://bettersupabase.com/docs/registry/ui-recipes/kitchen-display

{/* 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 [orders block](https://bettersupabase.com/blocks/orders) must be installed (`npx better-supabase add orders`). Its page on bettersupabase.com is [/recipes/kitchen-display](https://bettersupabase.com/recipes/kitchen-display).

Web, with shadcn components:

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

```tsx title="apps/examples/nextjs/src/features/shop/components/kitchen-display.tsx"
/**
 * Render inside `<Suspense>`: the open tickets at one station, or at every
 * station, oldest first. Each ticket moves its lines to the next state, and the
 * page refreshes when a line changes anywhere in the kitchen.
 */
export async function KitchenDisplay({
  searchParams,
}: {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
  const [session, params] = await Promise.all([getSession(), searchParams]);

  if (!can(session, "orders.read") && !can(session, "orders.prepare")) {
    forbidden();
  }

  const canPrepare =
    can(session, "orders.prepare") || can(session, "orders.manage");

  const [kitchen, t] = await Promise.all([
    getKitchen(
      v.is(StationParam, params["station"]) ? params["station"] : null,
    ),
    getExtracted("shop"),
  ]);

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

  if (kitchen.kind === "error") {
    return (
      <Alert variant="destructive" data-recipe="kitchen-display">
        <TriangleAlertIcon />
        <AlertTitle>{t("The kitchen 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 { organizationId, stations, stationId, tickets } = kitchen.data;

  return (
    <div data-recipe="kitchen-display" className="flex flex-col gap-4">
      <KitchenLive organizationId={organizationId} />
      {stations.length > 1 ? (
        <nav aria-label={t("Stations")} className="flex flex-wrap gap-1">
          <Link
            href="/shop/kitchen"
            aria-current={stationId === null ? "page" : undefined}
            className={buttonVariants({
              size: "sm",
              variant: stationId === null ? "secondary" : "ghost",
            })}
          >
            {t("All stations")}
          </Link>
          {stations.map((each) => (
            <Link
              key={each.id}
              href={`/shop/kitchen?station=${each.id}`}
              aria-current={stationId === each.id ? "page" : undefined}
              className={buttonVariants({
                size: "sm",
                variant: stationId === each.id ? "secondary" : "ghost",
              })}
            >
              {each.name}
            </Link>
          ))}
        </nav>
      ) : null}
      {tickets.length === 0 ? (
        <Empty className="border">
          <EmptyHeader>
            <EmptyMedia variant="icon">
              <ChefHatIcon />
            </EmptyMedia>
            <EmptyTitle>{t("No open tickets")}</EmptyTitle>
            <EmptyDescription>
              {t(
                "Send a paid order to the kitchen and its lines show up here.",
              )}
            </EmptyDescription>
          </EmptyHeader>
          <EmptyContent>
            <Link
              href="/shop/orders"
              className={buttonVariants({ size: "sm" })}
            >
              {t("Open orders")}
            </Link>
          </EmptyContent>
        </Empty>
      ) : (
        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
          {tickets.map((ticket) => (
            <KitchenTicket
              key={ticket.orderId}
              ticket={ticket}
              stationId={stationId}
              canPrepare={canPrepare}
            />
          ))}
        </div>
      )}
    </div>
  );
}
```