@movalib/movalib-commons 1.68.32 → 1.68.34

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.
@@ -0,0 +1,416 @@
1
+ # Feature : Ordre de Réparation (OR)
2
+
3
+ > **Pour Claude Code** : lis ce fichier en entier avant de commencer. Chaque partie est indépendante et validable. Ne passe à la partie suivante qu'après confirmation.
4
+ > Base-toi sur l'implémentation existante des `MyCalendarQuote, MyCalendarEventMemoList` en zone mémos pour tout ce qui n'est pas explicitement spécifié.
5
+
6
+ ---
7
+
8
+ ## Contexte général
9
+
10
+ - **Feature flag** : `garage.settings[]` contient `CUSTOM_REPAIR_ORDER` avec `valueBool: true`. Toute l'UI et l'accessibilité de cette feature sont conditionnées par ce flag. cf : getSettingBool(garage?.settings ?? [], CodeSettings.CUSTOM_REPAIR_ORDER)
11
+ - **Zone concernée** : Zone mémos (sidebar/panel des mémos sur un événement): MyCalendarEventMemoList
12
+ - **Objet métier** : Un OR est un objet de type `Event` avec `eventType = "REPAIR_ORDER"`
13
+ - **États d'un OR** : `DRAFT` | `CLOSED`
14
+
15
+ ---
16
+
17
+ ## Endpoints
18
+
19
+ ```
20
+ GET /garage/:garageId/repair-order
21
+ POST /garage/:garageId/repair-order
22
+ PATCH /garage/:garageId/repair-order/:id
23
+ DELETE /garage/:garageId/repair-order/:id
24
+
25
+ // Transformations
26
+ POST /garage/:garageId/repair-order/:id/quote
27
+ POST /garage/:garageId/repair-order/:id/event
28
+
29
+ // Transformation en facture : POST standard de facture avec eventId en plus ( InvoiceService.createInvoice() avec eventID ( id de l'OR))
30
+ // => l'API gère automatiquement le passage de l'OR en CLOSED
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Découpage en parties
36
+
37
+ | Partie | Contenu | Statut |
38
+ |---|---|---|
39
+ | **1** | Types TypeScript & constantes | ☐ |
40
+ | **2** | Service API (RepairOrderService) | ☐ |
41
+ | **3** | Toggle devis / OR dans la zone mémos | ☐ |
42
+ | **4** | Liste des OR DRAFT + dialog OR CLOSED | ☐ |
43
+ | **5** | Formulaire création / édition OR | ☐ |
44
+ | **6** | Actions de transformation (devis / facture / RDV) | ☐ |
45
+
46
+ ---
47
+
48
+ ---
49
+
50
+ # PARTIE 1 — Types TypeScript & Constantes
51
+
52
+ > **Objectif** : Définir les types et constantes nécessaires à toute la feature.
53
+ > **Fichiers à créer/modifier** : cherche où sont définis le type `Event` et les types liés aux mémos pour s'aligner.
54
+
55
+ ## 1.1 — Constante de setting
56
+
57
+ CodeSettings.CUSTOM_REPAIR_ORDER
58
+
59
+ ## 1.2 — Enum état OR
60
+
61
+ ```ts
62
+ export enum RepairOrderState {
63
+ DRAFT = 'DRAFT',
64
+ CLOSED = 'CLOSED',
65
+ }
66
+ ```
67
+
68
+ ## 1.3 — Type RepairOrder
69
+
70
+ ```ts
71
+ // Se baser sur le type Event.EventType = "QUOTE existant en zone mémos comme référence de structure.
72
+ // Ajouter les champs spécifiques à l'OR :
73
+
74
+ export interface RepairOrder {
75
+ id: string;
76
+ eventType: 'REPAIR_ORDER';
77
+ state: RepairOrderState;
78
+ repairerId?: string; // ID de l'utilisateur réparateur (composant existant) EmployeeChoice.tsx
79
+ // ... reprendre tous les champs de QuoteEventDialog + formEventLight
80
+ // Cherche l'interface QuoteEventDialog pour compléter fidèlement
81
+ }
82
+ ```
83
+
84
+ ## 1.4 — Type payload création/édition
85
+
86
+ ```ts
87
+ // Même pattern que ( QuoteEventDialog.tsx fichier qui gere les appels API pour les devis de zone mémos)
88
+ export interface RepairOrderPayload {
89
+ state: RepairOrderState;
90
+ repairerId?: string;
91
+ // ... aligner sur QuotePayload
92
+ }
93
+ ```
94
+
95
+ ## ✅ Validation Partie 1
96
+ - [ ] Constante CUSTOM_REPAIR_ORDER ajoutée
97
+ - [ ] Enum RepairOrderState créé
98
+ - [ ] Interface RepairOrder créée (alignée sur Quote)
99
+ - [ ] Interface RepairOrderPayload créée
100
+
101
+ ---
102
+
103
+ ---
104
+
105
+ # PARTIE 2 — Service API
106
+
107
+ > **Objectif** : Créer le service `RepairOrderService` avec tous les appels API.
108
+ > **Fichiers à créer** : cherche où est défini `src/services/GarageService.ts` (ou équivalent mémos) et créer `RepairOrderService` au même endroit avec le même pattern.
109
+ Attention je veux le meme systeme de pagination qui est présent pour les devis. cf: MyCalendarQuote.tsx
110
+ >
111
+ ## 2.1 — CRUD de base
112
+
113
+ ```ts
114
+ // RepairOrderService.ts
115
+
116
+ const BASE = (garageId: string) =>
117
+ `${API_BASE_URL}/garage/${garageId}/repair-order`;
118
+
119
+ // GET — liste tous les OR du garage
120
+ getAll(garageId: string): Promise<RepairOrder[]>
121
+
122
+ // POST — création
123
+ create(garageId: string, payload: RepairOrderPayload): Promise<RepairOrder>
124
+
125
+ // PATCH — modification (DRAFT uniquement)
126
+ update(garageId: string, id: string, payload: Partial<RepairOrderPayload>): Promise<RepairOrder>
127
+
128
+ // DELETE — suppression (DRAFT uniquement)
129
+ delete(garageId: string, id: string): Promise<void>
130
+ ```
131
+
132
+ ## 2.2 — Transformations
133
+
134
+ ```ts
135
+ // → Devis
136
+ toQuote(garageId: string, repairOrderId: string): Promise<Quote>
137
+ // POST /garage/:garageId/repair-order/:id/quote
138
+
139
+ // → Facture
140
+ // Utiliser le POST standard de création de facture
141
+ // Ajouter eventId = repairOrderId dans le payload
142
+ // Cherche comment est fait le POST facture existant et ajouter le champ eventId
143
+ // L'API gère automatiquement le passage de l'OR en CLOSED
144
+
145
+ // → RDV (Event)
146
+ toEvent(garageId: string, repairOrderId: string): Promise<Event>
147
+ // POST /garage/:garageId/repair-order/:id/event
148
+ ```
149
+
150
+ ## ✅ Validation Partie 2
151
+ - [ ] Service créé avec le même pattern que QuoteService
152
+ - [ ] CRUD complet implémenté
153
+ - [ ] Les 3 méthodes de transformation implémentées
154
+ - [ ] Gestion des erreurs alignée sur le pattern existant
155
+
156
+ ---
157
+
158
+ ---
159
+
160
+ # PARTIE 3 — Toggle Devis / OR dans la zone mémos
161
+
162
+ > **Objectif** : Ajouter un toggle dans le header de la zone mémos pour switcher entre la vue Devis et la vue OR.
163
+ > **Condition d'affichage** : uniquement si `garage.settings` contient `CUSTOM_REPAIR_ORDER` avec `valueBool: true`.
164
+ > **Fichiers à modifier** : cherche le composant de la zone mémos (MemoZone, MemoPanel, SidebarMemos ou similaire).
165
+
166
+ ## 3.1 — Condition d'activation
167
+
168
+ ```ts
169
+ // Cherche comment les autres settings (ex: CUSTOM_*) sont lus dans le composant mémos
170
+ // Appliquer le même pattern pour :
171
+ const isRepairOrderEnabled = garage.settings?.find(
172
+ (s) => s.key === 'CUSTOM_REPAIR_ORDER'
173
+ )?.valueBool ?? false;
174
+ ```
175
+
176
+ ## 3.2 — Toggle UI
177
+
178
+ ```tsx
179
+ // Ajouter dans le header de la zone mémos, uniquement si isRepairOrderEnabled
180
+
181
+ // State local :
182
+ const [memoView, setMemoView] = useState<'QUOTE' | 'REPAIR_ORDER'>('QUOTE');
183
+
184
+ // Composant toggle (utiliser ToggleButtonGroup MUI ou le composant toggle existant dans le projet) :
185
+ {isRepairOrderEnabled && (
186
+ <ToggleButtonGroup
187
+ value={memoView}
188
+ exclusive
189
+ onChange={(_, value) => value && setMemoView(value)}
190
+ size="small"
191
+ >
192
+ <ToggleButton value="QUOTE">Devis</ToggleButton>
193
+ <ToggleButton value="REPAIR_ORDER">OR</ToggleButton>
194
+ </ToggleButtonGroup>
195
+ )}
196
+ ```
197
+
198
+ ## 3.3 — Affichage conditionnel
199
+
200
+ ```tsx
201
+ // Dans le rendu de la zone mémos :
202
+ {memoView === 'QUOTE' && <QuoteMemoList ... />} {/* composant existant, inchangé */}
203
+ {memoView === 'REPAIR_ORDER' && isRepairOrderEnabled && <RepairOrderMemoList ... />}
204
+ ```
205
+
206
+ ## ✅ Validation Partie 3
207
+ - [ ] Toggle invisible si CUSTOM_REPAIR_ORDER = false
208
+ - [ ] Toggle visible et fonctionnel si CUSTOM_REPAIR_ORDER = true
209
+ - [ ] Vue devis inchangée
210
+ - [ ] Bascule vers vue OR affiche un placeholder (le composant sera fait en Partie 4)
211
+
212
+ ---
213
+
214
+ ---
215
+
216
+ # PARTIE 4 — Liste des OR DRAFT + Dialog OR CLOSED
217
+
218
+ > **Objectif** : Créer le composant liste OR (zone mémos) et le dialog listant les OR CLOSED.
219
+ > **Base** : Reprendre le composant liste Devis en zone mémos comme référence visuelle et structurelle.
220
+
221
+ ## 4.1 — Composant `RepairOrderMemoList`
222
+
223
+ ```tsx
224
+ // Nouveau composant, même structure que le composant liste devis en zone mémos.
225
+
226
+ // Comportements :
227
+ // - Appelle RepairOrderService.getAll() au mount
228
+ // - Filtre les résultats : afficher UNIQUEMENT state === 'DRAFT'
229
+ // - Même card/ligne que les devis (aligner sur l'existant)
230
+ // - Bouton "Nouvel OR" → ouvre le formulaire OR (Partie 5)
231
+ // - Bouton "OR archivés" (ou icône) → ouvre RepairOrderClosedDialog
232
+
233
+ // Chaque item de la liste :
234
+ // - Affiche : numéro OR, client, véhicule, réparateur, montant
235
+ // - Actions : éditer (si DRAFT), supprimer (si DRAFT)
236
+ ```
237
+
238
+ ## 4.2 — Composant `RepairOrderClosedDialog`
239
+
240
+ ```tsx
241
+ // Dialog MUI listant tous les OR en état CLOSED.
242
+ // Cherche un dialog de liste existant dans le projet comme référence de layout.
243
+
244
+ // Contenu :
245
+ // - Titre : "Ordres de réparation archivés"
246
+ // - Champ recherche : filtre par numéro OR ou nom client (filtre local sur les données déjà chargées)
247
+ // - Liste des OR CLOSED : numéro, client, véhicule, date, montant
248
+ // - Chaque ligne : read-only, pas d'action de suppression
249
+ // - Bouton fermer
250
+
251
+ // Props :
252
+ interface RepairOrderClosedDialogProps {
253
+ open: boolean;
254
+ onClose: () => void;
255
+ garageId: string;
256
+ }
257
+ ```
258
+
259
+ ## ✅ Validation Partie 4
260
+ - [ ] Liste affiche uniquement les DRAFT
261
+ - [ ] Bouton "OR archivés" ouvre le dialog CLOSED
262
+ - [ ] Recherche par numéro / client fonctionnelle dans le dialog
263
+ - [ ] Les OR CLOSED sont read-only dans le dialog
264
+ - [ ] Bouton "Nouvel OR" présent (peut pointer vers un placeholder pour l'instant)
265
+
266
+ ---
267
+
268
+ ---
269
+
270
+ # PARTIE 5 — Formulaire Création / Édition OR
271
+
272
+ > **Objectif** : Créer le formulaire OR (création et édition), basé sur le formulaire devis existant en zone mémos, avec un champ réparateur en plus.
273
+ > **Base** : Reprendre le formulaire devis mémos comme référence. Ajouter uniquement ce qui diffère.
274
+
275
+ ## 5.1 — Champ Réparateur
276
+
277
+ ```tsx
278
+ // Un composant de sélection d'utilisateur du garage existe déjà dans le projet.
279
+ // Cherche son nom (ex: GarageUserSelect, RepairerSelect, UserSelector ou similaire).
280
+ // L'intégrer dans le formulaire avec le champ : repairerId
281
+
282
+ // Placement suggéré : après les infos client/véhicule, avant les lignes de détail
283
+ ```
284
+
285
+ ## 5.2 — Structure du formulaire
286
+
287
+ ```
288
+ [Infos client / véhicule] ← identique au formulaire devis
289
+ [Réparateur] ← nouveau champ spécifique OR
290
+ [Lignes de détail] ← identique au formulaire devis
291
+ [Totaux] ← identique au formulaire devis
292
+ [Actions] ← voir 5.3
293
+ ```
294
+
295
+ ## 5.3 — Actions du formulaire
296
+
297
+ **À la création (mode création) :**
298
+ ```
299
+ [Annuler] [Enregistrer comme brouillon]
300
+ ```
301
+
302
+ **Après création (mode édition sur un DRAFT) :**
303
+ ```
304
+ [Supprimer] [Télécharger] [Enregistrer]
305
+ ```
306
+ > Le bouton "Télécharger" ouvre :
307
+ > ```tsx
308
+ > <DownloadRepairOrderDialog
309
+ > open={isShowConfirmDownloadRepairOrder}
310
+ > onClose={onCloseConfirmDownloadRepairOrder}
311
+ > event={localEvent}
312
+ > garage={garage}
313
+ > />
314
+ > ```
315
+ > Cherche comment `DownloadRepairOrderDialog` est déjà utilisé dans le projet et reproduire le même pattern d'ouverture/fermeture.
316
+
317
+ **Mode lecture (OR CLOSED) :**
318
+ ```
319
+ Formulaire entièrement read-only, aucune action d'édition
320
+ Les actions de transformation (Partie 6) restent visibles
321
+ ```
322
+
323
+ ## 5.4 — Gestion du state local
324
+
325
+ ```ts
326
+ // Même pattern que le formulaire devis mémos (Formik ou useState selon ce qui est utilisé)
327
+ // Cherche le formulaire devis mémos et aligner exactement
328
+ ```
329
+
330
+ ## ✅ Validation Partie 5
331
+ - [ ] Formulaire création fonctionnel (POST)
332
+ - [ ] Formulaire édition fonctionnel (PATCH, DRAFT uniquement)
333
+ - [ ] Champ réparateur intégré
334
+ - [ ] Bouton télécharger ouvre DownloadRepairOrderDialog
335
+ - [ ] OR CLOSED : formulaire read-only
336
+ - [ ] Suppression uniquement sur DRAFT
337
+
338
+ ---
339
+
340
+ ---
341
+
342
+ # PARTIE 6 — Actions de Transformation
343
+
344
+ > **Objectif** : Ajouter les actions de transformation d'un OR vers Devis / Facture / RDV.
345
+ > **Condition** : Ces actions sont disponibles sur un OR (DRAFT ou CLOSED, à confirmer avec le back).
346
+ > **Base** : Cherche comment la transformation devis → RDV est faite actuellement et appliquer le même pattern.
347
+
348
+ ## 6.1 — Placement des actions
349
+
350
+ ```tsx
351
+ // Dans le formulaire OR (Partie 5), section actions :
352
+ // Ajouter un groupe d'actions "Transformer en" visible après la création de l'OR
353
+
354
+ [Transformer en Devis] [Transformer en Facture] [Transformer en RDV]
355
+ ```
356
+
357
+ ## 6.2 — Transformer en Devis
358
+
359
+ ```ts
360
+ // Appel : RepairOrderService.toQuote(garageId, repairOrderId)
361
+ // POST /garage/:garageId/repair-order/:id/quote
362
+ // => L'API gère le passage de l'OR en CLOSED
363
+ // Après succès : fermer le formulaire OR, rafraîchir la liste mémos côté devis
364
+ // Optionnel : ouvrir directement le devis créé (aligner sur le comportement existant)
365
+ ```
366
+
367
+ ## 6.3 — Transformer en Facture
368
+
369
+ ```ts
370
+ // Utiliser le POST standard de création de facture
371
+ // Cherche comment est déclenché ce POST depuis les devis actuellement
372
+ // Ajouter eventId = repairOrderId dans le payload
373
+ // => L'API gère le passage de l'OR en CLOSED automatiquement
374
+ ```
375
+
376
+ ## 6.4 — Transformer en RDV
377
+
378
+ ```ts
379
+ // Même comportement que la transformation devis → RDV existante
380
+ // Cherche ce comportement dans le code et reproduire à l'identique
381
+ // POST /garage/:garageId/repair-order/:id/event
382
+ // => Ouvrir le dialog de création de RDV existant, pré-rempli avec les données de l'OR
383
+ // => L'API gère le passage de l'OR en CLOSED
384
+ ```
385
+
386
+ ## 6.5 — Feedback utilisateur
387
+
388
+ ```ts
389
+ // Après chaque transformation réussie :
390
+ // - Snackbar/toast de confirmation (aligner sur le pattern existant)
391
+ // - Rafraîchissement de la liste mémos
392
+ // - Fermeture du formulaire OR si pertinent
393
+ ```
394
+
395
+ ## ✅ Validation Partie 6
396
+ - [ ] Transformation → Devis fonctionnelle
397
+ - [ ] Transformation → Facture fonctionnelle (avec eventId dans payload)
398
+ - [ ] Transformation → RDV ouvre le dialog existant pré-rempli
399
+ - [ ] L'OR passe bien en CLOSED après transformation (vérifié via GET)
400
+ - [ ] Feedback utilisateur présent sur chaque action
401
+ - [ ] Liste mémos rafraîchie après transformation
402
+
403
+ ---
404
+
405
+ ---
406
+
407
+ ## Checklist globale finale
408
+
409
+ - [ ] Partie 1 — Types & constantes
410
+ - [ ] Partie 2 — Service API
411
+ - [ ] Partie 3 — Toggle zone mémos
412
+ - [ ] Partie 4 — Liste DRAFT + dialog CLOSED
413
+ - [ ] Partie 5 — Formulaire création/édition
414
+ - [ ] Partie 6 — Transformations
415
+
416
+ > **Rappel Claude Code** : à chaque partie, lis d'abord le code existant correspondant (Quote, devis mémos, transformations existantes) avant d'écrire une seule ligne. L'objectif est de s'intégrer proprement dans les patterns déjà en place, pas de réinventer.
@@ -0,0 +1,75 @@
1
+ # Session — Module Inventaire (stock)
2
+
3
+ Date : 2026-05-22
4
+
5
+ ---
6
+
7
+ ## Fichiers créés
8
+
9
+ | Fichier | Rôle |
10
+ |---|---|
11
+ | `src/models/Inventory.ts` | Classe `Inventory`, enums `InventoryStatus` (DRAFT / IN_PROGRESS / CLOSED / CANCELLED) et `InventoryType` (FULL / PARTIAL), labels FR |
12
+ | `src/models/Billing/Stock/InventoryLine.ts` | Classe `InventoryLine` — lignes de comptage (théorique, comptée, écart, `isCounted`, `hasGap`, `isAddedDuringSession`) |
13
+ | `src/services/billing/InventoryService.ts` | Tous les appels API inventaire (voir détail ci-dessous) |
14
+ | `src/pages/billing/Inventories.tsx` | Page liste des inventaires avec filtres + colonne actions |
15
+ | `src/components/Billing/Form/PickerInventoryStatus.tsx` | Picker bouton + popover pour filtrer par statut (même pattern que `PickerMovementType`) |
16
+ | `src/components/Billing/Dialog/CreateInventoryDialog.tsx` | Dialog création d'inventaire (type, dépôt, includeZeroStock, scope produits/emplacements pour PARTIAL, commentaire) |
17
+ | `src/components/Billing/Dialog/InventoryLinesDialog.tsx` | Dialog comptage des lignes : inline edit quantité + commentaire (champs non-contrôlés via refs), ajout ligne hors-scope, suppression ligne manuelle |
18
+
19
+ ---
20
+
21
+ ## Fichiers modifiés
22
+
23
+ | Fichier | Changement |
24
+ |---|---|
25
+ | `src/pages/billing/billingIndex.tsx` | Entrée menu "Inventaires" + route `/inventories` gatées par `isStockActive` |
26
+ | `src/models/Billing/Stock/Depot.ts` | Classe `DepotLocation` ajoutée (id, code, label) |
27
+ | `src/services/billing/DepotService.ts` | `useFetchDepotLocations`, `useFetchAllDepotLocations`, `DepotLocationService` (create/update/delete), `invalidateQueriesDepotLocation` |
28
+ | `src/pages/setting/tabContent/Depot/DepotModule.tsx` | Refonte en cards par dépôt avec liste d'emplacements inline (edit/delete) + bouton création emplacement |
29
+ | `src/models/Billing/Product.ts` | Champ `depotLocationIds: string[]` ajouté |
30
+ | `src/components/Billing/Dialog/ProductFormDialog.tsx` | Picker `PickerDepotLocations` inséré sous le switch "Suivi en stock" |
31
+ | `src/components/Billing/Form/PickerDepotLocations.tsx` | Autocomplete multi-sélection emplacements groupés par dépôt, chips deletable, bg `primary.light` sur options sélectionnées |
32
+
33
+ ---
34
+
35
+ ## InventoryService — méthodes
36
+
37
+ ```ts
38
+ useFetchInventories(garageId, page, limit, filter)
39
+ // filter: { depotId?, status?, startDate?, endDate? }
40
+
41
+ useFetchInventoryLines(garageId, inventoryId, page, limit, filter)
42
+ // filter: { depotLocationId?, isCounted?, hasGap? }
43
+
44
+ InventoryService.create(garageId, payload, queryClient)
45
+ // payload: { type, depotId, startDate, comment, includeZeroStock, scope? }
46
+ // scope: { catalogIds[], locationIds[] } — PARTIAL seulement
47
+
48
+ InventoryService.start(garageId, inventoryId, queryClient)
49
+ // POST .../start (DRAFT → IN_PROGRESS)
50
+
51
+ InventoryService.close(garageId, inventoryId, queryClient, forceClose?)
52
+ // POST .../close?forceClose=true (IN_PROGRESS → CLOSED)
53
+
54
+ InventoryService.cancel(garageId, inventoryId, queryClient)
55
+ // POST .../cancel
56
+
57
+ InventoryService.addLine(garageId, inventoryId, catalogId, queryClient)
58
+ // POST .../lines { catalogId }
59
+
60
+ InventoryService.deleteLine(garageId, inventoryId, lineId, queryClient)
61
+ // DELETE .../lines/{lineId} (isAddedDuringSession && !isCounted)
62
+
63
+ InventoryService.patchLine(garageId, inventoryId, lineId, payload, queryClient)
64
+ // PATCH .../lines/{lineId} { countedQuantity, comment? }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Points d'attention
70
+
71
+ - **DepotLocationFormDialog** : dialog Formik+Yup pour créer/modifier un emplacement, même style que `DepotFormDialog`.
72
+ - **InventoryLinesDialog** — champs quantité et commentaire sont **non-contrôlés** (`defaultValue` + `inputRef`) pour éviter la perte de focus à chaque frappe (re-render de `displayedColumns` dans `MovaTableBack`).
73
+ - **CreateInventoryDialog** — produits sélectionnés stockés dans un state séparé (`selectedProducts`) indépendant des résultats de recherche, pour que les chips restent affichés quand la recherche est vidée.
74
+ - **Filtres Inventaires** : réutilise `AtPeriodFilterMenu` de `Stocks.tsx` (export nommé).
75
+ - **billingIndex** : les entrées "Mouvements" et "Inventaires" sont toutes deux gatées par `isStockActive(garage)`.
@@ -0,0 +1,67 @@
1
+ # TICKET — Refacto performance MyServices.tsx
2
+
3
+ **Branche :** `vite-compiler-up-to-date`
4
+ **Fichier :** `src/pages/setting/tabContent/MyServices.tsx`
5
+ **Date :** 2026-05-12
6
+
7
+ ---
8
+
9
+ ## Contexte
10
+
11
+ Le composant `MyServices` présentait de graves problèmes de performance sur les tableaux de prestations, notamment lors de la modification des valeurs (downtime, appointmentDelay). Chaque appel à `refreshGarage` (déclenché après une sauvegarde) mettait à jour la prop `garage` dans le parent, ce qui provoquait un remount complet de tous les composants de ligne — le `memo()` était inefficace car les composants étaient définis à l'intérieur du render.
12
+
13
+ ---
14
+
15
+ ## Travail réalisé
16
+
17
+ ### 1. Refacto architecture — extraction des composants + Context
18
+
19
+ **Problème :** `CategoryPrestationRow` et `PrestationRow` étaient définis à l'intérieur de `MyServices`. React recréait un nouveau type de composant à chaque render → unmount/remount complet de toutes les lignes à chaque `refreshGarage`.
20
+
21
+ **Solution :**
22
+ - Extraction de `CategoryPrestationRow` et `PrestationRow` en composants top-level
23
+ - Création de `MyServicesContext` pour partager sans prop drilling : `garageId`, `garageKey`, `prestationCategories`, `teamManagementActive`, `showSnackbar`, `refreshGarage`, `savePrestation`
24
+ - `PrestationRow` consomme le cache TanStack Query via `useQuery({ select })` — ne re-render que si SA prestation change, indépendamment du reste
25
+
26
+ **Fix stale closure :** `handleInputChange` utilise `setFormRow(prev => ...)` — plus de lecture périmée sur les frappes rapides.
27
+
28
+ ---
29
+
30
+ ### 2. Fix toggle "Visible par les clients" ne se mettant pas à jour
31
+
32
+ **Problème :** Le `setQueryData` dans `handleChangeAvailableOnline` utilisait `[GARAGES_SETTINGS, garageId]` (string) au lieu de `garageKey` qui contient `garage.id` (nombre) — la clé ne correspondait pas à l'entrée de cache existante.
33
+
34
+ **Solution :** `setQueryData(garageKey, ...)` à la place.
35
+
36
+ ---
37
+
38
+ ### 3. Loader pendant la sauvegarde des valeurs de prestation
39
+
40
+ Pendant l'appel API de mise à jour (downtime / appointmentDelay), les cellules concernées affichent un `CircularProgress` à la place de la valeur jusqu'à la résolution de la Promise.
41
+
42
+ - `savePrestation` retourne désormais une `Promise<void>`
43
+ - `PrestationRow` gère un état `isSaving` activé au submit, désactivé dans le `.finally()`
44
+
45
+ ---
46
+
47
+ ### 4. Barre de recherche sur chaque tableau
48
+
49
+ **Tableau des prestations actives :**
50
+ - Champ de recherche filtrant par nom de prestation
51
+ - Sans recherche : affichage normal par catégories (accordion)
52
+ - Avec recherche : liste plate triée alphabétiquement, header adapté (Nom / Immobilisation / Délai / Visible / Actions)
53
+ - Aucun résultat : placeholder "Aucune prestation ne correspond à votre recherche"
54
+
55
+ **Tableau des prestations inactives :**
56
+ - Même comportement de filtrage sur `visibleRows`
57
+ - Placeholder différencié selon que la liste est vide naturellement ou à cause de la recherche
58
+
59
+ **Les deux inputs disposent d'une icône `ClearIcon` (croix) qui apparaît uniquement quand du texte est présent, et remet l'input à vide au clic.**
60
+
61
+ ---
62
+
63
+ ## Points techniques notables
64
+
65
+ - `garageKey` conserve le type original de `garage.id` (nombre) pour correspondre à la clé TanStack Query existante ; `garageId` (string) est réservé aux appels API REST
66
+ - `memo()` sur `CategoryPrestationRow` a un comparateur explicite sur `code`, `color` et `prestations`
67
+ - `filteredActiveCategories` et `flatActivePrestations` sont mémoïsés avec `useMemo`