@rsuci/shared-form-components 1.0.77 → 1.0.79
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/dist/components/IdDocInput.d.ts.map +1 -1
- package/dist/components/IdDocInput.js +26 -28
- package/dist/components/inputs/DatePicker.d.ts.map +1 -1
- package/dist/components/inputs/DatePicker.js +8 -5
- package/dist/components/inputs/ListRadioInput.js +2 -2
- package/dist/components/wrappers/ImageUpload.d.ts.map +1 -1
- package/dist/components/wrappers/ImageUpload.js +32 -15
- package/dist/components/wrappers/PhotoCapture.d.ts.map +1 -1
- package/dist/components/wrappers/PhotoCapture.js +34 -20
- package/dist/lib/utils/variableValueConverter.d.ts +13 -1
- package/dist/lib/utils/variableValueConverter.d.ts.map +1 -1
- package/dist/lib/utils/variableValueConverter.js +118 -22
- package/package.json +1 -1
|
@@ -1 +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;
|
|
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;AAKrE,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,CA+XzC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
import React, { useState, useEffect, useRef } from 'react';
|
|
11
11
|
import { AlertCircle, Camera, Upload, X, FileText, CreditCard } from 'lucide-react';
|
|
12
|
+
import { VariableValueConverter } from '../lib/utils/variableValueConverter';
|
|
12
13
|
// Types de documents d'identité supportés
|
|
13
14
|
const DOC_TYPES = [
|
|
14
15
|
{ code: 'CNI', label: 'Carte Nationale d\'Identité' },
|
|
@@ -44,10 +45,8 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
44
45
|
}, [value]);
|
|
45
46
|
// Mettre à jour l'aperçu quand la valeur change
|
|
46
47
|
useEffect(() => {
|
|
47
|
-
if (currentData.
|
|
48
|
-
|
|
49
|
-
reader.onload = (e) => setPreview(e.target?.result);
|
|
50
|
-
reader.readAsDataURL(currentData.file);
|
|
48
|
+
if (currentData.imageData) {
|
|
49
|
+
setPreview(currentData.imageData);
|
|
51
50
|
}
|
|
52
51
|
else {
|
|
53
52
|
setPreview(null);
|
|
@@ -55,7 +54,7 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
55
54
|
fileInputRef.current.value = '';
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
|
-
}, [currentData.
|
|
57
|
+
}, [currentData.imageData]);
|
|
59
58
|
// Nettoyer le stream quand le composant est démonté
|
|
60
59
|
useEffect(() => {
|
|
61
60
|
return () => {
|
|
@@ -64,24 +63,25 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
64
63
|
}
|
|
65
64
|
};
|
|
66
65
|
}, [stream]);
|
|
67
|
-
// Mettre à jour les données
|
|
66
|
+
// Mettre à jour les données (sérialiser en JSON string comme DatePicker le fait pour les dates)
|
|
68
67
|
const updateData = (updates) => {
|
|
69
68
|
const newData = { ...currentData, ...updates };
|
|
70
|
-
|
|
69
|
+
const serializedValue = VariableValueConverter.serialize(newData, variable.typeCode);
|
|
70
|
+
onChange(serializedValue);
|
|
71
71
|
};
|
|
72
72
|
// Gérer la sélection de fichier
|
|
73
|
-
const handleFileSelect = (event) => {
|
|
73
|
+
const handleFileSelect = async (event) => {
|
|
74
74
|
if (disabled)
|
|
75
75
|
return;
|
|
76
76
|
const file = event.target.files?.[0];
|
|
77
77
|
if (!file)
|
|
78
78
|
return;
|
|
79
|
-
processFile(file);
|
|
79
|
+
await processFile(file);
|
|
80
80
|
};
|
|
81
81
|
// Traiter le fichier sélectionné
|
|
82
|
-
const processFile = (file) => {
|
|
83
|
-
// Validation de la taille
|
|
84
|
-
const maxSize = variable.proprietes?.maxFileSize ||
|
|
82
|
+
const processFile = async (file) => {
|
|
83
|
+
// Validation de la taille (5MB)
|
|
84
|
+
const maxSize = variable.proprietes?.maxFileSize || 5 * 1024 * 1024;
|
|
85
85
|
if (file.size > maxSize) {
|
|
86
86
|
alert(`Fichier trop volumineux. Taille maximum: ${Math.round(maxSize / 1024 / 1024)}MB`);
|
|
87
87
|
return;
|
|
@@ -95,12 +95,13 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
95
95
|
alert(`Type de fichier non supporté. Types acceptés: ${acceptedTypes.join(', ')}`);
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
|
-
//
|
|
98
|
+
// Convertir en base64
|
|
99
|
+
const imageData = await VariableValueConverter.fileToBase64(file);
|
|
99
100
|
updateData({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
imageData,
|
|
102
|
+
name: file.name,
|
|
103
|
+
size: file.size,
|
|
104
|
+
type: file.type,
|
|
104
105
|
timestamp: new Date(),
|
|
105
106
|
source: captureMode === 'camera' ? 'camera' : 'upload'
|
|
106
107
|
});
|
|
@@ -113,7 +114,7 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
113
114
|
setIsCapturing(true);
|
|
114
115
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
115
116
|
video: {
|
|
116
|
-
facingMode: 'environment'
|
|
117
|
+
facingMode: 'environment'
|
|
117
118
|
}
|
|
118
119
|
});
|
|
119
120
|
setStream(mediaStream);
|
|
@@ -144,16 +145,13 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
144
145
|
const context = canvas.getContext('2d');
|
|
145
146
|
if (!context)
|
|
146
147
|
return;
|
|
147
|
-
// Définir les dimensions du canvas
|
|
148
148
|
canvas.width = video.videoWidth;
|
|
149
149
|
canvas.height = video.videoHeight;
|
|
150
|
-
// Dessiner l'image du video sur le canvas
|
|
151
150
|
context.drawImage(video, 0, 0);
|
|
152
|
-
|
|
153
|
-
canvas.toBlob((blob) => {
|
|
151
|
+
canvas.toBlob(async (blob) => {
|
|
154
152
|
if (blob) {
|
|
155
153
|
const file = new File([blob], `iddoc_${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
156
|
-
processFile(file);
|
|
154
|
+
await processFile(file);
|
|
157
155
|
stopCamera();
|
|
158
156
|
}
|
|
159
157
|
}, 'image/jpeg', 0.9);
|
|
@@ -161,10 +159,10 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
161
159
|
// Supprimer le document
|
|
162
160
|
const clearDocument = () => {
|
|
163
161
|
updateData({
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
imageData: undefined,
|
|
163
|
+
name: undefined,
|
|
164
|
+
size: undefined,
|
|
165
|
+
type: undefined,
|
|
168
166
|
timestamp: undefined,
|
|
169
167
|
source: undefined
|
|
170
168
|
});
|
|
@@ -183,6 +181,6 @@ const IdDocInput = ({ variable, value, onChange, onBlur, error, disabled }) => {
|
|
|
183
181
|
startCamera();
|
|
184
182
|
}, disabled: disabled, className: `flex items-center px-3 py-2 rounded-lg text-sm font-medium transition-colors ${captureMode === 'camera'
|
|
185
183
|
? '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
|
|
184
|
+
: '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 5MB" })] })] })), 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.type?.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.name }), _jsx("p", { className: "text-xs text-gray-500", children: currentData.size ? `${Math.round(currentData.size / 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.name && (_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.name })] }), 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
185
|
};
|
|
188
186
|
export default IdDocInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/components/inputs/DatePicker.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,GAAG;QAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC;IAClE,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,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,
|
|
1
|
+
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/components/inputs/DatePicker.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,GAAG;QAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC;IAClE,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,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAkEzC,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -24,16 +24,19 @@ const DatePicker = ({ variable, value, onChange, onBlur, error, disabled, isCons
|
|
|
24
24
|
};
|
|
25
25
|
// Conversion de la valeur string vers Date pour l'affichage
|
|
26
26
|
const parsedDate = VariableValueConverter.parse(value, variable.typeCode);
|
|
27
|
-
// Formatage pour l'input
|
|
27
|
+
// Formatage pour l'input HTML (qui exige YYYY-MM-DD / YYYY-MM-DDTHH:mm)
|
|
28
28
|
let inputValue = '';
|
|
29
29
|
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
|
30
|
+
const y = parsedDate.getFullYear();
|
|
31
|
+
const m = (parsedDate.getMonth() + 1).toString().padStart(2, '0');
|
|
32
|
+
const d = parsedDate.getDate().toString().padStart(2, '0');
|
|
30
33
|
if (variable.typeCode === 'DATEHEURE') {
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
const hh = parsedDate.getHours().toString().padStart(2, '0');
|
|
35
|
+
const mm = parsedDate.getMinutes().toString().padStart(2, '0');
|
|
36
|
+
inputValue = `${y}-${m}-${d}T${hh}:${mm}`;
|
|
33
37
|
}
|
|
34
38
|
else {
|
|
35
|
-
|
|
36
|
-
inputValue = parsedDate.toISOString().split('T')[0];
|
|
39
|
+
inputValue = `${y}-${m}-${d}`;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
const inputType = variable.typeCode === 'DATEHEURE' ? 'datetime-local' : 'date';
|
|
@@ -40,11 +40,11 @@ const ListRadioInput = ({ variable, value, onChange, onBlur, error, disabled, is
|
|
|
40
40
|
}, [value]);
|
|
41
41
|
const handleOptionChange = (code, selected) => {
|
|
42
42
|
const newData = { ...data, [code]: selected };
|
|
43
|
-
onChange(
|
|
43
|
+
onChange(newData);
|
|
44
44
|
};
|
|
45
45
|
const handleClear = (code) => {
|
|
46
46
|
const newData = { ...data, [code]: null };
|
|
47
|
-
onChange(
|
|
47
|
+
onChange(newData);
|
|
48
48
|
};
|
|
49
49
|
return (_jsxs("div", { className: "space-y-1", style: containerStyle, children: [_jsxs("div", { className: "flex items-center text-sm font-medium text-blue-600 mb-2 pl-1", children: [_jsx("span", { children: "Oui" }), _jsx("span", { className: "mx-2", children: "/" }), _jsx("span", { children: "Non" })] }), _jsx("div", { className: "space-y-1", children: options.map((option) => {
|
|
50
50
|
const optionValue = data[option.code] ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageUpload.d.ts","sourceRoot":"","sources":["../../../src/components/wrappers/ImageUpload.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ImageUpload.d.ts","sourceRoot":"","sources":["../../../src/components/wrappers/ImageUpload.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAKnD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,UAAU,CAAC,EAAE;YACX,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;IACF,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,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;CACpB;AAED,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA4G3C,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,CAAC"}
|
|
@@ -2,32 +2,45 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
4
|
import { AlertCircle } from 'lucide-react';
|
|
5
|
+
import { VariableValueConverter } from '../../lib/utils/variableValueConverter';
|
|
5
6
|
const ImageUpload = ({ variable, value, onChange, error, disabled }) => {
|
|
6
7
|
const [preview, setPreview] = useState(null);
|
|
7
8
|
const fileInputRef = React.useRef(null);
|
|
8
9
|
useEffect(() => {
|
|
9
|
-
if (value
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
if (value) {
|
|
11
|
+
// Gérer les deux formats : objet ou string JSON
|
|
12
|
+
let fileData = value;
|
|
13
|
+
if (typeof value === 'string') {
|
|
14
|
+
try {
|
|
15
|
+
fileData = JSON.parse(value);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
fileData = null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (fileData && typeof fileData === 'object' && fileData.imageData) {
|
|
22
|
+
setPreview(fileData.imageData);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
setPreview(null);
|
|
26
|
+
if (fileInputRef.current)
|
|
27
|
+
fileInputRef.current.value = '';
|
|
28
|
+
}
|
|
14
29
|
}
|
|
15
30
|
else {
|
|
16
31
|
setPreview(null);
|
|
17
|
-
|
|
18
|
-
if (fileInputRef.current) {
|
|
32
|
+
if (fileInputRef.current)
|
|
19
33
|
fileInputRef.current.value = '';
|
|
20
|
-
}
|
|
21
34
|
}
|
|
22
35
|
}, [value]);
|
|
23
|
-
const handleFileSelect = (event) => {
|
|
36
|
+
const handleFileSelect = async (event) => {
|
|
24
37
|
if (disabled)
|
|
25
38
|
return;
|
|
26
39
|
const file = event.target.files?.[0];
|
|
27
40
|
if (!file)
|
|
28
41
|
return;
|
|
29
|
-
// Validation de la taille
|
|
30
|
-
const maxSize = variable.proprietes?.maxFileSize || 5 * 1024 * 1024;
|
|
42
|
+
// Validation de la taille (5MB)
|
|
43
|
+
const maxSize = variable.proprietes?.maxFileSize || 5 * 1024 * 1024;
|
|
31
44
|
if (file.size > maxSize) {
|
|
32
45
|
alert(`Fichier trop volumineux. Taille maximum: ${Math.round(maxSize / 1024 / 1024)}MB`);
|
|
33
46
|
return;
|
|
@@ -38,15 +51,19 @@ const ImageUpload = ({ variable, value, onChange, error, disabled }) => {
|
|
|
38
51
|
alert(`Type de fichier non supporté. Types acceptés: ${acceptedTypes.join(', ')}`);
|
|
39
52
|
return;
|
|
40
53
|
}
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
// Convertir en base64
|
|
55
|
+
const imageData = await VariableValueConverter.fileToBase64(file);
|
|
56
|
+
// Sérialiser en JSON string avant d'émettre (comme DatePicker le fait pour les dates)
|
|
57
|
+
const fileData = {
|
|
58
|
+
imageData,
|
|
44
59
|
name: file.name,
|
|
45
60
|
size: file.size,
|
|
46
61
|
type: file.type,
|
|
47
62
|
timestamp: new Date(),
|
|
48
63
|
source: 'upload'
|
|
49
|
-
}
|
|
64
|
+
};
|
|
65
|
+
const serializedValue = VariableValueConverter.serialize(fileData, variable.typeCode);
|
|
66
|
+
onChange(serializedValue);
|
|
50
67
|
};
|
|
51
68
|
return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "text-sm text-gray-600 mb-2", children: "\uD83D\uDCC1 Upload d'image uniquement" }), _jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleFileSelect, disabled: disabled, className: `w-full px-3 py-2 border border-dashed 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 cursor-pointer hover:border-green-500'}` }), preview && (_jsxs("div", { className: "relative", children: [_jsx("img", { src: preview, alt: "Aper\u00E7u", className: "w-full max-w-xs mx-auto rounded-lg shadow-md" }), _jsx("button", { type: "button", onClick: () => {
|
|
52
69
|
onChange(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhotoCapture.d.ts","sourceRoot":"","sources":["../../../src/components/wrappers/PhotoCapture.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PhotoCapture.d.ts","sourceRoot":"","sources":["../../../src/components/wrappers/PhotoCapture.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAKnD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,UAAU,CAAC,EAAE;YACX,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;IACF,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,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;CACpB;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAkQ7C,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,CAAC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
4
|
import { AlertCircle } from 'lucide-react';
|
|
5
|
+
import { VariableValueConverter } from '../../lib/utils/variableValueConverter';
|
|
5
6
|
const PhotoCapture = ({ variable, value, onChange, error, disabled }) => {
|
|
6
7
|
const [preview, setPreview] = useState(null);
|
|
7
8
|
const [captureMode, setCaptureMode] = useState('upload');
|
|
@@ -11,18 +12,30 @@ const PhotoCapture = ({ variable, value, onChange, error, disabled }) => {
|
|
|
11
12
|
const videoRef = React.useRef(null);
|
|
12
13
|
const canvasRef = React.useRef(null);
|
|
13
14
|
useEffect(() => {
|
|
14
|
-
if (value
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
if (value) {
|
|
16
|
+
// Gérer les deux formats : objet ou string JSON
|
|
17
|
+
let fileData = value;
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
try {
|
|
20
|
+
fileData = JSON.parse(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
fileData = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (fileData && typeof fileData === 'object' && fileData.imageData) {
|
|
27
|
+
setPreview(fileData.imageData);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
setPreview(null);
|
|
31
|
+
if (fileInputRef.current)
|
|
32
|
+
fileInputRef.current.value = '';
|
|
33
|
+
}
|
|
19
34
|
}
|
|
20
35
|
else {
|
|
21
36
|
setPreview(null);
|
|
22
|
-
|
|
23
|
-
if (fileInputRef.current) {
|
|
37
|
+
if (fileInputRef.current)
|
|
24
38
|
fileInputRef.current.value = '';
|
|
25
|
-
}
|
|
26
39
|
}
|
|
27
40
|
}, [value]);
|
|
28
41
|
// Nettoyer le stream quand le composant est démonté
|
|
@@ -41,9 +54,9 @@ const PhotoCapture = ({ variable, value, onChange, error, disabled }) => {
|
|
|
41
54
|
return;
|
|
42
55
|
processFile(file);
|
|
43
56
|
};
|
|
44
|
-
const processFile = (file) => {
|
|
45
|
-
// Validation de la taille
|
|
46
|
-
const maxSize = variable.proprietes?.maxFileSize || 5 * 1024 * 1024;
|
|
57
|
+
const processFile = async (file) => {
|
|
58
|
+
// Validation de la taille (5MB)
|
|
59
|
+
const maxSize = variable.proprietes?.maxFileSize || 5 * 1024 * 1024;
|
|
47
60
|
if (file.size > maxSize) {
|
|
48
61
|
alert(`Fichier trop volumineux. Taille maximum: ${Math.round(maxSize / 1024 / 1024)}MB`);
|
|
49
62
|
return;
|
|
@@ -54,15 +67,19 @@ const PhotoCapture = ({ variable, value, onChange, error, disabled }) => {
|
|
|
54
67
|
alert(`Type de fichier non supporté. Types acceptés: ${acceptedTypes.join(', ')}`);
|
|
55
68
|
return;
|
|
56
69
|
}
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
// Convertir en base64
|
|
71
|
+
const imageData = await VariableValueConverter.fileToBase64(file);
|
|
72
|
+
// Sérialiser en JSON string avant d'émettre (comme DatePicker le fait pour les dates)
|
|
73
|
+
const fileData = {
|
|
74
|
+
imageData,
|
|
60
75
|
name: file.name,
|
|
61
76
|
size: file.size,
|
|
62
77
|
type: file.type,
|
|
63
78
|
timestamp: new Date(),
|
|
64
79
|
source: captureMode === 'camera' ? 'camera' : 'upload'
|
|
65
|
-
}
|
|
80
|
+
};
|
|
81
|
+
const serializedValue = VariableValueConverter.serialize(fileData, variable.typeCode);
|
|
82
|
+
onChange(serializedValue);
|
|
66
83
|
};
|
|
67
84
|
const startCamera = async () => {
|
|
68
85
|
if (disabled)
|
|
@@ -100,16 +117,13 @@ const PhotoCapture = ({ variable, value, onChange, error, disabled }) => {
|
|
|
100
117
|
const context = canvas.getContext('2d');
|
|
101
118
|
if (!context)
|
|
102
119
|
return;
|
|
103
|
-
// Définir les dimensions du canvas
|
|
104
120
|
canvas.width = video.videoWidth;
|
|
105
121
|
canvas.height = video.videoHeight;
|
|
106
|
-
// Dessiner l'image du video sur le canvas
|
|
107
122
|
context.drawImage(video, 0, 0);
|
|
108
|
-
|
|
109
|
-
canvas.toBlob((blob) => {
|
|
123
|
+
canvas.toBlob(async (blob) => {
|
|
110
124
|
if (blob) {
|
|
111
125
|
const file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
112
|
-
processFile(file);
|
|
126
|
+
await processFile(file);
|
|
113
127
|
stopCamera();
|
|
114
128
|
}
|
|
115
129
|
}, 'image/jpeg', 0.8);
|
|
@@ -15,11 +15,14 @@ export interface GPSData {
|
|
|
15
15
|
timestamp: Date;
|
|
16
16
|
}
|
|
17
17
|
export interface FileData {
|
|
18
|
-
|
|
18
|
+
imageData: string;
|
|
19
19
|
name: string;
|
|
20
20
|
size: number;
|
|
21
21
|
type: string;
|
|
22
22
|
timestamp: Date;
|
|
23
|
+
source?: 'camera' | 'upload';
|
|
24
|
+
docType?: string;
|
|
25
|
+
docNumber?: string;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
28
|
* Classe utilitaire pour la conversion des valeurs de variables
|
|
@@ -41,6 +44,11 @@ export declare class VariableValueConverter {
|
|
|
41
44
|
private static parseGPS;
|
|
42
45
|
private static parseFile;
|
|
43
46
|
private static parseRSU;
|
|
47
|
+
/**
|
|
48
|
+
* Déséchappe itérativement une chaîne JSON multi-échappée jusqu'à obtenir un objet JSON propre.
|
|
49
|
+
* Équivalent frontend de ParseGeographicJsonObject du backend.
|
|
50
|
+
*/
|
|
51
|
+
private static unescapeToJsonObject;
|
|
44
52
|
private static parseDistrict;
|
|
45
53
|
private static parseGeographicData;
|
|
46
54
|
private static serializeNumeric;
|
|
@@ -49,6 +57,10 @@ export declare class VariableValueConverter {
|
|
|
49
57
|
private static serializeCheckboxValue;
|
|
50
58
|
private static serializeGPS;
|
|
51
59
|
private static serializeFile;
|
|
60
|
+
/**
|
|
61
|
+
* Convertit un objet File en data URL base64
|
|
62
|
+
*/
|
|
63
|
+
static fileToBase64(file: File): Promise<string>;
|
|
52
64
|
private static serializeRSU;
|
|
53
65
|
private static serializeDistrict;
|
|
54
66
|
private static serializeGeographicData;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variableValueConverter.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/variableValueConverter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGlE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,
|
|
1
|
+
{"version":3,"file":"variableValueConverter.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/variableValueConverter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGlE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,sBAAsB;IAEjC;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa;IAuEjF;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM;IAiElE,OAAO,CAAC,MAAM,CAAC,YAAY;IAK3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAexB,OAAO,CAAC,MAAM,CAAC,aAAa;IAqB5B,OAAO,CAAC,MAAM,CAAC,SAAS;IAQxB,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAIjC,OAAO,CAAC,MAAM,CAAC,QAAQ;IAcvB,OAAO,CAAC,MAAM,CAAC,SAAS;IAmBxB,OAAO,CAAC,MAAM,CAAC,QAAQ;IAKvB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAkDnC,OAAO,CAAC,MAAM,CAAC,aAAa;IA8B5B,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAoElC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAI/B,OAAO,CAAC,MAAM,CAAC,aAAa;IAQ5B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAUhC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAIrC,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B,OAAO,CAAC,MAAM,CAAC,aAAa;IAc5B;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAShD,OAAO,CAAC,MAAM,CAAC,YAAY;IAK3B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAWhC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IA2DtC;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,EAAE;IAuBpE;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE;;;;;IAUlD;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO;IA4ChE;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM;CAsC1E"}
|
|
@@ -46,6 +46,7 @@ export class VariableValueConverter {
|
|
|
46
46
|
return this.parseGPS(value);
|
|
47
47
|
case 'PHOTO':
|
|
48
48
|
case 'IMAGE':
|
|
49
|
+
case 'IDDOC':
|
|
49
50
|
return this.parseFile(value);
|
|
50
51
|
case 'RSU':
|
|
51
52
|
return this.parseRSU(value);
|
|
@@ -99,6 +100,7 @@ export class VariableValueConverter {
|
|
|
99
100
|
return this.serializeGPS(value);
|
|
100
101
|
case 'PHOTO':
|
|
101
102
|
case 'IMAGE':
|
|
103
|
+
case 'IDDOC':
|
|
102
104
|
return this.serializeFile(value);
|
|
103
105
|
case 'RSU':
|
|
104
106
|
return this.serializeRSU(value);
|
|
@@ -128,6 +130,13 @@ export class VariableValueConverter {
|
|
|
128
130
|
static parseDate(value) {
|
|
129
131
|
if (!value)
|
|
130
132
|
return null;
|
|
133
|
+
// Format DD/MM/YYYY
|
|
134
|
+
if (value.includes('/')) {
|
|
135
|
+
const [day, month, year] = value.split('/');
|
|
136
|
+
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
137
|
+
return isNaN(date.getTime()) ? null : date;
|
|
138
|
+
}
|
|
139
|
+
// Fallback ISO (YYYY-MM-DD) pour rétrocompatibilité
|
|
131
140
|
const date = new Date(value);
|
|
132
141
|
return isNaN(date.getTime()) ? null : date;
|
|
133
142
|
}
|
|
@@ -179,12 +188,17 @@ export class VariableValueConverter {
|
|
|
179
188
|
static parseFile(value) {
|
|
180
189
|
try {
|
|
181
190
|
const parsed = JSON.parse(value);
|
|
191
|
+
if (!parsed || typeof parsed !== 'object')
|
|
192
|
+
return null;
|
|
182
193
|
return {
|
|
183
|
-
|
|
184
|
-
name: parsed.name,
|
|
185
|
-
size: parsed.size,
|
|
186
|
-
type: parsed.type,
|
|
187
|
-
timestamp: new Date(parsed.timestamp)
|
|
194
|
+
imageData: parsed.imageData || '',
|
|
195
|
+
name: parsed.name || '',
|
|
196
|
+
size: parsed.size || 0,
|
|
197
|
+
type: parsed.type || '',
|
|
198
|
+
timestamp: parsed.timestamp ? new Date(parsed.timestamp) : new Date(),
|
|
199
|
+
source: parsed.source,
|
|
200
|
+
docType: parsed.docType,
|
|
201
|
+
docNumber: parsed.docNumber
|
|
188
202
|
};
|
|
189
203
|
}
|
|
190
204
|
catch {
|
|
@@ -195,18 +209,71 @@ export class VariableValueConverter {
|
|
|
195
209
|
// RSU stocke directement l'ID comme string
|
|
196
210
|
return value || '';
|
|
197
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Déséchappe itérativement une chaîne JSON multi-échappée jusqu'à obtenir un objet JSON propre.
|
|
214
|
+
* Équivalent frontend de ParseGeographicJsonObject du backend.
|
|
215
|
+
*/
|
|
216
|
+
static unescapeToJsonObject(value) {
|
|
217
|
+
if (!value)
|
|
218
|
+
return null;
|
|
219
|
+
let current = value;
|
|
220
|
+
const maxIterations = 5;
|
|
221
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
222
|
+
// Si c'est déjà un objet JSON, tenter le parse
|
|
223
|
+
if (current.startsWith('{') && current.endsWith('}')) {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(current);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Si c'est une string JSON (commence par "), désérialiser un niveau
|
|
232
|
+
if (current.startsWith('"') && current.endsWith('"')) {
|
|
233
|
+
try {
|
|
234
|
+
const unescaped = JSON.parse(current);
|
|
235
|
+
if (typeof unescaped === 'string') {
|
|
236
|
+
current = unescaped;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
return unescaped;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Tenter de supprimer manuellement les guillemets et échappements
|
|
243
|
+
if (current.length >= 2) {
|
|
244
|
+
current = current.slice(1, -1).replace(/\\"/g, '"');
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
// Dernière tentative
|
|
253
|
+
if (current.startsWith('{') && current.endsWith('}')) {
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(current);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
198
263
|
static parseDistrict(value) {
|
|
199
264
|
if (!value)
|
|
200
265
|
return null;
|
|
201
266
|
try {
|
|
202
|
-
//
|
|
203
|
-
if (value.startsWith('{')) {
|
|
204
|
-
const parsed =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
267
|
+
// Tenter le parsing avec support du multi-échappement
|
|
268
|
+
if (value.startsWith('{') || value.startsWith('"')) {
|
|
269
|
+
const parsed = this.unescapeToJsonObject(value);
|
|
270
|
+
if (parsed && typeof parsed === 'object') {
|
|
271
|
+
return {
|
|
272
|
+
id: parsed.id,
|
|
273
|
+
code: parsed.code,
|
|
274
|
+
designation: parsed.designation
|
|
275
|
+
};
|
|
276
|
+
}
|
|
210
277
|
}
|
|
211
278
|
// Sinon, c'est juste un ID - retourner un objet minimal
|
|
212
279
|
const id = parseInt(value, 10);
|
|
@@ -226,8 +293,12 @@ export class VariableValueConverter {
|
|
|
226
293
|
if (!value)
|
|
227
294
|
return null;
|
|
228
295
|
try {
|
|
229
|
-
// Parser le JSON stocké
|
|
230
|
-
const parsed =
|
|
296
|
+
// Parser le JSON stocké avec support du multi-échappement
|
|
297
|
+
const parsed = (value.startsWith('{') || value.startsWith('"'))
|
|
298
|
+
? this.unescapeToJsonObject(value)
|
|
299
|
+
: JSON.parse(value);
|
|
300
|
+
if (!parsed || typeof parsed !== 'object')
|
|
301
|
+
return null;
|
|
231
302
|
const result = {};
|
|
232
303
|
// Reconstruire l'objet GeographicData
|
|
233
304
|
if (parsed.district) {
|
|
@@ -285,12 +356,20 @@ export class VariableValueConverter {
|
|
|
285
356
|
static serializeDate(date) {
|
|
286
357
|
if (!date || isNaN(date.getTime()))
|
|
287
358
|
return '';
|
|
288
|
-
|
|
359
|
+
const d = date.getDate().toString().padStart(2, '0');
|
|
360
|
+
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
361
|
+
const y = date.getFullYear();
|
|
362
|
+
return `${d}/${m}/${y}`;
|
|
289
363
|
}
|
|
290
364
|
static serializeDateTime(date) {
|
|
291
365
|
if (!date || isNaN(date.getTime()))
|
|
292
366
|
return '';
|
|
293
|
-
|
|
367
|
+
const d = date.getDate().toString().padStart(2, '0');
|
|
368
|
+
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
369
|
+
const y = date.getFullYear();
|
|
370
|
+
const hh = date.getHours().toString().padStart(2, '0');
|
|
371
|
+
const mm = date.getMinutes().toString().padStart(2, '0');
|
|
372
|
+
return `${d}/${m}/${y} ${hh}:${mm}`;
|
|
294
373
|
}
|
|
295
374
|
static serializeCheckboxValue(values) {
|
|
296
375
|
return Array.isArray(values) ? values.filter(v => v).join('|') : '';
|
|
@@ -304,12 +383,29 @@ export class VariableValueConverter {
|
|
|
304
383
|
});
|
|
305
384
|
}
|
|
306
385
|
static serializeFile(fileData) {
|
|
307
|
-
|
|
386
|
+
const obj = {
|
|
387
|
+
imageData: fileData.imageData,
|
|
308
388
|
name: fileData.name,
|
|
309
389
|
size: fileData.size,
|
|
310
390
|
type: fileData.type,
|
|
311
|
-
timestamp: fileData.timestamp.toISOString()
|
|
312
|
-
|
|
391
|
+
timestamp: fileData.timestamp instanceof Date ? fileData.timestamp.toISOString() : fileData.timestamp,
|
|
392
|
+
source: fileData.source
|
|
393
|
+
};
|
|
394
|
+
if (fileData.docType)
|
|
395
|
+
obj.docType = fileData.docType;
|
|
396
|
+
if (fileData.docNumber)
|
|
397
|
+
obj.docNumber = fileData.docNumber;
|
|
398
|
+
return JSON.stringify(obj);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Convertit un objet File en data URL base64
|
|
402
|
+
*/
|
|
403
|
+
static fileToBase64(file) {
|
|
404
|
+
return new Promise((resolve, reject) => {
|
|
405
|
+
const reader = new FileReader();
|
|
406
|
+
reader.onload = () => resolve(reader.result);
|
|
407
|
+
reader.onerror = reject;
|
|
408
|
+
reader.readAsDataURL(file);
|
|
313
409
|
});
|
|
314
410
|
}
|
|
315
411
|
static serializeRSU(value) {
|
|
@@ -423,9 +519,9 @@ export class VariableValueConverter {
|
|
|
423
519
|
case 'NUMERIQUE':
|
|
424
520
|
return !isNaN(parseFloat(value));
|
|
425
521
|
case 'DATE':
|
|
426
|
-
return
|
|
522
|
+
return this.parseDate(value) !== null;
|
|
427
523
|
case 'DATEHEURE':
|
|
428
|
-
return
|
|
524
|
+
return this.parseDateTime(value) !== null;
|
|
429
525
|
case 'HOUR':
|
|
430
526
|
return /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value);
|
|
431
527
|
case 'PHONE':
|
package/package.json
CHANGED