Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
Refonte de l'écran Journal d'activité (MalioDataTable + drawer de filtre)
Date : 2026-06-24
Branche : feature/SIRH-41-ajouter-plus-d-info-dans-le-journal-d-activite
Problème / objectif
L'écran frontend/pages/audit-logs.vue (journal d'activité, ROLE_SUPER_ADMIN) est aujourd'hui
fait main : <select>/<input> natifs, tableau en grille CSS, lignes dépliables affichant le diff
JSON brut, pagination « précédent/suivant » figée à 50/page. Il faut le moderniser :
- Passer le tableau en
MalioDataTable(1er usage dans SIRH). - Mettre les filtres dans un drawer, sur le même principe que STARSEED (les écrans de liste
modules/.../pages/.../index.vue:MalioDrawer+MalioAccordion, état brouillon/appliqué, footer Réinitialiser/Appliquer, badge de compteur de filtres actifs). - Passer tous les composants de l'écran en composants Malio quand l'équivalent existe.
- Exploiter les nouvelles données forensiques (IP, appareil, User-Agent, device id) déjà captées par le backend.
Référence de pattern
- STARSEED, écran canonique :
/home/m-tristan/workspace/Starseed/frontend/modules/commercial/pages/clients/index.vue(drawer de filtre,MalioAccordion, brouillon→appliqué,MalioDataTable, badge compteur). - Adaptations SIRH : libellés en français en dur (convention des drawers SIRH existants —
employees/index.vue,sites.vue— pas d'i18n comme STARSEED) ; filtres non persistés en URL (comme STARSEED et l'écran actuel). - Malio
@malio/layer-ui1.7.15 (docnode_modules/@malio/layer-ui/COMPONENTS.md).
Périmètre
Inclus : refonte complète de audit-logs.vue (tableau, filtres, détail) + évolutions backend
nécessaires (perPage + nouveaux filtres) + DTO TS + docs.
Exclus : toute autre page ; l'audit reste ROLE_SUPER_ADMIN ; pas de doc in-app (outil caché,
aucun article existant — décision déjà prise au lot précédent).
A. Tableau — MalioDataTable
API (1.7.15) : :columns ({key,label}[]), :items, :total-items, v-model:page,
v-model:per-page, :per-page-options, row-clickable, événements row-click /
update:page / update:per-page, slots #cell-{key} et #empty.
Colonnes :
| key | label | rendu |
|---|---|---|
createdAt |
Date action | JJ/MM/AAAA HH:MM (déjà formaté par le provider) |
username |
Utilisateur | texte brut |
action |
Action | badge couleur via #cell-action (create=vert, update=bleu, delete=rouge, validate=violet, site_validate=indigo, défaut=neutre) |
entityType |
Type | libellé FR via #cell-entityType (work_hour→Heures, absence→Absence, employee→Employé, contract_suspension→Suspension, rtt_payment→RTT, fractioned_days→Fract., paid_leave_days→Congés payés, week_comment→Commentaire) |
employeeName |
Employé | nom ou — |
deviceLabel |
Appareil | deviceLabel ou — |
description |
Description | tronqué (truncate + title) via #cell-description |
:per-page-options="[25, 50, 100]",perPagepar défaut 50.@row-click→ ouvre le drawer de détail avec la ligne cliquée.:items= directement lesAuditLogde la page courante (le DTO porte déjà toutes les clés ; leskeyde colonnes correspondent aux champs).
B. Drawer de détail (clic ligne)
MalioDrawer (droite, drawer-class="max-w-xl"), titre #header = « Détail de l'action ».
Contenu (lecture seule, sections) :
- Méta : Utilisateur, Employé, Date action, Date affectée, Action (badge), Type (libellé).
- Contexte technique : IP (
ipAddress), Appareil (deviceLabel), User-Agent brut (userAgent, enbreak-all/petite police), Device id (deviceId). Champs nuls →—. - Changements : si
changesnon nul, rendu lisible — pour chaque clé présente dansold/new, une ligneclé : ancienne → nouvelle(au lieu du double bloc JSON brut actuel). Helper frontformatChanges(changes)qui fusionne les clés deoldetnew. Sichangesnul → « Aucun détail de modification ».
État : selectedLog: AuditLog | null + detailOpen: boolean. Fermeture standard MalioDrawer.
C. Drawer de filtre (principe STARSEED)
Bouton « Filtrer » (MalioButton variant="tertiary" icon-name="mdi:tune") dans la barre de titre ;
son label porte le compteur de filtres actifs (Filtrer (N) si N>0).
MalioDrawer (drawer-class="max-w-[450px]", body-class="p-0",
footer-class="justify-between border-t border-black p-6"), titre #header = « Filtres ».
Corps en MalioAccordion (un MalioAccordionItem par section) :
| Section | Composant | Champ filtre |
|---|---|---|
| Période | MalioDateRange (v-model = {start,end} ISO) |
from/to sur affectedDate (sémantique actuelle conservée) |
| Employé | MalioSelect (options = employés chargés au mount) |
employeeId (valeur unique) |
| Type d'entité | liste de MalioCheckbox (multi) |
entityType[] |
| Action | liste de MalioCheckbox (multi) |
action[] |
| Utilisateur / compte | MalioInputText (icon mdi:magnify) |
username (ILIKE partiel) |
| IP | MalioInputText |
ip (ILIKE partiel) |
| Appareil | MalioInputText |
device (ILIKE partiel sur device_label OU device_id) |
Footer : MalioButton variant="tertiary" Réinitialiser (gauche) + MalioButton variant="primary"
Appliquer (droite).
État brouillon → appliqué (pattern STARSEED) :
draft*refs (éditées dans le drawer) etapplied*refs (pilotent le fetch).openFilters(): copieapplied*→draft*puis ouvre.applyFilters(): copiedraft*→applied*, remetpage=1, refetch, ferme le drawer.resetFilters(): videdraft*etapplied*, remetpage=1, refetch, laisse le drawer ouvert.activeFilterCount(computed surapplied*) → badge bouton.- Helpers
toggle(arrayRef, value, selected)pour les multi-select. - Options Type d'entité / Action = listes statiques (mêmes codes que le provider) ; options Employé
chargées une fois au
onMounted(réutiliser le chargement employés déjà fait par l'écran actuel).
D. Composable useAuditLogsList
Composable spécifique à l'écran (frontend/composables/useAuditLogsList.ts) — pas de
usePaginatedList générique (un seul consommateur → YAGNI). Expose :
- état :
items,total,page,perPage,loading, lesdraft*/applied*,activeFilterCount,employeeOptions. - actions :
load()(fetch avec filtres appliqués + page/perPage),goToPage(n),setPerPage(n),openFilters(),applyFilters(),resetFilters(),loadEmployeeOptions(). load()doit ignorer les réponses périmées (garde anti-race : compteur de requête, on jette les réponses dont l'index n'est pas le dernier émis).
La page audit-logs.vue se réduit à : barre de titre (titre + bouton Filtrer), MalioDataTable,
drawer filtre, drawer détail — toute la logique vit dans le composable.
E. Backend
frontend/services/dto/audit-log.ts (AuditLogFilters)
Étendre :
export type AuditLogFilters = {
employeeId?: number
from?: string
to?: string
entityType?: string[]
action?: string[]
username?: string
ip?: string
device?: string
page?: number
perPage?: number
}
fetchAuditLogs sérialise les tableaux en entityType[]/action[] (syntaxe PHP) et n'inclut que
les filtres non vides.
src/ApiResource/AuditLogResource.php
Ajouter les QueryParameter : perPage, username, ip, device, action (entityType existe
déjà). (Les QueryParameter sont surtout documentaires : le provider lit $request->query.)
src/State/AuditLogProvider.php
- Lire
perPage(défaut 50, clampé à un ensemble autorisé[25,50,100], fallback 50 ; borne dure). - Lire
username,ip,device(chaînes,nullsi vide). - Lire
entityTypeetactionen tableaux ($request->query->all('entityType')/->all('action')),null/[]si absent. Conserver la rétro-compat : sientityTypearrive en scalaire, le normaliser en tableau à un élément. - Passer le tout au repository ;
perPageremplace la constantePER_PAGE. La réponse renvoieperPageréel.
src/Repository/Contract/AuditLogReadRepositoryInterface.php + AuditLogRepository.php
Faire évoluer findByFilters / countByFilters :
findByFilters(
?int $employeeId,
?DateTimeImmutable $from,
?DateTimeImmutable $to,
?array $entityTypes, // list<string>|null
?array $actions, // list<string>|null
?string $username,
?string $ip,
?string $device,
int $limit,
int $offset,
): array
countByFilters(... mêmes filtres ...): int
Clauses : employeeId =, dates BETWEEN sur affectedDate (inchangé), entityTypes/actions
IN (:...) si non vides, username/ip ILIKE %v% (paramètre échappé), device →
(device_label ILIKE :d OR device_id ILIKE :d). Tri inchangé (createdAt DESC).
Mutualiser la construction des critères entre les deux méthodes (méthode privée
applyFilters(QueryBuilder, ...)) pour rester DRY.
Tests
- Backend :
AuditLogProviderTestétendu — vérifier queperPage,username,ip,device,entityType[],action[]sont lus et transmis au repository (repo stubbé, on asserte les arguments via un spy), et queperPagehors liste retombe sur 50. - Backend : test repository des nouvelles clauses si un test repository existe ; sinon couvrir via le provider (le repo réel n'est pas unit-testé aujourd'hui — ne pas introduire d'intégration DB).
- Front : pas de test auto (convention SIRH, pas de build) — revue de diff. Le composable
useAuditLogsListreste pur/réactif et testable manuellement.
Documentation
doc/audit-logging.md: section « Filtres disponibles » mise à jour (employé, période, type[], action[], utilisateur, IP, appareil ; pagination perPage) + mention du drawer et du drawer de détail.CLAUDE.md: compléter la puce « Contexte forensique » / journal pour noter l'écran refondu (MalioDataTable, drawer de filtre façon STARSEED, drawer de détail, filtres back username/ip/device/action[]/entityType[]/perPage).
Risques / notes
- 1er
MalioDataTablede SIRH : valider le rendu (le composant gère sa propre pagination/markup ; ne pas réappliquer le gabarit grille maison du CLAUDE.md à ce tableau). MalioDateRangefiltreaffectedDate(cohérent avec l'existant) ; ne pas confondre aveccreatedAt(date d'action affichée en colonne).- Évolution de signature de
AuditLogReadRepositoryInterface: mettre à jour l'implémentation et le provider dans le même lot (ils sont les seuls consommateurs).