# Analytics Dashboard

> An admin dashboard with a collapsible sidebar, period-aware stat cards, a revenue chart, an activity feed and a sortable orders table.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/analytics-dashboard
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/analytics-dashboard.json
```

Adds the page at `app/analytics-dashboard/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [App Sidebar](https://chromotion.dev/r/app-sidebar.md): `@/components/app-sidebar`
- [Animated Tabs](https://chromotion.dev/r/animated-tabs.md): `@/components/animated-tabs`
- [Stat Card](https://chromotion.dev/r/stat-card.md): `@/components/stat-card`
- [Activity Timeline](https://chromotion.dev/r/activity-timeline.md): `@/components/activity-timeline`
- [Data Table](https://chromotion.dev/r/data-table.md): `@/components/data-table`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/analytics-dashboard/page.tsx"
"use client"

import * as React from "react"
import {
  BarChart3,
  Bell,
  CreditCard,
  Download,
  Hexagon,
  House,
  LifeBuoy,
  Rocket,
  Settings,
  ShoppingCart,
  UserPlus,
  Users,
} from "lucide-react"

import { ActivityTimeline } from "@/components/activity-timeline"
import { AnimatedTabs } from "@/components/animated-tabs"
import { AppSidebar } from "@/components/app-sidebar"
import { DataTable, type DataTableColumn } from "@/components/data-table"
import { StatCard } from "@/components/stat-card"
import { cn } from "@/lib/utils"

type Period = "7d" | "30d" | "90d"

const periods: { id: Period; label: string }[] = [
  { id: "7d", label: "7 days" },
  { id: "30d", label: "30 days" },
  { id: "90d", label: "90 days" },
]

const stats: Record<Period, { label: string; value: string; change: number; data: number[]; invertTrend?: boolean }[]> = {
  "7d": [
    { label: "Revenue", value: "$12,840", change: 8.2, data: [9, 11, 10, 13, 12, 15, 16] },
    { label: "Orders", value: "342", change: 4.6, data: [40, 44, 41, 52, 49, 55, 61] },
    { label: "New customers", value: "128", change: -2.1, data: [22, 19, 20, 17, 18, 16, 16] },
    { label: "Refund rate", value: "1.2%", change: -0.4, invertTrend: true, data: [1.8, 1.7, 1.6, 1.5, 1.4, 1.3, 1.2] },
  ],
  "30d": [
    { label: "Revenue", value: "$48,920", change: 12.4, data: [30, 34, 33, 38, 41, 40, 45, 47, 46, 52] },
    { label: "Orders", value: "1,486", change: 9.8, data: [120, 132, 128, 140, 151, 149, 160, 158, 171, 176] },
    { label: "New customers", value: "562", change: 6.3, data: [41, 45, 44, 50, 48, 53, 55, 58, 57, 61] },
    { label: "Refund rate", value: "1.5%", change: 0.2, invertTrend: true, data: [1.3, 1.3, 1.4, 1.3, 1.5, 1.4, 1.6, 1.5, 1.5, 1.5] },
  ],
  "90d": [
    { label: "Revenue", value: "$139,300", change: 21.7, data: [80, 86, 91, 95, 99, 108, 112, 121, 127, 139] },
    { label: "Orders", value: "4,210", change: 17.5, data: [310, 322, 340, 351, 362, 380, 391, 402, 415, 421] },
    { label: "New customers", value: "1,704", change: 11.9, data: [120, 131, 128, 142, 150, 149, 161, 166, 170, 178] },
    { label: "Refund rate", value: "1.4%", change: -0.6, invertTrend: true, data: [2, 1.9, 1.9, 1.8, 1.7, 1.6, 1.6, 1.5, 1.4, 1.4] },
  ],
}

const revenue: Record<Period, { label: string; value: number }[]> = {
  "7d": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((label, index) => ({
    label,
    value: [58, 64, 61, 72, 69, 85, 91][index],
  })),
  "30d": Array.from({ length: 10 }, (_, index) => ({
    label: `D${index * 3 + 1}`,
    value: [42, 55, 48, 63, 58, 71, 66, 80, 74, 88][index],
  })),
  "90d": Array.from({ length: 12 }, (_, index) => ({
    label: `W${index + 1}`,
    value: [38, 44, 47, 52, 55, 61, 63, 70, 74, 81, 86, 95][index],
  })),
}

type Order = {
  id: string
  customer: string
  email: string
  plan: "Starter" | "Pro" | "Enterprise"
  amount: number
  status: "Paid" | "Pending" | "Refunded"
  date: string
}

const orders: Order[] = [
  { id: "#3210", customer: "Olivia Martin", email: "olivia@nimbus.io", plan: "Pro", amount: 240, status: "Paid", date: "2026-11-06" },
  { id: "#3209", customer: "Jackson Lee", email: "jackson@orbit.dev", plan: "Starter", amount: 49, status: "Pending", date: "2026-11-06" },
  { id: "#3208", customer: "Isabella Nguyen", email: "isabella@tidy.app", plan: "Enterprise", amount: 1200, status: "Paid", date: "2026-11-05" },
  { id: "#3207", customer: "William Kim", email: "will@loop.so", plan: "Pro", amount: 240, status: "Refunded", date: "2026-11-05" },
  { id: "#3206", customer: "Sofia Davis", email: "sofia@fieldnote.co", plan: "Pro", amount: 240, status: "Paid", date: "2026-11-04" },
  { id: "#3205", customer: "Liam Johnson", email: "liam@relay.dev", plan: "Starter", amount: 49, status: "Paid", date: "2026-11-04" },
  { id: "#3204", customer: "Emma Brown", email: "emma@parcel.io", plan: "Enterprise", amount: 1200, status: "Pending", date: "2026-11-03" },
  { id: "#3203", customer: "Noah Wilson", email: "noah@keystone.app", plan: "Pro", amount: 240, status: "Paid", date: "2026-11-03" },
  { id: "#3202", customer: "Ava Garcia", email: "ava@summit.so", plan: "Starter", amount: 49, status: "Paid", date: "2026-11-02" },
  { id: "#3201", customer: "Lucas Moore", email: "lucas@quill.dev", plan: "Pro", amount: 240, status: "Paid", date: "2026-11-01" },
]

const statusStyle: Record<Order["status"], string> = {
  Paid: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
  Pending: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
  Refunded: "bg-zinc-500/10 text-muted-foreground",
}

const columns: DataTableColumn<Order>[] = [
  {
    key: "customer",
    header: "Customer",
    sortable: true,
    cell: (row) => (
      <div className="min-w-0">
        <p className="truncate font-medium">{row.customer}</p>
        <p className="truncate text-xs text-muted-foreground">{row.email}</p>
      </div>
    ),
  },
  { key: "id", header: "Order", className: "font-mono text-xs text-muted-foreground" },
  { key: "plan", header: "Plan", sortable: true },
  {
    key: "status",
    header: "Status",
    sortable: true,
    cell: (row) => (
      <span className={cn("inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium", statusStyle[row.status])}>
        <span className="size-1.5 rounded-full bg-current" />
        {row.status}
      </span>
    ),
  },
  { key: "amount", header: "Amount", sortable: true, align: "right", cell: (row) => `$${row.amount.toLocaleString("en-US")}` },
  { key: "date", header: "Date", sortable: true, className: "whitespace-nowrap text-muted-foreground" },
]

const getRowId = (row: Order) => row.id

const activity = [
  { icon: <ShoppingCart />, color: "#22c55e", title: "Isabella Nguyen upgraded to Enterprise", time: "12m ago" },
  { icon: <UserPlus />, color: "#3b82f6", title: "18 new customers from the newsletter", time: "1h ago" },
  { icon: <CreditCard />, color: "#f97316", title: "Refund issued to William Kim", time: "3h ago" },
  { icon: <Rocket />, color: "#a855f7", title: "Checkout v3 rolled out to 50% of traffic", time: "Yesterday" },
]

export default function AnalyticsDashboard() {
  const [period, setPeriod] = React.useState<Period>("30d")
  const [selection, setSelection] = React.useState<string[]>([])
  const bars = revenue[period]
  const peak = Math.max(...bars.map((bar) => bar.value))

  return (
    <AppSidebar
      collapseBelow={768}
      brand={
        <>
          <Hexagon className="size-5 shrink-0 fill-primary text-primary" /> Hexa
        </>
      }
      brandIcon={<Hexagon className="size-5 shrink-0 fill-primary text-primary" />}
      groups={[
        {
          items: [
            { label: "Overview", href: "#", icon: <House />, active: true },
            { label: "Orders", href: "#", icon: <ShoppingCart />, badge: 12 },
            { label: "Customers", href: "#", icon: <Users /> },
            { label: "Reports", href: "#", icon: <BarChart3 /> },
          ],
        },
        {
          label: "Account",
          items: [
            { label: "Notifications", href: "#", icon: <Bell /> },
            { label: "Billing", href: "#", icon: <CreditCard /> },
            { label: "Settings", href: "#", icon: <Settings /> },
            { label: "Help center", href: "#", icon: <LifeBuoy /> },
          ],
        },
      ]}
      user={{ name: "Ada Lovelace", email: "ada@hexa.dev", initials: "AL" }}
      accentColor="var(--ring)"
    >
      <header className="sticky top-0 z-10 flex h-14 items-center justify-between gap-4 border-b bg-background/80 px-6 backdrop-blur">
        <h1 className="text-sm font-medium">Overview</h1>
        <AnimatedTabs tabs={periods} value={period} onChange={(id) => setPeriod(id as Period)} />
      </header>

      <div className="flex flex-col gap-6 p-6">
        <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
          {stats[period].map((stat) => (
            <StatCard key={stat.label} {...stat} caption={`vs. previous ${period}`} className="max-w-none" />
          ))}
        </div>

        <div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_340px]">
          <section className="flex h-80 flex-col rounded-2xl border bg-card p-5 shadow-sm">
            <div className="flex items-baseline justify-between">
              <h2 className="text-sm font-medium">Revenue</h2>
              <p className="text-xs text-muted-foreground">Last {periods.find((item) => item.id === period)?.label}</p>
            </div>
            <div className="mt-4 flex flex-1 gap-2 sm:gap-3">
              {bars.map((bar) => (
                <div key={bar.label} className="flex flex-1 flex-col items-center gap-2">
                  <div className="relative w-full flex-1">
                    <div
                      title={`${bar.label}: ${bar.value}`}
                      className={cn(
                        "absolute inset-x-0 bottom-0 rounded-md transition-[height,background-color] duration-500",
                        bar.value === peak ? "bg-primary" : "bg-primary/20 hover:bg-primary/35"
                      )}
                      style={{ height: `${bar.value}%` }}
                    />
                  </div>
                  <span className="text-[11px] text-muted-foreground">{bar.label}</span>
                </div>
              ))}
            </div>
          </section>

          <section className="rounded-2xl border bg-card p-5 shadow-sm">
            <h2 className="mb-5 text-sm font-medium">Recent activity</h2>
            <ActivityTimeline items={activity} variant="compact" />
          </section>
        </div>

        <section className="flex flex-col gap-3">
          <h2 className="text-sm font-medium">Recent orders</h2>
          <DataTable
            data={orders}
            columns={columns}
            getRowId={getRowId}
            onSelectionChange={setSelection}
            pageSize={5}
            searchPlaceholder="Search orders…"
            toolbar={
              <button
                type="button"
                className="inline-flex h-9 items-center gap-2 rounded-lg border bg-background px-3 text-sm font-medium hover:bg-accent"
              >
                <Download className="size-4" />
                Export{selection.length > 0 && ` (${selection.length})`}
              </button>
            }
          />
        </section>
      </div>
    </AppSidebar>
  )
}
```
