Skip to content
6

Dashboard 4

Commerce operations dashboard in a card-in-card style: four inset stat tiles, a Today band pairing an hourly two-series revenue chart with ad-budget and peak-hours cards, and a week-in-review band driven by a range select. The budget bar is a real progressbar, both charts ship a screen-reader data table, and each panel only shows an action when it has one.

Preview

Installation

npx shadcn@latest add https://hirael.com/r/dashboard-04.json

Code

components/blocks/dashboard-04.tsx
'use client';

import * as React from 'react';
import {
  ArrowDownRight,
  ArrowUpRight,
  Clock4,
  CreditCard,
  Minus,
  Package,
  RotateCcw,
  Settings2,
  ShoppingCart,
  Users,
  type LucideIcon,
} from 'lucide-react';

import { cn } from '@/lib/utils';
import { Badge } from '@/registry/hirael/bases/radix/ui/badge';
import { Button } from '@/registry/hirael/bases/radix/ui/button';
import { Card, CardAction, CardContent, CardHeader, CardTitle } from '@/registry/hirael/bases/radix/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/registry/hirael/bases/radix/ui/select';

/**
 * One delta, one source of truth. The sign gives the direction, `goodWhen`
 * gives the intent, and both the colour and the spoken label come from those
 * two fields, so a falling refund rate can never render as bad news.
 */
interface Delta {
  value: number;
  unit: '%' | 'pp';
  goodWhen: 'up' | 'down';
}

interface Stat {
  icon: LucideIcon;
  label: string;
  value: string;
  delta: Delta;
}

const STATS: readonly Stat[] = [
  {
    icon: ShoppingCart,
    label: 'Open orders',
    value: '36',
    delta: { value: 12.5, unit: '%', goodWhen: 'up' },
  },
  {
    icon: Package,
    label: 'Items sold',
    value: '1,482',
    delta: { value: 6.8, unit: '%', goodWhen: 'up' },
  },
  {
    icon: Users,
    label: 'Store sessions',
    value: '9,214',
    delta: { value: 4.1, unit: '%', goodWhen: 'up' },
  },
  {
    icon: RotateCcw,
    label: 'Refund rate',
    value: '0.8%',
    delta: { value: -0.2, unit: 'pp', goodWhen: 'down' },
  },
];

/** Revenue every two hours, so the ticks and the data table share one source. */
const HOURLY: readonly { hour: string; today: number; yesterday: number }[] = [
  { hour: '00', today: 12, yesterday: 10 },
  { hour: '02', today: 9, yesterday: 8 },
  { hour: '04', today: 14, yesterday: 11 },
  { hour: '06', today: 24, yesterday: 19 },
  { hour: '08', today: 46, yesterday: 39 },
  { hour: '10', today: 74, yesterday: 61 },
  { hour: '12', today: 92, yesterday: 78 },
  { hour: '14', today: 84, yesterday: 73 },
  { hour: '16', today: 102, yesterday: 84 },
  { hour: '18', today: 96, yesterday: 80 },
  { hour: '20', today: 71, yesterday: 64 },
  { hour: '22', today: 48, yesterday: 41 },
  { hour: '24', today: 31, yesterday: 26 },
];

const REVENUE = {
  today: 1284.5,
  yesterday: 1092.2,
  delta: { value: 17.6, unit: '%', goodWhen: 'up' } satisfies Delta,
};

/** `peak` is a fact about the hour, not a brightness threshold in the markup. */
const PEAK_HOURS: readonly { hour: string; orders: number; peak?: boolean }[] = [
  { hour: '07', orders: 14 },
  { hour: '08', orders: 18 },
  { hour: '09', orders: 26 },
  { hour: '10', orders: 41 },
  { hour: '11', orders: 58 },
  { hour: '12', orders: 92, peak: true },
  { hour: '13', orders: 100, peak: true },
  { hour: '14', orders: 86, peak: true },
  { hour: '15', orders: 54 },
  { hour: '16', orders: 38 },
  { hour: '17', orders: 27 },
  { hour: '18', orders: 19 },
];

type WeekRange = 'this' | 'last';

interface WeekData {
  orders: {
    value: string;
    delta: Delta;
    days: readonly { day: string; orders: number }[];
  };
  minis: readonly {
    label: string;
    value: string;
    delta: Delta;
    spark: readonly number[];
  }[];
}

const WEEK: Record<WeekRange, WeekData> = {
  this: {
    orders: {
      value: '1,318',
      delta: { value: 11.2, unit: '%', goodWhen: 'up' },
      days: [
        { day: 'Mon', orders: 148 },
        { day: 'Tue', orders: 176 },
        { day: 'Wed', orders: 162 },
        { day: 'Thu', orders: 196 },
        { day: 'Fri', orders: 228 },
        { day: 'Sat', orders: 184 },
        { day: 'Sun', orders: 224 },
      ],
    },
    minis: [
      {
        label: 'Gross revenue',
        value: '$24,820',
        delta: { value: 9.2, unit: '%', goodWhen: 'up' },
        spark: [30, 34, 31, 38, 44, 41, 48],
      },
      {
        label: 'Returning buyers',
        value: '58.4%',
        delta: { value: 1.9, unit: 'pp', goodWhen: 'up' },
        spark: [52, 54, 53, 55, 56, 57, 58],
      },
      {
        label: 'Checkout conversion',
        value: '3.1%',
        delta: { value: 0.4, unit: 'pp', goodWhen: 'up' },
        spark: [2.5, 2.7, 2.6, 2.9, 3.0, 2.9, 3.1],
      },
    ],
  },
  last: {
    orders: {
      value: '1,186',
      delta: { value: 4.6, unit: '%', goodWhen: 'up' },
      days: [
        { day: 'Mon', orders: 132 },
        { day: 'Tue', orders: 158 },
        { day: 'Wed', orders: 149 },
        { day: 'Thu', orders: 171 },
        { day: 'Fri', orders: 198 },
        { day: 'Sat', orders: 166 },
        { day: 'Sun', orders: 212 },
      ],
    },
    minis: [
      {
        label: 'Gross revenue',
        value: '$22,730',
        delta: { value: 5.8, unit: '%', goodWhen: 'up' },
        spark: [27, 30, 28, 33, 37, 35, 41],
      },
      {
        label: 'Returning buyers',
        value: '56.5%',
        delta: { value: -0.3, unit: 'pp', goodWhen: 'up' },
        spark: [57, 56, 57, 56, 55, 56, 56],
      },
      {
        label: 'Checkout conversion',
        value: '2.7%',
        delta: { value: 0.1, unit: 'pp', goodWhen: 'up' },
        spark: [2.4, 2.5, 2.4, 2.6, 2.7, 2.6, 2.7],
      },
    ],
  },
};

const BUDGET = { spent: 223.1, cap: 400 };

const usd = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  minimumFractionDigits: 2,
});

const UNIT_WORD: Record<Delta['unit'], string> = {
  '%': 'percent',
  pp: 'percentage points',
};

const linePath = (values: readonly number[], max: number, h: number) => {
  const step = values.length > 1 ? 100 / (values.length - 1) : 100;
  return values
    .map((v, i) => `${i === 0 ? 'M' : 'L'}${(i * step).toFixed(2)} ${(h - 2 - (v / max) * (h - 6)).toFixed(2)}`)
    .join(' ');
};

const DeltaChip = ({ delta, label }: { delta: Delta; label: string }) => {
  const { value, unit, goodWhen } = delta;
  const Icon = value > 0 ? ArrowUpRight : value < 0 ? ArrowDownRight : Minus;
  const direction = value > 0 ? 'up' : value < 0 ? 'down' : 'unchanged';
  const sign = value > 0 ? '+' : value < 0 ? '−' : '';
  const tone =
    value === 0
      ? 'bg-accent text-muted-foreground'
      : value > 0 === (goodWhen === 'up')
        ? 'bg-success/10 text-success'
        : 'bg-destructive/10 text-destructive';

  return (
    <span
      dir="ltr"
      aria-label={`${label} ${direction} ${Math.abs(value)} ${UNIT_WORD[unit]}`}
      className={cn(
        'inline-flex w-fit items-center gap-1 rounded-sm px-1.5 py-0.5 font-mono text-[11px] leading-none tabular-nums',
        tone,
      )}
    >
      <Icon className="size-3" aria-hidden />
      {sign}
      {Math.abs(value)}
      {unit}
    </span>
  );
};

const PanelCard = ({
  icon: Icon,
  label,
  action,
  children,
  className,
}: {
  icon: LucideIcon;
  label: string;
  /** Only pass an action when there is one. An empty menu is not a feature. */
  action?: React.ReactNode;
  children: React.ReactNode;
  className?: string;
}) => {
  return (
    <Card className={cn('gap-2 rounded-md py-2.5', className)}>
      <CardHeader className="px-3.5">
        <CardTitle className="flex items-center gap-1.5 font-mono text-[10px] font-normal uppercase tracking-[0.12em] text-muted-foreground">
          <Icon className="size-3.5" aria-hidden />
          {label}
        </CardTitle>
        {action && <CardAction>{action}</CardAction>}
      </CardHeader>
      <CardContent className="flex flex-1 flex-col px-2.5 pb-0">
        <div className="flex flex-1 flex-col rounded-sm border border-border bg-background p-4">{children}</div>
      </CardContent>
    </Card>
  );
};

const Sparkline = ({ points }: { points: readonly number[] }) => {
  const max = Math.max(...points);
  const min = Math.min(...points);
  const span = max - min || 1;
  const step = points.length > 1 ? 100 / (points.length - 1) : 100;
  const pts = points.map((v, i) => `${(i * step).toFixed(1)},${(22 - ((v - min) / span) * 16).toFixed(1)}`).join(' ');

  return (
    <svg viewBox="0 0 100 26" preserveAspectRatio="none" aria-hidden className="mt-3 h-12 w-full">
      <polyline
        points={pts}
        fill="none"
        vectorEffect="non-scaling-stroke"
        strokeWidth="1.5"
        className="stroke-foreground/45"
      />
    </svg>
  );
};

const Dashboard04 = () => {
  const [range, setRange] = React.useState<WeekRange>('this');
  const week = WEEK[range];

  const chartMax = Math.max(...HOURLY.flatMap((h) => [h.today, h.yesterday]));
  const todayLine = linePath(
    HOURLY.map((h) => h.today),
    chartMax,
    46,
  );
  const yesterdayLine = linePath(
    HOURLY.map((h) => h.yesterday),
    chartMax,
    46,
  );
  const barMax = Math.max(...week.orders.days.map((d) => d.orders));
  const peakMax = Math.max(...PEAK_HOURS.map((h) => h.orders));
  const budgetPct = Math.round((BUDGET.spent / BUDGET.cap) * 100);
  const peakWindow = PEAK_HOURS.filter((h) => h.peak);

  return (
    <section className="bg-background py-20 sm:py-28">
      <div className="container flex w-full flex-col gap-8">
        <div className="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
          <div className="flex max-w-xl flex-col gap-3">
            <Badge variant="outline" className="w-fit">
              storefront
            </Badge>
            <h2 className="font-serif text-4xl font-medium tracking-tight sm:text-5xl">Today at the counter.</h2>
          </div>
          <Button variant="outline" size="sm">
            <Settings2 className="size-3.5" aria-hidden />
            Customize
          </Button>
        </div>

        <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
          {STATS.map((s) => (
            <PanelCard key={s.label} icon={s.icon} label={s.label}>
              <div className="flex flex-col gap-2">
                <span className="text-3xl font-semibold tracking-[-0.035em] tabular-nums">{s.value}</span>
                <div className="flex items-center gap-1.5">
                  <DeltaChip delta={s.delta} label={s.label} />
                  <span className="font-mono text-[10px] uppercase tracking-[0.08em] text-muted-foreground">
                    vs yesterday
                  </span>
                </div>
              </div>
            </PanelCard>
          ))}
        </div>

        <div className="flex items-center justify-between gap-3">
          <h3 className="text-lg font-semibold tracking-[-0.02em]">Today</h3>
          <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
            Store time · resets 00:00 UTC
          </span>
        </div>

        <div className="grid grid-cols-1 gap-4 lg:grid-cols-5">
          <PanelCard icon={CreditCard} label="Gross revenue" className="lg:col-span-3">
            <div className="flex items-start justify-between gap-4">
              <div className="flex gap-8">
                <div className="flex flex-col gap-1">
                  <span className="inline-flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    <span aria-hidden className="size-2 rounded-xs bg-foreground/85" />
                    Today
                  </span>
                  <span className="text-xl font-semibold tracking-[-0.02em] tabular-nums">
                    {usd.format(REVENUE.today)}
                  </span>
                </div>
                <div className="flex flex-col gap-1">
                  <span className="inline-flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    <span aria-hidden className="size-2 rounded-xs bg-muted-foreground/45" />
                    Yesterday
                  </span>
                  <span className="text-xl font-semibold tracking-[-0.02em] tabular-nums text-muted-foreground">
                    {usd.format(REVENUE.yesterday)}
                  </span>
                </div>
              </div>
              <DeltaChip delta={REVENUE.delta} label="Gross revenue" />
            </div>
            <div className="mt-4 flex flex-1 flex-col justify-end">
              <svg viewBox="0 0 100 46" preserveAspectRatio="none" aria-hidden className="h-44 w-full sm:h-56">
                {[11, 22, 33].map((y) => (
                  <line
                    key={y}
                    x1="0"
                    x2="100"
                    y1={y}
                    y2={y}
                    vectorEffect="non-scaling-stroke"
                    className="stroke-border"
                    strokeDasharray="2 3"
                  />
                ))}
                <path d={`${todayLine} L100 46 L0 46 Z`} className="fill-foreground/8" />
                <path
                  d={todayLine}
                  fill="none"
                  vectorEffect="non-scaling-stroke"
                  strokeWidth="1.5"
                  className="stroke-foreground/85"
                />
                <path
                  d={yesterdayLine}
                  fill="none"
                  vectorEffect="non-scaling-stroke"
                  strokeWidth="1.5"
                  strokeDasharray="4 3"
                  className="stroke-muted-foreground/60"
                />
              </svg>
              <div aria-hidden className="mt-2 flex justify-between">
                {HOURLY.filter((_, i) => i % 2 === 0).map((h) => (
                  <span
                    key={h.hour}
                    className="font-mono text-[10px] uppercase tracking-[0.1em] tabular-nums text-muted-foreground"
                  >
                    {h.hour}
                  </span>
                ))}
              </div>

              {/* The chart is pixels; this is the same data as text. */}
              <table className="sr-only">
                <caption>Revenue by hour, today versus yesterday</caption>
                <thead>
                  <tr>
                    <th scope="col">Hour</th>
                    <th scope="col">Today</th>
                    <th scope="col">Yesterday</th>
                  </tr>
                </thead>
                <tbody>
                  {HOURLY.map((h) => (
                    <tr key={h.hour}>
                      <th scope="row">{h.hour}:00</th>
                      <td>{h.today}</td>
                      <td>{h.yesterday}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </PanelCard>

          <div className="grid grid-cols-1 gap-4 lg:col-span-2 lg:grid-rows-2">
            <PanelCard icon={CreditCard} label="Ad budget">
              <div className="flex flex-1 flex-col justify-between gap-4">
                <div className="flex items-end justify-between gap-4">
                  <div className="flex flex-col gap-0.5">
                    <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                      Spent today
                    </span>
                    <span className="text-xl font-semibold tracking-[-0.02em] tabular-nums">
                      {usd.format(BUDGET.spent)}
                    </span>
                  </div>
                  <div className="flex flex-col gap-0.5 text-end">
                    <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                      Daily cap
                    </span>
                    <span className="text-xl font-semibold tracking-[-0.02em] tabular-nums text-muted-foreground">
                      {usd.format(BUDGET.cap)}
                    </span>
                  </div>
                </div>
                <div className="flex flex-col gap-1.5">
                  <div
                    role="progressbar"
                    aria-label="Daily ad budget used"
                    aria-valuemin={0}
                    aria-valuemax={100}
                    aria-valuenow={budgetPct}
                    aria-valuetext={`${budgetPct}% of ${usd.format(BUDGET.cap)}`}
                    className="h-2 w-full overflow-hidden rounded-full bg-accent"
                  >
                    <div className="h-full rounded-full bg-foreground/80" style={{ width: `${budgetPct}%` }} />
                  </div>
                  <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    {budgetPct}% used · resets 00:00 UTC
                  </span>
                </div>
              </div>
            </PanelCard>

            <PanelCard icon={Clock4} label="Peak hours">
              <div className="flex flex-1 flex-col justify-between gap-3">
                <div className="flex flex-col gap-0.5">
                  <span className="text-xl font-semibold tracking-[-0.02em]">
                    {peakWindow[0]?.hour}:00 – {peakWindow.at(-1)?.hour}:00
                  </span>
                  <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    31% of today&apos;s orders
                  </span>
                </div>
                <div aria-hidden className="flex h-16 items-end gap-1">
                  {PEAK_HOURS.map((h) => (
                    <span
                      key={h.hour}
                      className={cn('flex-1 rounded-t-xs', h.peak ? 'bg-foreground/85' : 'bg-muted-foreground/30')}
                      style={{ height: `${(h.orders / peakMax) * 100}%` }}
                    />
                  ))}
                </div>
                <table className="sr-only">
                  <caption>Orders by hour</caption>
                  <thead>
                    <tr>
                      <th scope="col">Hour</th>
                      <th scope="col">Orders</th>
                    </tr>
                  </thead>
                  <tbody>
                    {PEAK_HOURS.map((h) => (
                      <tr key={h.hour}>
                        <th scope="row">{h.hour}:00</th>
                        <td>{h.orders}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </PanelCard>
          </div>
        </div>

        <div className="flex flex-wrap items-center justify-between gap-3">
          <h3 className="text-lg font-semibold tracking-[-0.02em]">Week in review</h3>
          <Select value={range} onValueChange={(v) => setRange(v as WeekRange)}>
            <SelectTrigger size="sm" className="w-[130px]" aria-label="Week range">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="this">This week</SelectItem>
              <SelectItem value="last">Last week</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="flex flex-col gap-4">
          <PanelCard icon={ShoppingCart} label="Orders">
            <div className="flex items-start justify-between gap-4">
              <div className="flex flex-col gap-0.5">
                <span className="text-3xl font-semibold tracking-[-0.035em] tabular-nums">{week.orders.value}</span>
                <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                  Orders completed
                </span>
              </div>
              <DeltaChip delta={week.orders.delta} label="Orders" />
            </div>
            <div
              role="group"
              aria-label="Orders per day"
              className="mt-4 grid h-32 items-end gap-2"
              style={{
                gridTemplateColumns: `repeat(${week.orders.days.length}, minmax(0, 1fr))`,
              }}
            >
              {week.orders.days.map((d) => (
                <div key={d.day} className="flex h-full flex-col justify-end gap-1.5">
                  <div
                    aria-hidden
                    // Capped so seven bars across a wide card stay bars
                    // rather than slabs.
                    className="mx-auto w-full max-w-16 rounded-t-xs bg-foreground/80 transition-all duration-300 ease-out"
                    style={{ height: `${(d.orders / barMax) * 100}%` }}
                  />
                  <span className="text-center font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    {d.day}
                    <span className="sr-only">: {d.orders} orders</span>
                  </span>
                </div>
              ))}
            </div>
          </PanelCard>

          <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
            {week.minis.map((m) => (
              <PanelCard key={m.label} icon={CreditCard} label={m.label}>
                <div className="flex items-start justify-between gap-3">
                  <span className="text-2xl font-semibold tracking-[-0.03em] tabular-nums">{m.value}</span>
                  <DeltaChip delta={m.delta} label={m.label} />
                </div>
                <Sparkline points={m.spark} />
              </PanelCard>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

export default Dashboard04;

Dependencies

shadcn registry

badgebuttoncardselect

npm

lucide-react