@rsuci/shared-form-components 1.0.29 → 1.0.33

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 (51) hide show
  1. package/dist/components/form-renderer/ConfirmationModal.d.ts +20 -0
  2. package/dist/components/form-renderer/ConfirmationModal.d.ts.map +1 -0
  3. package/dist/components/form-renderer/ConfirmationModal.js +79 -0
  4. package/dist/components/form-renderer/FormActions.d.ts +23 -0
  5. package/dist/components/form-renderer/FormActions.d.ts.map +1 -0
  6. package/dist/components/form-renderer/FormActions.js +116 -0
  7. package/dist/components/form-renderer/FormNavigationButtons.d.ts +23 -0
  8. package/dist/components/form-renderer/FormNavigationButtons.d.ts.map +1 -0
  9. package/dist/components/form-renderer/FormNavigationButtons.js +35 -0
  10. package/dist/components/form-renderer/FormProgress.d.ts +19 -0
  11. package/dist/components/form-renderer/FormProgress.d.ts.map +1 -0
  12. package/dist/components/form-renderer/FormProgress.js +26 -0
  13. package/dist/components/form-renderer/FormRenderer.d.ts +39 -0
  14. package/dist/components/form-renderer/FormRenderer.d.ts.map +1 -0
  15. package/dist/components/form-renderer/FormRenderer.js +113 -0
  16. package/dist/components/form-renderer/FormRendererContext.d.ts +109 -0
  17. package/dist/components/form-renderer/FormRendererContext.d.ts.map +1 -0
  18. package/dist/components/form-renderer/FormRendererContext.js +114 -0
  19. package/dist/components/form-renderer/GroupeInstanceTabs.d.ts +18 -0
  20. package/dist/components/form-renderer/GroupeInstanceTabs.d.ts.map +1 -0
  21. package/dist/components/form-renderer/GroupeInstanceTabs.js +174 -0
  22. package/dist/components/form-renderer/ValidationModal.d.ts +25 -0
  23. package/dist/components/form-renderer/ValidationModal.d.ts.map +1 -0
  24. package/dist/components/form-renderer/ValidationModal.js +37 -0
  25. package/dist/components/form-renderer/index.d.ts +19 -0
  26. package/dist/components/form-renderer/index.d.ts.map +1 -0
  27. package/dist/components/form-renderer/index.js +24 -0
  28. package/dist/hooks/useFormInstances.d.ts +87 -0
  29. package/dist/hooks/useFormInstances.d.ts.map +1 -0
  30. package/dist/hooks/useFormInstances.js +197 -0
  31. package/dist/hooks/useFormNavigation.d.ts +72 -0
  32. package/dist/hooks/useFormNavigation.d.ts.map +1 -0
  33. package/dist/hooks/useFormNavigation.js +147 -0
  34. package/dist/hooks/useFormRenderer.d.ts +87 -0
  35. package/dist/hooks/useFormRenderer.d.ts.map +1 -0
  36. package/dist/hooks/useFormRenderer.js +177 -0
  37. package/dist/hooks/useFormValidation.d.ts +50 -0
  38. package/dist/hooks/useFormValidation.d.ts.map +1 -0
  39. package/dist/hooks/useFormValidation.js +175 -0
  40. package/dist/index.d.ts +14 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +21 -0
  43. package/dist/lib/__tests__/date-functions.test.d.ts +5 -0
  44. package/dist/lib/__tests__/date-functions.test.d.ts.map +1 -0
  45. package/dist/lib/__tests__/date-functions.test.js +184 -0
  46. package/dist/lib/utils/groupeInstanceManager.d.ts +88 -0
  47. package/dist/lib/utils/groupeInstanceManager.d.ts.map +1 -0
  48. package/dist/lib/utils/groupeInstanceManager.js +606 -0
  49. package/dist/types/form-renderer.d.ts +115 -1
  50. package/dist/types/form-renderer.d.ts.map +1 -1
  51. package/package.json +5 -1
@@ -0,0 +1,606 @@
1
+ /**
2
+ * Gestionnaire des instances multiples pour les groupes d'enquête
3
+ * RSU v2 - Moteur de Rendu des Formulaires d'Enquête
4
+ */
5
+ export class GroupeInstanceManager {
6
+ /**
7
+ * Récupère le nombre maximum d'instances autorisées depuis la variable de contrôle
8
+ * RÈGLE DE PROTECTION : Ne jamais permettre de réduire sous le nombre d'instances existantes
9
+ */
10
+ static getMaxInstances(groupe, responses) {
11
+ if (!groupe.estMultiple || !groupe.codeVariable) {
12
+ console.log('GroupeInstanceManager.getMaxInstances - Groupe non multiple ou sans variable de contrôle:', {
13
+ estMultiple: groupe.estMultiple,
14
+ codeVariable: groupe.codeVariable,
15
+ groupeCode: groupe.code
16
+ });
17
+ return 1;
18
+ }
19
+ console.log('GroupeInstanceManager.getMaxInstances - Recherche variable de contrôle:', {
20
+ groupeCode: groupe.code,
21
+ codeVariable: groupe.codeVariable,
22
+ responsesKeys: Object.keys(responses),
23
+ responses: responses
24
+ });
25
+ // Chercher la réponse de la variable de contrôle
26
+ // D'abord essayer avec la clé directe
27
+ let controlResponse = responses[groupe.codeVariable];
28
+ // Si pas trouvé, chercher dans toutes les réponses par variableCode
29
+ if (!controlResponse) {
30
+ const foundResponse = Object.values(responses).find(response => response.variableCode === groupe.codeVariable);
31
+ if (foundResponse) {
32
+ controlResponse = foundResponse;
33
+ }
34
+ console.log('GroupeInstanceManager.getMaxInstances - Recherche par variableCode:', {
35
+ found: !!foundResponse,
36
+ controlResponse: foundResponse
37
+ });
38
+ }
39
+ const controlValue = controlResponse?.valeur;
40
+ console.log('GroupeInstanceManager.getMaxInstances - Valeur de contrôle trouvée:', {
41
+ controlResponse: controlResponse,
42
+ controlValue: controlValue,
43
+ valueType: typeof controlValue
44
+ });
45
+ // Convertir la valeur en nombre
46
+ let maxFromControl = 1; // Valeur par défaut
47
+ if (typeof controlValue === 'number') {
48
+ maxFromControl = controlValue;
49
+ }
50
+ else if (typeof controlValue === 'string') {
51
+ const parsed = parseInt(controlValue, 10);
52
+ if (!isNaN(parsed)) {
53
+ maxFromControl = parsed;
54
+ }
55
+ }
56
+ // PROTECTION DES DONNÉES : Obtenir le nombre d'instances existantes
57
+ const existingInstancesCount = groupe.instances?.length || 0;
58
+ // Le maximum effectif est au moins égal au nombre d'instances existantes
59
+ const protectedMax = Math.max(maxFromControl, existingInstancesCount);
60
+ console.log('GroupeInstanceManager.getMaxInstances - Résultat avec protection:', {
61
+ groupeCode: groupe.code,
62
+ codeVariable: groupe.codeVariable,
63
+ maxFromControl: maxFromControl,
64
+ existingInstancesCount: existingInstancesCount,
65
+ protectedMax: protectedMax,
66
+ isProtectionActive: protectedMax > maxFromControl
67
+ });
68
+ // S'assurer qu'on a au moins 1 instance
69
+ return Math.max(1, protectedMax);
70
+ }
71
+ /**
72
+ * Récupère le nombre minimum d'instances (basé sur les instances existantes)
73
+ */
74
+ static getMinInstances(groupe) {
75
+ if (!groupe.estMultiple) {
76
+ return 1;
77
+ }
78
+ const existingInstances = groupe.instances?.length || 0;
79
+ return Math.max(1, existingInstances);
80
+ }
81
+ /**
82
+ * Compte le nombre d'instances existantes pour un groupe
83
+ */
84
+ static getInstancesCount(groupe, responses) {
85
+ if (!groupe.estMultiple) {
86
+ return 1;
87
+ }
88
+ console.log('GroupeInstanceManager.getInstancesCount - Début:', {
89
+ groupeCode: groupe.code,
90
+ variableCodes: groupe.variables.map(v => v.code),
91
+ responsesKeys: Object.keys(responses)
92
+ });
93
+ // Compter les instances basées sur les réponses
94
+ const instanceNumbers = new Set();
95
+ // Méthode 1: Par numeroMembre
96
+ groupe.variables.forEach(variable => {
97
+ Object.values(responses).forEach(response => {
98
+ if (response.variableCode === variable.code && response.numeroMembre) {
99
+ instanceNumbers.add(response.numeroMembre);
100
+ }
101
+ });
102
+ });
103
+ // Méthode 2: Par clé avec format "CODE_NUMERO"
104
+ groupe.variables.forEach(variable => {
105
+ Object.keys(responses).forEach(key => {
106
+ const response = responses[key];
107
+ if (response.variableCode === variable.code) {
108
+ // Extraire le numéro d'instance de la clé si elle suit le format "CODE_NUMERO"
109
+ const keyParts = key.split('_');
110
+ if (keyParts.length >= 2) {
111
+ const lastPart = keyParts[keyParts.length - 1];
112
+ const instanceNum = parseInt(lastPart, 10);
113
+ if (!isNaN(instanceNum) && instanceNum > 0) {
114
+ instanceNumbers.add(instanceNum);
115
+ }
116
+ }
117
+ }
118
+ });
119
+ });
120
+ const count = Math.max(1, instanceNumbers.size);
121
+ console.log('GroupeInstanceManager.getInstancesCount - Résultat:', {
122
+ groupeCode: groupe.code,
123
+ instanceNumbers: Array.from(instanceNumbers).sort(),
124
+ finalCount: count
125
+ });
126
+ return count;
127
+ }
128
+ /**
129
+ * Crée une nouvelle instance pour un groupe multiple
130
+ */
131
+ static createInstance(groupe, numeroInstance) {
132
+ const libelle = `${groupe.designation} ${numeroInstance}`;
133
+ return {
134
+ numeroInstance,
135
+ libelle,
136
+ estComplete: false,
137
+ reponses: {}
138
+ };
139
+ }
140
+ /**
141
+ * Initialise les instances pour un groupe multiple
142
+ */
143
+ static initializeInstances(groupe, responses) {
144
+ if (!groupe.estMultiple) {
145
+ return [];
146
+ }
147
+ console.log('GroupeInstanceManager.initializeInstances - Début:', {
148
+ groupeCode: groupe.code,
149
+ codeVariable: groupe.codeVariable,
150
+ responsesKeys: Object.keys(responses),
151
+ allResponses: responses
152
+ });
153
+ // Identifier toutes les instances existantes pour ce groupe
154
+ const instanceNumbers = new Set();
155
+ // Collecter les réponses existantes pour ce groupe (avec et sans numeroMembre)
156
+ const groupResponses = [];
157
+ // Parcourir toutes les réponses pour trouver celles de ce groupe
158
+ Object.values(responses).forEach(response => {
159
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
160
+ if (belongsToGroup) {
161
+ groupResponses.push(response);
162
+ if (response.numeroMembre) {
163
+ instanceNumbers.add(response.numeroMembre);
164
+ }
165
+ }
166
+ });
167
+ // Aussi chercher par clé avec format "CODE_NUMERO"
168
+ Object.keys(responses).forEach(key => {
169
+ const response = responses[key];
170
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
171
+ if (belongsToGroup) {
172
+ // Extraire le numéro d'instance de la clé si elle suit le format "CODE_NUMERO"
173
+ const keyParts = key.split('_');
174
+ if (keyParts.length >= 2) {
175
+ const lastPart = keyParts[keyParts.length - 1];
176
+ const instanceNum = parseInt(lastPart, 10);
177
+ if (!isNaN(instanceNum) && instanceNum > 0) {
178
+ instanceNumbers.add(instanceNum);
179
+ }
180
+ }
181
+ }
182
+ });
183
+ console.log('GroupeInstanceManager.initializeInstances - Données trouvées:', {
184
+ groupeCode: groupe.code,
185
+ instanceNumbers: Array.from(instanceNumbers).sort(),
186
+ groupResponsesCount: groupResponses.length,
187
+ responsesWithNumeroMembre: groupResponses.filter(r => r.numeroMembre).length,
188
+ responsesWithoutNumeroMembre: groupResponses.filter(r => !r.numeroMembre).length
189
+ });
190
+ const maxInstances = this.getMaxInstances(groupe, responses);
191
+ // Déterminer le nombre d'instances à créer
192
+ let instanceCount = Math.max(1, instanceNumbers.size);
193
+ // CORRECTION: Ne pas forcer la création de maxInstances instances
194
+ // Respecter la limite de la variable de contrôle
195
+ instanceCount = Math.min(instanceCount, maxInstances);
196
+ // Si aucune instance existante, créer au moins 1 instance
197
+ if (instanceCount === 0) {
198
+ instanceCount = 1;
199
+ }
200
+ console.log('GroupeInstanceManager.initializeInstances - Calcul instances:', {
201
+ groupeCode: groupe.code,
202
+ maxInstances,
203
+ existingInstancesCount: instanceNumbers.size,
204
+ finalInstanceCount: instanceCount
205
+ });
206
+ const instances = [];
207
+ // Séparer les réponses avec et sans numeroMembre
208
+ const responsesWithMember = groupResponses.filter(r => r.numeroMembre);
209
+ const responsesWithoutMember = groupResponses.filter(r => !r.numeroMembre);
210
+ for (let i = 1; i <= instanceCount; i++) {
211
+ const instance = this.createInstance(groupe, i);
212
+ // Remplir les réponses existantes pour cette instance
213
+ groupe.variables.forEach(variable => {
214
+ let existingResponse;
215
+ // 1. Chercher d'abord par numeroMembre
216
+ existingResponse = responsesWithMember.find(r => r.variableCode === variable.code && r.numeroMembre === i);
217
+ // 2. Si pas trouvé, chercher par clé avec format "CODE_NUMERO"
218
+ if (!existingResponse) {
219
+ const keyWithInstance = `${variable.code}_${i}`;
220
+ existingResponse = responses[keyWithInstance];
221
+ }
222
+ // 3. Si toujours pas trouvé et qu'on a des réponses sans numeroMembre,
223
+ // les distribuer aux instances (première instance = première réponse, etc.)
224
+ if (!existingResponse && responsesWithoutMember.length > 0) {
225
+ // Pour la première instance, prendre les réponses sans numeroMembre
226
+ if (i === 1) {
227
+ existingResponse = responsesWithoutMember.find(r => r.variableCode === variable.code);
228
+ if (existingResponse) {
229
+ console.log('GroupeInstanceManager.initializeInstances - Attribution réponse sans numeroMembre:', {
230
+ groupeCode: groupe.code,
231
+ instanceNumber: i,
232
+ variableCode: variable.code,
233
+ originalResponse: existingResponse
234
+ });
235
+ // Créer une copie avec numeroMembre pour cette instance
236
+ existingResponse = {
237
+ ...existingResponse,
238
+ numeroMembre: i
239
+ };
240
+ }
241
+ }
242
+ }
243
+ if (existingResponse) {
244
+ console.log('GroupeInstanceManager.initializeInstances - Réponse trouvée:', {
245
+ groupeCode: groupe.code,
246
+ instanceNumber: i,
247
+ variableCode: variable.code,
248
+ hasNumeroMembre: !!existingResponse.numeroMembre,
249
+ response: existingResponse
250
+ });
251
+ instance.reponses[variable.code] = existingResponse;
252
+ }
253
+ });
254
+ // Vérifier si l'instance est complète
255
+ instance.estComplete = this.isInstanceComplete(instance, groupe);
256
+ instances.push(instance);
257
+ console.log('GroupeInstanceManager.initializeInstances - Instance créée:', {
258
+ groupeCode: groupe.code,
259
+ instanceNumber: i,
260
+ responsesCount: Object.keys(instance.reponses).length,
261
+ isComplete: instance.estComplete
262
+ });
263
+ }
264
+ console.log('GroupeInstanceManager.initializeInstances - Résultat final:', {
265
+ groupeCode: groupe.code,
266
+ instancesCreated: instances.length,
267
+ instances: instances.map(inst => ({
268
+ numero: inst.numeroInstance,
269
+ responsesCount: Object.keys(inst.reponses).length,
270
+ isComplete: inst.estComplete
271
+ }))
272
+ });
273
+ return instances;
274
+ }
275
+ /**
276
+ * Vérifie si une instance est complètement renseignée
277
+ */
278
+ static isInstanceComplete(instance, groupe) {
279
+ const requiredVariables = groupe.variables.filter(v => v.estObligatoire);
280
+ return requiredVariables.every(variable => {
281
+ const response = instance.reponses[variable.code];
282
+ return response && response.valeur !== null && response.valeur !== '';
283
+ });
284
+ }
285
+ /**
286
+ * Valide si on peut ajouter une nouvelle instance
287
+ */
288
+ static canAddInstance(groupe, responses) {
289
+ const constraints = [];
290
+ if (!groupe.estMultiple) {
291
+ constraints.push({
292
+ type: 'instance_limit',
293
+ groupeCode: groupe.code,
294
+ message: 'Ce groupe ne supporte pas les instances multiples'
295
+ });
296
+ return {
297
+ isValid: false,
298
+ errors: [],
299
+ constraints,
300
+ canProceed: false
301
+ };
302
+ }
303
+ const currentCount = this.getInstancesCount(groupe, responses);
304
+ const maxInstances = this.getMaxInstances(groupe, responses);
305
+ if (currentCount >= maxInstances) {
306
+ constraints.push({
307
+ type: 'instance_limit',
308
+ groupeCode: groupe.code,
309
+ variableCode: groupe.codeVariable,
310
+ currentValue: currentCount,
311
+ requiredValue: maxInstances,
312
+ message: `Maximum ${maxInstances} instances autorisées (variable ${groupe.codeVariable})`
313
+ });
314
+ return {
315
+ isValid: false,
316
+ errors: [],
317
+ constraints,
318
+ canProceed: false
319
+ };
320
+ }
321
+ return {
322
+ isValid: true,
323
+ errors: [],
324
+ constraints: [],
325
+ canProceed: true
326
+ };
327
+ }
328
+ /**
329
+ * Valide si on peut supprimer une instance
330
+ */
331
+ static canRemoveInstance(groupe, responses) {
332
+ const constraints = [];
333
+ // CORRECTION: Utiliser le nombre d'instances du groupe directement
334
+ const currentCount = groupe.instances?.length || 0;
335
+ console.log('GroupeInstanceManager.canRemoveInstance - Debug:', {
336
+ groupeCode: groupe.code,
337
+ instancesLength: groupe.instances?.length,
338
+ currentCount,
339
+ canRemove: currentCount > 1
340
+ });
341
+ if (currentCount <= 1) {
342
+ constraints.push({
343
+ type: 'instance_limit',
344
+ groupeCode: groupe.code,
345
+ currentValue: currentCount,
346
+ requiredValue: 1,
347
+ message: 'Au moins une instance doit être conservée'
348
+ });
349
+ return {
350
+ isValid: false,
351
+ errors: [],
352
+ constraints,
353
+ canProceed: false
354
+ };
355
+ }
356
+ return {
357
+ isValid: true,
358
+ errors: [],
359
+ constraints: [],
360
+ canProceed: true
361
+ };
362
+ }
363
+ /**
364
+ * Valide si on peut modifier la variable de contrôle
365
+ */
366
+ static canModifyControlVariable(variableCode, newValue, groupesMultiples, responses) {
367
+ const constraints = [];
368
+ // Trouver tous les groupes qui utilisent cette variable de contrôle
369
+ const affectedGroups = groupesMultiples.filter(g => g.codeVariable === variableCode);
370
+ for (const groupe of affectedGroups) {
371
+ const existingInstances = this.getInstancesCount(groupe, responses);
372
+ if (newValue < existingInstances) {
373
+ constraints.push({
374
+ type: 'bidirectional_control',
375
+ variableCode,
376
+ groupeCode: groupe.code,
377
+ currentValue: newValue,
378
+ requiredValue: existingInstances,
379
+ message: `Impossible de réduire à ${newValue}. Le groupe "${groupe.designation}" a déjà ${existingInstances} instances créées.`
380
+ });
381
+ }
382
+ }
383
+ if (constraints.length > 0) {
384
+ return {
385
+ isValid: false,
386
+ errors: [],
387
+ constraints,
388
+ canProceed: false
389
+ };
390
+ }
391
+ return {
392
+ isValid: true,
393
+ errors: [],
394
+ constraints: [],
395
+ canProceed: true
396
+ };
397
+ }
398
+ /**
399
+ * Ajoute une nouvelle instance à un groupe
400
+ */
401
+ static addInstance(groupe, responses) {
402
+ const validation = this.canAddInstance(groupe, responses);
403
+ if (!validation.canProceed) {
404
+ return {
405
+ success: false,
406
+ error: validation.constraints[0]?.message || 'Impossible d\'ajouter une instance'
407
+ };
408
+ }
409
+ // CORRECTION: Calculer le bon numéro d'instance
410
+ if (!groupe.instances) {
411
+ groupe.instances = [];
412
+ }
413
+ const newInstanceNumber = groupe.instances.length + 1;
414
+ const newInstance = this.createInstance(groupe, newInstanceNumber);
415
+ // Ajouter l'instance au groupe
416
+ groupe.instances.push(newInstance);
417
+ // Mettre à jour les propriétés calculées
418
+ groupe.instancesCount = groupe.instances.length;
419
+ console.log('GroupeInstanceManager.addInstance - Instance ajoutée:', {
420
+ groupeCode: groupe.code,
421
+ newInstanceNumber,
422
+ totalInstances: groupe.instances.length,
423
+ maxInstances: this.getMaxInstances(groupe, responses)
424
+ });
425
+ return {
426
+ success: true,
427
+ instance: newInstance
428
+ };
429
+ }
430
+ /**
431
+ * Supprime une instance d'un groupe
432
+ */
433
+ static removeInstance(groupe, instanceNumber, responses) {
434
+ const validation = this.canRemoveInstance(groupe, responses);
435
+ if (!validation.canProceed) {
436
+ return {
437
+ success: false,
438
+ error: validation.constraints[0]?.message || 'Impossible de supprimer cette instance'
439
+ };
440
+ }
441
+ // Supprimer l'instance du tableau
442
+ if (groupe.instances) {
443
+ groupe.instances = groupe.instances.filter(i => i.numeroInstance !== instanceNumber);
444
+ groupe.instancesCount = groupe.instances.length;
445
+ }
446
+ // Supprimer les réponses associées à cette instance
447
+ Object.keys(responses).forEach(key => {
448
+ const response = responses[key];
449
+ if (response.numeroMembre === instanceNumber) {
450
+ // Trouver si cette réponse appartient à ce groupe
451
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
452
+ if (belongsToGroup) {
453
+ delete responses[key];
454
+ }
455
+ }
456
+ });
457
+ // Réorganiser les instances pour maintenir une numérotation séquentielle
458
+ this.reorderInstances(groupe, responses);
459
+ return {
460
+ success: true
461
+ };
462
+ }
463
+ /**
464
+ * Met à jour les propriétés calculées d'un groupe multiple
465
+ */
466
+ static updateGroupeProperties(groupe, responses) {
467
+ if (!groupe.estMultiple) {
468
+ return;
469
+ }
470
+ groupe.maxInstances = this.getMaxInstances(groupe, responses);
471
+ groupe.minInstances = this.getMinInstances(groupe);
472
+ // Initialiser les instances si elles n'existent pas
473
+ if (!groupe.instances) {
474
+ groupe.instances = this.initializeInstances(groupe, responses);
475
+ }
476
+ // PROTECTION DES DONNÉES : Ne plus supprimer automatiquement les instances
477
+ // La logique de protection est maintenant dans getMaxInstances()
478
+ console.log('GroupeInstanceManager.updateGroupeProperties - Protection des données active:', {
479
+ groupeCode: groupe.code,
480
+ currentInstances: groupe.instances.length,
481
+ maxInstances: groupe.maxInstances,
482
+ protectionActive: groupe.instances.length > 0
483
+ });
484
+ groupe.instancesCount = groupe.instances.length;
485
+ // Mettre à jour le statut de completion des instances
486
+ groupe.instances.forEach(instance => {
487
+ instance.estComplete = this.isInstanceComplete(instance, groupe);
488
+ });
489
+ }
490
+ /**
491
+ * Synchronise les réponses d'une instance avec le store global
492
+ */
493
+ static syncInstanceResponses(instance, groupe, globalResponses) {
494
+ // Copier les réponses de l'instance vers le store global
495
+ Object.entries(instance.reponses).forEach(([variableCode, response]) => {
496
+ const globalKey = `${variableCode}_${instance.numeroInstance}`;
497
+ globalResponses[globalKey] = {
498
+ ...response,
499
+ numeroMembre: instance.numeroInstance
500
+ };
501
+ });
502
+ // Copier les réponses du store global vers l'instance
503
+ groupe.variables.forEach(variable => {
504
+ const existingResponse = Object.values(globalResponses).find(r => r.variableCode === variable.code && r.numeroMembre === instance.numeroInstance);
505
+ if (existingResponse) {
506
+ instance.reponses[variable.code] = existingResponse;
507
+ }
508
+ });
509
+ }
510
+ /**
511
+ * Valide la navigation vers l'instance suivante
512
+ */
513
+ static canNavigateToNextInstance(currentInstance, groupe) {
514
+ const constraints = [];
515
+ if (!this.isInstanceComplete(currentInstance, groupe)) {
516
+ const requiredVariables = groupe.variables.filter(v => v.estObligatoire);
517
+ const missingVariables = requiredVariables.filter(v => {
518
+ const response = currentInstance.reponses[v.code];
519
+ return !response || response.valeur === null || response.valeur === '';
520
+ });
521
+ constraints.push({
522
+ type: 'navigation_block',
523
+ groupeCode: groupe.code,
524
+ message: `Veuillez renseigner tous les champs obligatoires de cette instance avant de continuer. Champs manquants: ${missingVariables.map(v => v.designation).join(', ')}`
525
+ });
526
+ return {
527
+ isValid: false,
528
+ errors: [],
529
+ constraints,
530
+ canProceed: false
531
+ };
532
+ }
533
+ return {
534
+ isValid: true,
535
+ errors: [],
536
+ constraints: [],
537
+ canProceed: true
538
+ };
539
+ }
540
+ /**
541
+ * Trouve la prochaine instance incomplète dans un groupe
542
+ */
543
+ static findNextIncompleteInstance(groupe) {
544
+ if (!groupe.instances) {
545
+ return null;
546
+ }
547
+ return groupe.instances.find(instance => !instance.estComplete) || null;
548
+ }
549
+ /**
550
+ * Calcule la progression d'un groupe multiple
551
+ */
552
+ static calculateGroupProgress(groupe) {
553
+ if (!groupe.estMultiple || !groupe.instances) {
554
+ return {
555
+ completedInstances: 0,
556
+ totalInstances: 1,
557
+ progressPercentage: 0
558
+ };
559
+ }
560
+ const completedInstances = groupe.instances.filter(i => i.estComplete).length;
561
+ const totalInstances = groupe.instances.length;
562
+ const progressPercentage = totalInstances > 0 ? (completedInstances / totalInstances) * 100 : 0;
563
+ return {
564
+ completedInstances,
565
+ totalInstances,
566
+ progressPercentage
567
+ };
568
+ }
569
+ /**
570
+ * Réorganise les numéros d'instances après suppression
571
+ */
572
+ static reorderInstances(groupe, responses) {
573
+ if (!groupe.instances) {
574
+ return;
575
+ }
576
+ // Trier les instances par numéro
577
+ groupe.instances.sort((a, b) => a.numeroInstance - b.numeroInstance);
578
+ // Réassigner les numéros séquentiellement
579
+ groupe.instances.forEach((instance, index) => {
580
+ const newNumber = index + 1;
581
+ const oldNumber = instance.numeroInstance;
582
+ if (newNumber !== oldNumber) {
583
+ // Mettre à jour le numéro de l'instance
584
+ instance.numeroInstance = newNumber;
585
+ instance.libelle = `${groupe.designation} ${newNumber}`;
586
+ // Mettre à jour les réponses dans le store global
587
+ Object.keys(responses).forEach(key => {
588
+ const response = responses[key];
589
+ if (response.numeroMembre === oldNumber) {
590
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
591
+ if (belongsToGroup) {
592
+ response.numeroMembre = newNumber;
593
+ // Mettre à jour la clé si nécessaire
594
+ delete responses[key];
595
+ responses[`${response.variableCode}_${newNumber}`] = response;
596
+ }
597
+ }
598
+ });
599
+ // Mettre à jour les réponses dans l'instance
600
+ Object.keys(instance.reponses).forEach(variableCode => {
601
+ instance.reponses[variableCode].numeroMembre = newNumber;
602
+ });
603
+ }
604
+ });
605
+ }
606
+ }