@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.
@@ -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;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"}
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.file) {
48
- const reader = new FileReader();
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.file]);
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
- onChange(JSON.stringify(newData));
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 || 10 * 1024 * 1024; // 10MB par défaut
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
- // Sauvegarder le fichier
98
+ // Convertir en base64
99
+ const imageData = await VariableValueConverter.fileToBase64(file);
99
100
  updateData({
100
- file,
101
- fileName: file.name,
102
- fileSize: file.size,
103
- fileType: file.type,
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' // Caméra arrière par défaut
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
- // Convertir en blob
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
- file: undefined,
165
- fileName: undefined,
166
- fileSize: undefined,
167
- fileType: undefined,
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 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 })] }))] }));
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,CA+DzC,CAAC;AAEF,eAAe,UAAU,CAAC"}
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 selon le type
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
- // Format pour datetime-local: "2024-03-15T14:30"
32
- inputValue = parsedDate.toISOString().slice(0, 16);
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
- // Format pour date: "2024-03-15"
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(JSON.stringify(newData));
43
+ onChange(newData);
44
44
  };
45
45
  const handleClear = (code) => {
46
46
  const newData = { ...data, [code]: null };
47
- onChange(JSON.stringify(newData));
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;AAGnD,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,CAmG3C,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,eAAe,WAAW,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 && typeof value === 'object' && value.file) {
10
- const file = value.file;
11
- const reader = new FileReader();
12
- reader.onload = (e) => setPreview(e.target?.result);
13
- reader.readAsDataURL(file);
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
- // Réinitialiser l'input file quand la valeur est supprimée
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; // 5MB par défaut
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
- // Sauvegarder le fichier
42
- onChange({
43
- file,
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;AAGnD,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,CA6P7C,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,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 && typeof value === 'object' && value.file) {
15
- const file = value.file;
16
- const reader = new FileReader();
17
- reader.onload = (e) => setPreview(e.target?.result);
18
- reader.readAsDataURL(file);
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
- // Réinitialiser l'input file quand la valeur est supprimée
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; // 5MB par défaut
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
- // Sauvegarder le fichier
58
- onChange({
59
- file,
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
- // Convertir en blob
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
- file: File;
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,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;CACjB;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;IAsEjF;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM;IAgElE,OAAO,CAAC,MAAM,CAAC,YAAY;IAK3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAMxB,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;IAexB,OAAO,CAAC,MAAM,CAAC,QAAQ;IAKvB,OAAO,CAAC,MAAM,CAAC,aAAa;IA4B5B,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAiElC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAI/B,OAAO,CAAC,MAAM,CAAC,aAAa;IAK5B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAKhC,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAIrC,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B,OAAO,CAAC,MAAM,CAAC,aAAa;IAU5B,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"}
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
- file: parsed.file,
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
- // Si c'est un JSON, parser l'objet complet
203
- if (value.startsWith('{')) {
204
- const parsed = JSON.parse(value);
205
- return {
206
- id: parsed.id,
207
- code: parsed.code,
208
- designation: parsed.designation
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 = JSON.parse(value);
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
- return date.toISOString().split('T')[0];
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
- return date.toISOString();
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
- return JSON.stringify({
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
- // Note: Le fichier lui-même devrait être uploadé séparément
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 !isNaN(new Date(value).getTime());
522
+ return this.parseDate(value) !== null;
427
523
  case 'DATEHEURE':
428
- return !isNaN(new Date(value).getTime());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsuci/shared-form-components",
3
- "version": "1.0.77",
3
+ "version": "1.0.79",
4
4
  "description": "Composants partagés de rendu de formulaires RSU v2 - Package local pour frontend Admin et Public",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",