From 2cb8bcd4e3f9b4a111243cf024967284cca556c1 Mon Sep 17 00:00:00 2001 From: miquelmatoses Date: Tue, 23 Jun 2026 00:43:51 +0200 Subject: [PATCH] refactor(admin): migrate AdminDashboard onto shared ui layer (flat Card, tokens) Audit items #1 and #2. Presentational only: no data fetching, state, handlers, endpoint calls, or admin behavior changed. - Added components/ui/StatCard.jsx and components/ui/Sparkline.jsx (token-colored charting primitive). Admin now imports the shared layer; the local StatCard and Sparkline definitions are deleted. - Replaced all 16 hand-rolled card strings (rounded-xl border-gray-100 shadow-sm): 14 div-based shells now use the canonical flat Card (ADR 0015); the one link card cannot be a div Card, so its classes were aligned to the flat spec (rounded, gray-200 border, no shadow). Zero rounded-xl/shadow-sm card strings remain. - Replaced hardcoded brand hex with tokens: Sparkline default and call sites use colors.blue / colors.green; the recharts grid stroke uses var(--mm-color-gray-200), matching the existing var(--mm-color-blue) bar fill. - Dropped the phantom var(--mm-font-heading) (defined nowhere). It resolved to the inherited font, so the StatCard number stays Roboto and the page h1 stays Playfair via the global h1 rule. No visual change from the drop. Scope note: raw Tailwind gray text utilities are left as-is. The mm-design gray token values are identical to Tailwind's gray scale, so these are not a color drift; converting them to inline var() styles would add inline styles for zero visual change. Full adoption for the remaining link card is a follow-up. Admin runtime visuals are not covered by tests (the build is the automated gate); visual QA on deploy is the operator's. Co-Authored-By: Claude Opus 4.8 --- src/components/ui/Sparkline.jsx | 49 ++++++++++++ src/components/ui/StatCard.jsx | 19 +++++ src/components/ui/index.js | 2 + src/pages/AdminDashboardPage.jsx | 128 +++++++++---------------------- 4 files changed, 106 insertions(+), 92 deletions(-) create mode 100644 src/components/ui/Sparkline.jsx create mode 100644 src/components/ui/StatCard.jsx diff --git a/src/components/ui/Sparkline.jsx b/src/components/ui/Sparkline.jsx new file mode 100644 index 000000000..6dea651f6 --- /dev/null +++ b/src/components/ui/Sparkline.jsx @@ -0,0 +1,49 @@ +/** + * Sparkline — tiny inline SVG trend line for dashboard cards. + * + * This is a charting primitive, not an icon, so the inline here is the + * sanctioned exception to the MoonIcons-only rule. The default stroke is the + * mm-design blue token; pass `color` (a token value) to override. + * + * data: array of { day: 'YYYY-MM-DD', count: number }. Missing days render as 0. + */ +import { colors } from '../../design/tokens' + +export default function Sparkline({ data, color = colors.blue, days = 30 }) { + if (!data || data.length === 0) { + return
No data
+ } + + // Fill in all days (missing days = 0) + const filled = [] + const today = new Date() + for (let i = days - 1; i >= 0; i--) { + const d = new Date(today) + d.setDate(d.getDate() - i) + const key = d.toISOString().split('T')[0] + const found = data.find(r => r.day === key) + filled.push(found ? found.count : 0) + } + + const max = Math.max(...filled, 1) + const W = 200 + const H = 40 + const pts = filled.map((v, i) => { + const x = (i / (filled.length - 1)) * W + const y = H - (v / max) * H + return `${x},${y}` + }).join(' ') + + return ( + + + + ) +} diff --git a/src/components/ui/StatCard.jsx b/src/components/ui/StatCard.jsx new file mode 100644 index 000000000..4111865a1 --- /dev/null +++ b/src/components/ui/StatCard.jsx @@ -0,0 +1,19 @@ +/** + * StatCard — a labelled KPI number on the canonical flat Card. + * + * The value renders in the inherited body font. The previous admin markup + * applied an undefined --mm-font-heading variable, which resolved to the + * inherited font anyway; dropping it keeps the exact same rendering without + * the phantom variable. + */ +import Card from './Card' + +export default function StatCard({ label, value, sub }) { + return ( + + {label} + {value ?? '—'} + {sub && {sub}} + + ) +} diff --git a/src/components/ui/index.js b/src/components/ui/index.js index 4745510a6..929a0a488 100644 --- a/src/components/ui/index.js +++ b/src/components/ui/index.js @@ -3,3 +3,5 @@ export { default as Card } from './Card' export { default as Badge } from './Badge' export { default as SectionLabel } from './SectionLabel' export { default as DisplayHeading } from './DisplayHeading' +export { default as StatCard } from './StatCard' +export { default as Sparkline } from './Sparkline' diff --git a/src/pages/AdminDashboardPage.jsx b/src/pages/AdminDashboardPage.jsx index 003d4d835..12a7675fe 100644 --- a/src/pages/AdminDashboardPage.jsx +++ b/src/pages/AdminDashboardPage.jsx @@ -33,6 +33,8 @@ import { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, } from 'recharts' +import { Card, StatCard, Sparkline } from '../components/ui' +import { colors } from '../design/tokens' // --------------------------------------------------------------------------- // Shared helpers @@ -57,18 +59,6 @@ function fmt(dt) { // Shared UI primitives // --------------------------------------------------------------------------- -function StatCard({ label, value, sub }) { - return ( -
- {label} - - {value ?? '—'} - - {sub && {sub}} -
- ) -} - function TabButton({ label, active, onClick }) { return ( - + ))} @@ -1122,7 +1069,7 @@ function BlogTab() { {/* Post list */} {error &&

{error}

} -
+ @@ -1195,11 +1142,11 @@ function BlogTab() { })}
-
+ {/* Create / Edit form */} {form && ( -
+

{isNew ? 'New post' : `Edit: ${form.slug}`} @@ -1321,7 +1268,7 @@ function BlogTab() { {saving ? 'Saving…' : 'Publish'}

-
+ )} @@ -1347,10 +1294,7 @@ export default function AdminDashboardPage() { return (
-

+

Admin Dashboard

Staff-only. Handle with care.