RegistryUI recipes
Targets progress
Sales targets for the period with the progress toward each one.
It loads its data on the server through the block's client; the targets block must be installed (npx better-supabase add targets). Its page on bettersupabase.com is /recipes/targets-progress.
Web, with shadcn components:
npx shadcn add badge card empty progress skeleton/**
* Render inside `<Suspense>`: the targets running today and how far along they
* are.
*/
export async function TargetsProgress() {
const [session, t, format, locale] = await Promise.all([
getSession(),
getExtracted("crm"),
getFormatter(),
getLocale(),
]);
if (!can(session, "targets.read")) {
return null;
}
const targets = await settle(loadTargets);
if (targets.kind === "no-organization") {
return null;
}
const canManage = can(session, "targets.manage");
const amountOf = (target: TargetRow, value: bigint) =>
target.currency === null
? format.number(value)
: formatMoney(money(value, target.currency), locale);
return (
<Card data-recipe="targets-progress">
<CardHeader>
<CardTitle>{t("Targets")}</CardTitle>
<CardDescription>
{t("Won deals against this period's goals.")}
</CardDescription>
{canManage && targets.kind === "ok" && targets.data.length > 0 ? (
<CardAction>
<CreateTargetDialog variant="outline" />
</CardAction>
) : null}
</CardHeader>
<CardContent>
{targets.kind === "error" ? (
<CrmLoadError title={t("The targets didn't load")} />
) : targets.data.length === 0 ? (
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<TargetIcon />
</EmptyMedia>
<EmptyTitle>{t("No targets running")}</EmptyTitle>
<EmptyDescription>
{canManage
? t("Set a goal for the amount or number of deals won.")
: t("An admin sets the sales targets.")}
</EmptyDescription>
</EmptyHeader>
{canManage ? (
<EmptyContent>
<CreateTargetDialog />
</EmptyContent>
) : null}
</Empty>
) : (
<ul className="grid gap-6 md:grid-cols-2">
{targets.data.map((target) => (
<li key={target.id} className="flex flex-col gap-2">
<Progress
value={Math.min(target.attainment * 100, 100)}
aria-label={target.name}
>
<ProgressLabel>{target.name}</ProgressLabel>
{target.attainment >= 1 ? (
<Badge variant="success-light" className="ml-auto">
{t("Reached")}
</Badge>
) : (
<span className="ml-auto text-sm text-muted-foreground tabular-nums">
{format.number(target.attainment, {
style: "percent",
maximumFractionDigits: 0,
})}
</span>
)}
</Progress>
<span className="text-xs text-muted-foreground tabular-nums">
{t("{progress} of {goal}, {start} to {end}", {
progress: amountOf(target, target.progress),
goal: amountOf(target, target.goal),
start: Temporal.PlainDate.from(
target.periodStart,
).toLocaleString(locale, { dateStyle: "medium" }),
end: Temporal.PlainDate.from(
target.periodEnd,
).toLocaleString(locale, { dateStyle: "medium" }),
})}
</span>
</li>
))}
</ul>
)}
</CardContent>
</Card>
);
}Last updated on