@atlashub/smartstack-cli 1.5.0 → 1.5.2

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 (159) hide show
  1. package/.documentation/agents.html +920 -916
  2. package/.documentation/apex.html +1022 -1018
  3. package/.documentation/business-analyse.html +1505 -1501
  4. package/.documentation/commands.html +684 -680
  5. package/.documentation/css/styles.css +2168 -2168
  6. package/.documentation/efcore.html +2509 -2505
  7. package/.documentation/gitflow.html +2622 -2618
  8. package/.documentation/hooks.html +417 -413
  9. package/.documentation/index.html +327 -323
  10. package/.documentation/init.html +565 -0
  11. package/.documentation/installation.html +548 -462
  12. package/.documentation/js/app.js +794 -794
  13. package/.documentation/ralph-loop.html +534 -530
  14. package/.documentation/test-web.html +517 -513
  15. package/config/default-config.json +86 -86
  16. package/config/settings.json +53 -53
  17. package/config/settings.local.example.json +16 -16
  18. package/dist/index.js +18 -8
  19. package/dist/index.js.map +1 -1
  20. package/package.json +88 -88
  21. package/templates/agents/action.md +36 -36
  22. package/templates/agents/efcore/conflicts.md +84 -84
  23. package/templates/agents/efcore/db-deploy.md +51 -51
  24. package/templates/agents/efcore/db-reset.md +59 -59
  25. package/templates/agents/efcore/db-seed.md +56 -56
  26. package/templates/agents/efcore/db-status.md +64 -64
  27. package/templates/agents/efcore/migration.md +85 -85
  28. package/templates/agents/efcore/rebase-snapshot.md +62 -62
  29. package/templates/agents/efcore/scan.md +90 -90
  30. package/templates/agents/efcore/squash.md +67 -67
  31. package/templates/agents/explore-codebase.md +65 -65
  32. package/templates/agents/explore-docs.md +97 -97
  33. package/templates/agents/fix-grammar.md +49 -49
  34. package/templates/agents/gitflow/abort.md +45 -45
  35. package/templates/agents/gitflow/cleanup.md +85 -85
  36. package/templates/agents/gitflow/commit.md +40 -40
  37. package/templates/agents/gitflow/exec.md +48 -48
  38. package/templates/agents/gitflow/finish.md +92 -92
  39. package/templates/agents/gitflow/init.md +139 -139
  40. package/templates/agents/gitflow/merge.md +62 -62
  41. package/templates/agents/gitflow/plan.md +42 -42
  42. package/templates/agents/gitflow/pr.md +78 -78
  43. package/templates/agents/gitflow/review.md +49 -49
  44. package/templates/agents/gitflow/start.md +61 -61
  45. package/templates/agents/gitflow/status.md +32 -32
  46. package/templates/agents/snipper.md +36 -36
  47. package/templates/agents/websearch.md +46 -46
  48. package/templates/commands/_resources/formatting-guide.md +124 -124
  49. package/templates/commands/ai-prompt.md +315 -315
  50. package/templates/commands/apex/1-analyze.md +100 -100
  51. package/templates/commands/apex/2-plan.md +145 -145
  52. package/templates/commands/apex/3-execute.md +171 -171
  53. package/templates/commands/apex/4-examine.md +116 -116
  54. package/templates/commands/apex/5-tasks.md +209 -209
  55. package/templates/commands/apex.md +76 -76
  56. package/templates/commands/application/create.md +362 -362
  57. package/templates/commands/application/templates-backend.md +463 -463
  58. package/templates/commands/application/templates-frontend.md +517 -517
  59. package/templates/commands/application/templates-i18n.md +478 -478
  60. package/templates/commands/application/templates-seed.md +362 -362
  61. package/templates/commands/application.md +303 -303
  62. package/templates/commands/business-analyse/0-orchestrate.md +640 -640
  63. package/templates/commands/business-analyse/1-init.md +269 -269
  64. package/templates/commands/business-analyse/2-discover.md +520 -520
  65. package/templates/commands/business-analyse/3-analyse.md +408 -408
  66. package/templates/commands/business-analyse/4-specify.md +598 -598
  67. package/templates/commands/business-analyse/5-validate.md +326 -326
  68. package/templates/commands/business-analyse/6-handoff.md +746 -746
  69. package/templates/commands/business-analyse/7-doc-html.md +602 -602
  70. package/templates/commands/business-analyse/bug.md +325 -325
  71. package/templates/commands/business-analyse/change-request.md +368 -368
  72. package/templates/commands/business-analyse/hotfix.md +200 -200
  73. package/templates/commands/business-analyse.md +640 -640
  74. package/templates/commands/controller/create.md +216 -216
  75. package/templates/commands/controller/postman-templates.md +528 -528
  76. package/templates/commands/controller/templates.md +600 -600
  77. package/templates/commands/controller.md +337 -337
  78. package/templates/commands/create/agent.md +138 -138
  79. package/templates/commands/create/command.md +166 -166
  80. package/templates/commands/create/hook.md +234 -234
  81. package/templates/commands/create/plugin.md +329 -329
  82. package/templates/commands/create/project.md +507 -507
  83. package/templates/commands/create/skill.md +199 -199
  84. package/templates/commands/create.md +220 -220
  85. package/templates/commands/debug.md +95 -95
  86. package/templates/commands/documentation/module.md +202 -202
  87. package/templates/commands/documentation/templates.md +432 -432
  88. package/templates/commands/documentation.md +190 -190
  89. package/templates/commands/efcore/_env-check.md +153 -153
  90. package/templates/commands/efcore/conflicts.md +186 -186
  91. package/templates/commands/efcore/db-deploy.md +193 -193
  92. package/templates/commands/efcore/db-reset.md +426 -426
  93. package/templates/commands/efcore/db-seed.md +326 -326
  94. package/templates/commands/efcore/db-status.md +226 -226
  95. package/templates/commands/efcore/migration.md +400 -400
  96. package/templates/commands/efcore/rebase-snapshot.md +264 -264
  97. package/templates/commands/efcore/scan.md +198 -198
  98. package/templates/commands/efcore/squash.md +298 -298
  99. package/templates/commands/efcore.md +224 -224
  100. package/templates/commands/epct.md +69 -69
  101. package/templates/commands/explain.md +186 -186
  102. package/templates/commands/explore.md +45 -45
  103. package/templates/commands/feature-full.md +267 -267
  104. package/templates/commands/gitflow/1-init.md +1038 -1038
  105. package/templates/commands/gitflow/10-start.md +768 -768
  106. package/templates/commands/gitflow/11-finish.md +457 -457
  107. package/templates/commands/gitflow/12-cleanup.md +276 -276
  108. package/templates/commands/gitflow/13-sync.md +216 -216
  109. package/templates/commands/gitflow/14-rebase.md +251 -251
  110. package/templates/commands/gitflow/2-status.md +277 -277
  111. package/templates/commands/gitflow/3-commit.md +344 -344
  112. package/templates/commands/gitflow/4-plan.md +145 -145
  113. package/templates/commands/gitflow/5-exec.md +147 -147
  114. package/templates/commands/gitflow/6-abort.md +344 -344
  115. package/templates/commands/gitflow/7-pull-request.md +453 -355
  116. package/templates/commands/gitflow/8-review.md +240 -176
  117. package/templates/commands/gitflow/9-merge.md +451 -365
  118. package/templates/commands/gitflow.md +128 -128
  119. package/templates/commands/implement.md +663 -663
  120. package/templates/commands/init.md +567 -562
  121. package/templates/commands/mcp-integration.md +330 -330
  122. package/templates/commands/notification.md +129 -129
  123. package/templates/commands/oneshot.md +57 -57
  124. package/templates/commands/quick-search.md +72 -72
  125. package/templates/commands/ralph-loop/cancel-ralph.md +18 -18
  126. package/templates/commands/ralph-loop/help.md +126 -126
  127. package/templates/commands/ralph-loop/ralph-loop.md +18 -18
  128. package/templates/commands/review.md +106 -106
  129. package/templates/commands/utils/test-web-config.md +160 -160
  130. package/templates/commands/utils/test-web.md +151 -151
  131. package/templates/commands/validate.md +233 -233
  132. package/templates/commands/workflow.md +193 -193
  133. package/templates/gitflow/config.json +138 -138
  134. package/templates/hooks/ef-migration-check.md +139 -139
  135. package/templates/hooks/hooks.json +25 -25
  136. package/templates/hooks/stop-hook.sh +177 -177
  137. package/templates/skills/ai-prompt/SKILL.md +778 -778
  138. package/templates/skills/application/SKILL.md +563 -563
  139. package/templates/skills/application/templates-backend.md +450 -450
  140. package/templates/skills/application/templates-frontend.md +531 -531
  141. package/templates/skills/application/templates-i18n.md +520 -520
  142. package/templates/skills/application/templates-seed.md +647 -647
  143. package/templates/skills/business-analyse/SKILL.md +191 -191
  144. package/templates/skills/business-analyse/questionnaire.md +283 -283
  145. package/templates/skills/business-analyse/templates-frd.md +477 -477
  146. package/templates/skills/business-analyse/templates-react.md +580 -580
  147. package/templates/skills/controller/SKILL.md +240 -240
  148. package/templates/skills/controller/postman-templates.md +614 -614
  149. package/templates/skills/controller/templates.md +1468 -1468
  150. package/templates/skills/documentation/SKILL.md +133 -133
  151. package/templates/skills/documentation/templates.md +476 -476
  152. package/templates/skills/feature-full/SKILL.md +838 -838
  153. package/templates/skills/notification/SKILL.md +555 -555
  154. package/templates/skills/ui-components/SKILL.md +870 -870
  155. package/templates/skills/workflow/SKILL.md +582 -582
  156. package/templates/test-web/api-health.json +38 -38
  157. package/templates/test-web/minimal.json +19 -19
  158. package/templates/test-web/npm-package.json +46 -46
  159. package/templates/test-web/seo-check.json +54 -54
@@ -1,555 +1,555 @@
1
- ---
2
- name: notification
3
- description: |
4
- Integre le systeme de notifications SmartStack dans le developpement.
5
- Utiliser ce skill quand:
6
- - L'utilisateur veut envoyer des notifications depuis une feature
7
- - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
- - Creation d'un module qui necessite des notifications utilisateur
9
- - Integration SignalR pour real-time
10
- Types: In-App, Email (via Workflow), Push (futur)
11
- ---
12
-
13
- # Skill Notification SmartStack
14
-
15
- > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
- > Les notifications push sont prevues mais pas encore implementees.
17
-
18
- ## QUAND CE SKILL S'ACTIVE
19
-
20
- Claude invoque automatiquement ce skill quand il detecte :
21
-
22
- | Declencheur | Exemple |
23
- |-------------|---------|
24
- | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
- | Mention notification | "Il faut alerter l'admin si..." |
26
- | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
- | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
- | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
-
30
- ---
31
-
32
- ## ARCHITECTURE NOTIFICATIONS
33
-
34
- ```
35
- ┌─────────────────────────────────────────────────────────────────────────────┐
36
- │ NOTIFICATION FLOW │
37
- ├─────────────────────────────────────────────────────────────────────────────┤
38
- │ │
39
- │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
- │ │ │ │
41
- │ ▼ │ │
42
- │ ┌─────────────────────────┐ │ │
43
- │ │ INotificationService │ │ │
44
- │ │ SendNotificationAsync │ │ │
45
- │ └────────────┬────────────┘ │ │
46
- │ │ │ │
47
- │ ┌───────┴───────┐ │ │
48
- │ ▼ ▼ │ │
49
- │ ┌─────────┐ ┌───────────────────┐ │ │
50
- │ │ DB │ │ SignalR Hub │ │ │
51
- │ │ (store) │ │ (real-time push) │ │ │
52
- │ └─────────┘ └─────────┬─────────┘ │ │
53
- │ │ │ │
54
- │ ▼ │ │
55
- │ ┌──────────────────┐ │ │
56
- │ │ Frontend Hook │ │ │
57
- │ │ useSignalR() │ │ │
58
- │ └─────────┬────────┘ │ │
59
- │ │ │ │
60
- │ ▼ │ │
61
- │ ┌──────────────────┐ │ │
62
- │ │ NotificationBell │ │ │
63
- │ │ (UI Component) │ │ │
64
- │ └──────────────────┘ │ │
65
- │ │ │
66
- │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
- │ │ │
68
- └─────────────────────────────────────────────────────────────────────────────┘
69
- ```
70
-
71
- ---
72
-
73
- ## TYPES DE NOTIFICATION
74
-
75
- ### Enum NotificationType
76
-
77
- | Type | Declencheur | Usage |
78
- |------|-------------|-------|
79
- | `TicketCreated` | Nouveau ticket | Support |
80
- | `TicketAssigned` | Ticket assigne | Support |
81
- | `TicketStatusChanged` | Changement statut | Support |
82
- | `TicketCommentAdded` | Nouveau commentaire | Support |
83
- | `TicketResolved` | Ticket resolu | Support |
84
- | `RoleAssigned` | Role attribue | Admin |
85
- | `RoleRemoved` | Role retire | Admin |
86
- | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
- | `SystemAnnouncement` | Annonce systeme | Global |
88
- | `AccountUpdated` | Compte modifie | User |
89
- | `SlaWarning` | SLA proche expiration | SLA |
90
- | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
- | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
-
93
- ### Ajouter un Nouveau Type
94
-
95
- ```csharp
96
- // 1. Domain/Support/Enums/NotificationType.cs
97
- public enum NotificationType
98
- {
99
- // ... existants ...
100
-
101
- // Ajouter le nouveau type
102
- $NEW_TYPE = XX, // Increment depuis le dernier
103
- }
104
-
105
- // 2. Utilisation dans le service
106
- await _notificationService.SendNotificationAsync(
107
- userId,
108
- NotificationType.$NEW_TYPE,
109
- title,
110
- message,
111
- relatedEntityType: "EntityName",
112
- relatedEntityId: entityId,
113
- actionUrl: "/path/to/entity"
114
- );
115
- ```
116
-
117
- ---
118
-
119
- ## WORKFLOW INTEGRATION
120
-
121
- ### ETAPE 1: Identifier le Besoin
122
-
123
- | Question | Reponse → Action |
124
- |----------|------------------|
125
- | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
- | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
- | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
- | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
-
130
- ### ETAPE 2: Injection du Service
131
-
132
- ```csharp
133
- // Dans le Controller ou Service
134
- public class MyController : ControllerBase
135
- {
136
- private readonly INotificationService _notificationService;
137
- private readonly ILogger<MyController> _logger;
138
-
139
- public MyController(
140
- INotificationService notificationService,
141
- ILogger<MyController> logger)
142
- {
143
- _notificationService = notificationService;
144
- _logger = logger;
145
- }
146
- }
147
- ```
148
-
149
- ### ETAPE 3: Envoi de Notification
150
-
151
- ```csharp
152
- // Notification simple
153
- await _notificationService.SendNotificationAsync(
154
- userId: targetUserId,
155
- type: NotificationType.TicketCreated,
156
- title: "Nouveau ticket",
157
- message: $"Le ticket #{ticket.Number} a ete cree",
158
- cancellationToken: ct
159
- );
160
-
161
- // Notification avec entite liee
162
- await _notificationService.SendNotificationAsync(
163
- userId: ticket.AssignedToId.Value,
164
- type: NotificationType.TicketAssigned,
165
- title: "Ticket assigne",
166
- message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
- relatedEntityType: "Ticket",
168
- relatedEntityId: ticket.Id,
169
- actionUrl: $"/support/tickets/{ticket.Id}",
170
- cancellationToken: ct
171
- );
172
-
173
- // Notification a plusieurs utilisateurs
174
- await _notificationService.SendNotificationsAsync(
175
- userIds: teamUserIds,
176
- type: NotificationType.SystemAnnouncement,
177
- title: "Annonce equipe",
178
- message: "Reunion planifiee demain a 10h",
179
- cancellationToken: ct
180
- );
181
-
182
- // Notification a un role
183
- await _notificationService.SendNotificationToRoleAsync(
184
- roleName: "Admin",
185
- type: NotificationType.SystemAnnouncement,
186
- title: "Alerte systeme",
187
- message: "Maintenance planifiee ce soir",
188
- cancellationToken: ct
189
- );
190
- ```
191
-
192
- ### ETAPE 4: Preferences Utilisateur
193
-
194
- ```csharp
195
- // Verifier si l'utilisateur veut etre notifie
196
- var (shouldEmail, shouldInApp, shouldPush) =
197
- await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
-
199
- if (shouldInApp)
200
- {
201
- await _notificationService.SendNotificationAsync(...);
202
- }
203
-
204
- if (shouldEmail)
205
- {
206
- // Declencher workflow email
207
- await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
- {
209
- ["ticketNumber"] = ticket.Number,
210
- ["ticketTitle"] = ticket.Title,
211
- ["userEmail"] = user.Email,
212
- ["userName"] = user.DisplayName
213
- });
214
- }
215
- ```
216
-
217
- ---
218
-
219
- ## FRONTEND INTEGRATION
220
-
221
- ### Hook useSignalR
222
-
223
- ```typescript
224
- // hooks/useSignalR.ts
225
- import { useSignalR } from '@/hooks/useSignalR';
226
-
227
- function MyComponent() {
228
- useSignalR({
229
- onNotification: (notification) => {
230
- // Notification recue en real-time
231
- console.log('New notification:', notification);
232
- toast.info(notification.title);
233
- },
234
- onUnreadCountUpdate: (count) => {
235
- // Mise a jour du badge
236
- setUnreadCount(count);
237
- },
238
- onPermissionsChanged: () => {
239
- // Permissions modifiees - recharger
240
- queryClient.invalidateQueries(['permissions']);
241
- },
242
- });
243
- }
244
- ```
245
-
246
- ### API Notifications
247
-
248
- ```typescript
249
- // services/api/notificationsApi.ts
250
- import { apiClient } from './apiClient';
251
-
252
- export const notificationsApi = {
253
- // Recuperer les non-lues
254
- getUnread: (limit = 10) =>
255
- apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
-
257
- // Recuperer avec pagination
258
- getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
- apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
- params: { page, pageSize, isRead }
261
- }),
262
-
263
- // Compter les non-lues
264
- getUnreadCount: () =>
265
- apiClient.get<{ count: number }>('/notifications/unread/count'),
266
-
267
- // Marquer comme lue
268
- markAsRead: (id: string) =>
269
- apiClient.post(`/notifications/${id}/read`),
270
-
271
- // Marquer toutes comme lues
272
- markAllAsRead: () =>
273
- apiClient.post('/notifications/read-all'),
274
-
275
- // Supprimer
276
- delete: (id: string) =>
277
- apiClient.delete(`/notifications/${id}`),
278
-
279
- // Supprimer toutes
280
- deleteAll: () =>
281
- apiClient.delete('/notifications'),
282
- };
283
- ```
284
-
285
- ### Composant NotificationBell
286
-
287
- ```tsx
288
- // components/notifications/NotificationBell.tsx
289
- import { Bell } from 'lucide-react';
290
- import { useSignalR } from '@/hooks/useSignalR';
291
- import { notificationsApi } from '@/services/api/notificationsApi';
292
-
293
- export function NotificationBell() {
294
- const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
- const [unreadCount, setUnreadCount] = useState(0);
296
- const [isOpen, setIsOpen] = useState(false);
297
-
298
- // Real-time updates
299
- useSignalR({
300
- onNotification: (notification) => {
301
- setNotifications(prev => [notification, ...prev]);
302
- setUnreadCount(prev => prev + 1);
303
- },
304
- onUnreadCountUpdate: (count) => {
305
- setUnreadCount(count);
306
- },
307
- });
308
-
309
- // Load initial data
310
- useEffect(() => {
311
- loadNotifications();
312
- }, []);
313
-
314
- return (
315
- <div className="relative">
316
- <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
- <Bell className="w-5 h-5" />
318
- {unreadCount > 0 && (
319
- <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
- {unreadCount > 99 ? '99+' : unreadCount}
321
- </span>
322
- )}
323
- </button>
324
-
325
- {isOpen && (
326
- <NotificationDropdown
327
- notifications={notifications}
328
- onMarkAsRead={handleMarkAsRead}
329
- onMarkAllAsRead={handleMarkAllAsRead}
330
- onDelete={handleDelete}
331
- />
332
- )}
333
- </div>
334
- );
335
- }
336
- ```
337
-
338
- ---
339
-
340
- ## TEMPLATES
341
-
342
- ### Template Service (Backend)
343
-
344
- ```csharp
345
- // Services/{Module}/{Module}Service.cs
346
-
347
- public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
- {
349
- // ... creation de l'entite ...
350
-
351
- // Notification au createur (confirmation)
352
- await _notificationService.SendNotificationAsync(
353
- _currentUser.Id,
354
- NotificationType.{Entity}Created,
355
- "{Entity} creee",
356
- $"Votre {entity.Name} a ete creee avec succes",
357
- relatedEntityType: "{Entity}",
358
- relatedEntityId: entity.Id,
359
- actionUrl: $"/{module}/{entity.Id}",
360
- ct);
361
-
362
- // Notification aux responsables
363
- if (entity.AssignedToId.HasValue)
364
- {
365
- await _notificationService.SendNotificationAsync(
366
- entity.AssignedToId.Value,
367
- NotificationType.{Entity}Assigned,
368
- "{Entity} assignee",
369
- $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
- relatedEntityType: "{Entity}",
371
- relatedEntityId: entity.Id,
372
- actionUrl: $"/{module}/{entity.Id}",
373
- ct);
374
- }
375
-
376
- return Result.Success();
377
- }
378
- ```
379
-
380
- ### Template Controller (Backend)
381
-
382
- ```csharp
383
- // Controllers/{Area}/{Module}Controller.cs
384
-
385
- [HttpPost]
386
- [RequirePermission(Permissions.{Module}.Create)]
387
- [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
- public async Task<ActionResult<{Entity}Dto>> Create(
389
- [FromBody] Create{Entity}Request request,
390
- CancellationToken ct)
391
- {
392
- var entity = await _service.CreateAsync(request, ct);
393
-
394
- // Notification automatique dans le service
395
- // OU notification ici si logique specifique au controller
396
-
397
- _logger.LogInformation(
398
- "User {UserId} created {EntityType} {EntityId}",
399
- _currentUser.Id, nameof({Entity}), entity.Id);
400
-
401
- return CreatedAtAction(
402
- nameof(GetById),
403
- new { id = entity.Id },
404
- entity);
405
- }
406
- ```
407
-
408
- ### Template Hook (Frontend)
409
-
410
- ```typescript
411
- // hooks/use{Module}Notifications.ts
412
-
413
- import { useSignalR } from '@/hooks/useSignalR';
414
- import { useQueryClient } from '@tanstack/react-query';
415
- import { toast } from 'sonner';
416
-
417
- export function use{Module}Notifications() {
418
- const queryClient = useQueryClient();
419
-
420
- useSignalR({
421
- onNotification: (notification) => {
422
- // Filtrer les notifications de ce module
423
- if (notification.relatedEntityType === '{Entity}') {
424
- // Invalider les queries pour refresh
425
- queryClient.invalidateQueries(['{module}']);
426
-
427
- // Toast avec action
428
- toast.info(notification.title, {
429
- description: notification.message,
430
- action: notification.actionUrl ? {
431
- label: 'Voir',
432
- onClick: () => navigate(notification.actionUrl),
433
- } : undefined,
434
- });
435
- }
436
- },
437
- });
438
- }
439
- ```
440
-
441
- ---
442
-
443
- ## CHECKLIST INTEGRATION
444
-
445
- ```
446
- □ Type de notification identifie (existant ou nouveau)
447
- □ Si nouveau type: ajoute a NotificationType enum
448
- □ Service injecte: INotificationService
449
- □ Notification envoyee avec tous les champs:
450
- □ userId (destinataire)
451
- □ type (NotificationType)
452
- □ title (titre court)
453
- □ message (description)
454
- □ relatedEntityType (optionnel)
455
- □ relatedEntityId (optionnel)
456
- □ actionUrl (optionnel)
457
- □ Logging ajoute (LogInformation)
458
- □ Frontend: useSignalR hook configure
459
- □ Frontend: toast ou UI update sur reception
460
- □ Si email requis: workflow configure
461
- ```
462
-
463
- ---
464
-
465
- ## PATTERNS AVANCES
466
-
467
- ### Pattern: Notification avec Delai (SLA)
468
-
469
- ```csharp
470
- // Pour les SLA warnings, utiliser un job Hangfire
471
- public class SlaNotificationJob
472
- {
473
- public async Task CheckSlaBreaches()
474
- {
475
- var tickets = await _context.Tickets
476
- .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
- .Where(t => !t.SlaWarningNotificationSent)
478
- .ToListAsync();
479
-
480
- foreach (var ticket in tickets)
481
- {
482
- await _notificationService.SendNotificationAsync(
483
- ticket.AssignedToId ?? ticket.CreatedById,
484
- NotificationType.SlaWarning,
485
- "SLA proche expiration",
486
- $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
- relatedEntityType: "Ticket",
488
- relatedEntityId: ticket.Id,
489
- actionUrl: $"/support/tickets/{ticket.Id}");
490
-
491
- ticket.SlaWarningNotificationSent = true;
492
- }
493
-
494
- await _context.SaveChangesAsync();
495
- }
496
- }
497
- ```
498
-
499
- ### Pattern: Notification Groupee
500
-
501
- ```csharp
502
- // Pour eviter le spam, grouper les notifications similaires
503
- public async Task SendBatchNotification(
504
- Guid userId,
505
- NotificationType type,
506
- List<EntitySummary> entities)
507
- {
508
- if (entities.Count == 1)
509
- {
510
- await _notificationService.SendNotificationAsync(
511
- userId, type,
512
- $"Nouveau {entities[0].Type}",
513
- entities[0].Description,
514
- relatedEntityType: entities[0].Type,
515
- relatedEntityId: entities[0].Id);
516
- }
517
- else
518
- {
519
- await _notificationService.SendNotificationAsync(
520
- userId, type,
521
- $"{entities.Count} nouveaux elements",
522
- $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
- actionUrl: "/dashboard/pending");
524
- }
525
- }
526
- ```
527
-
528
- ---
529
-
530
- ## REGLES ABSOLUES
531
-
532
- 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
- 2. **TOUJOURS** utiliser les types NotificationType enum
534
- 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
- 4. **TOUJOURS** ajouter actionUrl pour navigation
536
- 5. **TOUJOURS** logger les notifications envoyees
537
- 6. **TOUJOURS** respecter les preferences utilisateur
538
- 7. **JAMAIS** envoyer de notification sans destinataire valide
539
- 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
- 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
- 10. **JAMAIS** oublier le CancellationToken
542
-
543
- ---
544
-
545
- ## FICHIERS CLES
546
-
547
- | Fichier | Role |
548
- |---------|------|
549
- | `Domain/Support/Notification.cs` | Entite notification |
550
- | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
- | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
- | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
- | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
- | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
- | `web/src/components/notifications/NotificationBell.tsx` | UI Component |
1
+ ---
2
+ name: notification
3
+ description: |
4
+ Integre le systeme de notifications SmartStack dans le developpement.
5
+ Utiliser ce skill quand:
6
+ - L'utilisateur veut envoyer des notifications depuis une feature
7
+ - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
+ - Creation d'un module qui necessite des notifications utilisateur
9
+ - Integration SignalR pour real-time
10
+ Types: In-App, Email (via Workflow), Push (futur)
11
+ ---
12
+
13
+ # Skill Notification SmartStack
14
+
15
+ > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
+ > Les notifications push sont prevues mais pas encore implementees.
17
+
18
+ ## QUAND CE SKILL S'ACTIVE
19
+
20
+ Claude invoque automatiquement ce skill quand il detecte :
21
+
22
+ | Declencheur | Exemple |
23
+ |-------------|---------|
24
+ | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
+ | Mention notification | "Il faut alerter l'admin si..." |
26
+ | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
+ | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
+ | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
+
30
+ ---
31
+
32
+ ## ARCHITECTURE NOTIFICATIONS
33
+
34
+ ```
35
+ ┌─────────────────────────────────────────────────────────────────────────────┐
36
+ │ NOTIFICATION FLOW │
37
+ ├─────────────────────────────────────────────────────────────────────────────┤
38
+ │ │
39
+ │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
+ │ │ │ │
41
+ │ ▼ │ │
42
+ │ ┌─────────────────────────┐ │ │
43
+ │ │ INotificationService │ │ │
44
+ │ │ SendNotificationAsync │ │ │
45
+ │ └────────────┬────────────┘ │ │
46
+ │ │ │ │
47
+ │ ┌───────┴───────┐ │ │
48
+ │ ▼ ▼ │ │
49
+ │ ┌─────────┐ ┌───────────────────┐ │ │
50
+ │ │ DB │ │ SignalR Hub │ │ │
51
+ │ │ (store) │ │ (real-time push) │ │ │
52
+ │ └─────────┘ └─────────┬─────────┘ │ │
53
+ │ │ │ │
54
+ │ ▼ │ │
55
+ │ ┌──────────────────┐ │ │
56
+ │ │ Frontend Hook │ │ │
57
+ │ │ useSignalR() │ │ │
58
+ │ └─────────┬────────┘ │ │
59
+ │ │ │ │
60
+ │ ▼ │ │
61
+ │ ┌──────────────────┐ │ │
62
+ │ │ NotificationBell │ │ │
63
+ │ │ (UI Component) │ │ │
64
+ │ └──────────────────┘ │ │
65
+ │ │ │
66
+ │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
+ │ │ │
68
+ └─────────────────────────────────────────────────────────────────────────────┘
69
+ ```
70
+
71
+ ---
72
+
73
+ ## TYPES DE NOTIFICATION
74
+
75
+ ### Enum NotificationType
76
+
77
+ | Type | Declencheur | Usage |
78
+ |------|-------------|-------|
79
+ | `TicketCreated` | Nouveau ticket | Support |
80
+ | `TicketAssigned` | Ticket assigne | Support |
81
+ | `TicketStatusChanged` | Changement statut | Support |
82
+ | `TicketCommentAdded` | Nouveau commentaire | Support |
83
+ | `TicketResolved` | Ticket resolu | Support |
84
+ | `RoleAssigned` | Role attribue | Admin |
85
+ | `RoleRemoved` | Role retire | Admin |
86
+ | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
+ | `SystemAnnouncement` | Annonce systeme | Global |
88
+ | `AccountUpdated` | Compte modifie | User |
89
+ | `SlaWarning` | SLA proche expiration | SLA |
90
+ | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
+ | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
+
93
+ ### Ajouter un Nouveau Type
94
+
95
+ ```csharp
96
+ // 1. Domain/Support/Enums/NotificationType.cs
97
+ public enum NotificationType
98
+ {
99
+ // ... existants ...
100
+
101
+ // Ajouter le nouveau type
102
+ $NEW_TYPE = XX, // Increment depuis le dernier
103
+ }
104
+
105
+ // 2. Utilisation dans le service
106
+ await _notificationService.SendNotificationAsync(
107
+ userId,
108
+ NotificationType.$NEW_TYPE,
109
+ title,
110
+ message,
111
+ relatedEntityType: "EntityName",
112
+ relatedEntityId: entityId,
113
+ actionUrl: "/path/to/entity"
114
+ );
115
+ ```
116
+
117
+ ---
118
+
119
+ ## WORKFLOW INTEGRATION
120
+
121
+ ### ETAPE 1: Identifier le Besoin
122
+
123
+ | Question | Reponse → Action |
124
+ |----------|------------------|
125
+ | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
+ | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
+ | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
+ | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
+
130
+ ### ETAPE 2: Injection du Service
131
+
132
+ ```csharp
133
+ // Dans le Controller ou Service
134
+ public class MyController : ControllerBase
135
+ {
136
+ private readonly INotificationService _notificationService;
137
+ private readonly ILogger<MyController> _logger;
138
+
139
+ public MyController(
140
+ INotificationService notificationService,
141
+ ILogger<MyController> logger)
142
+ {
143
+ _notificationService = notificationService;
144
+ _logger = logger;
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### ETAPE 3: Envoi de Notification
150
+
151
+ ```csharp
152
+ // Notification simple
153
+ await _notificationService.SendNotificationAsync(
154
+ userId: targetUserId,
155
+ type: NotificationType.TicketCreated,
156
+ title: "Nouveau ticket",
157
+ message: $"Le ticket #{ticket.Number} a ete cree",
158
+ cancellationToken: ct
159
+ );
160
+
161
+ // Notification avec entite liee
162
+ await _notificationService.SendNotificationAsync(
163
+ userId: ticket.AssignedToId.Value,
164
+ type: NotificationType.TicketAssigned,
165
+ title: "Ticket assigne",
166
+ message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
+ relatedEntityType: "Ticket",
168
+ relatedEntityId: ticket.Id,
169
+ actionUrl: $"/support/tickets/{ticket.Id}",
170
+ cancellationToken: ct
171
+ );
172
+
173
+ // Notification a plusieurs utilisateurs
174
+ await _notificationService.SendNotificationsAsync(
175
+ userIds: teamUserIds,
176
+ type: NotificationType.SystemAnnouncement,
177
+ title: "Annonce equipe",
178
+ message: "Reunion planifiee demain a 10h",
179
+ cancellationToken: ct
180
+ );
181
+
182
+ // Notification a un role
183
+ await _notificationService.SendNotificationToRoleAsync(
184
+ roleName: "Admin",
185
+ type: NotificationType.SystemAnnouncement,
186
+ title: "Alerte systeme",
187
+ message: "Maintenance planifiee ce soir",
188
+ cancellationToken: ct
189
+ );
190
+ ```
191
+
192
+ ### ETAPE 4: Preferences Utilisateur
193
+
194
+ ```csharp
195
+ // Verifier si l'utilisateur veut etre notifie
196
+ var (shouldEmail, shouldInApp, shouldPush) =
197
+ await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
+
199
+ if (shouldInApp)
200
+ {
201
+ await _notificationService.SendNotificationAsync(...);
202
+ }
203
+
204
+ if (shouldEmail)
205
+ {
206
+ // Declencher workflow email
207
+ await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
+ {
209
+ ["ticketNumber"] = ticket.Number,
210
+ ["ticketTitle"] = ticket.Title,
211
+ ["userEmail"] = user.Email,
212
+ ["userName"] = user.DisplayName
213
+ });
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## FRONTEND INTEGRATION
220
+
221
+ ### Hook useSignalR
222
+
223
+ ```typescript
224
+ // hooks/useSignalR.ts
225
+ import { useSignalR } from '@/hooks/useSignalR';
226
+
227
+ function MyComponent() {
228
+ useSignalR({
229
+ onNotification: (notification) => {
230
+ // Notification recue en real-time
231
+ console.log('New notification:', notification);
232
+ toast.info(notification.title);
233
+ },
234
+ onUnreadCountUpdate: (count) => {
235
+ // Mise a jour du badge
236
+ setUnreadCount(count);
237
+ },
238
+ onPermissionsChanged: () => {
239
+ // Permissions modifiees - recharger
240
+ queryClient.invalidateQueries(['permissions']);
241
+ },
242
+ });
243
+ }
244
+ ```
245
+
246
+ ### API Notifications
247
+
248
+ ```typescript
249
+ // services/api/notificationsApi.ts
250
+ import { apiClient } from './apiClient';
251
+
252
+ export const notificationsApi = {
253
+ // Recuperer les non-lues
254
+ getUnread: (limit = 10) =>
255
+ apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
+
257
+ // Recuperer avec pagination
258
+ getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
+ apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
+ params: { page, pageSize, isRead }
261
+ }),
262
+
263
+ // Compter les non-lues
264
+ getUnreadCount: () =>
265
+ apiClient.get<{ count: number }>('/notifications/unread/count'),
266
+
267
+ // Marquer comme lue
268
+ markAsRead: (id: string) =>
269
+ apiClient.post(`/notifications/${id}/read`),
270
+
271
+ // Marquer toutes comme lues
272
+ markAllAsRead: () =>
273
+ apiClient.post('/notifications/read-all'),
274
+
275
+ // Supprimer
276
+ delete: (id: string) =>
277
+ apiClient.delete(`/notifications/${id}`),
278
+
279
+ // Supprimer toutes
280
+ deleteAll: () =>
281
+ apiClient.delete('/notifications'),
282
+ };
283
+ ```
284
+
285
+ ### Composant NotificationBell
286
+
287
+ ```tsx
288
+ // components/notifications/NotificationBell.tsx
289
+ import { Bell } from 'lucide-react';
290
+ import { useSignalR } from '@/hooks/useSignalR';
291
+ import { notificationsApi } from '@/services/api/notificationsApi';
292
+
293
+ export function NotificationBell() {
294
+ const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
+ const [unreadCount, setUnreadCount] = useState(0);
296
+ const [isOpen, setIsOpen] = useState(false);
297
+
298
+ // Real-time updates
299
+ useSignalR({
300
+ onNotification: (notification) => {
301
+ setNotifications(prev => [notification, ...prev]);
302
+ setUnreadCount(prev => prev + 1);
303
+ },
304
+ onUnreadCountUpdate: (count) => {
305
+ setUnreadCount(count);
306
+ },
307
+ });
308
+
309
+ // Load initial data
310
+ useEffect(() => {
311
+ loadNotifications();
312
+ }, []);
313
+
314
+ return (
315
+ <div className="relative">
316
+ <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
+ <Bell className="w-5 h-5" />
318
+ {unreadCount > 0 && (
319
+ <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
+ {unreadCount > 99 ? '99+' : unreadCount}
321
+ </span>
322
+ )}
323
+ </button>
324
+
325
+ {isOpen && (
326
+ <NotificationDropdown
327
+ notifications={notifications}
328
+ onMarkAsRead={handleMarkAsRead}
329
+ onMarkAllAsRead={handleMarkAllAsRead}
330
+ onDelete={handleDelete}
331
+ />
332
+ )}
333
+ </div>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## TEMPLATES
341
+
342
+ ### Template Service (Backend)
343
+
344
+ ```csharp
345
+ // Services/{Module}/{Module}Service.cs
346
+
347
+ public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
+ {
349
+ // ... creation de l'entite ...
350
+
351
+ // Notification au createur (confirmation)
352
+ await _notificationService.SendNotificationAsync(
353
+ _currentUser.Id,
354
+ NotificationType.{Entity}Created,
355
+ "{Entity} creee",
356
+ $"Votre {entity.Name} a ete creee avec succes",
357
+ relatedEntityType: "{Entity}",
358
+ relatedEntityId: entity.Id,
359
+ actionUrl: $"/{module}/{entity.Id}",
360
+ ct);
361
+
362
+ // Notification aux responsables
363
+ if (entity.AssignedToId.HasValue)
364
+ {
365
+ await _notificationService.SendNotificationAsync(
366
+ entity.AssignedToId.Value,
367
+ NotificationType.{Entity}Assigned,
368
+ "{Entity} assignee",
369
+ $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
+ relatedEntityType: "{Entity}",
371
+ relatedEntityId: entity.Id,
372
+ actionUrl: $"/{module}/{entity.Id}",
373
+ ct);
374
+ }
375
+
376
+ return Result.Success();
377
+ }
378
+ ```
379
+
380
+ ### Template Controller (Backend)
381
+
382
+ ```csharp
383
+ // Controllers/{Area}/{Module}Controller.cs
384
+
385
+ [HttpPost]
386
+ [RequirePermission(Permissions.{Module}.Create)]
387
+ [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
+ public async Task<ActionResult<{Entity}Dto>> Create(
389
+ [FromBody] Create{Entity}Request request,
390
+ CancellationToken ct)
391
+ {
392
+ var entity = await _service.CreateAsync(request, ct);
393
+
394
+ // Notification automatique dans le service
395
+ // OU notification ici si logique specifique au controller
396
+
397
+ _logger.LogInformation(
398
+ "User {UserId} created {EntityType} {EntityId}",
399
+ _currentUser.Id, nameof({Entity}), entity.Id);
400
+
401
+ return CreatedAtAction(
402
+ nameof(GetById),
403
+ new { id = entity.Id },
404
+ entity);
405
+ }
406
+ ```
407
+
408
+ ### Template Hook (Frontend)
409
+
410
+ ```typescript
411
+ // hooks/use{Module}Notifications.ts
412
+
413
+ import { useSignalR } from '@/hooks/useSignalR';
414
+ import { useQueryClient } from '@tanstack/react-query';
415
+ import { toast } from 'sonner';
416
+
417
+ export function use{Module}Notifications() {
418
+ const queryClient = useQueryClient();
419
+
420
+ useSignalR({
421
+ onNotification: (notification) => {
422
+ // Filtrer les notifications de ce module
423
+ if (notification.relatedEntityType === '{Entity}') {
424
+ // Invalider les queries pour refresh
425
+ queryClient.invalidateQueries(['{module}']);
426
+
427
+ // Toast avec action
428
+ toast.info(notification.title, {
429
+ description: notification.message,
430
+ action: notification.actionUrl ? {
431
+ label: 'Voir',
432
+ onClick: () => navigate(notification.actionUrl),
433
+ } : undefined,
434
+ });
435
+ }
436
+ },
437
+ });
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## CHECKLIST INTEGRATION
444
+
445
+ ```
446
+ □ Type de notification identifie (existant ou nouveau)
447
+ □ Si nouveau type: ajoute a NotificationType enum
448
+ □ Service injecte: INotificationService
449
+ □ Notification envoyee avec tous les champs:
450
+ □ userId (destinataire)
451
+ □ type (NotificationType)
452
+ □ title (titre court)
453
+ □ message (description)
454
+ □ relatedEntityType (optionnel)
455
+ □ relatedEntityId (optionnel)
456
+ □ actionUrl (optionnel)
457
+ □ Logging ajoute (LogInformation)
458
+ □ Frontend: useSignalR hook configure
459
+ □ Frontend: toast ou UI update sur reception
460
+ □ Si email requis: workflow configure
461
+ ```
462
+
463
+ ---
464
+
465
+ ## PATTERNS AVANCES
466
+
467
+ ### Pattern: Notification avec Delai (SLA)
468
+
469
+ ```csharp
470
+ // Pour les SLA warnings, utiliser un job Hangfire
471
+ public class SlaNotificationJob
472
+ {
473
+ public async Task CheckSlaBreaches()
474
+ {
475
+ var tickets = await _context.Tickets
476
+ .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
+ .Where(t => !t.SlaWarningNotificationSent)
478
+ .ToListAsync();
479
+
480
+ foreach (var ticket in tickets)
481
+ {
482
+ await _notificationService.SendNotificationAsync(
483
+ ticket.AssignedToId ?? ticket.CreatedById,
484
+ NotificationType.SlaWarning,
485
+ "SLA proche expiration",
486
+ $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
+ relatedEntityType: "Ticket",
488
+ relatedEntityId: ticket.Id,
489
+ actionUrl: $"/support/tickets/{ticket.Id}");
490
+
491
+ ticket.SlaWarningNotificationSent = true;
492
+ }
493
+
494
+ await _context.SaveChangesAsync();
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Pattern: Notification Groupee
500
+
501
+ ```csharp
502
+ // Pour eviter le spam, grouper les notifications similaires
503
+ public async Task SendBatchNotification(
504
+ Guid userId,
505
+ NotificationType type,
506
+ List<EntitySummary> entities)
507
+ {
508
+ if (entities.Count == 1)
509
+ {
510
+ await _notificationService.SendNotificationAsync(
511
+ userId, type,
512
+ $"Nouveau {entities[0].Type}",
513
+ entities[0].Description,
514
+ relatedEntityType: entities[0].Type,
515
+ relatedEntityId: entities[0].Id);
516
+ }
517
+ else
518
+ {
519
+ await _notificationService.SendNotificationAsync(
520
+ userId, type,
521
+ $"{entities.Count} nouveaux elements",
522
+ $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
+ actionUrl: "/dashboard/pending");
524
+ }
525
+ }
526
+ ```
527
+
528
+ ---
529
+
530
+ ## REGLES ABSOLUES
531
+
532
+ 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
+ 2. **TOUJOURS** utiliser les types NotificationType enum
534
+ 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
+ 4. **TOUJOURS** ajouter actionUrl pour navigation
536
+ 5. **TOUJOURS** logger les notifications envoyees
537
+ 6. **TOUJOURS** respecter les preferences utilisateur
538
+ 7. **JAMAIS** envoyer de notification sans destinataire valide
539
+ 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
+ 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
+ 10. **JAMAIS** oublier le CancellationToken
542
+
543
+ ---
544
+
545
+ ## FICHIERS CLES
546
+
547
+ | Fichier | Role |
548
+ |---------|------|
549
+ | `Domain/Support/Notification.cs` | Entite notification |
550
+ | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
+ | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
+ | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
+ | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
+ | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
+ | `web/src/components/notifications/NotificationBell.tsx` | UI Component |