RegistryUI recipes
Kitchen display
Open tickets per kitchen station, each line moved from queued to served, refreshed over Realtime.
It loads its data on the server through the block's client; the orders block must be installed (npx better-supabase add orders). Its page on bettersupabase.com is /recipes/kitchen-display.
Web, with shadcn components:
npx shadcn add alert badge button card empty skeleton/**
* 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>
);
}Last updated on