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 with an orders bar chart and three sparkline metric cards driven by a range select.

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,
  MoreHorizontal,
  Package,
  RotateCcw,
  Settings2,
  ShoppingCart,
  Users,
  type LucideIcon,
} from "lucide-react";

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

type Stat = {
  icon: LucideIcon;
  label: string;
  value: string;
  delta: string;
  up: boolean;
  good: boolean;
};

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

const TODAY_SERIES = [12, 9, 14, 24, 46, 74, 92, 84, 102, 96, 71, 48, 31];
const YESTERDAY_SERIES = [10, 8, 11, 19, 39, 61, 78, 73, 84, 80, 64, 41, 26];
const HOUR_TICKS = ["00", "04", "08", "12", "16", "20", "24"] as const;

const PEAK_BARS = [14, 18, 26, 41, 58, 92, 100, 86, 54, 38, 27, 19] as const;

type WeekRange = "this" | "last";

const WEEK: Record<
  WeekRange,
  {
    orders: { value: string; delta: string; bars: readonly number[] };
    minis: readonly {
      label: string;
      value: string;
      delta: string;
      good: boolean;
      spark: readonly number[];
    }[];
  }
> = {
  this: {
    orders: {
      value: "1,318",
      delta: "+11.2%",
      bars: [148, 176, 162, 196, 228, 184, 224],
    },
    minis: [
      {
        label: "Gross revenue",
        value: "$24,820",
        delta: "+9.2%",
        good: true,
        spark: [30, 34, 31, 38, 44, 41, 48],
      },
      {
        label: "Returning buyers",
        value: "58.4%",
        delta: "+1.9%",
        good: true,
        spark: [52, 54, 53, 55, 56, 57, 58],
      },
      {
        label: "Checkout conversion",
        value: "3.1%",
        delta: "+0.4%",
        good: true,
        spark: [2.5, 2.7, 2.6, 2.9, 3.0, 2.9, 3.1],
      },
    ],
  },
  last: {
    orders: {
      value: "1,186",
      delta: "+4.6%",
      bars: [132, 158, 149, 171, 198, 166, 212],
    },
    minis: [
      {
        label: "Gross revenue",
        value: "$22,730",
        delta: "+5.8%",
        good: true,
        spark: [27, 30, 28, 33, 37, 35, 41],
      },
      {
        label: "Returning buyers",
        value: "56.5%",
        delta: "-0.3%",
        good: false,
        spark: [57, 56, 57, 56, 55, 56, 56],
      },
      {
        label: "Checkout conversion",
        value: "2.7%",
        delta: "+0.1%",
        good: true,
        spark: [2.4, 2.5, 2.4, 2.6, 2.7, 2.6, 2.7],
      },
    ],
  },
};

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

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

function DeltaChip({
  delta,
  up,
  good,
}: {
  delta: string;
  up: boolean;
  good: boolean;
}) {
  return (
    <span
      className={`inline-flex w-fit items-center gap-1 rounded-sm px-1.5 py-0.5 font-mono text-[11px] leading-none ${
        good
          ? "bg-success/10 text-success"
          : "bg-destructive/10 text-destructive"
      }`}
    >
      {up ? (
        <ArrowUpRight className="size-3" />
      ) : (
        <ArrowDownRight className="size-3" />
      )}
      {delta}
    </span>
  );
}

function PanelCard({
  icon: Icon,
  label,
  children,
  className,
}: {
  icon: LucideIcon;
  label: string;
  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" />
          {label}
        </CardTitle>
        <CardAction>
          <Button
            variant="ghost"
            size="icon"
            className="-my-1 size-6 text-muted-foreground"
            aria-label={`${label} options`}
          >
            <MoreHorizontal className="size-3.5" />
          </Button>
        </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>
  );
}

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

  const chartMax = Math.max(...TODAY_SERIES, ...YESTERDAY_SERIES);
  const todayLine = linePath(TODAY_SERIES, chartMax, 46);
  const yesterdayLine = linePath(YESTERDAY_SERIES, chartMax, 46);
  const barMax = Math.max(...week.orders.bars);
  const budgetPct = Math.round((BUDGET.spent / BUDGET.cap) * 100);

  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" />
            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} up={s.up} good={s.good} />
                  <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">
            Live · updates every minute
          </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 className="size-2 rounded-xs bg-foreground/85" />
                    Today
                  </span>
                  <span className="text-xl font-semibold tabular-nums tracking-[-0.02em]">
                    $1,284.50
                  </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 className="size-2 rounded-xs bg-muted-foreground/45" />
                    Yesterday
                  </span>
                  <span className="text-xl font-semibold tabular-nums tracking-[-0.02em] text-muted-foreground">
                    $1,092.20
                  </span>
                </div>
              </div>
              <DeltaChip delta="+17.6%" up good />
            </div>
            <div className="mt-4 flex flex-1 flex-col justify-end">
              <svg
                viewBox="0 0 100 46"
                preserveAspectRatio="none"
                role="img"
                aria-label="Revenue by hour, today versus yesterday"
                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 className="mt-2 flex justify-between">
                {HOUR_TICKS.map((t) => (
                  <span
                    key={t}
                    className="font-mono text-[10px] tabular-nums uppercase tracking-[0.1em] text-muted-foreground"
                  >
                    {t}
                  </span>
                ))}
              </div>
            </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 tabular-nums tracking-[-0.02em]">
                      ${BUDGET.spent.toFixed(2)}
                    </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 tabular-nums tracking-[-0.02em] text-muted-foreground">
                      ${BUDGET.cap.toFixed(2)}
                    </span>
                  </div>
                </div>
                <div className="flex flex-col gap-1.5">
                  <div
                    role="img"
                    aria-label={`${budgetPct} percent of daily ad budget used`}
                    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]">
                    12 PM – 2 PM
                  </span>
                  <span className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    31% of today&apos;s orders
                  </span>
                </div>
                <div
                  role="img"
                  aria-label="Orders by hour"
                  className="flex h-16 items-end gap-1"
                >
                  {PEAK_BARS.map((h, i) => (
                    <span
                      key={i}
                      className={`flex-1 rounded-t-xs ${
                        h >= 86 ? "bg-foreground/85" : "bg-muted-foreground/30"
                      }`}
                      style={{ height: `${h}%` }}
                    />
                  ))}
                </div>
              </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 tabular-nums tracking-[-0.035em]">
                  {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} up good />
            </div>
            <div
              role="img"
              aria-label="Orders per day"
              className="mt-4 grid h-32 items-end gap-2"
              style={{
                gridTemplateColumns: `repeat(${week.orders.bars.length}, minmax(0, 1fr))`,
              }}
            >
              {week.orders.bars.map((b, i) => (
                <div
                  key={i}
                  className="flex h-full flex-col justify-end gap-1.5"
                >
                  <div
                    className="rounded-t-xs bg-foreground/80 transition-all duration-300 ease-out"
                    style={{ height: `${(b / barMax) * 100}%` }}
                  />
                  <span className="text-center font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">
                    {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][i]}
                  </span>
                </div>
              ))}
            </div>
          </PanelCard>

          <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
            {week.minis.map((m) => {
              const max = Math.max(...m.spark);
              const min = Math.min(...m.spark);
              const span = max - min || 1;
              const step = 100 / (m.spark.length - 1);
              const pts = m.spark
                .map(
                  (v, i) =>
                    `${(i * step).toFixed(1)},${(22 - ((v - min) / span) * 16).toFixed(1)}`,
                )
                .join(" ");
              return (
                <PanelCard key={m.label} icon={CreditCard} label={m.label}>
                  <div className="flex items-start justify-between gap-3">
                    <span className="text-2xl font-semibold tabular-nums tracking-[-0.03em]">
                      {m.value}
                    </span>
                    <DeltaChip
                      delta={m.delta}
                      up={m.delta.startsWith("+")}
                      good={m.good}
                    />
                  </div>
                  <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>
                </PanelCard>
              );
            })}
          </div>
        </div>
      </div>
    </section>
  );
}

Dependencies

shadcn registry

badgebuttoncardselect

npm

lucide-react