@rsuci/shared-form-components 1.0.29 → 1.0.32

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 (48) 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 +21 -0
  5. package/dist/components/form-renderer/FormActions.d.ts.map +1 -0
  6. package/dist/components/form-renderer/FormActions.js +81 -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/index.d.ts +17 -0
  23. package/dist/components/form-renderer/index.d.ts.map +1 -0
  24. package/dist/components/form-renderer/index.js +23 -0
  25. package/dist/hooks/useFormInstances.d.ts +87 -0
  26. package/dist/hooks/useFormInstances.d.ts.map +1 -0
  27. package/dist/hooks/useFormInstances.js +197 -0
  28. package/dist/hooks/useFormNavigation.d.ts +72 -0
  29. package/dist/hooks/useFormNavigation.d.ts.map +1 -0
  30. package/dist/hooks/useFormNavigation.js +147 -0
  31. package/dist/hooks/useFormRenderer.d.ts +87 -0
  32. package/dist/hooks/useFormRenderer.d.ts.map +1 -0
  33. package/dist/hooks/useFormRenderer.js +177 -0
  34. package/dist/hooks/useFormValidation.d.ts +50 -0
  35. package/dist/hooks/useFormValidation.d.ts.map +1 -0
  36. package/dist/hooks/useFormValidation.js +175 -0
  37. package/dist/index.d.ts +13 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +20 -0
  40. package/dist/lib/__tests__/date-functions.test.d.ts +5 -0
  41. package/dist/lib/__tests__/date-functions.test.d.ts.map +1 -0
  42. package/dist/lib/__tests__/date-functions.test.js +184 -0
  43. package/dist/lib/utils/groupeInstanceManager.d.ts +88 -0
  44. package/dist/lib/utils/groupeInstanceManager.d.ts.map +1 -0
  45. package/dist/lib/utils/groupeInstanceManager.js +606 -0
  46. package/dist/types/form-renderer.d.ts +115 -1
  47. package/dist/types/form-renderer.d.ts.map +1 -1
  48. package/package.json +5 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Contexte React pour le FormRenderer
3
+ * Partage l'état et les fonctions entre tous les sous-composants
4
+ * RSU v2 - Package Partagé
5
+ */
6
+ 'use client';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ import { createContext, useContext, useMemo } from 'react';
9
+ import { useFormRenderer } from '../../hooks/useFormRenderer';
10
+ // Création du contexte avec valeur par défaut null
11
+ const FormRendererContext = createContext(null);
12
+ /**
13
+ * Provider pour le contexte FormRenderer
14
+ * Encapsule useFormRenderer et fournit l'état à tous les enfants
15
+ */
16
+ export const FormRendererProvider = ({ children, formulaire, enquete, initialResponses, config, callbacks, services, geographicComponents, disabled, isSubmitting: externalIsSubmitting, isVariableVisible, debug }) => {
17
+ // Utiliser le hook principal
18
+ const formRenderer = useFormRenderer(formulaire, enquete, {
19
+ config,
20
+ initialResponses: initialResponses || enquete?.reponses,
21
+ onResponsesChange: callbacks.onResponsesChange,
22
+ onGroupChange: callbacks.onGroupChange,
23
+ isVariableVisible,
24
+ debug
25
+ });
26
+ // Construire la valeur du contexte
27
+ const contextValue = useMemo(() => ({
28
+ ...formRenderer,
29
+ // Données initiales
30
+ formulaire,
31
+ enquete,
32
+ // Configuration
33
+ config,
34
+ // Callbacks
35
+ callbacks,
36
+ // Services
37
+ services,
38
+ geographicComponents,
39
+ // Surcharges externes
40
+ isSubmitting: externalIsSubmitting ?? formRenderer.isSubmitting,
41
+ effectiveDisabled: disabled ?? formRenderer.effectiveDisabled
42
+ }), [
43
+ formRenderer,
44
+ formulaire,
45
+ enquete,
46
+ config,
47
+ callbacks,
48
+ services,
49
+ geographicComponents,
50
+ externalIsSubmitting,
51
+ disabled
52
+ ]);
53
+ return (_jsx(FormRendererContext.Provider, { value: contextValue, children: children }));
54
+ };
55
+ /**
56
+ * Hook pour accéder au contexte FormRenderer
57
+ * Doit être utilisé à l'intérieur d'un FormRendererProvider
58
+ */
59
+ export function useFormRendererContext() {
60
+ const context = useContext(FormRendererContext);
61
+ if (!context) {
62
+ throw new Error('useFormRendererContext doit être utilisé à l\'intérieur d\'un FormRendererProvider');
63
+ }
64
+ return context;
65
+ }
66
+ /**
67
+ * Hook pour accéder uniquement à la navigation
68
+ */
69
+ export function useFormRendererNavigation() {
70
+ const { navigation } = useFormRendererContext();
71
+ return navigation;
72
+ }
73
+ /**
74
+ * Hook pour accéder uniquement à la validation
75
+ */
76
+ export function useFormRendererValidation() {
77
+ const { validation } = useFormRendererContext();
78
+ return validation;
79
+ }
80
+ /**
81
+ * Hook pour accéder uniquement aux instances
82
+ */
83
+ export function useFormRendererInstances() {
84
+ const { instances } = useFormRendererContext();
85
+ return instances;
86
+ }
87
+ /**
88
+ * Hook pour accéder à la configuration
89
+ */
90
+ export function useFormRendererConfig() {
91
+ const { mode, features, config } = useFormRendererContext();
92
+ return { mode, features, config };
93
+ }
94
+ /**
95
+ * Hook pour accéder aux réponses
96
+ */
97
+ export function useFormRendererResponses() {
98
+ const { responses, updateResponse, getResponseValue } = useFormRendererContext();
99
+ return { responses, updateResponse, getResponseValue };
100
+ }
101
+ /**
102
+ * Hook pour accéder à l'état global
103
+ */
104
+ export function useFormRendererState() {
105
+ const { hasUnsavedChanges, isSubmitting, effectiveDisabled, isReadOnly, isConsultationMode } = useFormRendererContext();
106
+ return {
107
+ hasUnsavedChanges,
108
+ isSubmitting,
109
+ effectiveDisabled,
110
+ isReadOnly,
111
+ isConsultationMode
112
+ };
113
+ }
114
+ export default FormRendererContext;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Composant d'interface à onglets pour les instances multiples de groupes
3
+ * RSU v2 - Moteur de Rendu des Formulaires d'Enquête
4
+ */
5
+ import React from 'react';
6
+ import { GroupeFormulaire, EnqueteReponse, GroupeInstance } from '../../types/enquete';
7
+ export interface GroupeInstanceTabsProps {
8
+ groupe: GroupeFormulaire;
9
+ currentInstanceIndex: number;
10
+ responses: Record<string, EnqueteReponse>;
11
+ onInstanceChange: (instanceIndex: number) => void;
12
+ onInstanceAdded: (instance: GroupeInstance) => void;
13
+ onInstanceRemoved: (instanceNumber: number) => void;
14
+ disabled?: boolean;
15
+ }
16
+ declare const GroupeInstanceTabs: React.FC<GroupeInstanceTabsProps>;
17
+ export default GroupeInstanceTabs;
18
+ //# sourceMappingURL=GroupeInstanceTabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupeInstanceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/GroupeInstanceTabs.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvF,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,gBAAgB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,eAAe,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA0RzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Composant d'interface à onglets pour les instances multiples de groupes
3
+ * RSU v2 - Moteur de Rendu des Formulaires d'Enquête
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState, useEffect } from 'react';
8
+ import { Plus, Minus, AlertTriangle, Check, AlertCircle, AlertTriangle as ExclamationIcon } from 'lucide-react';
9
+ import { GroupeInstanceManager } from '../../lib/utils/groupeInstanceManager';
10
+ import ConfirmationModal from './ConfirmationModal';
11
+ const GroupeInstanceTabs = ({ groupe, currentInstanceIndex, responses, onInstanceChange, onInstanceAdded, onInstanceRemoved, disabled = false }) => {
12
+ const [isProcessing, setIsProcessing] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ const [forceUpdate, setForceUpdate] = useState(0);
15
+ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
16
+ // Ne pas afficher pour les groupes non multiples
17
+ if (!groupe.estMultiple || !groupe.instances) {
18
+ return null;
19
+ }
20
+ // Calculer les informations sur les instances
21
+ const currentCount = GroupeInstanceManager.getInstancesCount(groupe, responses);
22
+ const maxInstances = GroupeInstanceManager.getMaxInstances(groupe, responses);
23
+ const canAdd = GroupeInstanceManager.canAddInstance(groupe, responses);
24
+ const canRemove = GroupeInstanceManager.canRemoveInstance(groupe, responses);
25
+ // CORRECTION: Détecter les changements de maxInstances et forcer le re-rendu
26
+ useEffect(() => {
27
+ const newMaxInstances = GroupeInstanceManager.getMaxInstances(groupe, responses);
28
+ if (newMaxInstances !== maxInstances) {
29
+ console.log('GroupeInstanceTabs - Changement de maxInstances détecté:', {
30
+ groupeCode: groupe.code,
31
+ oldMaxInstances: maxInstances,
32
+ newMaxInstances,
33
+ codeVariable: groupe.codeVariable
34
+ });
35
+ setForceUpdate(prev => prev + 1);
36
+ }
37
+ }, [responses, groupe.codeVariable, groupe.maxInstances]);
38
+ // PROTECTION DES DONNÉES: Afficher toutes les instances existantes, même si la variable de contrôle est plus petite
39
+ // Le nombre d'onglets affichés = Math.max(maxInstances, instances existantes)
40
+ const minDisplayCount = Math.max(maxInstances, groupe.instances.length);
41
+ const displayedInstances = groupe.instances.slice(0, minDisplayCount);
42
+ console.log('GroupeInstanceTabs - Debug avec protection:', {
43
+ groupeCode: groupe.code,
44
+ codeVariable: groupe.codeVariable,
45
+ maxInstances,
46
+ totalInstances: groupe.instances.length,
47
+ minDisplayCount,
48
+ displayedInstances: displayedInstances.length,
49
+ canAdd: canAdd.canProceed,
50
+ canRemove: canRemove.canProceed,
51
+ canAddMessage: canAdd.constraints[0]?.message,
52
+ canRemoveMessage: canRemove.constraints[0]?.message
53
+ });
54
+ // Fonction pour déterminer l'état d'une instance
55
+ const getInstanceState = (instance) => {
56
+ const responseCount = Object.keys(instance.reponses).length;
57
+ if (responseCount === 0) {
58
+ return 'empty';
59
+ }
60
+ if (GroupeInstanceManager.isInstanceComplete(instance, groupe)) {
61
+ return 'complete';
62
+ }
63
+ return 'incomplete';
64
+ };
65
+ // Fonction pour obtenir l'icône et la couleur selon l'état
66
+ const getInstanceDisplay = (instance, isActive) => {
67
+ const state = getInstanceState(instance);
68
+ const baseClasses = `
69
+ flex items-center justify-center min-w-[3rem] h-10 px-3 rounded-lg font-medium text-sm
70
+ transition-all duration-200 cursor-pointer border-2
71
+ `;
72
+ let stateClasses = '';
73
+ let icon = null;
74
+ switch (state) {
75
+ case 'complete':
76
+ stateClasses = isActive
77
+ ? 'bg-blue-600 text-white border-blue-600 shadow-lg'
78
+ : 'bg-green-100 text-green-700 border-green-300 hover:bg-green-200';
79
+ icon = _jsx(Check, { className: "h-4 w-4 ml-1" });
80
+ break;
81
+ case 'incomplete':
82
+ stateClasses = isActive
83
+ ? 'bg-blue-600 text-white border-blue-600 shadow-lg'
84
+ : 'bg-green-100 text-orange-700 border-green-300 hover:bg-green-200';
85
+ icon = _jsx(AlertCircle, { className: "h-4 w-4 ml-1" });
86
+ break;
87
+ case 'empty':
88
+ stateClasses = isActive
89
+ ? 'bg-blue-600 text-white border-blue-600 shadow-lg'
90
+ : 'bg-gray-600 text-white border-gray-600 hover:bg-gray-700';
91
+ icon = _jsx(ExclamationIcon, { className: "h-4 w-4 ml-1" });
92
+ break;
93
+ }
94
+ return {
95
+ className: `${baseClasses} ${stateClasses}`,
96
+ icon
97
+ };
98
+ };
99
+ const handleInstanceClick = (instanceIndex) => {
100
+ // En mode consultation, permettre la navigation entre onglets mais pas les modifications
101
+ if (isProcessing)
102
+ return;
103
+ onInstanceChange(instanceIndex);
104
+ };
105
+ const handleAddInstance = async () => {
106
+ if (disabled || isProcessing)
107
+ return;
108
+ setIsProcessing(true);
109
+ setError(null);
110
+ try {
111
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
112
+ if (result.success && result.instance) {
113
+ onInstanceAdded(result.instance);
114
+ }
115
+ else {
116
+ setError(result.error || 'Erreur lors de l\'ajout de l\'instance');
117
+ }
118
+ }
119
+ catch (err) {
120
+ setError('Erreur inattendue lors de l\'ajout de l\'instance');
121
+ console.error('Erreur ajout instance:', err);
122
+ }
123
+ finally {
124
+ setIsProcessing(false);
125
+ }
126
+ };
127
+ const handleRemoveInstanceClick = () => {
128
+ if (disabled || isProcessing || !groupe.instances)
129
+ return;
130
+ setShowDeleteConfirmation(true);
131
+ };
132
+ const handleConfirmRemoveInstance = async () => {
133
+ if (disabled || isProcessing || !groupe.instances)
134
+ return;
135
+ setShowDeleteConfirmation(false);
136
+ setIsProcessing(true);
137
+ setError(null);
138
+ try {
139
+ // Supprimer l'instance actuellement affichée (correction du bug)
140
+ const currentInstance = groupe.instances[currentInstanceIndex];
141
+ if (!currentInstance) {
142
+ setError('Aucune instance sélectionnée à supprimer');
143
+ return;
144
+ }
145
+ const result = GroupeInstanceManager.removeInstance(groupe, currentInstance.numeroInstance, responses);
146
+ if (result.success) {
147
+ onInstanceRemoved(currentInstance.numeroInstance);
148
+ }
149
+ else {
150
+ setError(result.error || 'Erreur lors de la suppression de l\'instance');
151
+ }
152
+ }
153
+ catch (err) {
154
+ setError('Erreur inattendue lors de la suppression de l\'instance');
155
+ console.error('Erreur suppression instance:', err);
156
+ }
157
+ finally {
158
+ setIsProcessing(false);
159
+ }
160
+ };
161
+ const handleCancelRemoveInstance = () => {
162
+ setShowDeleteConfirmation(false);
163
+ };
164
+ return (_jsxs("div", { className: "w-full", children: [_jsxs("div", { className: "flex items-center space-x-2 overflow-x-auto pb-2 relative", children: [_jsx("div", { className: "flex space-x-2 flex-shrink-0", children: displayedInstances.map((instance, index) => {
165
+ const isActive = index === currentInstanceIndex;
166
+ const display = getInstanceDisplay(instance, isActive);
167
+ return (_jsxs("button", { onClick: () => handleInstanceClick(index), disabled: isProcessing, className: display.className, title: `Instance ${instance.numeroInstance} - ${getInstanceState(instance) === 'complete' ? 'Complète' : getInstanceState(instance) === 'incomplete' ? 'Incomplète' : 'Vide'}`, children: [_jsx("span", { className: "font-semibold", children: instance.numeroInstance }), display.icon] }, instance.numeroInstance));
168
+ }) }), _jsx("div", { className: "h-6 w-px bg-gray-300 flex-shrink-0" }), _jsxs("div", { className: "flex items-center space-x-2 flex-shrink-0", children: [_jsx("button", { type: "button", onClick: handleAddInstance, disabled: disabled || isProcessing || !canAdd.canProceed, className: `p-2 rounded-lg transition-colors flex-shrink-0 ${disabled || isProcessing || !canAdd.canProceed
169
+ ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
170
+ : 'bg-green-500 text-white hover:bg-green-600 focus:ring-2 focus:ring-green-500 focus:ring-offset-2'}`, title: canAdd.canProceed ? 'Ajouter une instance' : canAdd.constraints[0]?.message, children: _jsx(Plus, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: handleRemoveInstanceClick, disabled: disabled || isProcessing || !canRemove.canProceed, className: `p-2 rounded-lg transition-colors flex-shrink-0 ${disabled || isProcessing || !canRemove.canProceed
171
+ ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
172
+ : 'bg-red-500 text-white hover:bg-red-600 focus:ring-2 focus:ring-red-500 focus:ring-offset-2'}`, title: canRemove.canProceed ? 'Supprimer l\'instance courante' : canRemove.constraints[0]?.message, children: _jsx(Minus, { className: "h-4 w-4" }) }), isProcessing && (_jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 flex-shrink-0" }))] })] }), error && (_jsxs("div", { className: "mt-2 p-2 bg-red-50 border border-red-200 rounded-lg shadow-sm", children: [_jsxs("div", { className: "flex items-start space-x-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-red-500 flex-shrink-0 mt-0.5" }), _jsx("div", { className: "text-sm text-red-700", children: error })] }), _jsx("button", { onClick: () => setError(null), className: "absolute top-1 right-1 text-red-400 hover:text-red-600", children: "\u00D7" })] })), showDeleteConfirmation && (_jsx(ConfirmationModal, { isOpen: showDeleteConfirmation, title: "Confirmer la suppression", message: `Êtes-vous sûr de vouloir supprimer l'instance ${groupe.instances?.[currentInstanceIndex]?.numeroInstance || currentInstanceIndex + 1} ? Cette action est irréversible.`, confirmText: "Supprimer", cancelText: "Annuler", onConfirm: handleConfirmRemoveInstance, onCancel: handleCancelRemoveInstance, type: "danger" }))] }));
173
+ };
174
+ export default GroupeInstanceTabs;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Exports du module form-renderer
3
+ * RSU v2 - Composants partagés de rendu de formulaires
4
+ */
5
+ export { default as ConfirmationModal } from './ConfirmationModal';
6
+ export type { ConfirmationModalProps, ConfirmationModalType } from './ConfirmationModal';
7
+ export { default as GroupeInstanceTabs } from './GroupeInstanceTabs';
8
+ export type { GroupeInstanceTabsProps } from './GroupeInstanceTabs';
9
+ export { FormRendererProvider, useFormRendererContext, useFormRendererNavigation, useFormRendererValidation, useFormRendererInstances, useFormRendererConfig, useFormRendererResponses, useFormRendererState, type FormRendererContextValue, type FormRendererProviderProps } from './FormRendererContext';
10
+ export { default as FormProgress } from './FormProgress';
11
+ export type { FormProgressProps } from './FormProgress';
12
+ export { default as FormNavigationButtons } from './FormNavigationButtons';
13
+ export type { FormNavigationButtonsProps } from './FormNavigationButtons';
14
+ export { default as FormActions } from './FormActions';
15
+ export type { FormActionsProps } from './FormActions';
16
+ export { default as FormRenderer, type FormRendererProps } from './FormRenderer';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAMpE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC/B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAMtD,OAAO,EACL,OAAO,IAAI,YAAY,EACvB,KAAK,iBAAiB,EACvB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Exports du module form-renderer
3
+ * RSU v2 - Composants partagés de rendu de formulaires
4
+ */
5
+ // ========================================
6
+ // COMPOSANTS UI DE BASE
7
+ // ========================================
8
+ export { default as ConfirmationModal } from './ConfirmationModal';
9
+ export { default as GroupeInstanceTabs } from './GroupeInstanceTabs';
10
+ // ========================================
11
+ // CONTEXTE ET PROVIDER
12
+ // ========================================
13
+ export { FormRendererProvider, useFormRendererContext, useFormRendererNavigation, useFormRendererValidation, useFormRendererInstances, useFormRendererConfig, useFormRendererResponses, useFormRendererState } from './FormRendererContext';
14
+ // ========================================
15
+ // SOUS-COMPOSANTS DU FORM RENDERER
16
+ // ========================================
17
+ export { default as FormProgress } from './FormProgress';
18
+ export { default as FormNavigationButtons } from './FormNavigationButtons';
19
+ export { default as FormActions } from './FormActions';
20
+ // ========================================
21
+ // COMPOSANT PRINCIPAL FORM RENDERER
22
+ // ========================================
23
+ export { default as FormRenderer } from './FormRenderer';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Hook de gestion des instances multiples pour les formulaires d'enquête
3
+ * Gère l'ajout, la suppression et la synchronisation des instances de groupes
4
+ * RSU v2 - Package Partagé
5
+ */
6
+ import { GroupeFormulaire, GroupeInstance, EnqueteReponse } from '../types/enquete';
7
+ export interface UseFormInstancesOptions {
8
+ /** Callback appelé lors de l'ajout d'une instance */
9
+ onInstanceAdded?: (instance: GroupeInstance) => void;
10
+ /** Callback appelé lors de la suppression d'une instance */
11
+ onInstanceRemoved?: (instanceNumber: number) => void;
12
+ /** Callback appelé lors du changement d'instance active */
13
+ onInstanceChange?: (instanceIndex: number) => void;
14
+ /** Callback appelé lors de la mise à jour des groupes */
15
+ onGroupesUpdated?: (groupes: GroupeFormulaire[]) => void;
16
+ /** Mode debug */
17
+ debug?: boolean;
18
+ }
19
+ export interface UseFormInstancesResult {
20
+ /** Groupes avec leurs instances initialisées */
21
+ groupesWithInstances: GroupeFormulaire[];
22
+ /** Index de l'instance actuellement active */
23
+ currentInstanceIndex: number;
24
+ /** Instance actuellement active */
25
+ currentInstance: GroupeInstance | undefined;
26
+ /** Ajouter une instance au groupe actuel */
27
+ addInstance: (groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>) => {
28
+ success: boolean;
29
+ instance?: GroupeInstance;
30
+ error?: string;
31
+ };
32
+ /** Supprimer une instance du groupe actuel */
33
+ removeInstance: (groupe: GroupeFormulaire, instanceNumber: number, responses: Record<string, EnqueteReponse>) => {
34
+ success: boolean;
35
+ error?: string;
36
+ };
37
+ /** Changer d'instance active */
38
+ changeInstance: (instanceIndex: number) => void;
39
+ /** Synchroniser les réponses avec l'instance */
40
+ syncInstanceResponses: (instance: GroupeInstance, groupe: GroupeFormulaire, allResponses: Record<string, EnqueteReponse>) => void;
41
+ /** Mettre à jour les propriétés d'un groupe */
42
+ updateGroupeProperties: (groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>) => void;
43
+ /** Initialiser les groupes avec instances */
44
+ initializeGroupesWithInstances: (groupes: GroupeFormulaire[], responses: Record<string, EnqueteReponse>) => GroupeFormulaire[];
45
+ /** Vérifier si une instance est complète */
46
+ isInstanceComplete: (instance: GroupeInstance, groupe: GroupeFormulaire) => boolean;
47
+ /** Peut-on ajouter une instance ? */
48
+ canAddInstance: (groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>) => {
49
+ canProceed: boolean;
50
+ constraints: Array<{
51
+ message: string;
52
+ }>;
53
+ };
54
+ /** Peut-on supprimer une instance ? */
55
+ canRemoveInstance: (groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>) => {
56
+ canProceed: boolean;
57
+ constraints: Array<{
58
+ message: string;
59
+ }>;
60
+ };
61
+ /** Mettre à jour les groupes avec instances */
62
+ setGroupesWithInstances: React.Dispatch<React.SetStateAction<GroupeFormulaire[]>>;
63
+ }
64
+ /**
65
+ * Hook pour gérer les instances multiples dans un formulaire d'enquête
66
+ *
67
+ * @param initialGroupes - Liste initiale des groupes du formulaire
68
+ * @param initialResponses - Réponses initiales
69
+ * @param options - Options de configuration
70
+ * @returns Objet contenant l'état et les fonctions de gestion des instances
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * const {
75
+ * groupesWithInstances,
76
+ * addInstance,
77
+ * removeInstance,
78
+ * currentInstance
79
+ * } = useFormInstances(formulaire.groupes, enquete.reponses, {
80
+ * onInstanceAdded: (instance) => console.log('Added:', instance),
81
+ * onInstanceRemoved: (num) => console.log('Removed:', num)
82
+ * });
83
+ * ```
84
+ */
85
+ export declare function useFormInstances(initialGroupes: GroupeFormulaire[], initialResponses: Record<string, EnqueteReponse>, options?: UseFormInstancesOptions): UseFormInstancesResult;
86
+ export default useFormInstances;
87
+ //# sourceMappingURL=useFormInstances.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFormInstances.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormInstances.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Hook de gestion des instances multiples pour les formulaires d'enquête
3
+ * Gère l'ajout, la suppression et la synchronisation des instances de groupes
4
+ * RSU v2 - Package Partagé
5
+ */
6
+ import { useState, useCallback, useEffect } from 'react';
7
+ import { GroupeInstanceManager } from '../lib/utils/groupeInstanceManager';
8
+ /**
9
+ * Hook pour gérer les instances multiples dans un formulaire d'enquête
10
+ *
11
+ * @param initialGroupes - Liste initiale des groupes du formulaire
12
+ * @param initialResponses - Réponses initiales
13
+ * @param options - Options de configuration
14
+ * @returns Objet contenant l'état et les fonctions de gestion des instances
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const {
19
+ * groupesWithInstances,
20
+ * addInstance,
21
+ * removeInstance,
22
+ * currentInstance
23
+ * } = useFormInstances(formulaire.groupes, enquete.reponses, {
24
+ * onInstanceAdded: (instance) => console.log('Added:', instance),
25
+ * onInstanceRemoved: (num) => console.log('Removed:', num)
26
+ * });
27
+ * ```
28
+ */
29
+ export function useFormInstances(initialGroupes, initialResponses, options = {}) {
30
+ const { onInstanceAdded, onInstanceRemoved, onInstanceChange, onGroupesUpdated, debug = false } = options;
31
+ // État des groupes avec instances
32
+ const [groupesWithInstances, setGroupesWithInstances] = useState([]);
33
+ // Index de l'instance active
34
+ const [currentInstanceIndex, setCurrentInstanceIndex] = useState(0);
35
+ // Fonction utilitaire pour logger en mode debug
36
+ const log = useCallback((message, data) => {
37
+ if (debug) {
38
+ console.log(`[FormInstances] ${message}`, data || '');
39
+ }
40
+ }, [debug]);
41
+ /**
42
+ * Initialiser les groupes avec leurs instances
43
+ */
44
+ const initializeGroupesWithInstances = useCallback((groupes, responses) => {
45
+ if (!groupes)
46
+ return [];
47
+ log('Initialisation des groupes multiples:', {
48
+ groupesCount: groupes.length,
49
+ responsesCount: Object.keys(responses).length
50
+ });
51
+ return groupes.map(groupe => {
52
+ if (groupe.estMultiple) {
53
+ log('Traitement groupe multiple:', {
54
+ groupeCode: groupe.code,
55
+ codeVariable: groupe.codeVariable,
56
+ designation: groupe.designation
57
+ });
58
+ // Initialiser les propriétés pour les groupes multiples
59
+ GroupeInstanceManager.updateGroupeProperties(groupe, responses);
60
+ // S'assurer qu'il y a toujours au moins une instance
61
+ if (!groupe.instances || groupe.instances.length === 0) {
62
+ groupe.instances = [GroupeInstanceManager.createInstance(groupe, 1)];
63
+ }
64
+ // Synchroniser les réponses avec chaque instance
65
+ groupe.instances.forEach((instance, index) => {
66
+ GroupeInstanceManager.syncInstanceResponses(instance, groupe, responses);
67
+ });
68
+ }
69
+ return groupe;
70
+ });
71
+ }, [log]);
72
+ // Initialisation au montage
73
+ useEffect(() => {
74
+ if (initialGroupes?.length) {
75
+ const initialized = initializeGroupesWithInstances(initialGroupes, initialResponses);
76
+ setGroupesWithInstances(initialized);
77
+ }
78
+ }, []);
79
+ /**
80
+ * Ajouter une instance à un groupe
81
+ */
82
+ const addInstance = useCallback((groupe, responses) => {
83
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
84
+ if (result.success && result.instance) {
85
+ log('Instance ajoutée:', {
86
+ groupeCode: groupe.code,
87
+ instanceNumber: result.instance.numeroInstance
88
+ });
89
+ // Mettre à jour les groupes
90
+ setGroupesWithInstances(prev => {
91
+ const updated = prev.map(g => {
92
+ if (g.code === groupe.code) {
93
+ return { ...groupe };
94
+ }
95
+ return g;
96
+ });
97
+ if (onGroupesUpdated) {
98
+ onGroupesUpdated(updated);
99
+ }
100
+ return updated;
101
+ });
102
+ if (onInstanceAdded) {
103
+ onInstanceAdded(result.instance);
104
+ }
105
+ }
106
+ return result;
107
+ }, [log, onInstanceAdded, onGroupesUpdated]);
108
+ /**
109
+ * Supprimer une instance d'un groupe
110
+ */
111
+ const removeInstance = useCallback((groupe, instanceNumber, responses) => {
112
+ const result = GroupeInstanceManager.removeInstance(groupe, instanceNumber, responses);
113
+ if (result.success) {
114
+ log('Instance supprimée:', {
115
+ groupeCode: groupe.code,
116
+ instanceNumber
117
+ });
118
+ // Mettre à jour les groupes
119
+ setGroupesWithInstances(prev => {
120
+ const updated = prev.map(g => {
121
+ if (g.code === groupe.code) {
122
+ return { ...groupe };
123
+ }
124
+ return g;
125
+ });
126
+ if (onGroupesUpdated) {
127
+ onGroupesUpdated(updated);
128
+ }
129
+ return updated;
130
+ });
131
+ if (onInstanceRemoved) {
132
+ onInstanceRemoved(instanceNumber);
133
+ }
134
+ }
135
+ return result;
136
+ }, [log, onInstanceRemoved, onGroupesUpdated]);
137
+ /**
138
+ * Changer d'instance active
139
+ */
140
+ const changeInstance = useCallback((instanceIndex) => {
141
+ setCurrentInstanceIndex(instanceIndex);
142
+ if (onInstanceChange) {
143
+ onInstanceChange(instanceIndex);
144
+ }
145
+ }, [onInstanceChange]);
146
+ /**
147
+ * Synchroniser les réponses avec l'instance
148
+ */
149
+ const syncInstanceResponses = useCallback((instance, groupe, allResponses) => {
150
+ GroupeInstanceManager.syncInstanceResponses(instance, groupe, allResponses);
151
+ }, []);
152
+ /**
153
+ * Mettre à jour les propriétés d'un groupe
154
+ */
155
+ const updateGroupeProperties = useCallback((groupe, responses) => {
156
+ GroupeInstanceManager.updateGroupeProperties(groupe, responses);
157
+ }, []);
158
+ /**
159
+ * Vérifier si une instance est complète
160
+ */
161
+ const isInstanceComplete = useCallback((instance, groupe) => {
162
+ return GroupeInstanceManager.isInstanceComplete(instance, groupe);
163
+ }, []);
164
+ /**
165
+ * Peut-on ajouter une instance ?
166
+ */
167
+ const canAddInstance = useCallback((groupe, responses) => {
168
+ return GroupeInstanceManager.canAddInstance(groupe, responses);
169
+ }, []);
170
+ /**
171
+ * Peut-on supprimer une instance ?
172
+ */
173
+ const canRemoveInstance = useCallback((groupe, responses) => {
174
+ return GroupeInstanceManager.canRemoveInstance(groupe, responses);
175
+ }, []);
176
+ /**
177
+ * Instance actuellement active
178
+ */
179
+ const currentInstance = groupesWithInstances.find(g => g.estMultiple)
180
+ ?.instances?.[currentInstanceIndex];
181
+ return {
182
+ groupesWithInstances,
183
+ currentInstanceIndex,
184
+ currentInstance,
185
+ addInstance,
186
+ removeInstance,
187
+ changeInstance,
188
+ syncInstanceResponses,
189
+ updateGroupeProperties,
190
+ initializeGroupesWithInstances,
191
+ isInstanceComplete,
192
+ canAddInstance,
193
+ canRemoveInstance,
194
+ setGroupesWithInstances
195
+ };
196
+ }
197
+ export default useFormInstances;