Dashboard
An admin screen for a store. Switch the time period, and the stat cards and chart update to that period’s data.
Dashboard
Key points
- 1
Calculate the numbers from the raw data
Totals like revenue and order count come from adding up the daily data with reduce. Switch the period and they stay correct — you just calculate again.
- 2
Let a library draw the chart
Recharts draws the bars, the axes, and the numbers that appear on hover. You just tell it which data to use and which fields to draw.
- 3
Show up or down with color and symbols
Compared to the previous period, a green ▲ means up and a red ▼ means down. You can tell good from bad at a glance, without reading the numbers.
Tools used
Click to see the comparison in Part 2
Edit the code
Edit the code and the preview on the right updates.
import { useState } from "react" import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts" import { Button } from "./components/ui/button" // Daily sales and orders (in a real app, these come from a server). Today is treated as 10/4 const weekDays = [ { day: "9/28", sales: 1010, orders: 38 }, { day: "9/29", sales: 850, orders: 33 }, { day: "9/30", sales: 940, orders: 36 }, { day: "10/1", sales: 1170, orders: 44 }, { day: "10/2", sales: 650, orders: 27 }, { day: "10/3", sales: 1430, orders: 61 }, { day: "10/4", sales: 1570, orders: 64 }, ] // Last 30 days (9/5 to 10/4) = 23 days from 9/5 to 9/27 + the 7 days above (so the same day has the same numbers) // The 23 days are made with a fixed formula (demo only; no need to remember it) const monthDays = [ ...Array.from({ length: 23 }, (_, i) => ({ day: `9/${i + 5}`, sales: 730 + ((i * 37) % 11) * 80 + (i % 7 <= 1 ? 470 : 0), orders: 30 + ((i * 37) % 11) * 3 + (i % 7 <= 1 ? 18 : 0), })), ...weekDays, ] // Data for each period. previous holds the numbers for the period before (to compare up or down) const periods = { week: { label: "Last 7 days", days: weekDays, members: 48, previous: { sales: 6800, orders: 286, average: 24, members: 40 } }, month: { label: "Last 30 days", days: monthDays, members: 186, previous: { sales: 33000, orders: 1390, average: 24, members: 201 } }, } // Recent orders const recentOrders = [ { id: "A-1052", name: "Emily Johnson", amount: 85, status: "Shipped" }, { id: "A-1051", name: "Michael Brown", amount: 30, status: "Preparing" }, { id: "A-1050", name: "Sarah Davis", amount: 155, status: "Shipped" }, { id: "A-1049", name: "David Wilson", amount: 20, status: "Preparing" }, { id: "A-1048", name: "Olivia Moore", amount: 50, status: "Shipped" }, ] // Money format (1010 → $1,010) function yen(value) { return "$" + value.toLocaleString() } // Percent change from the previous period (negative when it went down). e.g. now 120, before 100 → 20 function changePercent(value, previous) { return Math.round(((value - previous) / previous) * 100) } export default function App() { // ① The selected period ("week" = last 7 days, "month" = last 30 days), kept in state //const period = "week" const [period, setPeriod] = useState("week") const current = periods[period] // Data for the selected period // ② Totals for the period //const sales = 1144000 // Sum of the daily sales (d.sales) const sales = current.days.reduce((sum, d) => sum + d.sales, 0) //const orders = 303 // Sum of the daily orders (d.orders) const orders = current.days.reduce((sum, d) => sum + d.orders, 0) const average = Math.round(sales / orders) // Average order value = sales ÷ orders // Data for the 4 stat cards (label = name, text = the big number, change = percent change) const stats = [ { label: "Sales", text: yen(sales), change: changePercent(sales, current.previous.sales) }, { label: "Orders", text: orders.toLocaleString(), change: changePercent(orders, current.previous.orders) }, { label: "Avg. order", text: yen(average), change: changePercent(average, current.previous.average) }, { label: "New members", text: String(current.members), change: changePercent(current.members, current.previous.members) }, ] return ( <div className="mx-auto my-6 flex w-full max-w-4xl flex-col gap-6 px-4 text-foreground"> {/* Heading and period buttons */} <div className="flex flex-wrap items-center justify-between gap-3"> <h1 className="text-xl font-semibold">Dashboard</h1> <div className="flex gap-2"> {/* ③ Change the period on click. The selected one is filled (default), the other is outline */} <Button size="sm" variant={period === "week" ? "default" : "outline"} onClick={() => setPeriod("week")}> Last 7 days </Button> <Button size="sm" variant={period === "month" ? "default" : "outline"} onClick={() => setPeriod("month")}> Last 30 days </Button> </div> </div> {/* ④ Stat cards: list the 4 stats with map */} <div className="grid grid-cols-2 gap-3 md:grid-cols-4"> {stats.map((stat) => ( <div key={stat.label} className="rounded-xl border bg-card p-4 text-card-foreground"> <p className="text-xs text-muted-foreground">{stat.label}</p> <p className="mt-1 text-xl font-semibold tabular-nums">{stat.text}</p> {/* ⑤ Green ▲ if it went up (change is 0 or more), red ▼ if it went down */} {/* <p className="mt-1 text-xs text-emerald-600"> */} <p className={stat.change >= 0 ? "mt-1 text-xs text-emerald-600" : "mt-1 text-xs text-red-600"}> {stat.change >= 0 ? "▲" : "▼"} {Math.abs(stat.change)}% <span className="text-muted-foreground"> vs previous period</span> </p> </div> ))} </div> {/* Sales chart (Recharts draws the list passed to data as bars) */} <div className="rounded-xl border bg-card p-4 text-card-foreground"> <p className="text-sm font-medium">Sales over time</p> <div className="mt-4 h-56"> <ResponsiveContainer width="100%" height="100%"> <BarChart data={current.days}> <CartesianGrid vertical={false} stroke="var(--border)" /> <XAxis dataKey="day" tickLine={false} axisLine={false} fontSize={12} /> <YAxis tickLine={false} axisLine={false} fontSize={12} width={40} tickFormatter={(v) => "$" + v / 1000 + "k"} /> <Tooltip formatter={(v) => [yen(v), "Sales"]} cursor={{ fill: "var(--muted)" }} /> <Bar dataKey="sales" fill="var(--primary)" radius={[4, 4, 0, 0]} /> </BarChart> </ResponsiveContainer> </div> </div> {/* Recent orders */} <div className="rounded-xl border bg-card p-4 text-card-foreground"> <p className="text-sm font-medium">Recent orders</p> <ul className="mt-3 divide-y text-sm"> {recentOrders.map((order) => ( <li key={order.id} className="flex items-center gap-3 py-2.5"> <span className="w-16 text-muted-foreground tabular-nums">{order.id}</span> <span className="flex-1">{order.name}</span> <span className="tabular-nums">{yen(order.amount)}</span> <span // ⑥ Green if "Shipped", otherwise (Preparing) yellow className={order.status === "Shipped" ? "w-16 rounded-full bg-emerald-100 py-0.5 text-center text-xs text-emerald-700" : "w-16 rounded-full bg-amber-100 py-0.5 text-center text-xs text-amber-700"} > {order.status} </span> </li> ))} </ul> </div> </div > ) }