@rsuci/shared-form-components 1.0.0
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.
- package/README.md +152 -0
- package/dist/components/GroupRenderer.d.ts +19 -0
- package/dist/components/GroupRenderer.d.ts.map +1 -0
- package/dist/components/GroupRenderer.js +214 -0
- package/dist/components/IdDocInput.d.ts +20 -0
- package/dist/components/IdDocInput.d.ts.map +1 -0
- package/dist/components/IdDocInput.js +188 -0
- package/dist/components/VariableRenderer.d.ts +35 -0
- package/dist/components/VariableRenderer.d.ts.map +1 -0
- package/dist/components/VariableRenderer.js +83 -0
- package/dist/components/display/LabelDisplay.d.ts +14 -0
- package/dist/components/display/LabelDisplay.d.ts.map +1 -0
- package/dist/components/display/LabelDisplay.js +10 -0
- package/dist/components/display/PanelDisplay.d.ts +14 -0
- package/dist/components/display/PanelDisplay.d.ts.map +1 -0
- package/dist/components/display/PanelDisplay.js +10 -0
- package/dist/components/geographic/DepartementInput.d.ts +38 -0
- package/dist/components/geographic/DepartementInput.d.ts.map +1 -0
- package/dist/components/geographic/DepartementInput.js +30 -0
- package/dist/components/geographic/DistrictInput.d.ts +30 -0
- package/dist/components/geographic/DistrictInput.d.ts.map +1 -0
- package/dist/components/geographic/DistrictInput.js +26 -0
- package/dist/components/geographic/QuartierInput.d.ts +38 -0
- package/dist/components/geographic/QuartierInput.d.ts.map +1 -0
- package/dist/components/geographic/QuartierInput.js +30 -0
- package/dist/components/geographic/RegionInput.d.ts +38 -0
- package/dist/components/geographic/RegionInput.d.ts.map +1 -0
- package/dist/components/geographic/RegionInput.js +30 -0
- package/dist/components/geographic/SousPrefectureInput.d.ts +38 -0
- package/dist/components/geographic/SousPrefectureInput.d.ts.map +1 -0
- package/dist/components/geographic/SousPrefectureInput.js +30 -0
- package/dist/components/geographic/VillageInput.d.ts +38 -0
- package/dist/components/geographic/VillageInput.d.ts.map +1 -0
- package/dist/components/geographic/VillageInput.js +30 -0
- package/dist/components/index.d.ts +27 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +33 -0
- package/dist/components/inputs/CheckboxInput.d.ts +19 -0
- package/dist/components/inputs/CheckboxInput.d.ts.map +1 -0
- package/dist/components/inputs/CheckboxInput.js +38 -0
- package/dist/components/inputs/ComboboxInput.d.ts +19 -0
- package/dist/components/inputs/ComboboxInput.d.ts.map +1 -0
- package/dist/components/inputs/ComboboxInput.js +21 -0
- package/dist/components/inputs/DatePicker.d.ts +19 -0
- package/dist/components/inputs/DatePicker.d.ts.map +1 -0
- package/dist/components/inputs/DatePicker.js +39 -0
- package/dist/components/inputs/GPSInput.d.ts +18 -0
- package/dist/components/inputs/GPSInput.d.ts.map +1 -0
- package/dist/components/inputs/GPSInput.js +44 -0
- package/dist/components/inputs/HourInput.d.ts +19 -0
- package/dist/components/inputs/HourInput.d.ts.map +1 -0
- package/dist/components/inputs/HourInput.js +20 -0
- package/dist/components/inputs/NumberInput.d.ts +20 -0
- package/dist/components/inputs/NumberInput.d.ts.map +1 -0
- package/dist/components/inputs/NumberInput.js +42 -0
- package/dist/components/inputs/PhoneInput.d.ts +19 -0
- package/dist/components/inputs/PhoneInput.d.ts.map +1 -0
- package/dist/components/inputs/PhoneInput.js +11 -0
- package/dist/components/inputs/RadioInput.d.ts +19 -0
- package/dist/components/inputs/RadioInput.d.ts.map +1 -0
- package/dist/components/inputs/RadioInput.js +19 -0
- package/dist/components/inputs/SelectInput.d.ts +19 -0
- package/dist/components/inputs/SelectInput.d.ts.map +1 -0
- package/dist/components/inputs/SelectInput.js +39 -0
- package/dist/components/inputs/StringInput.d.ts +19 -0
- package/dist/components/inputs/StringInput.d.ts.map +1 -0
- package/dist/components/inputs/StringInput.js +15 -0
- package/dist/components/roster/RosterCheckInput.d.ts +23 -0
- package/dist/components/roster/RosterCheckInput.d.ts.map +1 -0
- package/dist/components/roster/RosterCheckInput.js +111 -0
- package/dist/components/roster/RosterListInput.d.ts +23 -0
- package/dist/components/roster/RosterListInput.d.ts.map +1 -0
- package/dist/components/roster/RosterListInput.js +77 -0
- package/dist/components/roster/RosterPanel.d.ts +34 -0
- package/dist/components/roster/RosterPanel.d.ts.map +1 -0
- package/dist/components/roster/RosterPanel.js +40 -0
- package/dist/components/roster/index.d.ts +11 -0
- package/dist/components/roster/index.d.ts.map +1 -0
- package/dist/components/roster/index.js +8 -0
- package/dist/components/selectors/EnqueteInput.d.ts +29 -0
- package/dist/components/selectors/EnqueteInput.d.ts.map +1 -0
- package/dist/components/selectors/EnqueteInput.js +21 -0
- package/dist/components/selectors/MenageInput.d.ts +29 -0
- package/dist/components/selectors/MenageInput.d.ts.map +1 -0
- package/dist/components/selectors/MenageInput.js +21 -0
- package/dist/components/selectors/RSUInput.d.ts +24 -0
- package/dist/components/selectors/RSUInput.d.ts.map +1 -0
- package/dist/components/selectors/RSUInput.js +16 -0
- package/dist/components/wrappers/ImageUpload.d.ts +26 -0
- package/dist/components/wrappers/ImageUpload.d.ts.map +1 -0
- package/dist/components/wrappers/ImageUpload.js +60 -0
- package/dist/components/wrappers/PhotoCapture.d.ts +26 -0
- package/dist/components/wrappers/PhotoCapture.d.ts.map +1 -0
- package/dist/components/wrappers/PhotoCapture.js +137 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/lib/condition-engine.d.ts +138 -0
- package/dist/lib/condition-engine.d.ts.map +1 -0
- package/dist/lib/condition-engine.js +584 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/utils/cn.d.ts +12 -0
- package/dist/lib/utils/cn.d.ts.map +1 -0
- package/dist/lib/utils/cn.js +14 -0
- package/dist/lib/utils/interpolateVariableLabel.d.ts +54 -0
- package/dist/lib/utils/interpolateVariableLabel.d.ts.map +1 -0
- package/dist/lib/utils/interpolateVariableLabel.js +152 -0
- package/dist/lib/utils/variableValueConverter.d.ts +76 -0
- package/dist/lib/utils/variableValueConverter.d.ts.map +1 -0
- package/dist/lib/utils/variableValueConverter.js +470 -0
- package/dist/types/enquete-service.d.ts +26 -0
- package/dist/types/enquete-service.d.ts.map +1 -0
- package/dist/types/enquete-service.js +5 -0
- package/dist/types/enquete.d.ts +338 -0
- package/dist/types/enquete.d.ts.map +1 -0
- package/dist/types/enquete.js +5 -0
- package/dist/types/form-renderer.d.ts +54 -0
- package/dist/types/form-renderer.d.ts.map +1 -0
- package/dist/types/form-renderer.js +5 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/menages.d.ts +40 -0
- package/dist/types/menages.d.ts.map +1 -0
- package/dist/types/menages.js +5 -0
- package/dist/types/services.d.ts +88 -0
- package/dist/types/services.d.ts.map +1 -0
- package/dist/types/services.js +5 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# @rsu/shared-form-components
|
|
2
|
+
|
|
3
|
+
Composants React partagés pour le rendu des formulaires d'enquête RSU v2.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rsu/shared-form-components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🎯 Objectif
|
|
12
|
+
|
|
13
|
+
Ce package centralise tous les composants de rendu de formulaires pour :
|
|
14
|
+
- **Frontend Admin** (`rsu_app_frontend_v2`) : Gestion interne des enquêtes
|
|
15
|
+
- **Frontend Public** (`rsu_public_frontend_v2`) : Collecte de données par le public
|
|
16
|
+
|
|
17
|
+
## 🚀 Utilisation
|
|
18
|
+
|
|
19
|
+
### Exemple basique
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { FormRenderer } from '@rsu/shared-form-components';
|
|
23
|
+
|
|
24
|
+
function EnquetePage() {
|
|
25
|
+
const [enquete, setEnquete] = useState<EnqueteInstance>({...});
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<FormRenderer
|
|
29
|
+
enquete={enquete}
|
|
30
|
+
formulaire={formulaire}
|
|
31
|
+
disabled={false}
|
|
32
|
+
onSave={handleSave}
|
|
33
|
+
onComplete={handleComplete}
|
|
34
|
+
onCancel={handleCancel}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Avec injection de dépendances (Frontend Admin)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { FormRenderer, FormRendererServices } from '@rsu/shared-form-components';
|
|
44
|
+
import { menagesService, enquetesService } from '@/lib/api';
|
|
45
|
+
import DistrictSelector from '@/components/selectors/geographic/DistrictSelector';
|
|
46
|
+
// ... autres imports
|
|
47
|
+
|
|
48
|
+
const services: FormRendererServices = {
|
|
49
|
+
fetchMenages: () => menagesService.getAll(),
|
|
50
|
+
fetchEnquetes: (filter) => enquetesService.getFiltered(filter),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const geographicComponents = {
|
|
54
|
+
DistrictSelectorComponent: DistrictSelector,
|
|
55
|
+
RegionSelectorComponent: RegionSelector,
|
|
56
|
+
// ... autres composants
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
<FormRenderer
|
|
60
|
+
enquete={enquete}
|
|
61
|
+
formulaire={formulaire}
|
|
62
|
+
services={services}
|
|
63
|
+
geographicComponents={geographicComponents}
|
|
64
|
+
onSave={handleSave}
|
|
65
|
+
/>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 📋 Types supportés
|
|
69
|
+
|
|
70
|
+
### Types de base
|
|
71
|
+
- STRING, TEXTE, NUMERIQUE, DATE, DATEHEURE, HOUR
|
|
72
|
+
- CHECKBOX, RADIO, LISTE, COMBOBOX, PHONE
|
|
73
|
+
|
|
74
|
+
### Types avancés
|
|
75
|
+
- GPS, PHOTO, IMAGE
|
|
76
|
+
- LABEL, PANEL
|
|
77
|
+
|
|
78
|
+
### Types géographiques
|
|
79
|
+
- DISTRICT, REGION, DEPARTEMENT, SOUSPREFECTURE, QUARTIER, VILLAGE, RSU
|
|
80
|
+
|
|
81
|
+
### Types sélecteurs
|
|
82
|
+
- MENAGE, ENQUETE
|
|
83
|
+
|
|
84
|
+
### Types rosters (⭐ avec rendu natif)
|
|
85
|
+
- ROSTERCHECK, ROSTERLIST
|
|
86
|
+
|
|
87
|
+
### Types documents
|
|
88
|
+
- IDDOC
|
|
89
|
+
|
|
90
|
+
## 🔧 Architecture
|
|
91
|
+
|
|
92
|
+
### Injection de dépendances
|
|
93
|
+
|
|
94
|
+
Le package utilise le pattern d'injection de dépendances pour :
|
|
95
|
+
- **Services API** : `FormRendererServices` (optionnel)
|
|
96
|
+
- **Composants géographiques** : `GeographicComponents` (optionnel)
|
|
97
|
+
|
|
98
|
+
Cela permet :
|
|
99
|
+
- ✅ Utilisation avec différentes API
|
|
100
|
+
- ✅ Réutilisation de composants existants
|
|
101
|
+
- ✅ Flexibilité maximale
|
|
102
|
+
|
|
103
|
+
### Support des rosters
|
|
104
|
+
|
|
105
|
+
Les rosters (ROSTERCHECK et ROSTERLIST) sont automatiquement supportés avec :
|
|
106
|
+
- Rendu des variables selon leur type natif (RADIO, COMBOBOX, etc.)
|
|
107
|
+
- Indépendance des variables entre panels
|
|
108
|
+
- Comportement conditionnel pour ROSTERCHECK (checkbox + masquage)
|
|
109
|
+
|
|
110
|
+
## 📝 Changelog
|
|
111
|
+
|
|
112
|
+
### v1.0.0 (2025-11-25)
|
|
113
|
+
- ✅ Extraction depuis rsu_app_frontend_v2
|
|
114
|
+
- ✅ Support rosters avec RosterPanel
|
|
115
|
+
- ✅ Indépendance variables entre panels
|
|
116
|
+
- ✅ Comportement conditionnel ROSTERCHECK
|
|
117
|
+
- ✅ Injection de dépendances pour services et composants géographiques
|
|
118
|
+
- ✅ 27 types de variables supportés
|
|
119
|
+
|
|
120
|
+
## 🏗️ Développement
|
|
121
|
+
|
|
122
|
+
### Build
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm run build
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Watch mode
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm run watch
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Tests
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm test
|
|
138
|
+
npm run test:coverage
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 📚 Documentation technique
|
|
142
|
+
|
|
143
|
+
- Plan de mutualisation : `docs/mutualisation_FormRenderer.md`
|
|
144
|
+
- Refonte rosters : `docs/ROSTER_PANELS_FINAL.md`
|
|
145
|
+
|
|
146
|
+
## 👥 Auteurs
|
|
147
|
+
|
|
148
|
+
RSU v2 Architecture Team
|
|
149
|
+
|
|
150
|
+
## 📄 License
|
|
151
|
+
|
|
152
|
+
UNLICENSED - Usage interne uniquement
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composant de rendu des groupes pour les formulaires d'enquête
|
|
3
|
+
* RSU v2 - Moteur de Rendu des Formulaires d'Enquête
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { GroupeFormulaire, EnqueteReponse, VariableValue, ValidationError, AutoAction } from '../types/enquete';
|
|
7
|
+
import { ConditionEngine } from '../lib/condition-engine';
|
|
8
|
+
interface GroupRendererProps {
|
|
9
|
+
groupe: GroupeFormulaire;
|
|
10
|
+
responses: Record<string, EnqueteReponse>;
|
|
11
|
+
conditionEngine: ConditionEngine;
|
|
12
|
+
onChange: (variableCode: string, value: VariableValue, iteration?: number) => void;
|
|
13
|
+
onValidation: (errors: ValidationError[]) => void;
|
|
14
|
+
onAutoAction?: (actions: AutoAction[]) => void;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare const GroupRenderer: React.FC<GroupRendererProps>;
|
|
18
|
+
export default GroupRenderer;
|
|
19
|
+
//# sourceMappingURL=GroupRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupRenderer.d.ts","sourceRoot":"","sources":["../../src/components/GroupRenderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,eAAe,EACf,UAAU,EACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,UAAU,kBAAkB;IAC1B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnF,YAAY,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IAClD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiJD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkS/C,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composant de rendu des groupes pour les formulaires d'enquête
|
|
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, Trash2, Users, AlertCircle } from 'lucide-react';
|
|
9
|
+
import VariableRenderer from './VariableRenderer';
|
|
10
|
+
// Composant pour gérer les groupes multiples
|
|
11
|
+
const MultipleGroupManager = ({ groupe, entries, onAddEntry, onRemoveEntry, children }) => {
|
|
12
|
+
const canAddEntry = entries.length < (groupe.configMultiple?.maxIterations || 50);
|
|
13
|
+
const canRemoveEntry = entries.length > (groupe.configMultiple?.minIterations || 1);
|
|
14
|
+
return (_jsxs("div", { className: "multiple-group-manager space-y-6", children: [_jsxs("div", { className: "flex items-center justify-between p-4 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("div", { className: "flex items-center space-x-3", children: [_jsx(Users, { className: "h-5 w-5 text-blue-600" }), _jsxs("div", { children: [_jsx("h3", { className: "font-medium text-blue-900", children: groupe.designation }), _jsxs("p", { className: "text-sm text-blue-700", children: [entries.length, " ", entries.length > 1 ? 'entrées' : 'entrée', groupe.configMultiple?.maxIterations && (_jsxs("span", { children: [" (max: ", groupe.configMultiple.maxIterations, ")"] }))] })] })] }), groupe.configMultiple?.allowManualControl && (_jsxs("button", { onClick: onAddEntry, disabled: !canAddEntry, className: `flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors ${canAddEntry
|
|
15
|
+
? 'bg-green-600 text-white hover:bg-green-700 focus:ring-2 focus:ring-green-500'
|
|
16
|
+
: 'bg-gray-300 text-gray-500 cursor-not-allowed'}`, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), "Ajouter"] }))] }), _jsx("div", { className: "space-y-4", children: children })] }));
|
|
17
|
+
};
|
|
18
|
+
// Composant pour une entrée individuelle dans un groupe multiple
|
|
19
|
+
const MultipleGroupEntry = ({ groupe, iterationNumber, totalIterations, visibleVariables, responses, onChange, onRemove, canRemove, disabled }) => {
|
|
20
|
+
const entryTitle = groupe.configMultiple?.labelTemplate?.replace('{index}', iterationNumber.toString()) ||
|
|
21
|
+
`${groupe.designation} ${iterationNumber}`;
|
|
22
|
+
return (_jsxs("div", { className: "iteration-card bg-white border border-gray-200 rounded-lg p-4 shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("h4", { className: "text-lg font-medium text-gray-800 flex items-center", children: [_jsx("span", { className: "w-6 h-6 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center text-sm font-semibold mr-2", children: iterationNumber }), entryTitle] }), groupe.configMultiple?.allowManualControl && canRemove && onRemove && (_jsx("button", { onClick: onRemove, disabled: disabled, className: "p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed", title: "Supprimer cette entr\u00E9e", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }), _jsx("div", { className: "variables-grid grid grid-cols-1 md:grid-cols-2 gap-4", children: visibleVariables.map(variable => {
|
|
23
|
+
const responseKey = `${variable.code}_${iterationNumber}`;
|
|
24
|
+
// Extraire les variables roster si c'est un type ROSTERCHECK ou ROSTERLIST
|
|
25
|
+
const rosterVariables = (variable.typeCode === 'ROSTERCHECK' || variable.typeCode === 'ROSTERLIST')
|
|
26
|
+
? variable.rosterVariables
|
|
27
|
+
: undefined;
|
|
28
|
+
// 🔍 LOG: Vérifier la transmission des rosterVariables dans MultipleGroupEntry
|
|
29
|
+
if (variable.typeCode === 'ROSTERCHECK' || variable.typeCode === 'ROSTERLIST') {
|
|
30
|
+
console.log(`🔍 [GroupRenderer/MultipleGroupEntry] Variable ${variable.code}:`, {
|
|
31
|
+
typeCode: variable.typeCode,
|
|
32
|
+
rosterVariables: rosterVariables,
|
|
33
|
+
rosterVariablesType: typeof rosterVariables,
|
|
34
|
+
isArray: Array.isArray(rosterVariables),
|
|
35
|
+
length: Array.isArray(rosterVariables) ? rosterVariables.length : 'N/A'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return (_jsx(VariableRenderer, { variable: variable, value: responses[responseKey]?.valeur, onChange: (value) => onChange(responseKey, value, iterationNumber), numeroMembre: iterationNumber, disabled: disabled, rosterVariables: rosterVariables }, responseKey));
|
|
39
|
+
}) })] }));
|
|
40
|
+
};
|
|
41
|
+
// Composant principal GroupRenderer
|
|
42
|
+
const GroupRenderer = ({ groupe, responses, conditionEngine, onChange, onValidation, onAutoAction, disabled = false }) => {
|
|
43
|
+
const [multipleEntries, setMultipleEntries] = useState([1]);
|
|
44
|
+
const [validationErrors, setValidationErrors] = useState([]);
|
|
45
|
+
// Obtenir le nombre d'itérations depuis la variable de contrôle
|
|
46
|
+
const getIterationCount = () => {
|
|
47
|
+
if (!groupe.estMultiple || !groupe.configMultiple?.variableControle) {
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const controlValue = responses[groupe.configMultiple.variableControle]?.valeur;
|
|
51
|
+
if (!controlValue || controlValue < 1) {
|
|
52
|
+
return groupe.configMultiple.minIterations || 1;
|
|
53
|
+
}
|
|
54
|
+
return Math.min(controlValue, groupe.configMultiple.maxIterations || 50);
|
|
55
|
+
};
|
|
56
|
+
// Filtrer les variables visibles selon les conditions (avec support des nouvelles fonctions)
|
|
57
|
+
const getVisibleVariables = () => {
|
|
58
|
+
console.log('📋 [GroupRenderer] Filtering visible variables for group:', groupe.designation);
|
|
59
|
+
return groupe.variables.filter(variable => {
|
|
60
|
+
console.log(`📋 [GroupRenderer] Processing variable: ${variable.code} (${variable.designation})`);
|
|
61
|
+
console.log(`📋 [GroupRenderer] - estVisible: ${variable.estVisible}`);
|
|
62
|
+
console.log(`📋 [GroupRenderer] - conditionsAffichage: ${variable.conditionsAffichage}`);
|
|
63
|
+
if (!variable.conditionsAffichage) {
|
|
64
|
+
console.log(`📋 [GroupRenderer] - No condition, returning estVisible: ${variable.estVisible}`);
|
|
65
|
+
return variable.estVisible;
|
|
66
|
+
}
|
|
67
|
+
// Évaluer la condition avec le moteur étendu
|
|
68
|
+
const isVisible = conditionEngine.evaluate(variable.conditionsAffichage);
|
|
69
|
+
console.log(`📋 [GroupRenderer] - Condition evaluation result: ${isVisible}`);
|
|
70
|
+
const finalResult = variable.estVisible && isVisible;
|
|
71
|
+
console.log(`📋 [GroupRenderer] - Final visibility: ${finalResult} (estVisible: ${variable.estVisible} && condition: ${isVisible})`);
|
|
72
|
+
return finalResult;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const visibleVariables = getVisibleVariables();
|
|
76
|
+
// Mettre à jour le nombre d'itérations quand la variable de contrôle change
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (groupe.estMultiple && groupe.configMultiple?.variableControle) {
|
|
79
|
+
const newCount = getIterationCount();
|
|
80
|
+
if (groupe.configMultiple.allowManualControl) {
|
|
81
|
+
// Pour les groupes avec contrôle manuel, ajuster le nombre d'entrées
|
|
82
|
+
if (newCount > multipleEntries.length) {
|
|
83
|
+
const additionalEntries = Array.from({ length: newCount - multipleEntries.length }, (_, i) => multipleEntries.length + i + 1);
|
|
84
|
+
setMultipleEntries([...multipleEntries, ...additionalEntries]);
|
|
85
|
+
}
|
|
86
|
+
else if (newCount < multipleEntries.length) {
|
|
87
|
+
setMultipleEntries(multipleEntries.slice(0, newCount));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Pour les groupes automatiques, générer les entrées
|
|
92
|
+
setMultipleEntries(Array.from({ length: newCount }, (_, i) => i + 1));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, [responses[groupe.configMultiple?.variableControle || '']?.valeur, groupe]);
|
|
96
|
+
// Validation du groupe
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const errors = [];
|
|
99
|
+
visibleVariables.forEach(variable => {
|
|
100
|
+
if (variable.estObligatoire) {
|
|
101
|
+
if (groupe.estMultiple) {
|
|
102
|
+
// Valider toutes les itérations
|
|
103
|
+
multipleEntries.forEach(iterationNumber => {
|
|
104
|
+
const responseKey = `${variable.code}_${iterationNumber}`;
|
|
105
|
+
const response = responses[responseKey];
|
|
106
|
+
if (!response || isEmpty(response.valeur)) {
|
|
107
|
+
const entryLabel = groupe.configMultiple?.labelTemplate?.replace('{index}', iterationNumber.toString()) ||
|
|
108
|
+
`Entrée ${iterationNumber}`;
|
|
109
|
+
errors.push({
|
|
110
|
+
variableCode: responseKey,
|
|
111
|
+
message: `${variable.designation} est obligatoire (${entryLabel})`,
|
|
112
|
+
type: 'required'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Groupe simple
|
|
119
|
+
const response = responses[variable.code];
|
|
120
|
+
if (!response || isEmpty(response.valeur)) {
|
|
121
|
+
errors.push({
|
|
122
|
+
variableCode: variable.code,
|
|
123
|
+
message: `${variable.designation} est obligatoire`,
|
|
124
|
+
type: 'required'
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
setValidationErrors(errors);
|
|
131
|
+
onValidation(errors);
|
|
132
|
+
}, [responses, visibleVariables, multipleEntries, groupe, onValidation]);
|
|
133
|
+
// Exécuter les actions automatiques quand les réponses changent
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!onAutoAction)
|
|
136
|
+
return;
|
|
137
|
+
const executeAutoActions = () => {
|
|
138
|
+
const allActions = [];
|
|
139
|
+
// Parcourir toutes les variables pour détecter les actions automatiques
|
|
140
|
+
visibleVariables.forEach(variable => {
|
|
141
|
+
if (variable.conditionsAffichage && variable.conditionsAffichage.includes('setValeur(')) {
|
|
142
|
+
// Évaluer la condition pour déclencher les actions automatiques
|
|
143
|
+
conditionEngine.evaluate(variable.conditionsAffichage);
|
|
144
|
+
// Récupérer les actions générées
|
|
145
|
+
const actions = conditionEngine.getAutoActions();
|
|
146
|
+
allActions.push(...actions);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (allActions.length > 0) {
|
|
150
|
+
onAutoAction(allActions);
|
|
151
|
+
conditionEngine.clearAutoActions(); // Nettoyer après exécution
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
// Exécuter avec un délai pour éviter les boucles infinies
|
|
155
|
+
const timeoutId = setTimeout(executeAutoActions, 100);
|
|
156
|
+
return () => clearTimeout(timeoutId);
|
|
157
|
+
}, [responses, visibleVariables, conditionEngine, onAutoAction]);
|
|
158
|
+
// Gestion des entrées multiples
|
|
159
|
+
const addEntry = () => {
|
|
160
|
+
if (!groupe.configMultiple?.allowManualControl)
|
|
161
|
+
return;
|
|
162
|
+
const maxIterations = groupe.configMultiple.maxIterations || 50;
|
|
163
|
+
if (multipleEntries.length < maxIterations) {
|
|
164
|
+
const newEntryNumber = Math.max(...multipleEntries) + 1;
|
|
165
|
+
setMultipleEntries([...multipleEntries, newEntryNumber]);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const removeEntry = (index) => {
|
|
169
|
+
if (!groupe.configMultiple?.allowManualControl)
|
|
170
|
+
return;
|
|
171
|
+
if (multipleEntries.length <= (groupe.configMultiple.minIterations || 1))
|
|
172
|
+
return;
|
|
173
|
+
const entryNumber = multipleEntries[index];
|
|
174
|
+
const newEntries = multipleEntries.filter((_, i) => i !== index);
|
|
175
|
+
setMultipleEntries(newEntries);
|
|
176
|
+
// Supprimer les réponses de cette itération
|
|
177
|
+
visibleVariables.forEach(variable => {
|
|
178
|
+
const responseKey = `${variable.code}_${entryNumber}`;
|
|
179
|
+
if (responses[responseKey]) {
|
|
180
|
+
onChange(responseKey, null, entryNumber);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
const isEmpty = (value) => {
|
|
185
|
+
return value === null ||
|
|
186
|
+
value === undefined ||
|
|
187
|
+
value === '' ||
|
|
188
|
+
(Array.isArray(value) && value.length === 0);
|
|
189
|
+
};
|
|
190
|
+
// Vérifier si le groupe doit être affiché
|
|
191
|
+
const shouldShowGroup = !groupe.conditionsAffichage ||
|
|
192
|
+
conditionEngine.evaluate(groupe.conditionsAffichage);
|
|
193
|
+
if (!shouldShowGroup) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
// Affichage des erreurs de validation
|
|
197
|
+
const ErrorSummary = () => {
|
|
198
|
+
if (validationErrors.length === 0)
|
|
199
|
+
return null;
|
|
200
|
+
return (_jsxs("div", { className: "mb-4 p-3 bg-red-50 border border-red-200 rounded-lg", children: [_jsxs("div", { className: "flex items-center mb-2", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-red-600 mr-2" }), _jsxs("h4", { className: "text-sm font-medium text-red-800", children: ["Erreurs de validation (", validationErrors.length, ")"] })] }), _jsxs("ul", { className: "text-sm text-red-700 space-y-1", children: [validationErrors.slice(0, 5).map((error, index) => (_jsxs("li", { children: ["\u2022 ", error.message] }, index))), validationErrors.length > 5 && (_jsxs("li", { className: "text-red-600 font-medium", children: ["... et ", validationErrors.length - 5, " autres erreurs"] }))] })] }));
|
|
201
|
+
};
|
|
202
|
+
if (groupe.estMultiple) {
|
|
203
|
+
return (_jsxs("div", { className: "group-renderer-multiple", children: [_jsx(ErrorSummary, {}), _jsx(MultipleGroupManager, { groupe: groupe, entries: multipleEntries, onAddEntry: addEntry, onRemoveEntry: removeEntry, children: multipleEntries.map((iterationNumber, index) => (_jsx(MultipleGroupEntry, { groupe: groupe, iterationNumber: iterationNumber, totalIterations: multipleEntries.length, visibleVariables: visibleVariables, responses: responses, onChange: onChange, onRemove: () => removeEntry(index), canRemove: multipleEntries.length > (groupe.configMultiple?.minIterations || 1), disabled: disabled }, iterationNumber))) })] }));
|
|
204
|
+
}
|
|
205
|
+
// Groupe simple
|
|
206
|
+
return (_jsxs("div", { className: "group-renderer-simple", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h2", { className: "text-xl font-semibold text-gray-900 mb-2", children: groupe.designation }), visibleVariables.length > 0 && (_jsxs("p", { className: "text-gray-600 text-sm", children: [visibleVariables.length, " ", visibleVariables.length > 1 ? 'champs' : 'champ'] }))] }), _jsx(ErrorSummary, {}), _jsx("div", { className: "variables-container space-y-6", children: visibleVariables.map(variable => {
|
|
207
|
+
// Extraire les variables roster si c'est un type ROSTERCHECK ou ROSTERLIST
|
|
208
|
+
const rosterVariables = (variable.typeCode === 'ROSTERCHECK' || variable.typeCode === 'ROSTERLIST')
|
|
209
|
+
? variable.rosterVariables
|
|
210
|
+
: undefined;
|
|
211
|
+
return (_jsx(VariableRenderer, { variable: variable, value: responses[variable.code]?.valeur, onChange: (value) => onChange(variable.code, value), disabled: disabled, rosterVariables: rosterVariables }, variable.code));
|
|
212
|
+
}) }), visibleVariables.length === 0 && (_jsx("div", { className: "text-center py-8 text-gray-500", children: _jsx("p", { children: "Aucun champ \u00E0 afficher dans ce groupe" }) }))] }));
|
|
213
|
+
};
|
|
214
|
+
export default GroupRenderer;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composant IdDocInput - Capture et upload de documents d'identité
|
|
3
|
+
* RSU v2 - Moteur de Rendu des Formulaires d'Enquête
|
|
4
|
+
*
|
|
5
|
+
* Ce composant permet de capturer ou uploader un document d'identité
|
|
6
|
+
* (CNI, passeport, carte électorale, etc.) avec prévisualisation.
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { VariableFormulaire, VariableValue } from '../types/enquete';
|
|
10
|
+
export interface IdDocInputProps {
|
|
11
|
+
variable: VariableFormulaire;
|
|
12
|
+
value: VariableValue;
|
|
13
|
+
onChange: (value: VariableValue) => void;
|
|
14
|
+
onBlur?: () => void;
|
|
15
|
+
error?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const IdDocInput: React.FC<IdDocInputProps>;
|
|
19
|
+
export default IdDocInput;
|
|
20
|
+
//# sourceMappingURL=IdDocInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IdDocInput.d.ts","sourceRoot":"","sources":["../../src/components/IdDocInput.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIrE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuBD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAkYzC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composant IdDocInput - Capture et upload de documents d'identité
|
|
3
|
+
* RSU v2 - Moteur de Rendu des Formulaires d'Enquête
|
|
4
|
+
*
|
|
5
|
+
* Ce composant permet de capturer ou uploader un document d'identité
|
|
6
|
+
* (CNI, passeport, carte électorale, etc.) avec prévisualisation.
|
|
7
|
+
*/
|
|
8
|
+
'use client';
|
|
9
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
11
|
+
import { AlertCircle, Camera, Upload, X, FileText, CreditCard } from 'lucide-react';
|
|
12
|
+
// Types de documents d'identité supportés
|
|
13
|
+
const DOC_TYPES = [
|
|
14
|
+
{ code: 'CNI', label: 'Carte Nationale d\'Identité' },
|
|
15
|
+
{ code: 'PASSEPORT', label: 'Passeport' },
|
|
16
|
+
{ code: 'CARTE_ELECTEUR', label: 'Carte d\'Électeur' },
|
|
17
|
+
{ code: 'PERMIS', label: 'Permis de Conduire' },
|
|
18
|
+
{ code: 'AUTRE', label: 'Autre Document' }
|
|
19
|
+
];
|
|
20
|
+
const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
21
|
+
const [preview, setPreview] = useState(null);
|
|
22
|
+
const [captureMode, setCaptureMode] = useState('upload');
|
|
23
|
+
const [stream, setStream] = useState(null);
|
|
24
|
+
const [isCapturing, setIsCapturing] = useState(false);
|
|
25
|
+
const fileInputRef = useRef(null);
|
|
26
|
+
const videoRef = useRef(null);
|
|
27
|
+
const canvasRef = useRef(null);
|
|
28
|
+
// Parser la valeur actuelle
|
|
29
|
+
const currentData = React.useMemo(() => {
|
|
30
|
+
if (!value)
|
|
31
|
+
return { docType: '' };
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(value);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { docType: '' };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'object') {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
return { docType: '' };
|
|
44
|
+
}, [value]);
|
|
45
|
+
// Mettre à jour l'aperçu quand la valeur change
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (currentData.file) {
|
|
48
|
+
const reader = new FileReader();
|
|
49
|
+
reader.onload = (e) => setPreview(e.target?.result);
|
|
50
|
+
reader.readAsDataURL(currentData.file);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
setPreview(null);
|
|
54
|
+
if (fileInputRef.current) {
|
|
55
|
+
fileInputRef.current.value = '';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, [currentData.file]);
|
|
59
|
+
// Nettoyer le stream quand le composant est démonté
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
return () => {
|
|
62
|
+
if (stream) {
|
|
63
|
+
stream.getTracks().forEach(track => track.stop());
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, [stream]);
|
|
67
|
+
// Mettre à jour les données
|
|
68
|
+
const updateData = (updates) => {
|
|
69
|
+
const newData = { ...currentData, ...updates };
|
|
70
|
+
onChange(JSON.stringify(newData));
|
|
71
|
+
};
|
|
72
|
+
// Gérer la sélection de fichier
|
|
73
|
+
const handleFileSelect = (event) => {
|
|
74
|
+
if (disabled)
|
|
75
|
+
return;
|
|
76
|
+
const file = event.target.files?.[0];
|
|
77
|
+
if (!file)
|
|
78
|
+
return;
|
|
79
|
+
processFile(file);
|
|
80
|
+
};
|
|
81
|
+
// Traiter le fichier sélectionné
|
|
82
|
+
const processFile = (file) => {
|
|
83
|
+
// Validation de la taille
|
|
84
|
+
const maxSize = variable.proprietes?.maxFileSize || 10 * 1024 * 1024; // 10MB par défaut
|
|
85
|
+
if (file.size > maxSize) {
|
|
86
|
+
alert(`Fichier trop volumineux. Taille maximum: ${Math.round(maxSize / 1024 / 1024)}MB`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Validation du type
|
|
90
|
+
const acceptedTypes = variable.proprietes?.acceptedTypes || [
|
|
91
|
+
'image/jpeg', 'image/png', 'image/webp',
|
|
92
|
+
'application/pdf'
|
|
93
|
+
];
|
|
94
|
+
if (!acceptedTypes.includes(file.type)) {
|
|
95
|
+
alert(`Type de fichier non supporté. Types acceptés: ${acceptedTypes.join(', ')}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Sauvegarder le fichier
|
|
99
|
+
updateData({
|
|
100
|
+
file,
|
|
101
|
+
fileName: file.name,
|
|
102
|
+
fileSize: file.size,
|
|
103
|
+
fileType: file.type,
|
|
104
|
+
timestamp: new Date(),
|
|
105
|
+
source: captureMode === 'camera' ? 'camera' : 'upload'
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
// Démarrer la caméra
|
|
109
|
+
const startCamera = async () => {
|
|
110
|
+
if (disabled)
|
|
111
|
+
return;
|
|
112
|
+
try {
|
|
113
|
+
setIsCapturing(true);
|
|
114
|
+
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
115
|
+
video: {
|
|
116
|
+
facingMode: 'environment' // Caméra arrière par défaut
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
setStream(mediaStream);
|
|
120
|
+
if (videoRef.current) {
|
|
121
|
+
videoRef.current.srcObject = mediaStream;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('Erreur accès caméra:', error);
|
|
126
|
+
alert('Impossible d\'accéder à la caméra. Vérifiez les permissions.');
|
|
127
|
+
setIsCapturing(false);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
// Arrêter la caméra
|
|
131
|
+
const stopCamera = () => {
|
|
132
|
+
if (stream) {
|
|
133
|
+
stream.getTracks().forEach(track => track.stop());
|
|
134
|
+
setStream(null);
|
|
135
|
+
}
|
|
136
|
+
setIsCapturing(false);
|
|
137
|
+
};
|
|
138
|
+
// Capturer la photo
|
|
139
|
+
const capturePhoto = () => {
|
|
140
|
+
if (!videoRef.current || !canvasRef.current)
|
|
141
|
+
return;
|
|
142
|
+
const video = videoRef.current;
|
|
143
|
+
const canvas = canvasRef.current;
|
|
144
|
+
const context = canvas.getContext('2d');
|
|
145
|
+
if (!context)
|
|
146
|
+
return;
|
|
147
|
+
// Définir les dimensions du canvas
|
|
148
|
+
canvas.width = video.videoWidth;
|
|
149
|
+
canvas.height = video.videoHeight;
|
|
150
|
+
// Dessiner l'image du video sur le canvas
|
|
151
|
+
context.drawImage(video, 0, 0);
|
|
152
|
+
// Convertir en blob
|
|
153
|
+
canvas.toBlob((blob) => {
|
|
154
|
+
if (blob) {
|
|
155
|
+
const file = new File([blob], `iddoc_${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
156
|
+
processFile(file);
|
|
157
|
+
stopCamera();
|
|
158
|
+
}
|
|
159
|
+
}, 'image/jpeg', 0.9);
|
|
160
|
+
};
|
|
161
|
+
// Supprimer le document
|
|
162
|
+
const clearDocument = () => {
|
|
163
|
+
updateData({
|
|
164
|
+
file: undefined,
|
|
165
|
+
fileName: undefined,
|
|
166
|
+
fileSize: undefined,
|
|
167
|
+
fileType: undefined,
|
|
168
|
+
timestamp: undefined,
|
|
169
|
+
source: undefined
|
|
170
|
+
});
|
|
171
|
+
if (fileInputRef.current) {
|
|
172
|
+
fileInputRef.current.value = '';
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700", children: "Type de document" }), _jsxs("select", { value: currentData.docType || '', onChange: (e) => updateData({ docType: e.target.value }), onBlur: onBlur, disabled: disabled, className: `w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-gray-900 ${error ? 'border-red-500' : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed text-gray-500' : 'bg-white'}`, children: [_jsx("option", { value: "", children: "S\u00E9lectionner le type..." }), DOC_TYPES.map((docType) => (_jsx("option", { value: docType.code, children: docType.label }, docType.code)))] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700", children: "Num\u00E9ro du document (optionnel)" }), _jsx("input", { type: "text", value: currentData.docNumber || '', onChange: (e) => updateData({ docNumber: e.target.value }), onBlur: onBlur, placeholder: "Ex: CI-123456789", disabled: disabled, className: `w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-gray-900 ${error ? 'border-red-500' : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed text-gray-500' : 'bg-white'}` })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700", children: "Image du document" }), _jsxs("div", { className: "flex space-x-2 mb-3", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
176
|
+
setCaptureMode('upload');
|
|
177
|
+
stopCamera();
|
|
178
|
+
}, disabled: disabled, className: `flex items-center px-3 py-2 rounded-lg text-sm font-medium transition-colors ${captureMode === 'upload'
|
|
179
|
+
? 'bg-blue-600 text-white'
|
|
180
|
+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`, children: [_jsx(Upload, { className: "w-4 h-4 mr-2" }), "Upload"] }), _jsxs("button", { type: "button", onClick: () => {
|
|
181
|
+
setCaptureMode('camera');
|
|
182
|
+
if (!isCapturing)
|
|
183
|
+
startCamera();
|
|
184
|
+
}, disabled: disabled, className: `flex items-center px-3 py-2 rounded-lg text-sm font-medium transition-colors ${captureMode === 'camera'
|
|
185
|
+
? 'bg-blue-600 text-white'
|
|
186
|
+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`, children: [_jsx(Camera, { className: "w-4 h-4 mr-2" }), "Cam\u00E9ra"] })] }), captureMode === 'upload' && !preview && (_jsxs("div", { className: "relative", children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*,application/pdf", onChange: handleFileSelect, disabled: disabled, className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer" }), _jsxs("div", { className: `flex flex-col items-center justify-center p-6 border-2 border-dashed rounded-lg ${error ? 'border-red-500' : 'border-gray-300'} ${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white hover:border-green-500 cursor-pointer'}`, children: [_jsx(CreditCard, { className: "w-10 h-10 text-gray-400 mb-2" }), _jsx("span", { className: "text-sm text-gray-600", children: "Cliquez ou glissez-d\u00E9posez" }), _jsx("span", { className: "text-xs text-gray-400 mt-1", children: "JPG, PNG, PDF - Max 10MB" })] })] })), captureMode === 'camera' && (_jsxs("div", { className: "space-y-3", children: [isCapturing && (_jsxs("div", { className: "relative", children: [_jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, className: "w-full max-w-md mx-auto rounded-lg border border-gray-300" }), _jsxs("div", { className: "flex justify-center space-x-2 mt-2", children: [_jsxs("button", { type: "button", onClick: capturePhoto, disabled: disabled, className: "flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50", children: [_jsx(Camera, { className: "w-4 h-4 mr-2" }), "Capturer"] }), _jsxs("button", { type: "button", onClick: stopCamera, disabled: disabled, className: "flex items-center px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50", children: [_jsx(X, { className: "w-4 h-4 mr-2" }), "Arr\u00EAter"] })] })] })), !isCapturing && !preview && (_jsxs("button", { type: "button", onClick: startCamera, disabled: disabled, className: "w-full flex items-center justify-center px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50", children: [_jsx(Camera, { className: "w-5 h-5 mr-2" }), "D\u00E9marrer la cam\u00E9ra"] }))] })), _jsx("canvas", { ref: canvasRef, style: { display: 'none' } }), preview && (_jsxs("div", { className: "relative", children: [currentData.fileType?.startsWith('image/') ? (_jsx("img", { src: preview, alt: "Aper\u00E7u du document", className: "w-full max-w-md mx-auto rounded-lg shadow-md border border-gray-200" })) : (_jsxs("div", { className: "flex items-center justify-center p-6 bg-gray-100 rounded-lg", children: [_jsx(FileText, { className: "w-10 h-10 text-gray-500 mr-3" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-gray-700", children: currentData.fileName }), _jsx("p", { className: "text-xs text-gray-500", children: currentData.fileSize ? `${Math.round(currentData.fileSize / 1024)} KB` : '' })] })] })), _jsx("button", { type: "button", onClick: clearDocument, disabled: disabled, className: "absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full hover:bg-red-600 focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50", children: _jsx(X, { className: "w-4 h-4" }) })] })), currentData.fileName && (_jsxs("div", { className: "p-2 bg-green-50 border border-green-200 rounded text-sm", children: [_jsxs("div", { className: "flex items-center text-green-800", children: [_jsx(FileText, { className: "w-4 h-4 mr-2" }), _jsx("span", { className: "font-medium", children: currentData.fileName })] }), currentData.source && (_jsxs("p", { className: "text-xs text-green-600 mt-1", children: ["Source: ", currentData.source === 'camera' ? 'Caméra' : 'Upload'] }))] }))] }), error && (_jsxs("div", { className: "flex items-center space-x-1 text-red-600 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4" }), _jsx("span", { children: error })] }))] }));
|
|
187
|
+
};
|
|
188
|
+
export default IdDocInput;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { VariableFormulaire, VariableValue, EnqueteReponse } from '../types/enquete';
|
|
3
|
+
import type { MenageCompletDto } from '../types/menages';
|
|
4
|
+
import type { EnqueteCompletDto } from '../types/enquete-service';
|
|
5
|
+
export interface VariableRendererProps {
|
|
6
|
+
variable: VariableFormulaire;
|
|
7
|
+
value: VariableValue;
|
|
8
|
+
onChange: (value: VariableValue) => void;
|
|
9
|
+
onBlur?: () => void;
|
|
10
|
+
error?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
numeroMembre?: number;
|
|
13
|
+
valeurMin?: number;
|
|
14
|
+
onFillFormFromMenage?: (menageData: MenageCompletDto) => void;
|
|
15
|
+
onFillFormFromEnquete?: (enqueteData: EnqueteCompletDto) => void;
|
|
16
|
+
formulaireVariables?: VariableFormulaire[];
|
|
17
|
+
rosterVariables?: Array<{
|
|
18
|
+
code: string;
|
|
19
|
+
ordre: number;
|
|
20
|
+
designation?: string;
|
|
21
|
+
valeur?: string;
|
|
22
|
+
}>;
|
|
23
|
+
allResponses?: Record<string, EnqueteReponse>;
|
|
24
|
+
RosterCheckInput?: React.ComponentType<VariableRendererProps>;
|
|
25
|
+
RosterListInput?: React.ComponentType<VariableRendererProps>;
|
|
26
|
+
interpolateVariableLabel?: (label: string, responses: Record<string, EnqueteReponse>, numeroMembre?: number) => string;
|
|
27
|
+
services?: any;
|
|
28
|
+
geographicComponents?: any;
|
|
29
|
+
}
|
|
30
|
+
declare const GeographicFallback: React.FC<VariableRendererProps>;
|
|
31
|
+
declare const getVariableRenderer: (typeCode: string, RosterCheckInput?: React.ComponentType<VariableRendererProps>, RosterListInput?: React.ComponentType<VariableRendererProps>) => React.ComponentType<VariableRendererProps>;
|
|
32
|
+
declare const VariableRenderer: React.FC<VariableRendererProps>;
|
|
33
|
+
export default VariableRenderer;
|
|
34
|
+
export { getVariableRenderer, GeographicFallback };
|
|
35
|
+
//# sourceMappingURL=VariableRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VariableRenderer.d.ts","sourceRoot":"","sources":["../../src/components/VariableRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAgCxC,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9D,qBAAqB,CAAC,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACjE,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,eAAe,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAG9C,gBAAgB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC9D,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC7D,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAGvH,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,oBAAoB,CAAC,EAAE,GAAG,CAAC;CAC5B;AAGD,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAQvD,CAAC;AAGF,QAAA,MAAM,mBAAmB,GACvB,UAAU,MAAM,EAChB,mBAAmB,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,EAC7D,kBAAkB,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,KAC3D,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAgD3C,CAAC;AAGF,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA+FrD,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,CAAC"}
|