RegistryUI recipes
Order state board
Today's kitchen orders in preparation-state columns; dragging an order forward moves all of its lines.
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/order-state-board.
Web, with shadcn components:
npx shadcn add badge card @reui/kanban/**
* The orders sent to the kitchen today, in a column for the state of their
* least advanced line. Dragging a card to a later column moves every line of
* the order up to that state; a move back or within a column puts the card
* back. The parent keys it by the cards' states, so a refresh starts it from
* the saved board.
*/
export function OrderStateBoard({
cards,
canPrepare,
}: {
cards: readonly BoardCardView[];
canPrepare: boolean;
}) {
const t = useExtracted("shop");
const errorMessage = useErrorMessage();
const label = usePreparationLabel();
const [columns, setColumns] = useState(() => columnsOf(cards));
const bump = useAction(bumpTicket);
const byId = new Map(cards.map((card) => [card.orderId, card]));
return (
<div data-recipe="order-state-board">
<Kanban
value={columns}
onValueChange={setColumns}
getItemValue={(card) => card.orderId}
onValueCommit={(_value, meta) => {
if (meta.kind !== "item") {
return;
}
const from = v.parse(StateSchema, meta.activeContainer);
const to = v.parse(StateSchema, meta.overContainer);
if (
PREPARATION_STATES.indexOf(to) <= PREPARATION_STATES.indexOf(from)
) {
setColumns(meta.previousValue);
if (from !== to) {
toast.warning(t("An order only moves forward on this board."));
}
return;
}
void bump
.run({
orderId: String(meta.event.active.id),
state: to,
stationId: null,
})
.then((result) => {
if (!result.ok) {
setColumns(meta.previousValue);
toast.error(errorMessage(result.error));
}
});
}}
>
<KanbanBoard className="flex overflow-x-auto">
{PREPARATION_STATES.map((state) => {
const column = columns[state] ?? [];
const count = column.length;
return (
<KanbanColumn
key={state}
value={state}
className="w-72 shrink-0 gap-3 rounded-lg bg-muted/50 p-3"
>
<div className="flex items-center justify-between gap-2">
<Badge variant={preparationBadgeVariant(state)}>
{label(state)}
</Badge>
<Badge variant="outline">{count}</Badge>
</div>
<KanbanColumnContent value={state} className="min-h-16">
{column.map((card) => (
<KanbanItem
key={card.orderId}
value={card.orderId}
disabled={!canPrepare || bump.pending}
>
<KanbanItemHandle cursor={canPrepare}>
<OrderCard card={card} />
</KanbanItemHandle>
</KanbanItem>
))}
{count === 0 ? (
<p className="text-xs text-muted-foreground">
{t("No orders here.")}
</p>
) : null}
</KanbanColumnContent>
</KanbanColumn>
);
})}
</KanbanBoard>
<KanbanOverlay>
{({ value, variant }) => {
const card =
variant === "item" ? byId.get(String(value)) : undefined;
return card ? <OrderCard card={card} /> : null;
}}
</KanbanOverlay>
</Kanban>
</div>
);
}Last updated on