---
name: notification
description: |
  Integre le systeme de notifications SmartStack dans le developpement.
  Utiliser ce skill quand:
  - L'utilisateur veut envoyer des notifications depuis une feature
  - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
  - Creation d'un module qui necessite des notifications utilisateur
  - Integration SignalR pour real-time
  Types: In-App, Email (via Workflow), Push (futur)
---

# Skill Notification SmartStack

> **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
> Les notifications push sont prevues mais pas encore implementees.

## QUAND CE SKILL S'ACTIVE

Claude invoque automatiquement ce skill quand il detecte :

| Declencheur | Exemple |
|-------------|---------|
| Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
| Mention notification | "Il faut alerter l'admin si..." |
| Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
| Real-time | "Afficher en temps reel les nouvelles notifications" |
| Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |

---

## ARCHITECTURE NOTIFICATIONS

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                         NOTIFICATION FLOW                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  [EVENT] ─────┬──────────────────────────────────────────────────────────┐   │
│               │                                                          │   │
│               ▼                                                          │   │
│  ┌─────────────────────────┐                                            │   │
│  │  INotificationService   │                                            │   │
│  │  SendNotificationAsync  │                                            │   │
│  └────────────┬────────────┘                                            │   │
│               │                                                          │   │
│       ┌───────┴───────┐                                                 │   │
│       ▼               ▼                                                 │   │
│  ┌─────────┐    ┌───────────────────┐                                   │   │
│  │   DB    │    │ SignalR Hub       │                                   │   │
│  │ (store) │    │ (real-time push)  │                                   │   │
│  └─────────┘    └─────────┬─────────┘                                   │   │
│                           │                                              │   │
│                           ▼                                              │   │
│                    ┌──────────────────┐                                 │   │
│                    │ Frontend Hook    │                                 │   │
│                    │ useSignalR()     │                                 │   │
│                    └─────────┬────────┘                                 │   │
│                              │                                          │   │
│                              ▼                                          │   │
│                    ┌──────────────────┐                                 │   │
│                    │ NotificationBell │                                 │   │
│                    │ (UI Component)   │                                 │   │
│                    └──────────────────┘                                 │   │
│                                                                          │   │
│  [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP    │   │
│                                                                          │   │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## TYPES DE NOTIFICATION

### Enum NotificationType

| Type | Declencheur | Usage |
|------|-------------|-------|
| `TicketCreated` | Nouveau ticket | Support |
| `TicketAssigned` | Ticket assigne | Support |
| `TicketStatusChanged` | Changement statut | Support |
| `TicketCommentAdded` | Nouveau commentaire | Support |
| `TicketResolved` | Ticket resolu | Support |
| `RoleAssigned` | Role attribue | Admin |
| `RoleRemoved` | Role retire | Admin |
| `RolePermissionsChanged` | Permissions modifiees | Admin |
| `SystemAnnouncement` | Annonce systeme | Global |
| `AccountUpdated` | Compte modifie | User |
| `SlaWarning` | SLA proche expiration | SLA |
| `SlaResponseBreached` | SLA reponse depasse | SLA |
| `SlaResolutionBreached` | SLA resolution depasse | SLA |

### Ajouter un Nouveau Type

```csharp
// 1. Domain/Support/Enums/NotificationType.cs
public enum NotificationType
{
    // ... existants ...

    // Ajouter le nouveau type
    $NEW_TYPE = XX,  // Increment depuis le dernier
}

// 2. Utilisation dans le service
await _notificationService.SendNotificationAsync(
    userId,
    NotificationType.$NEW_TYPE,
    title,
    message,
    relatedEntityType: "EntityName",
    relatedEntityId: entityId,
    actionUrl: "/path/to/entity"
);
```

---

## WORKFLOW INTEGRATION

### ETAPE 1: Identifier le Besoin

| Question | Reponse → Action |
|----------|------------------|
| L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
| L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
| L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
| L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |

### ETAPE 2: Injection du Service

```csharp
// Dans le Controller ou Service
public class MyController : ControllerBase
{
    private readonly INotificationService _notificationService;
    private readonly ILogger<MyController> _logger;

    public MyController(
        INotificationService notificationService,
        ILogger<MyController> logger)
    {
        _notificationService = notificationService;
        _logger = logger;
    }
}
```

### ETAPE 3: Envoi de Notification

```csharp
// Notification simple
await _notificationService.SendNotificationAsync(
    userId: targetUserId,
    type: NotificationType.TicketCreated,
    title: "Nouveau ticket",
    message: $"Le ticket #{ticket.Number} a ete cree",
    cancellationToken: ct
);

// Notification avec entite liee
await _notificationService.SendNotificationAsync(
    userId: ticket.AssignedToId.Value,
    type: NotificationType.TicketAssigned,
    title: "Ticket assigne",
    message: $"Le ticket #{ticket.Number} vous a ete assigne",
    relatedEntityType: "Ticket",
    relatedEntityId: ticket.Id,
    actionUrl: $"/support/tickets/{ticket.Id}",
    cancellationToken: ct
);

// Notification a plusieurs utilisateurs
await _notificationService.SendNotificationsAsync(
    userIds: teamUserIds,
    type: NotificationType.SystemAnnouncement,
    title: "Annonce equipe",
    message: "Reunion planifiee demain a 10h",
    cancellationToken: ct
);

// Notification a un role
await _notificationService.SendNotificationToRoleAsync(
    roleName: "Admin",
    type: NotificationType.SystemAnnouncement,
    title: "Alerte systeme",
    message: "Maintenance planifiee ce soir",
    cancellationToken: ct
);
```

### ETAPE 4: Preferences Utilisateur

```csharp
// Verifier si l'utilisateur veut etre notifie
var (shouldEmail, shouldInApp, shouldPush) =
    await _notificationService.ShouldNotifyAsync(userId, notificationType);

if (shouldInApp)
{
    await _notificationService.SendNotificationAsync(...);
}

if (shouldEmail)
{
    // Declencher workflow email
    await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
    {
        ["ticketNumber"] = ticket.Number,
        ["ticketTitle"] = ticket.Title,
        ["userEmail"] = user.Email,
        ["userName"] = user.DisplayName
    });
}
```

---

## FRONTEND INTEGRATION

### Hook useSignalR

```typescript
// hooks/useSignalR.ts
import { useSignalR } from '@/hooks/useSignalR';

function MyComponent() {
  useSignalR({
    onNotification: (notification) => {
      // Notification recue en real-time
      console.log('New notification:', notification);
      toast.info(notification.title);
    },
    onUnreadCountUpdate: (count) => {
      // Mise a jour du badge
      setUnreadCount(count);
    },
    onPermissionsChanged: () => {
      // Permissions modifiees - recharger
      queryClient.invalidateQueries(['permissions']);
    },
  });
}
```

### API Notifications

```typescript
// services/api/notificationsApi.ts
import { apiClient } from './apiClient';

export const notificationsApi = {
  // Recuperer les non-lues
  getUnread: (limit = 10) =>
    apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),

  // Recuperer avec pagination
  getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
    apiClient.get<PagedResult<NotificationDto>>('/notifications', {
      params: { page, pageSize, isRead }
    }),

  // Compter les non-lues
  getUnreadCount: () =>
    apiClient.get<{ count: number }>('/notifications/unread/count'),

  // Marquer comme lue
  markAsRead: (id: string) =>
    apiClient.post(`/notifications/${id}/read`),

  // Marquer toutes comme lues
  markAllAsRead: () =>
    apiClient.post('/notifications/read-all'),

  // Supprimer
  delete: (id: string) =>
    apiClient.delete(`/notifications/${id}`),

  // Supprimer toutes
  deleteAll: () =>
    apiClient.delete('/notifications'),
};
```

### Composant NotificationBell

```tsx
// components/notifications/NotificationBell.tsx
import { Bell } from 'lucide-react';
import { useSignalR } from '@/hooks/useSignalR';
import { notificationsApi } from '@/services/api/notificationsApi';

export function NotificationBell() {
  const [notifications, setNotifications] = useState<NotificationDto[]>([]);
  const [unreadCount, setUnreadCount] = useState(0);
  const [isOpen, setIsOpen] = useState(false);

  // Real-time updates
  useSignalR({
    onNotification: (notification) => {
      setNotifications(prev => [notification, ...prev]);
      setUnreadCount(prev => prev + 1);
    },
    onUnreadCountUpdate: (count) => {
      setUnreadCount(count);
    },
  });

  // Load initial data
  useEffect(() => {
    loadNotifications();
  }, []);

  return (
    <div className="relative">
      <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
        <Bell className="w-5 h-5" />
        {unreadCount > 0 && (
          <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">
            {unreadCount > 99 ? '99+' : unreadCount}
          </span>
        )}
      </button>

      {isOpen && (
        <NotificationDropdown
          notifications={notifications}
          onMarkAsRead={handleMarkAsRead}
          onMarkAllAsRead={handleMarkAllAsRead}
          onDelete={handleDelete}
        />
      )}
    </div>
  );
}
```

---

## TEMPLATES

### Template Service (Backend)

```csharp
// Services/{Module}/{Module}Service.cs

public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
{
    // ... creation de l'entite ...

    // Notification au createur (confirmation)
    await _notificationService.SendNotificationAsync(
        _currentUser.Id,
        NotificationType.{Entity}Created,
        "{Entity} creee",
        $"Votre {entity.Name} a ete creee avec succes",
        relatedEntityType: "{Entity}",
        relatedEntityId: entity.Id,
        actionUrl: $"/{module}/{entity.Id}",
        ct);

    // Notification aux responsables
    if (entity.AssignedToId.HasValue)
    {
        await _notificationService.SendNotificationAsync(
            entity.AssignedToId.Value,
            NotificationType.{Entity}Assigned,
            "{Entity} assignee",
            $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
            relatedEntityType: "{Entity}",
            relatedEntityId: entity.Id,
            actionUrl: $"/{module}/{entity.Id}",
            ct);
    }

    return Result.Success();
}
```

### Template Controller (Backend)

```csharp
// Controllers/{Area}/{Module}Controller.cs

[HttpPost]
[RequirePermission(Permissions.{Module}.Create)]
[ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
public async Task<ActionResult<{Entity}Dto>> Create(
    [FromBody] Create{Entity}Request request,
    CancellationToken ct)
{
    var entity = await _service.CreateAsync(request, ct);

    // Notification automatique dans le service
    // OU notification ici si logique specifique au controller

    _logger.LogInformation(
        "User {UserId} created {EntityType} {EntityId}",
        _currentUser.Id, nameof({Entity}), entity.Id);

    return CreatedAtAction(
        nameof(GetById),
        new { id = entity.Id },
        entity);
}
```

### Template Hook (Frontend)

```typescript
// hooks/use{Module}Notifications.ts

import { useSignalR } from '@/hooks/useSignalR';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';

export function use{Module}Notifications() {
  const queryClient = useQueryClient();

  useSignalR({
    onNotification: (notification) => {
      // Filtrer les notifications de ce module
      if (notification.relatedEntityType === '{Entity}') {
        // Invalider les queries pour refresh
        queryClient.invalidateQueries(['{module}']);

        // Toast avec action
        toast.info(notification.title, {
          description: notification.message,
          action: notification.actionUrl ? {
            label: 'Voir',
            onClick: () => navigate(notification.actionUrl),
          } : undefined,
        });
      }
    },
  });
}
```

---

## CHECKLIST INTEGRATION

```
□ Type de notification identifie (existant ou nouveau)
□ Si nouveau type: ajoute a NotificationType enum
□ Service injecte: INotificationService
□ Notification envoyee avec tous les champs:
  □ userId (destinataire)
  □ type (NotificationType)
  □ title (titre court)
  □ message (description)
  □ relatedEntityType (optionnel)
  □ relatedEntityId (optionnel)
  □ actionUrl (optionnel)
□ Logging ajoute (LogInformation)
□ Frontend: useSignalR hook configure
□ Frontend: toast ou UI update sur reception
□ Si email requis: workflow configure
```

---

## PATTERNS AVANCES

### Pattern: Notification avec Delai (SLA)

```csharp
// Pour les SLA warnings, utiliser un job Hangfire
public class SlaNotificationJob
{
    public async Task CheckSlaBreaches()
    {
        var tickets = await _context.Tickets
            .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
            .Where(t => !t.SlaWarningNotificationSent)
            .ToListAsync();

        foreach (var ticket in tickets)
        {
            await _notificationService.SendNotificationAsync(
                ticket.AssignedToId ?? ticket.CreatedById,
                NotificationType.SlaWarning,
                "SLA proche expiration",
                $"Le ticket #{ticket.Number} expire dans 30 minutes",
                relatedEntityType: "Ticket",
                relatedEntityId: ticket.Id,
                actionUrl: $"/support/tickets/{ticket.Id}");

            ticket.SlaWarningNotificationSent = true;
        }

        await _context.SaveChangesAsync();
    }
}
```

### Pattern: Notification Groupee

```csharp
// Pour eviter le spam, grouper les notifications similaires
public async Task SendBatchNotification(
    Guid userId,
    NotificationType type,
    List<EntitySummary> entities)
{
    if (entities.Count == 1)
    {
        await _notificationService.SendNotificationAsync(
            userId, type,
            $"Nouveau {entities[0].Type}",
            entities[0].Description,
            relatedEntityType: entities[0].Type,
            relatedEntityId: entities[0].Id);
    }
    else
    {
        await _notificationService.SendNotificationAsync(
            userId, type,
            $"{entities.Count} nouveaux elements",
            $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
            actionUrl: "/dashboard/pending");
    }
}
```

---

## REGLES ABSOLUES

1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
2. **TOUJOURS** utiliser les types NotificationType enum
3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
4. **TOUJOURS** ajouter actionUrl pour navigation
5. **TOUJOURS** logger les notifications envoyees
6. **TOUJOURS** respecter les preferences utilisateur
7. **JAMAIS** envoyer de notification sans destinataire valide
8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
10. **JAMAIS** oublier le CancellationToken

---

## FICHIERS CLES

| Fichier | Role |
|---------|------|
| `Domain/Support/Notification.cs` | Entite notification |
| `Domain/Support/Enums/NotificationType.cs` | Types de notification |
| `Application/Common/Interfaces/INotificationService.cs` | Interface service |
| `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
| `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
| `web/src/hooks/useSignalR.ts` | Hook frontend |
| `web/src/components/notifications/NotificationBell.tsx` | UI Component |
