@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.
- package/.documentation/agents.html +7 -2
- package/.documentation/apex.html +7 -2
- package/.documentation/business-analyse.html +7 -2
- package/.documentation/cli-commands.html +871 -0
- package/.documentation/commands.html +7 -2
- package/.documentation/efcore.html +7 -2
- package/.documentation/gitflow.html +7 -2
- package/.documentation/hooks.html +7 -2
- package/.documentation/index.html +7 -2
- package/.documentation/init.html +7 -2
- package/.documentation/installation.html +7 -2
- package/.documentation/ralph-loop.html +7 -2
- package/.documentation/test-web.html +7 -2
- package/dist/index.js +1932 -336
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/templates/agents/efcore/squash.md +67 -31
- package/templates/agents/gitflow/finish.md +68 -56
- package/templates/commands/business-analyse/0-orchestrate.md +72 -556
- package/templates/commands/business-analyse/1-init.md +23 -193
- package/templates/commands/business-analyse/2-discover.md +85 -462
- package/templates/commands/business-analyse/3-analyse.md +40 -342
- package/templates/commands/business-analyse/4-specify.md +72 -537
- package/templates/commands/business-analyse/5-validate.md +43 -237
- package/templates/commands/business-analyse/6-handoff.md +93 -682
- package/templates/commands/business-analyse/7-doc-html.md +45 -544
- package/templates/commands/business-analyse/_shared.md +176 -0
- package/templates/commands/business-analyse/bug.md +50 -257
- package/templates/commands/business-analyse/change-request.md +59 -283
- package/templates/commands/business-analyse/hotfix.md +36 -120
- package/templates/commands/business-analyse.md +55 -574
- package/templates/commands/efcore/_shared.md +206 -0
- package/templates/commands/efcore/conflicts.md +39 -201
- package/templates/commands/efcore/db-deploy.md +28 -237
- package/templates/commands/efcore/db-reset.md +41 -390
- package/templates/commands/efcore/db-seed.md +44 -323
- package/templates/commands/efcore/db-status.md +31 -210
- package/templates/commands/efcore/migration.md +45 -368
- package/templates/commands/efcore/rebase-snapshot.md +38 -241
- package/templates/commands/efcore/scan.md +35 -204
- package/templates/commands/efcore/squash.md +158 -251
- package/templates/commands/efcore.md +49 -177
- package/templates/commands/gitflow/1-init.md +94 -1318
- package/templates/commands/gitflow/10-start.md +86 -990
- package/templates/commands/gitflow/11-finish.md +264 -454
- package/templates/commands/gitflow/12-cleanup.md +40 -213
- package/templates/commands/gitflow/2-status.md +51 -386
- package/templates/commands/gitflow/3-commit.md +108 -801
- package/templates/commands/gitflow/4-plan.md +42 -13
- package/templates/commands/gitflow/5-exec.md +60 -5
- package/templates/commands/gitflow/6-abort.md +54 -277
- package/templates/commands/gitflow/7-pull-request.md +74 -717
- package/templates/commands/gitflow/8-review.md +51 -178
- package/templates/commands/gitflow/9-merge.md +74 -404
- package/templates/commands/gitflow/_shared.md +196 -0
- package/templates/commands/quickstart.md +154 -0
- package/templates/commands/ralph-loop/ralph-loop.md +104 -2
- package/templates/hooks/hooks.json +13 -0
- package/templates/hooks/ralph-mcp-logger.sh +46 -0
- package/templates/hooks/ralph-session-end.sh +69 -0
- package/templates/ralph/README.md +91 -0
- package/templates/ralph/ralph.config.yaml +113 -0
- package/templates/scripts/setup-ralph-loop.sh +173 -0
- package/templates/skills/_shared.md +117 -0
- package/templates/skills/ai-prompt/SKILL.md +87 -654
- package/templates/skills/application/SKILL.md +76 -499
- package/templates/skills/controller/SKILL.md +38 -165
- package/templates/skills/documentation/SKILL.md +2 -1
- package/templates/skills/feature-full/SKILL.md +107 -732
- package/templates/skills/notification/SKILL.md +85 -474
- package/templates/skills/ui-components/SKILL.md +62 -762
- package/templates/skills/workflow/SKILL.md +85 -489
- package/templates/commands/gitflow/rescue.md +0 -867
- 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
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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
|
-
**
|
|
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
|
|
119
|
-
|
|
38
|
+
### Usage EntityCard
|
|
120
39
|
```tsx
|
|
121
40
|
<EntityCard
|
|
122
41
|
avatar={{ letter: 'O', color: '#10a37f' }}
|
|
123
|
-
title="OpenAI"
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
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
|
|
239
|
-
| `secondary` | `bg-accent-700
|
|
240
|
-
| `ghost` | `border
|
|
241
|
-
|
|
242
|
-
---
|
|
76
|
+
| `primary` | `bg-accent-500 text-white` |
|
|
77
|
+
| `secondary` | `bg-accent-700 text-white` |
|
|
78
|
+
| `ghost` | `border bg-transparent` |
|
|
243
79
|
|
|
244
|
-
##
|
|
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
|
-
|
|
88
|
+
// Avec état vide
|
|
259
89
|
{items.length === 0 ? (
|
|
260
|
-
<div className="
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
98
|
+
Pattern alignement boutons en bas (obligatoire pour custom cards):
|
|
571
99
|
|
|
572
100
|
```tsx
|
|
573
|
-
|
|
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
|
|
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
|
-
{/*
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
//
|
|
748
|
-
|
|
749
|
-
<button>Hover me</button>
|
|
750
|
-
</Tooltip>
|
|
138
|
+
// Variantes: default, error, warning, success, info
|
|
139
|
+
// Positions: top, bottom, left, right
|
|
751
140
|
|
|
752
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
|
847
|
-
|
|
848
|
-
|
|
|
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 |
|