RegistryUI recipes
Inventory table
Stock levels per product and location, with a form to adjust a count.
It loads its data on the server through the block's client; the inventory block must be installed (npx better-supabase add inventory). Its page on bettersupabase.com is /recipes/inventory-table.
Web, with shadcn components:
npx shadcn add alert badge button card empty skeleton table// 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();
}
/** Render inside `<Suspense>`: stock per tracked variant and active location. */
export async function InventoryTable() {
const session = await getSession();
if (!can(session, "inventory.read")) {
forbidden();
}
const [stock, t] = await Promise.all([
getStockLevels(),
getExtracted("shop"),
]);
if (stock.kind === "no-organization") {
return null;
}
const canManage = can(session, "inventory.manage");
const locationName = new Map(
stock.kind === "ok"
? stock.data.locations.map((location) => [location.id, location.name])
: [],
);
return (
<Card data-recipe="inventory-table">
<CardHeader>
<CardTitle>{t("Stock levels")}</CardTitle>
<CardDescription>
{t(
"Units on hand, reserved by open orders and available to sell, per location.",
)}
</CardDescription>
</CardHeader>
<CardContent>
{stock.kind === "error" ? (
<Alert variant="destructive">
<TriangleAlertIcon />
<AlertTitle>{t("The stock levels 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>
) : stock.data.locations.length === 0 ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<WarehouseIcon />
</EmptyMedia>
<EmptyTitle>{t("No stock locations yet")}</EmptyTitle>
<EmptyDescription>
{t("Stock is counted per location. Add one to start.")}
</EmptyDescription>
</EmptyHeader>
{canManage ? (
<EmptyContent>
<CreateLocationButton />
</EmptyContent>
) : null}
</Empty>
) : stock.data.levels.length === 0 ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<BoxesIcon />
</EmptyMedia>
<EmptyTitle>{t("No products to count")}</EmptyTitle>
<EmptyDescription>
{t("Products from the catalog show up here with their stock.")}
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Link href="/shop" className={buttonVariants({ size: "sm" })}>
{t("Go to the catalog")}
</Link>
</EmptyContent>
</Empty>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("Product")}</TableHead>
<TableHead>{t("Location")}</TableHead>
<TableHead className="text-right">{t("On hand")}</TableHead>
<TableHead className="text-right">{t("Reserved")}</TableHead>
<TableHead className="text-right">{t("Available")}</TableHead>
<TableHead>
<span className="sr-only">{t("Status")}</span>
</TableHead>
{canManage ? (
<TableHead>
<span className="sr-only">{t("Actions")}</span>
</TableHead>
) : null}
</TableRow>
</TableHeader>
<TableBody>
{stock.data.levels.map((level) => {
const location = locationName.get(level.locationId) ?? "";
return (
<TableRow key={`${level.variantId}:${level.locationId}`}>
<TableCell>
<div className="flex flex-col gap-1">
<span className="text-sm">{level.productTitle}</span>
<span className="font-mono text-xs text-muted-foreground">
{level.sku}
</span>
</div>
</TableCell>
<TableCell>{location}</TableCell>
<TableCell className="text-right tabular-nums">
{level.onHand}
</TableCell>
<TableCell className="text-right tabular-nums">
{level.reserved}
</TableCell>
<TableCell className="text-right tabular-nums">
{level.available}
</TableCell>
<TableCell>
{level.available <= 0 ? (
<Badge variant="destructive-light">
{t("Out of stock")}
</Badge>
) : level.reorderPoint !== null &&
level.available <= level.reorderPoint ? (
<Badge variant="warning-light">{t("Low")}</Badge>
) : (
<Badge variant="success-light">{t("In stock")}</Badge>
)}
</TableCell>
{canManage ? (
<TableCell className="text-right">
<AdjustStockDialog
variantId={level.variantId}
locationId={level.locationId}
label={`${level.productTitle} (${level.sku}), ${location}`}
/>
</TableCell>
) : null}
</TableRow>
);
})}
</TableBody>
</Table>
)}
</CardContent>
</Card>
);
}Last updated on