RegistryUI recipes
Dispatch board
A week of technicians by day with their scheduled work orders, and the backlog to schedule.
It loads its data on the server through the block's client; the dispatch block must be installed (npx better-supabase add dispatch). Its page on bettersupabase.com is /recipes/dispatch-board.
Web, with shadcn components:
npx shadcn add alert badge button card dialog empty field input skeleton table/**
* Render inside `<Suspense>`: a week of technicians by day with their scheduled
* work orders, and the unscheduled backlog below. `?week=YYYY-MM-DD` picks the
* week.
*/
export async function DispatchBoard({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
const [session, t, format, query] = await Promise.all([
getSession(),
getExtracted("field"),
getFormatter(),
searchParams,
connection(),
]);
if (!can(session, "dispatch.read")) {
forbidden();
}
const manage = can(session, "dispatch.manage");
const monday = mondayOf(query["week"]);
const board = await settle(() => loadDispatchWeek(monday.toString()));
if (board.kind === "no-organization") {
return null;
}
const dayLabel = (date: string) =>
format.dateTime(new Date(`${date}T12:00:00Z`), {
weekday: "short",
day: "numeric",
timeZone: "UTC",
});
const range = format.dateTimeRange(
new Date(`${monday.toString()}T12:00:00Z`),
new Date(`${monday.add({ days: 6 }).toString()}T12:00:00Z`),
{ day: "numeric", month: "short", year: "numeric", timeZone: "UTC" },
);
const newWorkOrder = manage ? <NewWorkOrderDialog /> : null;
return (
<div data-recipe="dispatch-board" className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>{t("Week of {range}", { range })}</CardTitle>
<CardDescription>
{board.kind === "ok"
? t("Scheduled work per technician, in {zone}.", {
zone: board.data.timeZone,
})
: t("Scheduled work per technician.")}
</CardDescription>
<CardAction className="flex items-center gap-2">
<Link
href={`/field/dispatch?week=${monday.subtract({ weeks: 1 }).toString()}`}
className={buttonVariants({
variant: "outline",
size: "icon-sm",
})}
aria-label={t("Previous week")}
>
<ChevronLeftIcon />
</Link>
<Link
href={`/field/dispatch?week=${monday.add({ weeks: 1 }).toString()}`}
className={buttonVariants({
variant: "outline",
size: "icon-sm",
})}
aria-label={t("Next week")}
>
<ChevronRightIcon />
</Link>
</CardAction>
</CardHeader>
<CardContent>
{board.kind === "error" ? (
<FieldLoadError title={t("The schedule board didn't load")} />
) : board.data.rows.length === 0 ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<CalendarRangeIcon />
</EmptyMedia>
<EmptyTitle>{t("Nothing scheduled this week")}</EmptyTitle>
<EmptyDescription>
{t("Work orders show here once they have a technician.")}
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Link
href={`/field/dispatch?week=${monday.add({ weeks: 1 }).toString()}`}
className={buttonVariants({ variant: "outline" })}
>
{t("Show next week")}
</Link>
</EmptyContent>
</Empty>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("Technician")}</TableHead>
{board.data.days.map((day) => (
<TableHead key={day}>{dayLabel(day)}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{board.data.rows.map((row) => (
<TableRow key={row.technicianId}>
<TableCell className="align-top text-sm font-medium">
{row.name}
</TableCell>
{row.days.map(({ date, orders }) => (
<TableCell key={date} className="align-top">
<div className="flex flex-col gap-2">
{orders.map((order) => (
<div key={order.id} className="flex flex-col gap-1">
<span className="text-sm font-medium">
{order.title}
</span>
<span className="text-xs text-muted-foreground tabular-nums">
{order.startsAt
? format.dateTime(new Date(order.startsAt), {
timeStyle: "short",
timeZone: board.data.timeZone,
})
: null}
</span>
<WorkOrderStatusBadge status={order.status} />
</div>
))}
</div>
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>{t("Backlog")}</CardTitle>
<CardDescription>
{t("Work orders without a slot, most urgent first.")}
</CardDescription>
{newWorkOrder ? <CardAction>{newWorkOrder}</CardAction> : null}
</CardHeader>
<CardContent>
{board.kind === "error" ? (
<FieldLoadError title={t("The backlog didn't load")} />
) : board.data.backlog.length === 0 ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<InboxIcon />
</EmptyMedia>
<EmptyTitle>{t("The backlog is empty")}</EmptyTitle>
<EmptyDescription>
{t("Every open work order has a slot.")}
</EmptyDescription>
</EmptyHeader>
{newWorkOrder ? (
<EmptyContent>{newWorkOrder}</EmptyContent>
) : null}
</Empty>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("Job")}</TableHead>
<TableHead>{t("Address")}</TableHead>
<TableHead>{t("Priority")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{board.data.backlog.map((order) => (
<TableRow key={order.id}>
<TableCell className="text-sm font-medium">
{order.title}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{order.address ?? ""}
</TableCell>
<TableCell>
<WorkOrderPriorityBadge priority={order.priority} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
</div>
);
}Last updated on