@atlashub/smartstack-cli 2.5.3 → 2.6.1
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/.documentation/business-analyse.html +4 -4
- package/.documentation/commands.html +2 -2
- package/.documentation/index.html +2 -2
- package/.documentation/js/app.js +2 -2
- package/dist/index.js +163 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/component.tsx.hbs +14 -14
- package/templates/mcp-scaffolding/controller.cs.hbs +6 -5
- package/templates/skills/_resources/docs-manifest-schema.md +3 -3
- package/templates/skills/_resources/mcp-validate-documentation-spec.md +6 -6
- package/templates/skills/apex/steps/step-04b-doc-sync.md +4 -4
- package/templates/skills/apex/steps/step-05-examine.md +1 -1
- package/templates/skills/apex/templates/04b-doc-sync.md +1 -1
- package/templates/skills/application/SKILL.md +33 -16
- package/templates/skills/application/steps/step-00-init.md +86 -3
- package/templates/skills/application/steps/step-01-navigation.md +34 -0
- package/templates/skills/application/steps/step-02-permissions.md +37 -0
- package/templates/skills/application/steps/step-03-roles.md +23 -2
- package/templates/skills/application/steps/step-03b-provider.md +251 -0
- package/templates/skills/application/steps/step-04-backend.md +75 -0
- package/templates/skills/application/steps/step-05-frontend.md +149 -10
- package/templates/skills/application/steps/step-06-migration.md +27 -15
- package/templates/skills/application/steps/step-07-tests.md +404 -0
- package/templates/skills/application/steps/step-08-documentation.md +137 -0
- package/templates/skills/application/templates-frontend.md +133 -26
- package/templates/skills/application/templates-seed.md +116 -0
- package/templates/skills/business-analyse/SKILL.md +1 -1
- package/templates/skills/business-analyse/questionnaire/07-ui.md +15 -0
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +2 -2
- package/templates/skills/business-analyse/schemas/feature-schema.json +96 -7
- package/templates/skills/business-analyse/steps/step-03-specify.md +134 -5
- package/templates/skills/business-analyse/steps/step-05-handoff.md +61 -8
- package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
- package/templates/skills/business-analyse/templates-frd.md +8 -8
- package/templates/skills/business-analyse/templates-react.md +26 -26
- package/templates/skills/documentation/SKILL.md +6 -6
- package/templates/skills/documentation/data-schema.md +70 -44
- package/templates/skills/documentation/templates.md +6 -6
- package/templates/skills/ralph-loop/SKILL.md +1 -2
- package/templates/skills/ralph-loop/steps/step-01-task.md +1 -1
- package/templates/skills/ui-components/SKILL.md +33 -2
- package/templates/skills/ui-components/patterns/dashboard-chart.md +327 -0
- package/templates/skills/ui-components/style-guide.md +27 -0
|
@@ -2,68 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
This file describes how to generate `
|
|
5
|
+
This file describes how to generate `doc-data.ts` files for the `DocRenderer` shared component.
|
|
6
6
|
Instead of generating full TSX components (~250 lines) per module, generate only the data (~50 lines).
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## DocData Interface Reference
|
|
9
9
|
|
|
10
10
|
The full TypeScript interface is in `web/smartstack-web/src/components/docs/types.ts`.
|
|
11
11
|
|
|
12
12
|
## Data Extraction Mapping
|
|
13
13
|
|
|
14
|
-
### From
|
|
14
|
+
### From feature.json (single source of truth)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| Section 1: Overview | `overview.objective` | Functional objective text |
|
|
19
|
-
| Section 1: Overview | `overview.problem` | Problem statement |
|
|
20
|
-
| Section 1: Overview | `overview.solution` | Solution statement |
|
|
21
|
-
| Section 2: Use Cases | `useCases[]` | UC-XXX entries (id, name, actor, description, permission, priority) |
|
|
22
|
-
| Section 3: Functional Requirements | `features[]` | FR-XXX entries grouped as features |
|
|
23
|
-
| Section 5: Permission Matrix | `permissions[]` | Role-permission entries |
|
|
24
|
-
| Section 6: Gherkin Scenarios | (used for test data, not doc) | - |
|
|
25
|
-
| Section 7: Validations | `businessRules[]` | BR-XXX from BRD cross-referenced |
|
|
16
|
+
The business analysis skill (`/business-analyse`) produces a `feature.json` file per module at:
|
|
17
|
+
`docs/business/{app}/{module}/business-analyse/v{X.Y}/feature.json`
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
All documentation data is extracted from this file.
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|-------------|---------------|------------|
|
|
31
|
-
| Section 2: Business Objectives | `overview.stats` | Count objectives |
|
|
32
|
-
| Section 3: Business Rules | `businessRules[]` | BR-XXX entries (id, name, category, statement) |
|
|
33
|
-
| Section 6: Integrations | `technicalRef` | Integration notes |
|
|
21
|
+
#### Specification section → DocData fields
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
| feature.json Path | DocData Field | Extraction |
|
|
24
|
+
|-------------------|---------------|------------|
|
|
25
|
+
| `specification.useCases[]` | `useCases[]` | UC-XXX entries (id, name, actor, description, permission, priority) |
|
|
26
|
+
| `specification.functionalRequirements[]` | `features[]` | FR-XXX entries grouped as features |
|
|
27
|
+
| `specification.permissionsMatrix` | `permissions[]` | Role-permission entries |
|
|
28
|
+
| `specification.apiEndpoints[]` | `apiEndpoints[]` | Endpoint entries (method, path, handler, permission) |
|
|
29
|
+
| `specification.wireframes[]` | `steps[]` | Inferred from wireframe descriptions |
|
|
30
|
+
| `specification.gherkinScenarios` | (used for test data, not doc) | - |
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
|
32
|
+
#### Analysis section → DocData fields
|
|
33
|
+
|
|
34
|
+
| feature.json Path | DocData Field | Extraction |
|
|
35
|
+
|-------------------|---------------|------------|
|
|
36
|
+
| `analysis.businessRules[]` | `businessRules[]` | BR-XXX entries (id, name, category, statement) |
|
|
37
|
+
| `analysis.objectives[]` | `overview.stats` | Count objectives |
|
|
38
|
+
| `analysis.entities[]` | `technicalRef.entityNames` | Entity names from analysis |
|
|
39
|
+
| `analysis.integrations[]` | `technicalRef` | Integration notes |
|
|
40
|
+
|
|
41
|
+
#### Discovery section → DocData fields
|
|
42
|
+
|
|
43
|
+
| feature.json Path | DocData Field | Extraction |
|
|
44
|
+
|-------------------|---------------|------------|
|
|
45
|
+
| `discovery.problem` | `overview.problem` | Problem statement |
|
|
46
|
+
| `discovery.scope.inScope` | `overview.objective` | Functional objective text |
|
|
47
|
+
| `discovery.scope.outOfScope` | `overview.solution` | Solution statement (inferred) |
|
|
48
|
+
| `discovery.risks[]` | (optional) | Risk documentation |
|
|
49
|
+
|
|
50
|
+
#### Handoff section → DocData fields
|
|
51
|
+
|
|
52
|
+
| feature.json Path | DocData Field | Extraction |
|
|
53
|
+
|-------------------|---------------|------------|
|
|
54
|
+
| `handoff.filesToCreate.domain` | `technicalRef.entityNames` | Entity names |
|
|
55
|
+
| `handoff.apiEndpointSummary[]` | `apiEndpoints[]` | Endpoint entries (cross-validate) |
|
|
56
|
+
| `handoff.brToCodeMapping[]` | `technicalRef` | BR-to-code mapping |
|
|
43
57
|
|
|
44
58
|
### Generated/Inferred Fields
|
|
45
59
|
|
|
46
60
|
| Field | Source |
|
|
47
61
|
|-------|--------|
|
|
48
|
-
| `featureId` |
|
|
49
|
-
| `moduleName` |
|
|
50
|
-
| `applicationName` |
|
|
51
|
-
| `version` |
|
|
52
|
-
| `benefits[]` | Inferred from
|
|
62
|
+
| `featureId` | `metadata.featureId` from feature.json |
|
|
63
|
+
| `moduleName` | `metadata.moduleName` from feature.json |
|
|
64
|
+
| `applicationName` | `metadata.applicationName` from feature.json |
|
|
65
|
+
| `version` | `metadata.version` from feature.json |
|
|
66
|
+
| `benefits[]` | Inferred from analysis objectives or i18n |
|
|
53
67
|
| `beforeAfter` | Inferred from discovery problem/solution |
|
|
54
68
|
| `steps[]` | Inferred from use cases main scenario |
|
|
55
69
|
| `faq[]` | Inferred from discovery open questions |
|
|
56
70
|
| `screenshots` | Convention: `/assets/docs/{module}/step-{N}.png` |
|
|
57
71
|
|
|
72
|
+
## Fallback: No feature.json Available
|
|
73
|
+
|
|
74
|
+
If no feature.json exists (e.g., module was created without `/business-analyse`),
|
|
75
|
+
the `/documentation` skill extracts data directly from code:
|
|
76
|
+
|
|
77
|
+
| Source | DocData Field | Extraction |
|
|
78
|
+
|--------|---------------|------------|
|
|
79
|
+
| Controller `[Http*]` attributes | `apiEndpoints[]` | Method, path, permission |
|
|
80
|
+
| `Permissions.cs` constants | `permissions[]` | Permission paths |
|
|
81
|
+
| `Domain/Entities/*.cs` | `technicalRef.entityNames` | Entity class names |
|
|
82
|
+
| i18n JSON files | `overview`, `features` | Existing translations |
|
|
83
|
+
|
|
58
84
|
## Generated File Structure
|
|
59
85
|
|
|
60
|
-
###
|
|
86
|
+
### doc-data.ts (per module, ~50-80 lines)
|
|
61
87
|
|
|
62
88
|
```typescript
|
|
63
|
-
// web/smartstack-web/src/pages/docs/business/{app}/{module}/
|
|
64
|
-
import type {
|
|
89
|
+
// web/smartstack-web/src/pages/docs/business/{app}/{module}/doc-data.ts
|
|
90
|
+
import type { DocData } from '@/components/docs';
|
|
65
91
|
|
|
66
|
-
export const
|
|
92
|
+
export const docData: DocData = {
|
|
67
93
|
featureId: 'FEAT-001',
|
|
68
94
|
moduleName: 'Sla',
|
|
69
95
|
applicationName: 'Support',
|
|
@@ -142,13 +168,13 @@ export const frdData: FrdData = {
|
|
|
142
168
|
|
|
143
169
|
```typescript
|
|
144
170
|
// web/smartstack-web/src/pages/docs/business/{app}/{module}/index.tsx
|
|
145
|
-
import {
|
|
146
|
-
import {
|
|
171
|
+
import { DocRenderer } from '@/components/docs';
|
|
172
|
+
import { docData } from './doc-data';
|
|
147
173
|
|
|
148
174
|
export default function {Module}DocPage() {
|
|
149
175
|
return (
|
|
150
|
-
<
|
|
151
|
-
data={
|
|
176
|
+
<DocRenderer
|
|
177
|
+
data={docData}
|
|
152
178
|
backPath="/docs/business/{app}"
|
|
153
179
|
backLabel="nav.backToApp"
|
|
154
180
|
/>
|
|
@@ -169,14 +195,14 @@ export default function {Module}DocPage() {
|
|
|
169
195
|
"solution": "Monitoring automatique des SLA avec alertes et escalades configurables"
|
|
170
196
|
},
|
|
171
197
|
"benefits": {
|
|
172
|
-
"time": { "title": "Temps
|
|
173
|
-
"quality": { "title": "
|
|
198
|
+
"time": { "title": "Temps economise", "description": "Reduction de 70% du temps de suivi manuel" },
|
|
199
|
+
"quality": { "title": "Fiabilite", "description": "95% des SLA respectes grace au monitoring" },
|
|
174
200
|
"roi": { "title": "ROI", "description": "Retour sur investissement en 3 mois" }
|
|
175
201
|
},
|
|
176
202
|
"faq": {
|
|
177
203
|
"1": {
|
|
178
204
|
"question": "Dois-je configurer chaque SLA manuellement ?",
|
|
179
|
-
"answer": "Non, des templates
|
|
205
|
+
"answer": "Non, des templates predefinis sont disponibles pour les cas courants."
|
|
180
206
|
}
|
|
181
207
|
}
|
|
182
208
|
}
|
|
@@ -184,7 +210,7 @@ export default function {Module}DocPage() {
|
|
|
184
210
|
}
|
|
185
211
|
```
|
|
186
212
|
|
|
187
|
-
> **Note:** Seul FR est
|
|
213
|
+
> **Note:** Seul FR est genere. EN/IT/DE sont marques comme "deferred" et generes par un pipeline de traduction separe.
|
|
188
214
|
|
|
189
215
|
## Screenshot Convention
|
|
190
216
|
|
|
@@ -195,4 +221,4 @@ export default function {Module}DocPage() {
|
|
|
195
221
|
| `dashboard` | `/assets/docs/{module}/dashboard.png` | Dashboard view |
|
|
196
222
|
|
|
197
223
|
Screenshots are added manually or via Playwright E2E tests.
|
|
198
|
-
The `
|
|
224
|
+
The `DocRenderer` shows a "Screenshot a venir" placeholder when the image file is not found.
|
|
@@ -25,20 +25,20 @@ Chaque documentation de module DOIT inclure ces sections dans cet ordre:
|
|
|
25
25
|
## Data-Driven Approach (Recommended)
|
|
26
26
|
|
|
27
27
|
> **Principe:** Au lieu de générer un composant TSX complet (~250 lignes) par module,
|
|
28
|
-
> générer uniquement un fichier de DONNÉES (`
|
|
29
|
-
> Le rendu est assuré par le composant partagé `
|
|
28
|
+
> générer uniquement un fichier de DONNÉES (`doc-data.ts`, ~50 lignes) + un wrapper minimal.
|
|
29
|
+
> Le rendu est assuré par le composant partagé `DocRenderer` dans `web/.../components/docs/`.
|
|
30
30
|
|
|
31
31
|
### Fichiers à générer par module
|
|
32
32
|
|
|
33
|
-
**1. Data file** (`
|
|
33
|
+
**1. Data file** (`doc-data.ts`) : Voir [data-schema.md](data-schema.md) pour le mapping complet.
|
|
34
34
|
|
|
35
35
|
**2. Page wrapper** (`index.tsx`, ~10 lignes) :
|
|
36
36
|
```tsx
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
37
|
+
import { DocRenderer } from '@/components/docs';
|
|
38
|
+
import { docData } from './doc-data';
|
|
39
39
|
|
|
40
40
|
export default function {ModuleName}DocPage() {
|
|
41
|
-
return <
|
|
41
|
+
return <DocRenderer data={docData} backPath="/docs/business/{app}" />;
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ SOURCE_PATH=${HANDOFF:-$BA_OUTPUT}
|
|
|
51
51
|
- Read the handoff document
|
|
52
52
|
- Derive tasks from its specifications (per-layer breakdown)
|
|
53
53
|
- Set `source.type = "ba-handoff"` and `source.handoff_path`
|
|
54
|
-
- Look for
|
|
54
|
+
- Look for feature.json in the same directory
|
|
55
55
|
|
|
56
56
|
**If no handoff:**
|
|
57
57
|
- Generate task breakdown from `{task_description}`
|
|
@@ -11,7 +11,9 @@ description: |
|
|
|
11
11
|
- User asks for tooltips or infobubbles
|
|
12
12
|
- Creating a page with entity display
|
|
13
13
|
- Managing disabled states with explanatory messages
|
|
14
|
-
|
|
14
|
+
- User mentions "dashboard", "chart", "kpi", "analytics", "graph"
|
|
15
|
+
- Creating a dashboard with KPI cards or charts
|
|
16
|
+
Scope: Pages, Components, Cards, Tables, Grids, Kanban boards, Tooltips, Dashboards, Charts
|
|
15
17
|
---
|
|
16
18
|
|
|
17
19
|
# Skill UI Components SmartStack
|
|
@@ -26,6 +28,7 @@ description: |
|
|
|
26
28
|
| List creation | "Display products in cards" |
|
|
27
29
|
| Keywords | "card", "grid", "table", "kanban", "tooltip" |
|
|
28
30
|
| Disabled states | "Disable button with explanatory message" |
|
|
31
|
+
| Dashboard/Chart creation | "dashboard", "chart", "kpi", "analytics", "graph", "metrics" |
|
|
29
32
|
|
|
30
33
|
## Detailed patterns
|
|
31
34
|
|
|
@@ -33,6 +36,7 @@ description: |
|
|
|
33
36
|
- For DataTable: [patterns/data-table.md](patterns/data-table.md)
|
|
34
37
|
- For Grid layouts: [patterns/grid-layout.md](patterns/grid-layout.md)
|
|
35
38
|
- For Kanban boards: [patterns/kanban.md](patterns/kanban.md)
|
|
39
|
+
- For Dashboard & Charts: [patterns/dashboard-chart.md](patterns/dashboard-chart.md)
|
|
36
40
|
- For styling rules: [style-guide.md](style-guide.md)
|
|
37
41
|
- For accessibility: [accessibility.md](accessibility.md)
|
|
38
42
|
|
|
@@ -91,9 +95,36 @@ import { EntityCard, ProviderCard, TemplateCard } from '@/components/ui/EntityCa
|
|
|
91
95
|
| EntityCard for entities | Custom cards with manual divs |
|
|
92
96
|
| Responsive grid 1->2->3->4 | Fixed non-responsive grid |
|
|
93
97
|
| h-full + flex-1 + mt-auto | Unaligned buttons in grid |
|
|
94
|
-
| Empty and
|
|
98
|
+
| Empty, loading, and error states | Native HTML tooltip |
|
|
95
99
|
| Explicit TypeScript interfaces | `any` or implicit types |
|
|
96
100
|
| `<button>` for click actions | `<div role="button">` |
|
|
97
101
|
| `memo()` for list item components | Unmemoized components in loops |
|
|
98
102
|
| `useCallback` for event handlers | Inline arrow functions in JSX |
|
|
99
103
|
| Unique `id` for list keys | Array index as key |
|
|
104
|
+
|
|
105
|
+
### Error States with Retry
|
|
106
|
+
|
|
107
|
+
**ALWAYS provide user-friendly error states with retry capability.**
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
{error && (
|
|
111
|
+
<div className="p-4 bg-[var(--error-bg)] border border-[var(--error-border)] rounded-[var(--radius-card)]">
|
|
112
|
+
<div className="flex items-center">
|
|
113
|
+
<AlertCircle className="h-5 w-5 text-[var(--error-text)]" />
|
|
114
|
+
<span className="ml-2 text-[var(--error-text)]">{error}</span>
|
|
115
|
+
</div>
|
|
116
|
+
<button
|
|
117
|
+
onClick={() => refetch()}
|
|
118
|
+
className="mt-2 text-sm text-[var(--error-text)] hover:opacity-80 underline"
|
|
119
|
+
>
|
|
120
|
+
{t('common:actions.retry')}
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Rules:**
|
|
127
|
+
- Use semantic error CSS variables (`--error-bg`, `--error-border`, `--error-text`)
|
|
128
|
+
- Always provide retry button for recoverable errors
|
|
129
|
+
- Use Lucide `AlertCircle` icon (not inline SVG)
|
|
130
|
+
- Keep error messages user-friendly (not technical stack traces)
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Dashboard & Chart Patterns
|
|
2
|
+
|
|
3
|
+
> **Library:** Recharts (standardized for SmartStack)
|
|
4
|
+
> **Install:** `npm install recharts` (add to package.json if absent)
|
|
5
|
+
> **Reference:** https://recharts.org/
|
|
6
|
+
|
|
7
|
+
## CRITICAL RULES
|
|
8
|
+
|
|
9
|
+
1. **NEVER hardcode chart colors** - Use CSS variables (`--chart-1` to `--chart-5`)
|
|
10
|
+
2. **NEVER hardcode text** - Use `useTranslation()` for all labels, tooltips, legends
|
|
11
|
+
3. **ALWAYS use ResponsiveContainer** - Charts must be responsive
|
|
12
|
+
4. **ALWAYS provide loading/error states** - Skeleton loaders for charts
|
|
13
|
+
5. **ALWAYS use CSS variables** for backgrounds, borders, text (see style-guide.md)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## KPI CARD (StatCard)
|
|
18
|
+
|
|
19
|
+
Use for single-value metrics with trend indicator.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { LucideIcon } from 'lucide-react';
|
|
23
|
+
|
|
24
|
+
interface StatCardProps {
|
|
25
|
+
label: string;
|
|
26
|
+
value: string | number;
|
|
27
|
+
icon: LucideIcon;
|
|
28
|
+
trend?: { value: number; direction: 'up' | 'down' | 'flat' };
|
|
29
|
+
format?: 'number' | 'currency' | 'percent' | 'duration';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function StatCard({ label, value, icon: Icon, trend, format = 'number' }: StatCardProps) {
|
|
33
|
+
const formattedValue = formatValue(value, format);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="rounded-[var(--radius-card)] bg-[var(--bg-secondary)] border border-[var(--border-color)] p-6">
|
|
37
|
+
<div className="flex items-center justify-between">
|
|
38
|
+
<span className="text-sm font-medium text-[var(--text-muted)]">{label}</span>
|
|
39
|
+
<Icon className="h-5 w-5 text-[var(--color-primary-500)]" />
|
|
40
|
+
</div>
|
|
41
|
+
<div className="mt-2 text-2xl font-bold text-[var(--text-primary)]">
|
|
42
|
+
{formattedValue}
|
|
43
|
+
</div>
|
|
44
|
+
{trend && (
|
|
45
|
+
<div className={`mt-1 flex items-center text-sm ${
|
|
46
|
+
trend.direction === 'up' ? 'text-[var(--success-text)]' :
|
|
47
|
+
trend.direction === 'down' ? 'text-[var(--error-text)]' :
|
|
48
|
+
'text-[var(--text-muted)]'
|
|
49
|
+
}`}>
|
|
50
|
+
{trend.direction === 'up' ? '↑' : trend.direction === 'down' ? '↓' : '→'}
|
|
51
|
+
<span className="ml-1">{Math.abs(trend.value)}%</span>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### KPI Card with Thresholds
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
function getThresholdColor(value: number, thresholds?: { warning: number; critical: number }) {
|
|
63
|
+
if (!thresholds) return 'text-[var(--text-primary)]';
|
|
64
|
+
if (value >= thresholds.critical) return 'text-[var(--error-text)]';
|
|
65
|
+
if (value >= thresholds.warning) return 'text-[var(--warning-text)]';
|
|
66
|
+
return 'text-[var(--success-text)]';
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## CHART WRAPPER (ChartCard)
|
|
73
|
+
|
|
74
|
+
Wraps any Recharts chart in a consistent card layout.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
interface ChartCardProps {
|
|
78
|
+
title: string;
|
|
79
|
+
subtitle?: string;
|
|
80
|
+
children: React.ReactNode;
|
|
81
|
+
height?: number;
|
|
82
|
+
loading?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ChartCard({ title, subtitle, children, height = 300, loading }: ChartCardProps) {
|
|
86
|
+
if (loading) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="rounded-[var(--radius-card)] bg-[var(--bg-secondary)] border border-[var(--border-color)] p-6">
|
|
89
|
+
<div className="h-4 w-1/3 bg-[var(--bg-tertiary)] rounded animate-pulse mb-4" />
|
|
90
|
+
<div className="bg-[var(--bg-tertiary)] rounded animate-pulse" style={{ height }} />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="rounded-[var(--radius-card)] bg-[var(--bg-secondary)] border border-[var(--border-color)] p-6">
|
|
97
|
+
<h3 className="text-base font-semibold text-[var(--text-primary)]">{title}</h3>
|
|
98
|
+
{subtitle && (
|
|
99
|
+
<p className="text-sm text-[var(--text-muted)] mt-1">{subtitle}</p>
|
|
100
|
+
)}
|
|
101
|
+
<div className="mt-4" style={{ height }}>
|
|
102
|
+
{children}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## CHART TYPES
|
|
112
|
+
|
|
113
|
+
### Shared Tooltip Style
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
const TOOLTIP_STYLE = {
|
|
117
|
+
contentStyle: {
|
|
118
|
+
backgroundColor: 'var(--bg-secondary)',
|
|
119
|
+
border: '1px solid var(--border-color)',
|
|
120
|
+
borderRadius: 'var(--radius-card)',
|
|
121
|
+
color: 'var(--text-primary)',
|
|
122
|
+
fontSize: '0.875rem',
|
|
123
|
+
},
|
|
124
|
+
labelStyle: {
|
|
125
|
+
color: 'var(--text-primary)',
|
|
126
|
+
fontWeight: 600,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Chart Color Palette
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
const CHART_COLORS = [
|
|
135
|
+
'var(--chart-1)',
|
|
136
|
+
'var(--chart-2)',
|
|
137
|
+
'var(--chart-3)',
|
|
138
|
+
'var(--chart-4)',
|
|
139
|
+
'var(--chart-5)',
|
|
140
|
+
];
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Bar Chart
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
|
|
147
|
+
|
|
148
|
+
<ChartCard title={t('charts.itemsByMonth')}>
|
|
149
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
150
|
+
<BarChart data={data}>
|
|
151
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-color)" />
|
|
152
|
+
<XAxis dataKey="name" stroke="var(--text-muted)" fontSize={12} />
|
|
153
|
+
<YAxis stroke="var(--text-muted)" fontSize={12} />
|
|
154
|
+
<Tooltip {...TOOLTIP_STYLE} />
|
|
155
|
+
<Bar dataKey="value" fill="var(--chart-1)" radius={[4, 4, 0, 0]} />
|
|
156
|
+
</BarChart>
|
|
157
|
+
</ResponsiveContainer>
|
|
158
|
+
</ChartCard>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Stacked Bar Chart
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
165
|
+
<BarChart data={data}>
|
|
166
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-color)" />
|
|
167
|
+
<XAxis dataKey="month" stroke="var(--text-muted)" fontSize={12} />
|
|
168
|
+
<YAxis stroke="var(--text-muted)" fontSize={12} />
|
|
169
|
+
<Tooltip {...TOOLTIP_STYLE} />
|
|
170
|
+
<Bar dataKey="active" stackId="a" fill="var(--chart-1)" />
|
|
171
|
+
<Bar dataKey="pending" stackId="a" fill="var(--chart-2)" />
|
|
172
|
+
<Bar dataKey="closed" stackId="a" fill="var(--chart-3)" radius={[4, 4, 0, 0]} />
|
|
173
|
+
</BarChart>
|
|
174
|
+
</ResponsiveContainer>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Line Chart
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
|
|
181
|
+
|
|
182
|
+
<ChartCard title={t('charts.trendOverTime')}>
|
|
183
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
184
|
+
<LineChart data={data}>
|
|
185
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-color)" />
|
|
186
|
+
<XAxis dataKey="date" stroke="var(--text-muted)" fontSize={12} />
|
|
187
|
+
<YAxis stroke="var(--text-muted)" fontSize={12} />
|
|
188
|
+
<Tooltip {...TOOLTIP_STYLE} />
|
|
189
|
+
<Line type="monotone" dataKey="value" stroke="var(--chart-1)" strokeWidth={2} dot={false} />
|
|
190
|
+
<Line type="monotone" dataKey="target" stroke="var(--chart-2)" strokeWidth={2} strokeDasharray="5 5" dot={false} />
|
|
191
|
+
</LineChart>
|
|
192
|
+
</ResponsiveContainer>
|
|
193
|
+
</ChartCard>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Pie / Donut Chart
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend } from 'recharts';
|
|
200
|
+
|
|
201
|
+
<ChartCard title={t('charts.distributionByStatus')}>
|
|
202
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
203
|
+
<PieChart>
|
|
204
|
+
<Pie
|
|
205
|
+
data={data}
|
|
206
|
+
cx="50%"
|
|
207
|
+
cy="50%"
|
|
208
|
+
innerRadius={60} // 0 for pie, >0 for donut
|
|
209
|
+
outerRadius={100}
|
|
210
|
+
dataKey="value"
|
|
211
|
+
nameKey="name"
|
|
212
|
+
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
|
213
|
+
>
|
|
214
|
+
{data.map((_, index) => (
|
|
215
|
+
<Cell key={index} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
|
216
|
+
))}
|
|
217
|
+
</Pie>
|
|
218
|
+
<Tooltip {...TOOLTIP_STYLE} />
|
|
219
|
+
<Legend />
|
|
220
|
+
</PieChart>
|
|
221
|
+
</ResponsiveContainer>
|
|
222
|
+
</ChartCard>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Area Chart
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
|
|
229
|
+
|
|
230
|
+
<ChartCard title={t('charts.volumeOverTime')}>
|
|
231
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
232
|
+
<AreaChart data={data}>
|
|
233
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-color)" />
|
|
234
|
+
<XAxis dataKey="date" stroke="var(--text-muted)" fontSize={12} />
|
|
235
|
+
<YAxis stroke="var(--text-muted)" fontSize={12} />
|
|
236
|
+
<Tooltip {...TOOLTIP_STYLE} />
|
|
237
|
+
<Area type="monotone" dataKey="value" stroke="var(--chart-1)" fill="var(--chart-1)" fillOpacity={0.2} />
|
|
238
|
+
</AreaChart>
|
|
239
|
+
</ResponsiveContainer>
|
|
240
|
+
</ChartCard>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## DASHBOARD LAYOUT
|
|
246
|
+
|
|
247
|
+
### Standard Dashboard Grid
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
interface DashboardLayoutProps {
|
|
251
|
+
kpis: StatCardProps[];
|
|
252
|
+
charts: ChartCardProps[];
|
|
253
|
+
filters?: React.ReactNode;
|
|
254
|
+
loading?: boolean;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function DashboardLayout({ kpis, charts, filters, loading }: DashboardLayoutProps) {
|
|
258
|
+
return (
|
|
259
|
+
<div className="space-y-6">
|
|
260
|
+
{/* Filters */}
|
|
261
|
+
{filters && (
|
|
262
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
263
|
+
{filters}
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
|
|
267
|
+
{/* KPI Row - 1 col mobile, 2 col tablet, 4 col desktop */}
|
|
268
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
269
|
+
{kpis.map((kpi, i) => (
|
|
270
|
+
<StatCard key={i} {...kpi} />
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{/* Charts Row - 1 col mobile, 2 col desktop */}
|
|
275
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
276
|
+
{charts.map((chart, i) => (
|
|
277
|
+
<ChartCard key={i} {...chart} loading={loading} />
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Period Filter
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { useTranslation } from 'react-i18next';
|
|
289
|
+
|
|
290
|
+
function PeriodFilter({ value, onChange }: { value: string; onChange: (v: string) => void }) {
|
|
291
|
+
const { t } = useTranslation();
|
|
292
|
+
const periods = [
|
|
293
|
+
{ value: 'day', label: t('common:periods.day') },
|
|
294
|
+
{ value: 'week', label: t('common:periods.week') },
|
|
295
|
+
{ value: 'month', label: t('common:periods.month') },
|
|
296
|
+
{ value: 'quarter', label: t('common:periods.quarter') },
|
|
297
|
+
{ value: 'year', label: t('common:periods.year') },
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<select
|
|
302
|
+
value={value}
|
|
303
|
+
onChange={(e) => onChange(e.target.value)}
|
|
304
|
+
className="rounded-[var(--radius-button)] bg-[var(--bg-secondary)] border border-[var(--border-color)] text-[var(--text-primary)] px-3 py-1.5 text-sm"
|
|
305
|
+
>
|
|
306
|
+
{periods.map(p => (
|
|
307
|
+
<option key={p.value} value={p.value}>{p.label}</option>
|
|
308
|
+
))}
|
|
309
|
+
</select>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## ABSOLUTE RULES
|
|
317
|
+
|
|
318
|
+
| DO | DON'T |
|
|
319
|
+
|----|-------|
|
|
320
|
+
| CSS variables for ALL chart colors (`--chart-1` to `--chart-5`) | Hardcoded hex colors (`fill="#8884d8"`) |
|
|
321
|
+
| CSS variables for backgrounds/borders/text | Hardcoded Tailwind (`bg-blue-500`) |
|
|
322
|
+
| `ResponsiveContainer width="100%" height="100%"` | Fixed pixel dimensions |
|
|
323
|
+
| `useTranslation()` for labels and tooltips | Hardcoded text strings |
|
|
324
|
+
| Skeleton loading states for charts | Blank space during loading |
|
|
325
|
+
| Separate `StatCard` for KPI values | Inline divs with numbers |
|
|
326
|
+
| `ChartCard` wrapper for consistent styling | Naked charts without card wrapper |
|
|
327
|
+
| `memo()` for chart components in lists | Re-rendering charts on every state change |
|
|
@@ -40,6 +40,33 @@ const STATUS_CONFIG = {
|
|
|
40
40
|
};
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
### Chart Colors
|
|
44
|
+
|
|
45
|
+
For data visualizations (Recharts), use dedicated chart CSS variables:
|
|
46
|
+
|
|
47
|
+
| Variable | Usage | Example |
|
|
48
|
+
|----------|-------|---------|
|
|
49
|
+
| `--chart-1` | Primary data series | Main bar/line/area |
|
|
50
|
+
| `--chart-2` | Secondary data series | Comparison line |
|
|
51
|
+
| `--chart-3` | Tertiary data series | Third category |
|
|
52
|
+
| `--chart-4` | Quaternary data series | Fourth category |
|
|
53
|
+
| `--chart-5` | Quinary data series | Fifth category |
|
|
54
|
+
|
|
55
|
+
These variables adapt automatically to light/dark theme.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// CORRECT - CSS variable
|
|
59
|
+
<Bar dataKey="value" fill="var(--chart-1)" />
|
|
60
|
+
<Line stroke="var(--chart-2)" />
|
|
61
|
+
<Cell fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
|
62
|
+
|
|
63
|
+
// WRONG - hardcoded colors
|
|
64
|
+
<Bar dataKey="value" fill="#8884d8" />
|
|
65
|
+
<Line stroke="blue" />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**See [patterns/dashboard-chart.md](patterns/dashboard-chart.md) for complete chart patterns.**
|
|
69
|
+
|
|
43
70
|
## BUTTON VARIANTS
|
|
44
71
|
|
|
45
72
|
### Action Buttons (Status changes)
|