chati-dev 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -13
- package/bin/chati.js +32 -4
- package/framework/agents/build/dev.md +9 -5
- package/framework/agents/discover/brief.md +2 -0
- package/framework/agents/discover/brownfield-wu.md +2 -0
- package/framework/agents/discover/greenfield-wu.md +18 -1
- package/framework/agents/plan/detail.md +2 -0
- package/framework/agents/plan/tasks.md +31 -0
- package/framework/agents/plan/ux-brand-architect.md +21 -2
- package/framework/agents/plan/ux-component-engineer.md +10 -0
- package/framework/agents/quality/qa-implementation.md +1 -1
- package/framework/agents/quality/qa-planning.md +1 -1
- package/framework/agents/quality/qa-visual.md +33 -21
- package/framework/config.yaml +5 -4
- package/framework/constitution.md +58 -11
- package/framework/context/governance.md +12 -10
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +12 -4
- package/framework/data/qa-rubrics.yaml +335 -0
- package/framework/domains/constitution.yaml +3 -3
- package/framework/executors/json-validate.js +80 -0
- package/framework/executors/npm-script.js +72 -0
- package/framework/executors/path-check.js +67 -0
- package/framework/executors/registry.yaml +36 -0
- package/framework/hooks/advance-trigger.js +47 -3
- package/framework/hooks/constitution-guard.js +9 -4
- package/framework/hooks/git-push-authority.js +113 -0
- package/framework/hooks/license-guard.js +59 -9
- package/framework/hooks/model-governance.js +40 -17
- package/framework/hooks/prism-engine.js +78 -11
- package/framework/hooks/session-digest.js +12 -3
- package/framework/hooks/settings.json +7 -3
- package/framework/hooks/style-guard.js +52 -3
- package/framework/hooks/team-quality-gate.js +43 -5
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/confidence.yaml +85 -6
- package/framework/intelligence/context-engine.md +9 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/orchestrator/chati-router.js +59 -5
- package/framework/orchestrator/chati-update.md +19 -3
- package/framework/orchestrator/chati.md +20 -1
- package/framework/quality-gates/planning-gate.md +2 -0
- package/framework/rules/human-writing-style.md +47 -0
- package/framework/scaffold/motion-premium/README.md +12 -2
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
- package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
- package/framework/scaffold/motion-premium-3d/README.md +10 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
- package/framework/scaffold/saas-dashboard/README.md +58 -0
- package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
- package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
- package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
- package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
- package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
- package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
- package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
- package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
- package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
- package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
- package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
- package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
- package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
- package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
- package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
- package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
- package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
- package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
- package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
- package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
- package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
- package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
- package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
- package/framework/schemas/session.schema.json +5 -0
- package/framework/schemas/task.schema.json +9 -0
- package/framework/scripts/visual-qa.js +101 -3
- package/framework/tasks/orchestrator-health.md +4 -4
- package/framework/tasks/qa-impl-verdict.md +11 -5
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/qa-gate-tmpl.yaml +24 -11
- package/package.json +4 -3
- package/src/config/context-file-generator.js +0 -6
- package/src/dashboard/renderer.js +0 -36
- package/src/executors/runner.js +204 -0
- package/src/installer/core.js +13 -3
- package/src/installer/templates.js +5 -6
- package/src/intelligence/registry-manager.js +1 -1
- package/src/license/client.js +9 -3
- package/src/license/commands.js +12 -2
- package/src/license/machine-id.js +42 -1
- package/src/memory/gotchas.js +58 -40
- package/src/memory/magic-docs.js +1 -1
- package/src/memory/session-digest.js +9 -4
- package/src/orchestrator/cli.js +186 -15
- package/src/orchestrator/doctor.js +98 -16
- package/src/orchestrator/pipeline-manager.js +44 -22
- package/src/orchestrator/session-manager.js +64 -8
- package/src/telemetry/sender.js +7 -19
- package/src/terminal/run-team.js +3 -3
- package/src/upgrade/tracked-files-detector.js +34 -16
- package/src/utils/feature-flags.js +1 -1
- package/src/utils/flatten-entities.js +4 -40
- package/src/utils/schema-validator.js +0 -14
- package/src/wizard/i18n.js +3 -3
- package/src/api/index.js +0 -120
- package/src/autonomy/autonomous-gate.js +0 -294
- package/src/autonomy/build-loop.js +0 -281
- package/src/autonomy/build-state.js +0 -286
- package/src/autonomy/cause-analyzer.js +0 -177
- package/src/autonomy/escalation.js +0 -214
- package/src/autonomy/index.js +0 -51
- package/src/autonomy/mode-manager.js +0 -225
- package/src/autonomy/mode-suggester.js +0 -283
- package/src/autonomy/progress-reporter.js +0 -275
- package/src/autonomy/safety-net.js +0 -370
- package/src/config/agent-customizer.js +0 -231
- package/src/decision/analyzer.js +0 -291
- package/src/decision/engine.js +0 -250
- package/src/decision/index.js +0 -38
- package/src/decision/registry-healer.js +0 -468
- package/src/decision/registry-updater.js +0 -339
- package/src/extensions/loader.js +0 -145
- package/src/extensions/registry.js +0 -134
- package/src/gates/circuit-breaker.js +0 -151
- package/src/gates/g1-planning-complete.js +0 -154
- package/src/gates/g2-qa-planning.js +0 -156
- package/src/gates/g3-implementation.js +0 -215
- package/src/gates/g4-qa-implementation.js +0 -240
- package/src/gates/g5-deploy-ready.js +0 -181
- package/src/gates/gate-base.js +0 -185
- package/src/gates/index.js +0 -46
- package/src/health/auto-fix.js +0 -216
- package/src/health/engine.js +0 -246
- package/src/merger/semantic-merger.js +0 -292
- package/src/preview/detector.js +0 -238
- package/src/preview/index.js +0 -20
- package/src/preview/launcher.js +0 -235
- package/src/preview/log-buffer.js +0 -103
- package/src/quality/metrics-collector.js +0 -281
- package/src/quality/test-runner.js +0 -366
- package/src/tasks/executor.js +0 -195
- package/src/tasks/index.js +0 -4
- package/src/tasks/loader.js +0 -210
- package/src/tasks/router.js +0 -182
- package/src/utils/event-bus.js +0 -126
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* app/dashboard/page.tsx
|
|
3
|
+
* SaaS Dashboard Preset -- D_interaction phase (composed sample page)
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* ---------------
|
|
7
|
+
* A preset with only individual components is hard to evaluate. Teams adopting
|
|
8
|
+
* a scaffold need to see the components working together in a real layout:
|
|
9
|
+
* how tokens compose, how the grid breathes, how the type scale creates
|
|
10
|
+
* hierarchy across sections, and how the motion system feels at dashboard
|
|
11
|
+
* density.
|
|
12
|
+
*
|
|
13
|
+
* This file is the "show your work" page -- the proof that every design
|
|
14
|
+
* decision in the spec resolves coherently at the page level. It is ALSO
|
|
15
|
+
* the first thing a new developer opens, so it doubles as the definitive
|
|
16
|
+
* usage example for every component in the preset.
|
|
17
|
+
*
|
|
18
|
+
* LAYOUT ARCHITECTURE
|
|
19
|
+
* -------------------
|
|
20
|
+
* Section 1: StatCardGrid -- 4 KPI cards across, auto-fill responsive.
|
|
21
|
+
* Section 2: 2-column asymmetric grid -- Chart (wider) + secondary
|
|
22
|
+
* metric card (narrower). The asymmetry creates visual weight:
|
|
23
|
+
* the chart is the primary insight, the secondary card is context.
|
|
24
|
+
* Section 3: Full-width DataTable with realistic sample columns and
|
|
25
|
+
* pagination. Demonstrates sort indicators, status badges, and
|
|
26
|
+
* the empty state pattern.
|
|
27
|
+
*
|
|
28
|
+
* SPACING RATIONALE
|
|
29
|
+
* -----------------
|
|
30
|
+
* Sections are separated by --spacing-section (32px). Within sections,
|
|
31
|
+
* components are separated by --spacing-card-gap (20px). This two-level
|
|
32
|
+
* rhythm creates a clear hierarchy: sections are distinct regions,
|
|
33
|
+
* components within a section are related.
|
|
34
|
+
*
|
|
35
|
+
* The page wrapper uses padding-top to account for the sticky topbar
|
|
36
|
+
* (--spacing-topbar-h = 48px). This is set in AppShell's main scroll area,
|
|
37
|
+
* not here -- but it is documented here because the visual consequence
|
|
38
|
+
* (content not hiding behind topbar) is visible at this level.
|
|
39
|
+
*
|
|
40
|
+
* DATA
|
|
41
|
+
* ----
|
|
42
|
+
* All data is static TypeScript constants defined at the bottom of this
|
|
43
|
+
* file. No API calls, no SWR/React Query, no suspense boundaries for
|
|
44
|
+
* the sake of this demo. Replace with your data fetching layer.
|
|
45
|
+
*
|
|
46
|
+
* TOKEN USAGE
|
|
47
|
+
* -----------
|
|
48
|
+
* All spacing via CSS custom properties. No raw px values in JSX styles.
|
|
49
|
+
* Colors via semantic token layer (--color-text-*, --color-bg-*, etc.).
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
'use client';
|
|
53
|
+
|
|
54
|
+
import { Container } from '@/components/shell/Container';
|
|
55
|
+
import { StatCard } from '@/components/data/StatCard';
|
|
56
|
+
import { StatCardGrid } from '@/components/data/StatCardGrid';
|
|
57
|
+
import { Chart } from '@/components/data/Chart';
|
|
58
|
+
import { DataTable } from '@/components/data/DataTable';
|
|
59
|
+
import { EmptyState } from '@/components/feedback/EmptyState';
|
|
60
|
+
import { Badge } from '@/components/primitives/Badge';
|
|
61
|
+
import { Button } from '@/components/primitives/Button';
|
|
62
|
+
import {
|
|
63
|
+
DollarSign,
|
|
64
|
+
Users,
|
|
65
|
+
TrendingUp,
|
|
66
|
+
Activity,
|
|
67
|
+
FileText,
|
|
68
|
+
Plus,
|
|
69
|
+
} from 'lucide-react';
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Mock data
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const revenueSparkline = [12, 18, 14, 22, 19, 27, 24, 31, 28, 35, 33, 42];
|
|
76
|
+
const userSparkline = [340, 382, 360, 410, 395, 440, 420, 475, 460, 510, 490, 540];
|
|
77
|
+
const conversionSparkline = [3.1, 3.4, 3.2, 3.7, 3.5, 3.9, 3.8, 4.1, 3.9, 4.3, 4.2, 4.6];
|
|
78
|
+
const mrrSparkline = [8200, 8600, 8400, 9100, 8900, 9600, 9400, 10200, 9900, 10800, 10500, 11200];
|
|
79
|
+
|
|
80
|
+
const areaChartData = [
|
|
81
|
+
{ month: 'Jan', revenue: 12400, users: 340 },
|
|
82
|
+
{ month: 'Feb', revenue: 15200, users: 382 },
|
|
83
|
+
{ month: 'Mar', revenue: 13800, users: 360 },
|
|
84
|
+
{ month: 'Apr', revenue: 18100, users: 410 },
|
|
85
|
+
{ month: 'May', revenue: 16900, users: 395 },
|
|
86
|
+
{ month: 'Jun', revenue: 21400, users: 440 },
|
|
87
|
+
{ month: 'Jul', revenue: 19800, users: 420 },
|
|
88
|
+
{ month: 'Aug', revenue: 24200, users: 475 },
|
|
89
|
+
{ month: 'Sep', revenue: 22100, users: 460 },
|
|
90
|
+
{ month: 'Oct', revenue: 27800, users: 510 },
|
|
91
|
+
{ month: 'Nov', revenue: 25400, users: 490 },
|
|
92
|
+
{ month: 'Dec', revenue: 31600, users: 540 },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const chartSeries = [
|
|
96
|
+
{ key: 'revenue', label: 'Revenue', color: 'var(--color-viz-1)' },
|
|
97
|
+
{ key: 'users', label: 'Active Users', color: 'var(--color-viz-2)' },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
export type TransactionRow = {
|
|
101
|
+
id: string;
|
|
102
|
+
customer: string;
|
|
103
|
+
email: string;
|
|
104
|
+
amount: number;
|
|
105
|
+
status: 'completed' | 'pending' | 'failed';
|
|
106
|
+
date: string;
|
|
107
|
+
plan: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const transactions: TransactionRow[] = [
|
|
111
|
+
{ id: 'TXN-001', customer: 'Aria Nakamura', email: 'aria@synth.io', amount: 299, status: 'completed', date: '2026-06-07', plan: 'Pro' },
|
|
112
|
+
{ id: 'TXN-002', customer: 'Luca Ferreira', email: 'luca@vela.co', amount: 99, status: 'completed', date: '2026-06-07', plan: 'Starter' },
|
|
113
|
+
{ id: 'TXN-003', customer: 'Mira Osei', email: 'mira@datavault.com', amount: 899, status: 'pending', date: '2026-06-06', plan: 'Enterprise' },
|
|
114
|
+
{ id: 'TXN-004', customer: 'Dev Patel', email: 'dev@cortex.ai', amount: 299, status: 'completed', date: '2026-06-06', plan: 'Pro' },
|
|
115
|
+
{ id: 'TXN-005', customer: 'Soren Lindqvist', email: 'soren@nordic.se', amount: 99, status: 'failed', date: '2026-06-05', plan: 'Starter' },
|
|
116
|
+
{ id: 'TXN-006', customer: 'Yuki Tanaka', email: 'yuki@flux.jp', amount: 299, status: 'completed', date: '2026-06-05', plan: 'Pro' },
|
|
117
|
+
{ id: 'TXN-007', customer: 'Chiara Russo', email: 'chiara@bloom.it', amount: 899, status: 'completed', date: '2026-06-04', plan: 'Enterprise' },
|
|
118
|
+
{ id: 'TXN-008', customer: 'Omar Hassan', email: 'omar@pulse.ae', amount: 99, status: 'pending', date: '2026-06-04', plan: 'Starter' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const tableColumns = [
|
|
122
|
+
{
|
|
123
|
+
key: 'customer' as const,
|
|
124
|
+
header: 'Customer',
|
|
125
|
+
render: (row: TransactionRow) => (
|
|
126
|
+
<div>
|
|
127
|
+
<div
|
|
128
|
+
style={{
|
|
129
|
+
fontSize: 'var(--type-small)',
|
|
130
|
+
fontWeight: 500,
|
|
131
|
+
color: 'var(--color-text-primary)',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{row.customer}
|
|
135
|
+
</div>
|
|
136
|
+
<div
|
|
137
|
+
style={{
|
|
138
|
+
fontSize: 'var(--type-caption)',
|
|
139
|
+
color: 'var(--color-text-muted)',
|
|
140
|
+
marginTop: '2px',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{row.email}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: 'plan' as const,
|
|
150
|
+
header: 'Plan',
|
|
151
|
+
render: (row: TransactionRow) => (
|
|
152
|
+
<Badge
|
|
153
|
+
variant={
|
|
154
|
+
row.plan === 'Enterprise'
|
|
155
|
+
? 'accent'
|
|
156
|
+
: row.plan === 'Pro'
|
|
157
|
+
? 'info'
|
|
158
|
+
: 'default'
|
|
159
|
+
}
|
|
160
|
+
size="sm"
|
|
161
|
+
>
|
|
162
|
+
{row.plan}
|
|
163
|
+
</Badge>
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: 'amount' as const,
|
|
168
|
+
header: 'Amount',
|
|
169
|
+
render: (row: TransactionRow) => (
|
|
170
|
+
<span
|
|
171
|
+
style={{
|
|
172
|
+
fontFamily: 'var(--font-mono)',
|
|
173
|
+
fontSize: 'var(--type-mono)',
|
|
174
|
+
color: 'var(--color-text-primary)',
|
|
175
|
+
fontWeight: 500,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
${row.amount.toLocaleString()}
|
|
179
|
+
</span>
|
|
180
|
+
),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: 'status' as const,
|
|
184
|
+
header: 'Status',
|
|
185
|
+
render: (row: TransactionRow) => (
|
|
186
|
+
<Badge
|
|
187
|
+
variant={
|
|
188
|
+
row.status === 'completed'
|
|
189
|
+
? 'success'
|
|
190
|
+
: row.status === 'pending'
|
|
191
|
+
? 'warning'
|
|
192
|
+
: 'danger'
|
|
193
|
+
}
|
|
194
|
+
dot
|
|
195
|
+
size="sm"
|
|
196
|
+
>
|
|
197
|
+
{row.status.charAt(0).toUpperCase() + row.status.slice(1)}
|
|
198
|
+
</Badge>
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: 'date' as const,
|
|
203
|
+
header: 'Date',
|
|
204
|
+
render: (row: TransactionRow) => (
|
|
205
|
+
<span
|
|
206
|
+
style={{
|
|
207
|
+
fontSize: 'var(--type-caption)',
|
|
208
|
+
color: 'var(--color-text-muted)',
|
|
209
|
+
fontFamily: 'var(--font-mono)',
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{row.date}
|
|
213
|
+
</span>
|
|
214
|
+
),
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Page
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
export default function DashboardPage() {
|
|
223
|
+
return (
|
|
224
|
+
<Container>
|
|
225
|
+
{/* Page header */}
|
|
226
|
+
<header
|
|
227
|
+
style={{
|
|
228
|
+
paddingTop: 'var(--spacing-section)',
|
|
229
|
+
paddingBottom: 'var(--spacing-section)',
|
|
230
|
+
borderBottom: '1px solid var(--color-border-default)',
|
|
231
|
+
marginBottom: 'var(--spacing-section)',
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
<div
|
|
235
|
+
style={{
|
|
236
|
+
display: 'flex',
|
|
237
|
+
alignItems: 'flex-start',
|
|
238
|
+
justifyContent: 'space-between',
|
|
239
|
+
gap: 'var(--spacing-card-gap)',
|
|
240
|
+
flexWrap: 'wrap',
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
<div>
|
|
244
|
+
<h1
|
|
245
|
+
style={{
|
|
246
|
+
fontSize: 'var(--type-h1)',
|
|
247
|
+
lineHeight: 'var(--type-h1-lh)',
|
|
248
|
+
letterSpacing: 'var(--type-h1-ls)',
|
|
249
|
+
fontWeight: 700,
|
|
250
|
+
color: 'var(--color-text-primary)',
|
|
251
|
+
margin: 0,
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
Dashboard
|
|
255
|
+
</h1>
|
|
256
|
+
<p
|
|
257
|
+
style={{
|
|
258
|
+
fontSize: 'var(--type-small)',
|
|
259
|
+
color: 'var(--color-text-secondary)',
|
|
260
|
+
margin: 'var(--space-2) 0 0',
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
Welcome back. Here is what is happening with your product.
|
|
264
|
+
</p>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
{/* Header actions */}
|
|
268
|
+
<div style={{ display: 'flex', gap: 'var(--space-3)', alignItems: 'center' }}>
|
|
269
|
+
<Button variant="secondary" iconLeft={<FileText size={16} aria-hidden="true" />}>
|
|
270
|
+
Export report
|
|
271
|
+
</Button>
|
|
272
|
+
<Button variant="primary" iconLeft={<Plus size={16} aria-hidden="true" />}>
|
|
273
|
+
New transaction
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</header>
|
|
278
|
+
|
|
279
|
+
{/* Section 1: KPI stat cards */}
|
|
280
|
+
<section aria-label="Key performance indicators">
|
|
281
|
+
<StatCardGrid>
|
|
282
|
+
<StatCard
|
|
283
|
+
label="Total Revenue"
|
|
284
|
+
value="$142,840"
|
|
285
|
+
delta="+11.2%"
|
|
286
|
+
previousValue="$128,400"
|
|
287
|
+
unit="USD"
|
|
288
|
+
icon={<DollarSign size={20} aria-hidden="true" />}
|
|
289
|
+
trend="up"
|
|
290
|
+
sparklineData={revenueSparkline}
|
|
291
|
+
/>
|
|
292
|
+
<StatCard
|
|
293
|
+
label="Active Users"
|
|
294
|
+
value="5,420"
|
|
295
|
+
delta="+10.8%"
|
|
296
|
+
previousValue="4,890"
|
|
297
|
+
unit="users"
|
|
298
|
+
icon={<Users size={20} aria-hidden="true" />}
|
|
299
|
+
trend="up"
|
|
300
|
+
sparklineData={userSparkline}
|
|
301
|
+
/>
|
|
302
|
+
<StatCard
|
|
303
|
+
label="Conversion Rate"
|
|
304
|
+
value="4.6%"
|
|
305
|
+
delta="+0.5pp"
|
|
306
|
+
previousValue="4.1%"
|
|
307
|
+
unit="percent"
|
|
308
|
+
icon={<TrendingUp size={20} aria-hidden="true" />}
|
|
309
|
+
trend="up"
|
|
310
|
+
sparklineData={conversionSparkline}
|
|
311
|
+
/>
|
|
312
|
+
<StatCard
|
|
313
|
+
label="Monthly Recurring Revenue"
|
|
314
|
+
value="$11,200"
|
|
315
|
+
delta="+6.7%"
|
|
316
|
+
previousValue="$10,500"
|
|
317
|
+
unit="MRR"
|
|
318
|
+
icon={<Activity size={20} aria-hidden="true" />}
|
|
319
|
+
trend="up"
|
|
320
|
+
sparklineData={mrrSparkline}
|
|
321
|
+
/>
|
|
322
|
+
</StatCardGrid>
|
|
323
|
+
</section>
|
|
324
|
+
|
|
325
|
+
{/* Section 2: Chart + secondary metric (asymmetric 2-col grid) */}
|
|
326
|
+
<section
|
|
327
|
+
aria-label="Revenue and users trend"
|
|
328
|
+
style={{ marginTop: 'var(--spacing-section)' }}
|
|
329
|
+
>
|
|
330
|
+
<div
|
|
331
|
+
style={{
|
|
332
|
+
display: 'grid',
|
|
333
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 340px), 1fr))',
|
|
334
|
+
gap: 'var(--spacing-card-gap)',
|
|
335
|
+
alignItems: 'start',
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
{/* Area chart -- primary insight, wider column */}
|
|
339
|
+
<div
|
|
340
|
+
style={{
|
|
341
|
+
gridColumn: 'span 2',
|
|
342
|
+
// On wide viewports this naturally spans 2 of 3 grid columns
|
|
343
|
+
// On narrow viewports (single column) it fills full width
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<Chart
|
|
347
|
+
data={areaChartData}
|
|
348
|
+
series={chartSeries}
|
|
349
|
+
xKey="month"
|
|
350
|
+
ariaLabel="Revenue and subscriptions over the last 12 months"
|
|
351
|
+
formatX={(v: unknown) => String(v)}
|
|
352
|
+
formatY={(v: number) =>
|
|
353
|
+
v > 999 ? `$${(v / 1000).toFixed(0)}k` : String(v)
|
|
354
|
+
}
|
|
355
|
+
height={280}
|
|
356
|
+
/>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
{/* Secondary metric card -- narrower, contextual */}
|
|
360
|
+
<div
|
|
361
|
+
style={{
|
|
362
|
+
backgroundColor: 'var(--color-bg-raised)',
|
|
363
|
+
border: '1px solid var(--color-border-default)',
|
|
364
|
+
borderRadius: 'var(--radius-lg)',
|
|
365
|
+
padding: 'var(--spacing-card-pad)',
|
|
366
|
+
boxShadow: 'var(--shadow-1)',
|
|
367
|
+
}}
|
|
368
|
+
>
|
|
369
|
+
<h2
|
|
370
|
+
style={{
|
|
371
|
+
fontSize: 'var(--type-h4)',
|
|
372
|
+
lineHeight: 'var(--type-h4-lh)',
|
|
373
|
+
letterSpacing: 'var(--type-h4-ls)',
|
|
374
|
+
fontWeight: 600,
|
|
375
|
+
color: 'var(--color-text-primary)',
|
|
376
|
+
margin: '0 0 var(--space-5)',
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
Plan breakdown
|
|
380
|
+
</h2>
|
|
381
|
+
|
|
382
|
+
<ul
|
|
383
|
+
style={{
|
|
384
|
+
listStyle: 'none',
|
|
385
|
+
margin: 0,
|
|
386
|
+
padding: 0,
|
|
387
|
+
display: 'flex',
|
|
388
|
+
flexDirection: 'column',
|
|
389
|
+
gap: 'var(--space-4)',
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{[
|
|
393
|
+
{ label: 'Enterprise', count: 12, pct: 18, color: 'var(--color-viz-1)' },
|
|
394
|
+
{ label: 'Pro', count: 38, pct: 56, color: 'var(--color-viz-2)' },
|
|
395
|
+
{ label: 'Starter', count: 18, pct: 26, color: 'var(--color-viz-3)' },
|
|
396
|
+
].map((plan) => (
|
|
397
|
+
<li key={plan.label}>
|
|
398
|
+
<div
|
|
399
|
+
style={{
|
|
400
|
+
display: 'flex',
|
|
401
|
+
justifyContent: 'space-between',
|
|
402
|
+
marginBottom: 'var(--space-1)',
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
<span
|
|
406
|
+
style={{
|
|
407
|
+
fontSize: 'var(--type-small)',
|
|
408
|
+
color: 'var(--color-text-secondary)',
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
411
|
+
{plan.label}
|
|
412
|
+
</span>
|
|
413
|
+
<span
|
|
414
|
+
style={{
|
|
415
|
+
fontSize: 'var(--type-caption)',
|
|
416
|
+
fontFamily: 'var(--font-mono)',
|
|
417
|
+
color: 'var(--color-text-muted)',
|
|
418
|
+
}}
|
|
419
|
+
>
|
|
420
|
+
{plan.count} ({plan.pct}%)
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
{/* Progress bar -- animates only transform, not width */}
|
|
424
|
+
<div
|
|
425
|
+
style={{
|
|
426
|
+
height: '6px',
|
|
427
|
+
borderRadius: 'var(--radius-pill)',
|
|
428
|
+
backgroundColor: 'var(--color-bg-sunken)',
|
|
429
|
+
overflow: 'hidden',
|
|
430
|
+
}}
|
|
431
|
+
role="presentation"
|
|
432
|
+
aria-hidden="true"
|
|
433
|
+
>
|
|
434
|
+
<div
|
|
435
|
+
style={{
|
|
436
|
+
height: '100%',
|
|
437
|
+
width: '100%',
|
|
438
|
+
borderRadius: 'var(--radius-pill)',
|
|
439
|
+
backgroundColor: plan.color,
|
|
440
|
+
transformOrigin: 'left center',
|
|
441
|
+
transform: `scaleX(${plan.pct / 100})`,
|
|
442
|
+
transition: `transform var(--duration-layout) var(--ease-out)`,
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
</div>
|
|
446
|
+
</li>
|
|
447
|
+
))}
|
|
448
|
+
</ul>
|
|
449
|
+
|
|
450
|
+
<div
|
|
451
|
+
style={{
|
|
452
|
+
marginTop: 'var(--space-6)',
|
|
453
|
+
paddingTop: 'var(--space-5)',
|
|
454
|
+
borderTop: '1px solid var(--color-border-default)',
|
|
455
|
+
display: 'flex',
|
|
456
|
+
justifyContent: 'space-between',
|
|
457
|
+
alignItems: 'center',
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
<span
|
|
461
|
+
style={{
|
|
462
|
+
fontSize: 'var(--type-caption)',
|
|
463
|
+
color: 'var(--color-text-muted)',
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
Total customers
|
|
467
|
+
</span>
|
|
468
|
+
<span
|
|
469
|
+
style={{
|
|
470
|
+
fontSize: 'var(--type-h3)',
|
|
471
|
+
lineHeight: 'var(--type-h3-lh)',
|
|
472
|
+
fontWeight: 700,
|
|
473
|
+
color: 'var(--color-text-primary)',
|
|
474
|
+
fontFamily: 'var(--font-mono)',
|
|
475
|
+
}}
|
|
476
|
+
>
|
|
477
|
+
68
|
|
478
|
+
</span>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</section>
|
|
483
|
+
|
|
484
|
+
{/* Section 3: DataTable */}
|
|
485
|
+
<section
|
|
486
|
+
aria-label="Recent transactions"
|
|
487
|
+
style={{
|
|
488
|
+
marginTop: 'var(--spacing-section)',
|
|
489
|
+
paddingBottom: 'var(--spacing-section)',
|
|
490
|
+
}}
|
|
491
|
+
>
|
|
492
|
+
<div
|
|
493
|
+
style={{
|
|
494
|
+
marginBottom: 'var(--space-5)',
|
|
495
|
+
display: 'flex',
|
|
496
|
+
alignItems: 'center',
|
|
497
|
+
justifyContent: 'space-between',
|
|
498
|
+
gap: 'var(--space-4)',
|
|
499
|
+
}}
|
|
500
|
+
>
|
|
501
|
+
<div>
|
|
502
|
+
<h2
|
|
503
|
+
style={{
|
|
504
|
+
fontSize: 'var(--type-h3)',
|
|
505
|
+
lineHeight: 'var(--type-h3-lh)',
|
|
506
|
+
letterSpacing: 'var(--type-h3-ls)',
|
|
507
|
+
fontWeight: 600,
|
|
508
|
+
color: 'var(--color-text-primary)',
|
|
509
|
+
margin: 0,
|
|
510
|
+
}}
|
|
511
|
+
>
|
|
512
|
+
Recent transactions
|
|
513
|
+
</h2>
|
|
514
|
+
<p
|
|
515
|
+
style={{
|
|
516
|
+
fontSize: 'var(--type-caption)',
|
|
517
|
+
color: 'var(--color-text-muted)',
|
|
518
|
+
margin: 'var(--space-1) 0 0',
|
|
519
|
+
}}
|
|
520
|
+
>
|
|
521
|
+
{transactions.length} transactions this week
|
|
522
|
+
</p>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
|
|
526
|
+
<DataTable
|
|
527
|
+
caption="Recent transactions"
|
|
528
|
+
columns={tableColumns}
|
|
529
|
+
data={transactions}
|
|
530
|
+
emptyState={
|
|
531
|
+
<EmptyState
|
|
532
|
+
icon={<FileText size={28} aria-hidden="true" />}
|
|
533
|
+
title="No transactions yet"
|
|
534
|
+
description="When customers complete purchases, they will appear here."
|
|
535
|
+
primaryAction={{
|
|
536
|
+
label: 'Create test transaction',
|
|
537
|
+
onClick: () => {},
|
|
538
|
+
icon: <Plus size={16} aria-hidden="true" />,
|
|
539
|
+
}}
|
|
540
|
+
/>
|
|
541
|
+
}
|
|
542
|
+
/>
|
|
543
|
+
</section>
|
|
544
|
+
</Container>
|
|
545
|
+
);
|
|
546
|
+
}
|