Order KPIs
Orders, revenue, average order and fulfilment rate for the closed month, each on a bullet track against its target and moved against the month before; reads kpis().
Preview
import { formatCurrency, formatNumber, formatPercent } from "@/lib/format"
import { BulletChart } from "@/components/ui/bullet-chart"
import { MetricDelta } from "@/components/ui/metric-delta"
import { StatCard } from "@/components/ui/stat-card"
import { StatCardGroup } from "@/components/ui/stat-card-group"
import { type Kpi } from "../data"
// How each KPI prints. The bullet track and the headline share one formatter,
// so the value above the bar and the value on it never disagree.
const FORMATS: Record<Kpi["format"], (value: number) => string> = {
number: (value) => formatNumber(value, { maximumFractionDigits: 0 }),
currency: (value) => formatCurrency(value, "USD", { maximumFractionDigits: 0 }),
percent: (value) => formatPercent(value, { maximumFractionDigits: 1 }),
}
export type OrderKpisProps = {
kpis: Kpi[]
month: string
priorMonth: string
}
/**
* The month's four numbers. The card carries the headline, the bullet track
* says where it stands against the target, and the delta below it says which
* way it moved — three readings, none of them repeating another.
*/
export function OrderKpis({ kpis, month, priorMonth }: OrderKpisProps) {
return (
<StatCardGroup data-widget="widget-ecommerce-orders-overview-order-kpis" columns={4}>
{kpis.map((kpi) => {
const format = FORMATS[kpi.format]
return (
<StatCard
key={kpi.key}
label={kpi.label}
value={format(kpi.value)}
description={`In ${month}`}
footer={
<div className="flex flex-col gap-2">
<BulletChart
size="sm"
label="Against target"
// Four tracks on one row would otherwise share a name.
aria-label={`${kpi.label} against target`}
value={kpi.value}
target={kpi.target}
ranges={kpi.ranges}
valueFormatter={format}
/>
<div className="flex items-baseline justify-between gap-3 text-xs text-muted-foreground">
<span>{`vs ${priorMonth}`}</span>
<MetricDelta value={kpi.delta} size="sm" />
</div>
</div>
}
/>
)
})}
</StatCardGroup>
)
}Install
$
npx shadcn@latest add @vibra/widget-ecommerce-orders-overview-order-kpisNeeds the @vibra registry in your components.json — set it up once.
Source
import { formatCurrency, formatNumber, formatPercent } from "@/lib/format"
import { BulletChart } from "@/components/ui/bullet-chart"
import { MetricDelta } from "@/components/ui/metric-delta"
import { StatCard } from "@/components/ui/stat-card"
import { StatCardGroup } from "@/components/ui/stat-card-group"
import { type Kpi } from "../data"
// How each KPI prints. The bullet track and the headline share one formatter,
// so the value above the bar and the value on it never disagree.
const FORMATS: Record<Kpi["format"], (value: number) => string> = {
number: (value) => formatNumber(value, { maximumFractionDigits: 0 }),
currency: (value) => formatCurrency(value, "USD", { maximumFractionDigits: 0 }),
percent: (value) => formatPercent(value, { maximumFractionDigits: 1 }),
}
export type OrderKpisProps = {
kpis: Kpi[]
month: string
priorMonth: string
}
/**
* The month's four numbers. The card carries the headline, the bullet track
* says where it stands against the target, and the delta below it says which
* way it moved — three readings, none of them repeating another.
*/
export function OrderKpis({ kpis, month, priorMonth }: OrderKpisProps) {
return (
<StatCardGroup data-widget="widget-ecommerce-orders-overview-order-kpis" columns={4}>
{kpis.map((kpi) => {
const format = FORMATS[kpi.format]
return (
<StatCard
key={kpi.key}
label={kpi.label}
value={format(kpi.value)}
description={`In ${month}`}
footer={
<div className="flex flex-col gap-2">
<BulletChart
size="sm"
label="Against target"
// Four tracks on one row would otherwise share a name.
aria-label={`${kpi.label} against target`}
value={kpi.value}
target={kpi.target}
ranges={kpi.ranges}
valueFormatter={format}
/>
<div className="flex items-baseline justify-between gap-3 text-xs text-muted-foreground">
<span>{`vs ${priorMonth}`}</span>
<MetricDelta value={kpi.delta} size="sm" />
</div>
</div>
}
/>
)
})}
</StatCardGroup>
)
}/**
* What /orders/overview reads. Everything on the page is an aggregate over
* `db.orders`, joined to `db.customers` for the account behind a row. Two
* things the rows do not carry are rules over them: the sales region a country
* belongs to, and the month's target — every KPI is measured against the month
* before it plus 5%, rounded the way a target gets written down. "Now" is
* REFERENCE_DATE, so the closed month is always the same one.
*/
import { getInitials } from "@/lib/format"
import { db, REFERENCE_DATE, type Member, type Order } from "@/lib/sample-data"
// Read per call, never held: a sale rung up or an order refunded since the
// server started is in the book the next time the page asks.
const orders = (): Order[] => db.orders.all()
const dollars = (cents: number): number => cents / 100
/** Midnight UTC on the first of the month `k` months before the current one. */
function monthStart(k: number): Date {
return new Date(Date.UTC(REFERENCE_DATE.getUTCFullYear(), REFERENCE_DATE.getUTCMonth() - k, 1))
}
const MONTH_END = monthStart(0)
const MONTH_START = monthStart(1)
const PRIOR_START = monthStart(2)
const MONTH_NAME = new Intl.DateTimeFormat("en-US", { month: "long", timeZone: "UTC" })
/** The last complete month, which is the month every KPI is read for. */
export const CLOSED_MONTH = MONTH_NAME.format(MONTH_START)
/** The month the targets were set from. */
export const PRIOR_MONTH = MONTH_NAME.format(PRIOR_START)
// A cancelled order was never a sale, so it counts towards nothing but the
// status split at the bottom of the page.
const billable = (order: Order): boolean => order.status !== "cancelled"
function placedIn(from: Date, to: Date): Order[] {
return orders().filter((order) => order.placedAt >= from && order.placedAt < to)
}
const revenue = (rows: Order[]): number =>
dollars(rows.reduce((total, order) => total + order.totalCents, 0))
const fulfilled = (rows: Order[]): number =>
rows.length > 0 ? rows.filter((order) => order.status === "fulfilled").length / rows.length : 0
export type Kpi = {
key: string
label: string
value: number
target: number
/** Ascending band edges for the bullet track: behind, on track, ahead. */
ranges: number[]
/** Change against the month before, as a ratio. */
delta: number
format: "number" | "currency" | "percent"
}
/**
* The target every KPI is read against: the month before it, 5% higher, rounded
* to `step`. One rule for all four, so nothing on the row is hand-set.
*/
function targetFrom(previous: number, step: number): number {
return Math.round((previous * 1.05) / step) * step
}
// Read against the target rather than the axis: behind at 80%, on track at
// 100%, ahead past it. The bands are shares of the target, so they mean the
// same thing on a count, a dollar amount and a rate.
const bands = (target: number): number[] => [target * 0.8, target, target * 1.25]
/** The four headline numbers for the closed month, each against its target. */
export function kpis(): Kpi[] {
const month = placedIn(MONTH_START, MONTH_END).filter(billable)
const prior = placedIn(PRIOR_START, MONTH_START).filter(billable)
const count = month.length
const priorCount = prior.length
const money = revenue(month)
const priorMoney = revenue(prior)
const aov = count > 0 ? money / count : 0
const priorAov = priorCount > 0 ? priorMoney / priorCount : 0
const rate = fulfilled(month)
const priorRate = fulfilled(prior)
const rows: [string, string, number, number, number, Kpi["format"]][] = [
["orders", "Orders", count, priorCount, 5, "number"],
["revenue", "Revenue", money, priorMoney, 1_000, "currency"],
["aov", "Average order", aov, priorAov, 5, "currency"],
["fulfilment", "Fulfilled", rate, priorRate, 0.01, "percent"],
]
return rows.map(([key, label, value, previous, step, format]) => {
const target = targetFrom(previous, step)
return {
key,
label,
value,
target,
ranges: bands(target),
delta: previous > 0 ? value / previous - 1 : 0,
format,
}
})
}
// Weekday names in the order a week is read, not the order getUTCDay returns.
const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
/** How the whole order book falls across the days of the week. */
export function ordersByWeekday(): { day: string; orders: number }[] {
const counts = new Array<number>(WEEKDAYS.length).fill(0)
for (const order of orders()) {
if (!billable(order)) continue
// getUTCDay is Sunday-first; the chart is Monday-first.
counts[(order.placedAt.getUTCDay() + 6) % 7] += 1
}
return WEEKDAYS.map((day, index) => ({ day, orders: counts[index] }))
}
// Which sales region a country belongs to. The order carries a country, not a
// region, so the grouping is a rule over the row.
const REGIONS: Record<string, string> = {
"United States": "Americas",
Canada: "Americas",
Brazil: "Americas",
"United Kingdom": "EMEA",
Germany: "EMEA",
France: "EMEA",
Netherlands: "EMEA",
Sweden: "EMEA",
Spain: "EMEA",
Portugal: "EMEA",
Ireland: "EMEA",
Australia: "APAC",
Japan: "APAC",
India: "APAC",
}
const REGION_ORDER = ["Americas", "EMEA", "APAC"]
/** The sales region an order shipped to. */
export function regionOf(country: string): string {
return REGIONS[country] ?? "Rest of world"
}
/** Booked value per region across the whole book, largest first. */
export function revenueByRegion(): { name: string; value: number }[] {
const totals = new Map<string, number>()
for (const order of orders()) {
if (!billable(order)) continue
const region = regionOf(order.country)
totals.set(region, (totals.get(region) ?? 0) + order.totalCents)
}
return REGION_ORDER.filter((region) => totals.has(region))
.map((region) => ({ name: region, value: Math.round(dollars(totals.get(region) ?? 0)) }))
.sort((a, b) => b.value - a.value)
}
/** Everything the book is worth, for the middle of the donut. */
export function bookedTotal(): number {
return revenueByRegion().reduce((total, region) => total + region.value, 0)
}
// The five states an order can be in, in the order they read as progress.
const STATUSES: Order["status"][] = ["fulfilled", "paid", "pending", "refunded", "cancelled"]
/** How the whole book splits across the five order states. */
export function fulfilmentSplit(): { label: string; value: number; status: Order["status"] }[] {
const book = orders()
return STATUSES.map((status) => ({
status,
label: status.charAt(0).toUpperCase() + status.slice(1),
value: book.filter((order) => order.status === status).length,
}))
}
export type OrderRow = {
id: string
number: string
account: string
status: Order["status"]
payment: Order["paymentMethod"]
region: string
country: string
items: number
total: number
placedAt: Date
}
/** The order book as a table, newest first. */
export function pastOrders(): OrderRow[] {
const accounts = new Map(db.customers.all().map((customer) => [customer.id, customer]))
return orders().map((order) => ({
id: order.id,
number: order.number,
// A sale the till rang up for nobody in particular has no account.
account: accounts.get(order.customerId)?.company ?? (order.customerId || "Walk-in"),
status: order.status,
payment: order.paymentMethod,
region: regionOf(order.country),
country: order.country,
items: order.items.reduce((count, item) => count + item.qty, 0),
total: dollars(order.totalCents),
placedAt: order.placedAt,
}))
.sort((a, b) => b.placedAt.getTime() - a.placedAt.getTime())
.slice(0, 60)
}
/** The bell's contents: the newest notifications, unread first in the panel. */
export function shellNotifications() {
return db.notifications
.all()
.sort((a, b) => b.at.getTime() - a.at.getTime())
.slice(0, 6)
.map(({ id, title, description, at, read, href }) => ({ id, title, description, at, read, href }))
}
function ownerRow(): Member {
return db.members.all().find((member) => member.role === "owner") ?? db.members.all()[0]
}
/** The person looking at the page: whoever owns this workspace. */
export function currentUser() {
const owner = ownerRow()
return { name: owner.name, email: owner.email, initials: getInitials(owner.name), avatarUrl: owner.avatarUrl }
}
/** Which month the KPIs closed on, and what set their targets. */
export function lastUpdated(): string {
return `${CLOSED_MONTH} ${MONTH_START.getUTCFullYear()} · targets set from ${PRIOR_MONTH}`
}Its page
On its page the card sits among the rest of the dashboard and shares its range and its data with them.
From the Orders dashboard page