@atlashub/smartstack-cli 1.11.0 → 1.13.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.
Files changed (74) hide show
  1. package/.documentation/agents.html +7 -2
  2. package/.documentation/apex.html +7 -2
  3. package/.documentation/business-analyse.html +7 -2
  4. package/.documentation/cli-commands.html +871 -0
  5. package/.documentation/commands.html +7 -2
  6. package/.documentation/efcore.html +7 -2
  7. package/.documentation/gitflow.html +7 -2
  8. package/.documentation/hooks.html +7 -2
  9. package/.documentation/index.html +7 -2
  10. package/.documentation/init.html +7 -2
  11. package/.documentation/installation.html +7 -2
  12. package/.documentation/ralph-loop.html +7 -2
  13. package/.documentation/test-web.html +7 -2
  14. package/dist/index.js +1932 -336
  15. package/dist/index.js.map +1 -1
  16. package/package.json +8 -2
  17. package/templates/agents/efcore/squash.md +67 -31
  18. package/templates/agents/gitflow/finish.md +68 -56
  19. package/templates/commands/business-analyse/0-orchestrate.md +72 -556
  20. package/templates/commands/business-analyse/1-init.md +23 -193
  21. package/templates/commands/business-analyse/2-discover.md +85 -462
  22. package/templates/commands/business-analyse/3-analyse.md +40 -342
  23. package/templates/commands/business-analyse/4-specify.md +72 -537
  24. package/templates/commands/business-analyse/5-validate.md +43 -237
  25. package/templates/commands/business-analyse/6-handoff.md +93 -682
  26. package/templates/commands/business-analyse/7-doc-html.md +45 -544
  27. package/templates/commands/business-analyse/_shared.md +176 -0
  28. package/templates/commands/business-analyse/bug.md +50 -257
  29. package/templates/commands/business-analyse/change-request.md +59 -283
  30. package/templates/commands/business-analyse/hotfix.md +36 -120
  31. package/templates/commands/business-analyse.md +55 -574
  32. package/templates/commands/efcore/_shared.md +206 -0
  33. package/templates/commands/efcore/conflicts.md +39 -201
  34. package/templates/commands/efcore/db-deploy.md +28 -237
  35. package/templates/commands/efcore/db-reset.md +41 -390
  36. package/templates/commands/efcore/db-seed.md +44 -323
  37. package/templates/commands/efcore/db-status.md +31 -210
  38. package/templates/commands/efcore/migration.md +45 -368
  39. package/templates/commands/efcore/rebase-snapshot.md +38 -241
  40. package/templates/commands/efcore/scan.md +35 -204
  41. package/templates/commands/efcore/squash.md +158 -251
  42. package/templates/commands/efcore.md +49 -177
  43. package/templates/commands/gitflow/1-init.md +94 -1318
  44. package/templates/commands/gitflow/10-start.md +86 -990
  45. package/templates/commands/gitflow/11-finish.md +264 -454
  46. package/templates/commands/gitflow/12-cleanup.md +40 -213
  47. package/templates/commands/gitflow/2-status.md +51 -386
  48. package/templates/commands/gitflow/3-commit.md +108 -801
  49. package/templates/commands/gitflow/4-plan.md +42 -13
  50. package/templates/commands/gitflow/5-exec.md +60 -5
  51. package/templates/commands/gitflow/6-abort.md +54 -277
  52. package/templates/commands/gitflow/7-pull-request.md +74 -717
  53. package/templates/commands/gitflow/8-review.md +51 -178
  54. package/templates/commands/gitflow/9-merge.md +74 -404
  55. package/templates/commands/gitflow/_shared.md +196 -0
  56. package/templates/commands/quickstart.md +154 -0
  57. package/templates/commands/ralph-loop/ralph-loop.md +104 -2
  58. package/templates/hooks/hooks.json +13 -0
  59. package/templates/hooks/ralph-mcp-logger.sh +46 -0
  60. package/templates/hooks/ralph-session-end.sh +69 -0
  61. package/templates/ralph/README.md +91 -0
  62. package/templates/ralph/ralph.config.yaml +113 -0
  63. package/templates/scripts/setup-ralph-loop.sh +173 -0
  64. package/templates/skills/_shared.md +117 -0
  65. package/templates/skills/ai-prompt/SKILL.md +87 -654
  66. package/templates/skills/application/SKILL.md +76 -499
  67. package/templates/skills/controller/SKILL.md +38 -165
  68. package/templates/skills/documentation/SKILL.md +2 -1
  69. package/templates/skills/feature-full/SKILL.md +107 -732
  70. package/templates/skills/notification/SKILL.md +85 -474
  71. package/templates/skills/ui-components/SKILL.md +62 -762
  72. package/templates/skills/workflow/SKILL.md +85 -489
  73. package/templates/commands/gitflow/rescue.md +0 -867
  74. package/templates/skills/business-analyse/SKILL.md +0 -191
@@ -16,855 +16,155 @@ description: |
16
16
 
17
17
  # Skill UI Components SmartStack
18
18
 
19
- > **Synergie Skill/Composant:**
20
- > - **Skill** (`.claude/skills/ui-components/`) → Invocation automatique par Claude
21
- > - **Composant** (`components/ui/EntityCard.tsx`) → Source de vérité du rendu
22
- > - Templates et patterns dans ce fichier
19
+ **Référence:** [_shared.md](../_shared.md) pour architecture, i18n
23
20
 
24
21
  ## QUAND CE SKILL S'ACTIVE
25
22
 
26
- Claude invoque automatiquement ce skill quand il détecte :
27
-
28
23
  | Déclencheur | Exemple |
29
24
  |-------------|---------|
30
- | **Création page React** | Écriture de fichier dans `src/pages/**/*.tsx` |
31
- | **Création composant** | Écriture de fichier dans `src/components/**/*.tsx` |
25
+ | Création page/composant React | Fichier dans `src/pages/**/*.tsx` ou `src/components/**/*.tsx` |
32
26
  | Création de liste | "Affiche les produits en cards" |
33
- | Nouveau module avec UI | "Crée un module avec grille de cards" |
34
- | Refactoring UI | "Uniformise les cards de cette page" |
35
- | Mots-clés | "card", "grille", "tableau", "kanban", "liste" |
36
- | Tooltips/Infobulles | "Ajoute un tooltip sur ce bouton" |
27
+ | Mots-clés | "card", "grille", "tableau", "kanban", "tooltip" |
37
28
  | États désactivés | "Désactive le bouton avec un message explicatif" |
38
- | Permissions UI | "Affiche pourquoi l'utilisateur ne peut pas cliquer" |
39
-
40
- ---
41
-
42
- ## DESIGN SYSTEM DE RÉFÉRENCE
43
-
44
- **Page de référence:** `/platform/administration/ai/settings`
45
-
46
- ### Anatomie d'une Card Standard
47
-
48
- ```
49
- ┌─────────────────────────────────────────────────────────────────────────┐
50
- │ HEADER (bg-accent-50) │
51
- │ ┌─────┐ [Badge] │
52
- │ │ O │ Titre Principal │
53
- │ └─────┘ code-slug │
54
- ├─────────────────────────────────────────────────────────────────────────┤
55
- │ BODY │
56
- │ │
57
- │ Description du contenu sur une ou plusieurs │
58
- │ lignes avec texte secondaire. │
59
- │ │
60
- │ 15 modèle(s) │
61
- │ │
62
- │ ───────────────────────────────────────────── │
63
- │ ↗ Site officiel │
64
- │ 📖 Documentation API │
65
- │ │
66
- │ ┌─────────────────────────────────────────────────────────────────────┐│
67
- │ │ Obtenir une clé API (primary) ││
68
- │ └─────────────────────────────────────────────────────────────────────┘│
69
- │ ┌─────────────────────────────────────────────────────────────────────┐│
70
- │ │ Obtenir une clé API Admin (secondary) ││
71
- │ └─────────────────────────────────────────────────────────────────────┘│
72
- └─────────────────────────────────────────────────────────────────────────┘
73
- ```
74
-
75
- ### Caractéristiques du Design
76
-
77
- | Élément | Style |
78
- |---------|-------|
79
- | **Card** | `bg-[var(--bg-card)]` avec `border-2 border-[var(--color-accent-200)]` |
80
- | **Header** | Section colorée séparée `bg-[var(--color-accent-50)]` |
81
- | **Avatar** | Carré arrondi `rounded-lg` (PAS cercle), `shadow-lg` |
82
- | **Subtitle** | Utilise `<code>` pour le code/slug |
83
- | **Badge** | Icône avec tooltip au hover, dans le header |
84
- | **Links** | Séparés par `border-t`, icône + texte |
85
- | **Actions** | Full-width, variants primary/secondary |
86
- | **Spacer** | `flex-1 min-h-4` pour push buttons en bas |
87
-
88
- ### Couleurs d'Accent
89
-
90
- ```
91
- --color-accent-50 → Header background (light)
92
- --color-accent-200 → Border color
93
- --color-accent-400 → Border hover
94
- --color-accent-500 → Button primary background
95
- --color-accent-600 → Button primary hover
96
- --color-accent-700 → Button secondary background / Badge icon
97
- --color-accent-800 → Button secondary hover / Dark mode border
98
- --color-accent-900 → Dark mode header background
99
- ```
100
-
101
- ---
102
29
 
103
30
  ## COMPOSANT OBLIGATOIRE: EntityCard
104
31
 
105
- **Fichier:** `web/smartstack-web/src/components/ui/EntityCard.tsx`
106
-
107
- ### RÈGLE ABSOLUE
108
-
109
- > **TOUJOURS utiliser `EntityCard` pour les cards d'entités.**
110
- > Ne JAMAIS créer de cards custom avec des divs manuels.
111
-
112
- ### Import Standard
32
+ **TOUJOURS utiliser `EntityCard` pour les cards d'entités. JAMAIS de cards custom avec divs.**
113
33
 
114
34
  ```typescript
115
35
  import { EntityCard, ProviderCard, TemplateCard } from '@/components/ui/EntityCard';
116
36
  ```
117
37
 
118
- ### Usage EntityCard (Générique)
119
-
38
+ ### Usage EntityCard
120
39
  ```tsx
121
40
  <EntityCard
122
41
  avatar={{ letter: 'O', color: '#10a37f' }}
123
- title="OpenAI"
124
- subtitle="openai"
125
- description="OpenAI GPT models (GPT-4, GPT-4o, GPT-3.5)"
42
+ title="OpenAI" subtitle="openai"
43
+ description="OpenAI GPT models"
126
44
  stats="15 modèle(s)"
127
45
  badge={{ icon: Shield, tooltip: 'API Admin supportée' }}
128
- links={[
129
- { icon: ExternalLink, label: 'Site officiel', href: 'https://openai.com' },
130
- { icon: BookOpen, label: 'Documentation API', href: 'https://platform.openai.com/docs' },
131
- ]}
132
- actions={[
133
- { label: 'Obtenir une clé API', href: 'https://...', variant: 'primary', icon: Key },
134
- { label: 'Obtenir une clé API Admin', href: 'https://...', variant: 'secondary', icon: Shield },
135
- ]}
46
+ links={[{ icon: ExternalLink, label: 'Site', href: 'https://...' }]}
47
+ actions={[{ label: 'Config', onClick: () => {}, variant: 'primary' }]}
136
48
  />
137
49
  ```
138
50
 
139
51
  ### Usage ProviderCard (AI Providers)
140
-
141
52
  ```tsx
142
- <ProviderCard
143
- name="OpenAI"
144
- code="openai"
145
- description="OpenAI GPT models (GPT-4, GPT-4o, GPT-3.5)"
146
- modelCount={15}
147
- color="#10a37f"
148
- websiteUrl="https://openai.com"
149
- docsUrl="https://platform.openai.com/docs"
150
- apiKeyUrl="https://platform.openai.com/api-keys"
151
- adminApiKeyUrl="https://platform.openai.com/organization/admin-keys"
152
- hasAdminKey
153
- badgeIcon={Shield}
154
- badgeTooltip="API Admin supportée"
155
- />
53
+ <ProviderCard name="OpenAI" code="openai" description="..." modelCount={15} color="#10a37f"
54
+ websiteUrl="..." docsUrl="..." apiKeyUrl="..." hasAdminKey />
156
55
  ```
157
56
 
158
57
  ### Usage TemplateCard (Templates)
159
-
160
58
  ```tsx
161
- <TemplateCard
162
- name="Welcome Email"
163
- code="welcome"
164
- category="Transactional"
165
- isActive
166
- isSystem
167
- icon={Mail}
168
- iconColor="var(--color-accent-500)"
169
- translationsCount={3}
170
- onClick={() => navigate('/...')}
171
- onEdit={() => navigate('/edit')}
172
- onDelete={() => handleDelete()}
173
- labels={{
174
- activeLabel: t('emailTemplates.active'),
175
- inactiveLabel: t('emailTemplates.inactive'),
176
- systemLabel: 'System',
177
- editLabel: t('emailTemplates.edit'),
178
- deleteLabel: t('emailTemplates.delete'),
179
- }}
180
- />
59
+ <TemplateCard name="Welcome" code="welcome" category="Transactional" isActive isSystem
60
+ icon={Mail} translationsCount={3} onClick={() => {}} onEdit={() => {}} onDelete={() => {}} />
181
61
  ```
182
62
 
183
- ### TemplateCard Props
184
-
185
- | Prop | Type | Description |
186
- |------|------|-------------|
187
- | `name` | `string` | Nom du template (requis) |
188
- | `code` | `string` | Code/slug du template (requis) |
189
- | `category` | `string` | Catégorie affichée en tag |
190
- | `isActive` | `boolean` | État actif/inactif (badge dans header) |
191
- | `isSystem` | `boolean` | Template système (non supprimable) |
192
- | `icon` | `ElementType` | Icône Lucide pour l'avatar |
193
- | `iconColor` | `string` | Couleur de l'avatar (CSS) |
194
- | `translationsCount` | `number` | Nombre de traductions |
195
- | `onClick` | `() => void` | Click sur la card |
196
- | `onEdit` | `() => void` | Bouton éditer |
197
- | `onDelete` | `() => void` | Bouton supprimer (masqué si isSystem) |
198
- | `labels` | `object` | Labels i18n pour tous les textes |
199
-
200
- ---
201
-
202
- ## PROPS RÉFÉRENCE
203
-
204
63
  ### EntityCard Props
205
-
206
- | Prop | Type | Description |
207
- |------|------|-------------|
208
- | `avatar` | `{ letter, color, imageUrl? }` | Avatar carré arrondi |
209
- | `title` | `string` | Titre principal (requis) |
210
- | `subtitle` | `string` | Code/slug en gris (affiché en `<code>`) |
211
- | `description` | `string \| ReactNode` | Description (line-clamp-2) |
212
- | `stats` | `string \| ReactNode` | Statistique (nombre en bold) |
213
- | `badge` | `{ icon?, tooltip?, color? }` | Badge dans le header |
214
- | `links` | `Array<{ icon, label, href?, onClick? }>` | Liens avec icônes |
215
- | `actions` | `Array<{ label, href?, onClick?, variant, icon?, disabled?, loading? }>` | Boutons d'action |
216
- | `tags` | `Array<{ label, variant, onClick? }>` | Tags/catégories |
217
- | `onClick` | `() => void` | Click sur la card |
218
- | `customHeader` | `ReactNode` | Remplace le header par défaut |
219
- | `customBody` | `ReactNode` | Remplace le body par défaut |
220
- | `customFooter` | `ReactNode` | Remplace le footer par défaut |
221
-
222
- ### Action Props
223
-
224
- | Prop | Type | Description |
225
- |------|------|-------------|
226
- | `label` | `string` | Texte du bouton |
227
- | `href` | `string` | URL (rend un `<a>` au lieu de `<button>`) |
228
- | `onClick` | `() => void` | Handler de click |
229
- | `variant` | `'primary' \| 'secondary' \| 'ghost'` | Style du bouton |
230
- | `icon` | `ElementType` | Icône Lucide |
231
- | `disabled` | `boolean` | État désactivé |
232
- | `loading` | `boolean` | État de chargement |
64
+ | Prop | Type |
65
+ |------|------|
66
+ | `avatar` | `{ letter, color, imageUrl? }` |
67
+ | `title`, `subtitle`, `description` | `string` |
68
+ | `stats` | `string` |
69
+ | `badge` | `{ icon?, tooltip?, color? }` |
70
+ | `links` | `Array<{ icon, label, href?, onClick? }>` |
71
+ | `actions` | `Array<{ label, href?, onClick?, variant, icon?, disabled? }>` |
233
72
 
234
73
  ### Action Variants
235
-
236
74
  | Variant | Style |
237
75
  |---------|-------|
238
- | `primary` | `bg-accent-500 hover:bg-accent-600 text-white` |
239
- | `secondary` | `bg-accent-700 hover:bg-accent-800 text-white` |
240
- | `ghost` | `border border-border-color bg-transparent` |
241
-
242
- ---
76
+ | `primary` | `bg-accent-500 text-white` |
77
+ | `secondary` | `bg-accent-700 text-white` |
78
+ | `ghost` | `border bg-transparent` |
243
79
 
244
- ## PATTERNS DE GRILLE
245
-
246
- ### Grille Responsive Standard
80
+ ## GRILLE RESPONSIVE
247
81
 
248
82
  ```tsx
83
+ // Standard
249
84
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
250
- {items.map(item => (
251
- <EntityCard key={item.id} {...mapToCardProps(item)} />
252
- ))}
85
+ {items.map(item => <EntityCard key={item.id} {...mapToCardProps(item)} />)}
253
86
  </div>
254
- ```
255
-
256
- ### Grille avec État Vide
257
87
 
258
- ```tsx
88
+ // Avec état vide
259
89
  {items.length === 0 ? (
260
- <div className="col-span-full text-center py-12">
261
- <Package className="w-12 h-12 mx-auto mb-4 opacity-50" />
262
- <p className="text-[var(--text-secondary)]">Aucun élément trouvé</p>
263
- </div>
90
+ <div className="text-center py-12"><p>Aucun élément</p></div>
264
91
  ) : (
265
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
266
- {items.map(item => (
267
- <EntityCard key={item.id} {...mapToCardProps(item)} />
268
- ))}
269
- </div>
92
+ <div className="grid ...">...</div>
270
93
  )}
271
94
  ```
272
95
 
273
- ### Grille avec Loading
274
-
275
- ```tsx
276
- {loading ? (
277
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
278
- {Array.from({ length: 8 }).map((_, i) => (
279
- <div key={i} className="bg-[var(--bg-card)] border-2 border-[var(--color-accent-200)] rounded-[var(--radius-card)] overflow-hidden animate-pulse">
280
- <div className="bg-[var(--color-accent-50)] p-4 border-b border-[var(--color-accent-200)]">
281
- <div className="flex items-center gap-3">
282
- <div className="w-10 h-10 rounded-lg bg-[var(--bg-tertiary)]" />
283
- <div className="flex-1">
284
- <div className="h-4 bg-[var(--bg-tertiary)] rounded w-3/4 mb-2" />
285
- <div className="h-3 bg-[var(--bg-tertiary)] rounded w-1/2" />
286
- </div>
287
- </div>
288
- </div>
289
- <div className="p-4">
290
- <div className="h-3 bg-[var(--bg-tertiary)] rounded w-full mb-2" />
291
- <div className="h-3 bg-[var(--bg-tertiary)] rounded w-2/3" />
292
- </div>
293
- </div>
294
- ))}
295
- </div>
296
- ) : (
297
- // ... actual grid
298
- )}
299
- ```
300
-
301
- ---
302
-
303
- ## WORKFLOW DE GÉNÉRATION
304
-
305
- ### ÉTAPE 1: Identifier le Type d'Entité
306
-
307
- | Type | Composant | Exemple |
308
- |------|-----------|---------|
309
- | Provider IA | `ProviderCard` | OpenAI, Anthropic |
310
- | Template | `TemplateCard` | Email templates |
311
- | Entité standard | `EntityCard` | Produits, Utilisateurs |
312
- | Item avec actions | `EntityCard` + actions | Tickets, Commandes |
313
-
314
- ### ÉTAPE 2: Mapper les Props
315
-
316
- ```typescript
317
- // Pattern de mapping
318
- function mapEntityToCard(entity: Entity): EntityCardProps {
319
- return {
320
- avatar: {
321
- letter: entity.name[0].toUpperCase(),
322
- color: getColorForType(entity.type),
323
- },
324
- title: entity.name,
325
- subtitle: entity.code,
326
- description: entity.description,
327
- stats: entity.itemCount ? `${entity.itemCount} élément(s)` : undefined,
328
- badge: entity.hasSpecialFeature ? { icon: Star, tooltip: 'Feature spéciale' } : undefined,
329
- links: entity.websiteUrl ? [
330
- { icon: ExternalLink, label: 'Site web', href: entity.websiteUrl }
331
- ] : undefined,
332
- actions: [
333
- { label: 'Voir détails', onClick: () => navigate(`/${entity.id}`), variant: 'primary' }
334
- ],
335
- };
336
- }
337
- ```
338
-
339
- ### ÉTAPE 3: Utiliser dans la Page
340
-
341
- ```tsx
342
- // Pattern page complète
343
- export function EntitiesPage() {
344
- const [entities, setEntities] = useState<Entity[]>([]);
345
- const [loading, setLoading] = useState(true);
346
-
347
- useEffect(() => {
348
- loadEntities();
349
- }, []);
350
-
351
- return (
352
- <div className="space-y-6">
353
- <PageHeader title="Entités" subtitle="Gérer les entités" />
354
-
355
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
356
- {entities.map(entity => (
357
- <EntityCard key={entity.id} {...mapEntityToCard(entity)} />
358
- ))}
359
- </div>
360
- </div>
361
- );
362
- }
363
- ```
364
-
365
- ---
366
-
367
- ## QUAND UTILISER EntityCard vs Custom Cards
368
-
369
- ### Utiliser EntityCard pour:
370
- - Listes d'entités homogènes (produits, providers, templates)
371
- - Cards avec structure standard: avatar, titre, description, liens, actions
372
- - Grilles de cards cliquables
373
- - Affichage de catalogues
374
-
375
- ### NE PAS utiliser EntityCard pour:
376
- - Dashboard stats cards (sync status, métriques)
377
- - Cards avec états interactifs complexes (sélection, toggle inline)
378
- - Cards avec sous-listes intégrées (ex: roles assignés avec X pour supprimer)
379
- - Cards master/detail avec panneau de détails
380
- - Cards avec formulaires intégrés
381
-
382
- ---
383
-
384
- ## PATTERN: CUSTOM CARDS (Status, Stats, Dashboard)
385
-
386
- Pour les cards qui ne peuvent pas utiliser `EntityCard`, utiliser ce pattern pour garantir l'alignement des boutons en bas.
387
-
388
- ### Structure de Base avec Alignement Boutons
389
-
390
- ```tsx
391
- // ⚠️ OBLIGATOIRE: h-full flex flex-col sur le container
392
- <div className="h-full flex flex-col rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] overflow-hidden shadow-sm hover:shadow-md transition-shadow">
393
-
394
- {/* Header avec gradient */}
395
- <div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 to-[var(--color-accent-600)]/5 border-b border-[var(--border-color)]">
396
- <div className="flex items-center justify-between">
397
- <div className="flex items-center gap-3">
398
- <div className="p-2 rounded-lg bg-[var(--color-accent-500)]/20">
399
- <Icon className="w-5 h-5 text-[var(--color-accent-600)]" />
400
- </div>
401
- <h3 className="font-semibold text-[var(--text-primary)]">
402
- {title}
403
- </h3>
404
- </div>
405
- {/* Badge de statut (optionnel) */}
406
- <div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-[var(--success-bg)] text-[var(--success-text)]">
407
- <StatusIcon className="w-3.5 h-3.5" />
408
- {statusLabel}
409
- </div>
410
- </div>
411
- </div>
412
-
413
- {/* ⚠️ OBLIGATOIRE: flex-1 flex flex-col pour que le contenu prenne toute la hauteur disponible */}
414
- <div className="flex-1 flex flex-col p-4">
415
-
416
- {/* Stats Grid (optionnel) */}
417
- <div className="grid grid-cols-3 gap-3 mb-4">
418
- <StatBlock icon={Database} value={123} label="Items" />
419
- <StatBlock icon={Calendar} value="15 jan" label="Last sync" />
420
- <StatBlock icon={RefreshCw} value="14 jan" label="Delta sync" />
421
- </div>
422
-
423
- {/* Contenu variable (warnings, errors, etc.) */}
424
- {warning && (
425
- <div className="p-3 mb-4 rounded-lg bg-[var(--warning-bg)] border border-[var(--warning-border)]">
426
- <span className="text-sm text-[var(--warning-text)]">{warning}</span>
427
- </div>
428
- )}
429
-
430
- {/* ⚠️ OBLIGATOIRE: mt-auto pour pousser le bouton en bas */}
431
- <button className="mt-auto w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium rounded-[var(--radius-button)] bg-[var(--color-accent-600)] text-white hover:bg-[var(--color-accent-700)] disabled:opacity-50 transition-colors">
432
- <RefreshCw className="w-4 h-4" />
433
- {actionLabel}
434
- </button>
435
- </div>
436
- </div>
437
- ```
438
-
439
- ### Règles d'Alignement Boutons (CRITIQUES)
440
-
441
- | Élément | Classe CSS | Raison |
442
- |---------|------------|--------|
443
- | **Card container** | `h-full flex flex-col` | Permet à la card de remplir la hauteur de la grille |
444
- | **Content wrapper** | `flex-1 flex flex-col` | Le contenu prend l'espace disponible |
445
- | **Action button** | `mt-auto` | Pousse le bouton tout en bas |
446
-
447
- ### Exemple Complet: Sync Status Card
448
-
449
- ```tsx
450
- interface SyncStatusCardProps {
451
- resourceType: string;
452
- icon: LucideIcon;
453
- status: 'Success' | 'Failed' | 'Running' | 'Pending';
454
- lastSyncAt: string | null;
455
- itemCount: number;
456
- pendingConflicts: number;
457
- error: string | null;
458
- syncing: boolean;
459
- onSync: () => void;
460
- }
461
-
462
- function SyncStatusCard({
463
- resourceType, icon: Icon, status, lastSyncAt, itemCount,
464
- pendingConflicts, error, syncing, onSync
465
- }: SyncStatusCardProps) {
466
-
467
- const statusConfig = getStatusConfig(status); // Returns { icon, color, bg, label }
468
-
469
- return (
470
- <div className="h-full flex flex-col rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] overflow-hidden shadow-sm hover:shadow-md transition-shadow">
471
- {/* Header */}
472
- <div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 to-[var(--color-accent-600)]/5 border-b border-[var(--border-color)]">
473
- <div className="flex items-center justify-between">
474
- <div className="flex items-center gap-3">
475
- <div className="p-2 rounded-lg bg-[var(--color-accent-500)]/20">
476
- <Icon className="w-5 h-5 text-[var(--color-accent-600)]" />
477
- </div>
478
- <h3 className="font-semibold text-[var(--text-primary)]">{resourceType}</h3>
479
- </div>
480
- <div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${statusConfig.bg} ${statusConfig.color}`}>
481
- <statusConfig.icon className="w-3.5 h-3.5" />
482
- {statusConfig.label}
483
- </div>
484
- </div>
485
- </div>
486
-
487
- {/* Content */}
488
- <div className="flex-1 flex flex-col p-4">
489
- {/* Stats */}
490
- <div className="grid grid-cols-2 gap-3 mb-4">
491
- <div className="text-center p-3 rounded-lg bg-[var(--bg-secondary)]">
492
- <p className="text-xl font-bold">{itemCount}</p>
493
- <p className="text-xs text-[var(--text-tertiary)]">Synced</p>
494
- </div>
495
- <div className="text-center p-3 rounded-lg bg-[var(--bg-secondary)]">
496
- <p className="text-sm font-medium">{formatDate(lastSyncAt)}</p>
497
- <p className="text-xs text-[var(--text-tertiary)]">Last sync</p>
498
- </div>
499
- </div>
500
-
501
- {/* Warnings (variable height) */}
502
- {pendingConflicts > 0 && (
503
- <div className="p-3 mb-4 rounded-lg bg-[var(--warning-bg)] border border-[var(--warning-border)]">
504
- <span className="text-sm text-[var(--warning-text)]">
505
- {pendingConflicts} conflicts pending
506
- </span>
507
- </div>
508
- )}
509
-
510
- {/* Errors (variable height) */}
511
- {error && (
512
- <div className="p-3 mb-4 rounded-lg bg-[var(--error-bg)] border border-[var(--error-border)]">
513
- <p className="text-xs text-[var(--error-text)] line-clamp-2">{error}</p>
514
- </div>
515
- )}
516
-
517
- {/* Button - ALWAYS at bottom */}
518
- <button
519
- onClick={onSync}
520
- disabled={syncing}
521
- className="mt-auto w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium rounded-[var(--radius-button)] bg-[var(--color-accent-600)] text-white hover:bg-[var(--color-accent-700)] disabled:opacity-50 transition-colors"
522
- >
523
- {syncing ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
524
- Sync Now
525
- </button>
526
- </div>
527
- </div>
528
- );
529
- }
530
- ```
531
-
532
- ### Usage dans une Grille
533
-
534
- ```tsx
535
- {/* La grille DOIT utiliser des colonnes égales */}
536
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
537
- {syncStatuses.map((status) => (
538
- <SyncStatusCard
539
- key={status.resourceType}
540
- {...status}
541
- onSync={() => handleSync(status.resourceType)}
542
- />
543
- ))}
544
- </div>
545
- ```
546
-
547
- ### Pattern: Card Footer avec Métadonnées + Action
548
-
549
- Quand une card a besoin d'afficher des métadonnées (date, statut) ET un bouton d'action en bas :
550
-
551
- ```tsx
552
- {/* ⚠️ OBLIGATOIRE: mt-auto sur le footer pour l'aligner en bas */}
553
- <div className="mt-auto pt-4 border-t border-[var(--border-color)]">
554
- <div className="flex items-center justify-between">
555
- {/* Métadonnées à gauche */}
556
- <div className="flex items-center gap-1.5 text-xs text-[var(--text-tertiary)]">
557
- <Calendar className="w-3.5 h-3.5" />
558
- {formatDate(createdAt)}
559
- </div>
560
-
561
- {/* Bouton d'action à droite */}
562
- <button className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-[var(--radius-button)] bg-[var(--color-accent-600)] text-white hover:bg-[var(--color-accent-700)] transition-colors shadow-sm">
563
- <CheckCircle className="w-4 h-4" />
564
- {actionLabel}
565
- </button>
566
- </div>
567
- </div>
568
- ```
96
+ ## CUSTOM CARDS (Status, Dashboard)
569
97
 
570
- **Structure complète avec footer :**
98
+ Pattern alignement boutons en bas (obligatoire pour custom cards):
571
99
 
572
100
  ```tsx
573
- <div className="h-full flex flex-col rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] overflow-hidden">
101
+ // ⚠️ OBLIGATOIRE: h-full flex flex-col + flex-1 + mt-auto
102
+ <div className="h-full flex flex-col rounded-[var(--radius-card)] border ...">
574
103
  {/* Header */}
575
- <div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 to-[var(--color-accent-600)]/5 border-b border-[var(--border-color)]">
104
+ <div className="px-4 py-3 bg-gradient-to-r from-[var(--color-accent-500)]/10 ...">
576
105
  {/* ... */}
577
106
  </div>
578
-
579
- {/* Content - flex-1 flex flex-col pour occuper l'espace */}
580
- <div className="flex-1 flex flex-col p-4 space-y-4">
581
- {/* Contenu variable */}
582
- <p className="text-sm text-[var(--text-secondary)]">{description}</p>
583
-
584
- {/* Composants additionnels */}
585
- <SomeComponent />
586
-
587
- {/* ⚠️ Footer avec mt-auto - TOUJOURS en dernier dans le content */}
588
- <div className="mt-auto flex items-center justify-between pt-4 border-t border-[var(--border-color)]">
589
- <div className="flex items-center gap-1.5 text-xs text-[var(--text-tertiary)]">
590
- <Calendar className="w-3.5 h-3.5" />
591
- {date}
592
- </div>
593
- <button className="...">Action</button>
594
- </div>
595
- </div>
596
- </div>
597
- ```
598
-
599
- ### ⚠️ Erreurs Communes à Éviter
600
-
601
- ```tsx
602
- // ❌ MAUVAIS - Les boutons ne seront PAS alignés
603
- <div className="rounded-lg border ...">
604
- <div className="p-4">
605
- {/* contenu */}
606
- <button>Action</button> {/* Pas de mt-auto, pas d'alignement */}
607
- </div>
608
- </div>
609
-
610
- // ❌ MAUVAIS - Footer sans mt-auto
611
- <div className="h-full flex flex-col ...">
612
- <div className="flex-1 flex flex-col p-4">
613
- {/* contenu */}
614
- <div className="pt-4 border-t ..."> {/* Manque mt-auto ! */}
615
- <button>Action</button>
616
- </div>
617
- </div>
618
- </div>
619
-
620
- // ✅ BON - Footer aligné en bas avec mt-auto
621
- <div className="h-full flex flex-col rounded-lg border ...">
107
+ {/* Content avec flex-1 */}
622
108
  <div className="flex-1 flex flex-col p-4">
623
- {/* contenu */}
624
- <div className="mt-auto pt-4 border-t ..."> {/* mt-auto présent */}
625
- <div className="flex items-center justify-between">
626
- <span>{metadata}</span>
627
- <button>Action</button>
628
- </div>
629
- </div>
109
+ {/* Contenu variable */}
110
+ {/* ⚠️ OBLIGATOIRE: mt-auto pour bouton en bas */}
111
+ <button className="mt-auto w-full ...">Action</button>
630
112
  </div>
631
113
  </div>
632
114
  ```
633
115
 
634
- ---
635
-
636
- ## RÈGLES ABSOLUES
637
-
638
- 1. **TOUJOURS** utiliser `EntityCard` pour les cards d'entités
639
- 2. **TOUJOURS** utiliser le header coloré distinct
640
- 3. **TOUJOURS** utiliser `rounded-lg` pour l'avatar (carré arrondi)
641
- 4. **TOUJOURS** utiliser les variables CSS d'accent (`--color-accent-*`)
642
- 5. **TOUJOURS** grille responsive (1→2→3→4 colonnes)
643
- 6. **TOUJOURS** gérer les états vide et loading
644
- 7. **TOUJOURS** utiliser `href` pour les liens externes (ouvre nouvel onglet)
645
- 8. **TOUJOURS** utiliser `h-full flex flex-col` + `flex-1` + `mt-auto` pour aligner les boutons en bas des custom cards
646
- 9. **JAMAIS** de `rounded-full` pour l'avatar (c'est le design DataTable/table)
647
- 10. **JAMAIS** de cards custom en divs manuels pour les entités simples
648
- 11. **JAMAIS** de grille fixe non-responsive
649
- 12. **JAMAIS** de boutons non alignés dans une grille de cards (utiliser le pattern d'alignement)
650
-
651
- ---
652
-
653
116
  ## COMPOSANT: DataTable
654
117
 
655
- **Fichier:** `web/smartstack-web/src/components/ui/DataTable.tsx`
656
-
657
- ### Import
658
-
659
- ```typescript
118
+ ```tsx
660
119
  import { DataTable } from '@/components/ui/DataTable';
661
- ```
662
-
663
- ### Usage
664
120
 
665
- ```tsx
666
121
  <DataTable
667
122
  data={users}
668
123
  columns={[
669
124
  { key: 'name', label: 'Nom', sortable: true },
670
- { key: 'email', label: 'Email', sortable: true },
671
- { key: 'role', label: 'Rôle', render: (user) => <Badge>{user.role}</Badge> },
672
- { key: 'createdAt', label: 'Créé le', render: (user) => formatDate(user.createdAt) },
125
+ { key: 'role', label: 'Rôle', render: (user) => <Badge>{user.role}</Badge> }
673
126
  ]}
674
- pagination={{ pageSize: 10, showSizeSelector: true }}
127
+ pagination={{ pageSize: 10 }}
675
128
  searchable
676
- searchPlaceholder="Rechercher un utilisateur..."
677
129
  onRowClick={(user) => navigate(`/users/${user.id}`)}
678
- getRowKey={(user) => user.id}
679
- striped
680
130
  />
681
131
  ```
682
132
 
683
- ### Props Principales
684
-
685
- | Prop | Type | Description |
686
- |------|------|-------------|
687
- | `data` | `T[]` | Données à afficher |
688
- | `columns` | `DataTableColumn<T>[]` | Définition des colonnes |
689
- | `pagination` | `{ pageSize, showSizeSelector? }` | Config pagination |
690
- | `searchable` | `boolean` | Activer recherche globale |
691
- | `onRowClick` | `(item, index) => void` | Click sur ligne |
692
- | `selectable` | `boolean` | Activer sélection multi |
693
- | `loading` | `boolean` | État chargement |
694
- | `striped` | `boolean` | Lignes alternées |
695
- | `compact` | `boolean` | Mode compact |
696
-
697
- ### Column Config
698
-
699
- ```typescript
700
- interface DataTableColumn<T> {
701
- key: string; // Clé de l'objet ou custom
702
- label: string; // Label en-tête
703
- sortable?: boolean; // Tri activé
704
- render?: (item: T) => ReactNode; // Rendu custom
705
- width?: string; // Largeur CSS
706
- align?: 'left' | 'center' | 'right';
707
- hideOnMobile?: boolean;
708
- }
709
- ```
710
-
711
- ---
712
-
713
133
  ## COMPOSANT: Tooltip
714
134
 
715
- **Fichier:** `web/smartstack-web/src/components/ui/Tooltip.tsx`
716
-
717
- ### Import
718
-
719
- ```typescript
720
- import { Tooltip, type TooltipVariant, type TooltipPosition } from '@/components/ui/Tooltip';
721
- ```
722
-
723
- ### Variantes Disponibles
724
-
725
- | Variant | Couleur | Usage |
726
- |---------|---------|-------|
727
- | `default` | Gris (thème) | Information neutre |
728
- | `error` | Rouge | Permission refusée, erreur, action interdite |
729
- | `warning` | Orange | Avertissement, action irréversible |
730
- | `success` | Vert | Confirmation, action réussie |
731
- | `info` | Bleu | Information contextuelle, aide |
732
-
733
- ### Positions Disponibles
734
-
735
- | Position | Description |
736
- |----------|-------------|
737
- | `top` | Au-dessus de l'élément (défaut) |
738
- | `bottom` | En dessous de l'élément |
739
- | `left` | À gauche de l'élément |
740
- | `right` | À droite de l'élément |
741
-
742
- ### Usage de Base
743
-
744
135
  ```tsx
745
136
  import { Tooltip } from '@/components/ui/Tooltip';
746
137
 
747
- // Tooltip par défaut (informatif)
748
- <Tooltip content="Information contextuelle">
749
- <button>Hover me</button>
750
- </Tooltip>
138
+ // Variantes: default, error, warning, success, info
139
+ // Positions: top, bottom, left, right
751
140
 
752
- // Tooltip d'erreur (permission refusée)
753
- <Tooltip
754
- content="Action non autorisée - permission 'execute' requise"
755
- variant="error"
756
- position="top"
757
- >
141
+ <Tooltip content="Permission requise" variant="error">
758
142
  <button disabled>Action protégée</button>
759
143
  </Tooltip>
760
-
761
- // Tooltip de warning (action dangereuse)
762
- <Tooltip
763
- content="Cette action est irréversible"
764
- variant="warning"
765
- position="bottom"
766
- >
767
- <button>Supprimer</button>
768
- </Tooltip>
769
-
770
- // Tooltip de succès
771
- <Tooltip content="Fichier sauvegardé" variant="success">
772
- <span>✓ Saved</span>
773
- </Tooltip>
774
-
775
- // Tooltip informatif
776
- <Tooltip content="Cliquez pour plus de détails" variant="info">
777
- <HelpCircle className="w-4 h-4" />
778
- </Tooltip>
779
144
  ```
780
145
 
781
- ### Pattern: Bouton Désactivé avec Explication
782
-
146
+ ### Pattern Bouton Désactivé avec Explication
783
147
  ```tsx
784
- // Pattern recommandé pour les boutons désactivés avec permission
785
- const { hasPermission } = useAuth();
786
148
  const canExecute = hasPermission('module.action.execute');
787
-
788
- <Tooltip
789
- content={!canExecute ? t('errors.noPermission') : undefined}
790
- variant="error"
791
- disabled={canExecute} // Tooltip ne s'affiche que si désactivé
792
- >
793
- <button
794
- onClick={handleAction}
795
- disabled={!canExecute}
796
- className="... disabled:opacity-50 disabled:cursor-not-allowed"
797
- >
798
- Action
799
- </button>
149
+ <Tooltip content={!canExecute ? t('errors.noPermission') : undefined} variant="error" disabled={canExecute}>
150
+ <button disabled={!canExecute}>Action</button>
800
151
  </Tooltip>
801
152
  ```
802
153
 
803
- ### Props Complètes
804
-
805
- | Prop | Type | Default | Description |
806
- |------|------|---------|-------------|
807
- | `content` | `ReactNode` | - | Contenu du tooltip (requis) |
808
- | `children` | `ReactNode` | - | Élément déclencheur (requis) |
809
- | `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Position du tooltip |
810
- | `variant` | `'default' \| 'error' \| 'warning' \| 'success' \| 'info'` | `'default'` | Style/couleur du tooltip |
811
- | `delay` | `number` | `200` | Délai avant affichage (ms) |
812
- | `disabled` | `boolean` | `false` | Désactive le tooltip |
813
- | `className` | `string` | `''` | Classes CSS additionnelles |
814
-
815
- ### Variables CSS Utilisées
816
-
817
- Le tooltip utilise des variables CSS opaques pour garantir la lisibilité :
818
-
819
- ```css
820
- /* Light mode */
821
- --tooltip-error-bg: #fef2f2;
822
- --tooltip-warning-bg: #fefce8;
823
- --tooltip-success-bg: #f0fdf4;
824
- --tooltip-info-bg: #eff6ff;
825
-
826
- /* Dark mode */
827
- --tooltip-error-bg: #3b1c1e;
828
- --tooltip-warning-bg: #3b2f1a;
829
- --tooltip-success-bg: #1a3329;
830
- --tooltip-info-bg: #1a2c3b;
831
- ```
154
+ ## QUAND UTILISER EntityCard vs Custom
832
155
 
833
- ### ⚠️ Règles d'Usage
156
+ | EntityCard pour | Custom pour |
157
+ |-----------------|-------------|
158
+ | Listes entités homogènes | Dashboard stats cards |
159
+ | Catalogues | Cards avec états interactifs complexes |
160
+ | Grilles cliquables | Cards avec formulaires intégrés |
834
161
 
835
- 1. **TOUJOURS** utiliser `variant="error"` pour les permissions refusées
836
- 2. **TOUJOURS** utiliser `variant="warning"` pour les actions dangereuses/irréversibles
837
- 3. **TOUJOURS** désactiver le tooltip (`disabled={true}`) quand l'action est autorisée
838
- 4. **TOUJOURS** positionner le tooltip de manière à ne pas bloquer les éléments importants
839
- 5. **JAMAIS** utiliser le tooltip natif `title` HTML - utiliser ce composant à la place
840
- 6. **JAMAIS** de fonds transparents - le composant utilise des fonds opaques
162
+ ## REGLES ABSOLUES
841
163
 
842
- ---
843
-
844
- ## COMPOSANTS - RÉSUMÉ
845
-
846
- | Composant | Fichier | Usage |
847
- |-----------|---------|-------|
848
- | **EntityCard** | `EntityCard.tsx` | Cards d'entités avec header coloré |
849
- | **ProviderCard** | `EntityCard.tsx` | Preset pour providers IA |
850
- | **TemplateCard** | `EntityCard.tsx` | Preset pour templates |
851
- | **DataTable** | `DataTable.tsx` | Tableaux avec tri/pagination/recherche |
852
- | **Tooltip** | `Tooltip.tsx` | Tooltips contextuels avec variantes colorées |
853
-
854
- ---
855
-
856
- ## VALIDATION
857
-
858
- Après génération, vérifier :
859
-
860
- ```bash
861
- # TypeScript
862
- cd web/smartstack-web && npx tsc --noEmit
863
-
864
- # Lint
865
- npm run lint
866
-
867
- # Import correct
868
- grep -r "EntityCard" src/pages/ | grep -v "from '@/components/ui/EntityCard'"
869
- # ↑ Doit retourner vide (tous imports corrects)
870
- ```
164
+ | DO | DON'T |
165
+ |----|-------|
166
+ | EntityCard pour entités | Cards custom avec divs manuels |
167
+ | Header coloré distinct | rounded-full pour avatar (c'est table) |
168
+ | Grille responsive 1→2→3→4 | Grille fixe non-responsive |
169
+ | h-full + flex-1 + mt-auto | Boutons non alignés en grille |
170
+ | États vide et loading | Tooltip natif HTML |