data-primals-engine 1.0.11 → 1.1.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.
Files changed (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. package/vitest.config.js +2 -2
@@ -0,0 +1,622 @@
1
+ // C:/Dev/hackersonline-engine/client/src/ConditionBuilder.jsx
2
+
3
+ import React, {useState, useEffect, useRef} from 'react';
4
+ import {
5
+ FaPlus,
6
+ FaTrashAlt,
7
+ FaProjectDiagram,
8
+ FaChevronRight,
9
+ FaCode,
10
+ FaListAlt,
11
+ FaMinusCircle,
12
+ FaExchangeAlt
13
+ } from 'react-icons/fa';
14
+ import './ConditionBuilder.scss';
15
+ import { useTranslation, Trans } from "react-i18next";
16
+ import { CodeField } from "./Field.jsx";
17
+ import { useAuthContext } from "./contexts/AuthContext.jsx";
18
+ import { MONGO_CALC_OPERATORS} from "./filter.js";
19
+ import {ConditionBuilder2} from "./ConditionBuilder2.jsx";
20
+ import {mongoOperators} from "./constants.js";
21
+
22
+ const getFieldDefinition = (fields, fieldName) => fields?.find(f => f.name === fieldName);
23
+ const getModelFields = (modelName, allModels, user) => {
24
+ const model = allModels?.find(m => m.name === modelName && (m._user === user?.username || !m._user));
25
+ return model?.fields || [];
26
+ };
27
+
28
+ const ConditionRow = ({ condition, onChange, onRemove, allModels, currentModel, user }) => {
29
+ const { t } = useTranslation();
30
+ const [showCalcEditor, setShowCalcEditor] = useState(!!condition.transform);
31
+
32
+ const [fieldPath, setFieldPath] = useState(condition.path || []);
33
+ const [operator, setOperator] = useState(condition.op || '$eq');
34
+ const [value, setValue] = useState(condition.value ?? '');
35
+ const [transform, setTransform] = useState(condition.transform || null);
36
+
37
+ const modelFields = getModelFields(currentModel, allModels, user);
38
+
39
+
40
+ const updatePath = (levelIndex, selectedField) => {
41
+ const newPath = [...fieldPath.slice(0, levelIndex), selectedField];
42
+ setFieldPath(newPath);
43
+ setValue('');
44
+ setTransform(null);
45
+ onChange({ path: newPath, op: operator, value: '', transform: null });
46
+ };
47
+
48
+ const getFinalFieldDef = () => {
49
+ let fields = modelFields;
50
+ let def = null;
51
+ for (let i = 0; i < fieldPath.length; i++) {
52
+ def = getFieldDefinition(fields, fieldPath[i]);
53
+ if (!def) return null;
54
+ fields = def.type === 'relation' ? getModelFields(def.relation, allModels, user) : [];
55
+ }
56
+ return def;
57
+ };
58
+
59
+ useEffect(() => {
60
+ const def = getFinalFieldDef();
61
+ const newCondition = {
62
+ path: fieldPath,
63
+ op: operator,
64
+ value,
65
+ transform
66
+ };
67
+
68
+ // Si c'est une transformation pure sans opérateur, on nettoie la valeur
69
+ if (transform && !operator) {
70
+ newCondition.value = undefined;
71
+ }
72
+
73
+ // Comparaison profonde pour éviter les mises à jour inutiles
74
+ const oldCondition = { path: condition.path || [], op: condition.op, value: condition.value, transform: condition.transform };
75
+ if (JSON.stringify(newCondition) !== JSON.stringify(oldCondition)) {
76
+ onChange(newCondition);
77
+ }
78
+ }, [operator, value, transform, fieldPath, condition.path, condition.op, condition.value, condition.transform, onChange]);
79
+
80
+
81
+ const renderValueInput = () => {
82
+ if (fieldPath.length === 0) return null;
83
+
84
+ const def = getFinalFieldDef();
85
+ const isDate = def?.type === 'date';
86
+ const isDatetime = def?.type === 'datetime';
87
+ const isBoolean = def?.type === 'boolean';
88
+ const isEnum = def?.type === 'enum';
89
+ const isNumber = def?.type === 'number';
90
+ const isString = def?.type === 'string';
91
+
92
+ // Si l'opérateur existe et n'est pas '$exists'
93
+ if (operator && operator !== '$exists') {
94
+ return (
95
+ <div className="value-input-container">
96
+ {showCalcEditor ? (
97
+ <CalculationEditor
98
+ expr={transform || { op: '$add', args: [`$${fieldPath.join('.')}`] }}
99
+ onChange={(newTransform) => {
100
+ setTransform(newTransform);
101
+ if (!operator) setValue(undefined);
102
+ }}
103
+ />
104
+ ) : (
105
+ <input
106
+ placeholder={t('value', 'Valeur')}
107
+ value={value || ''}
108
+ onChange={(e) => setValue(e.target.value)}
109
+ className="condition-value-input"
110
+ />
111
+ )}
112
+ {/* Bouton pour basculer l'éditeur de calcul */}
113
+ <button type="button" onClick={() => setShowCalcEditor(!showCalcEditor)} title="Utiliser une expression calculée">
114
+ fx
115
+ </button>
116
+ </div>
117
+ );
118
+ }
119
+ if (operator === '$exists') {
120
+ return (
121
+ <select
122
+ value={String(value === true)}
123
+ onChange={(e) => setValue(e.target.value === 'true')}
124
+ className="condition-value-input"
125
+ >
126
+ <option value="true">{t('exists', 'Existe')}</option>
127
+ <option value="false">{t('doesNotExist', 'N\'existe pas')}</option>
128
+ </select>
129
+ );
130
+ }
131
+
132
+ if (isEnum && def.items) {
133
+ return (
134
+ <select
135
+ value={value || ''}
136
+ onChange={(e) => setValue(e.target.value)}
137
+ className="condition-value-input"
138
+ >
139
+ <option value="">{t('selectValue', 'Choisir une valeur...')}</option>
140
+ {def.items.map(item => (
141
+ <option key={item} value={item}>{t(item, item)}</option>
142
+ ))}
143
+ </select>
144
+ );
145
+ }
146
+
147
+ if (isBoolean) {
148
+ return (
149
+ <select
150
+ value={String(value === true)}
151
+ onChange={(e) => setValue(e.target.value === 'true')}
152
+ className="condition-value-input"
153
+ >
154
+ <option value="true">{t('true', 'Vrai')}</option>
155
+ <option value="false">{t('false', 'Faux')}</option>
156
+ </select>
157
+ );
158
+ }
159
+
160
+ if (isDate || isDatetime || isNumber || isString) {
161
+ return (
162
+ <>
163
+ <CalculationEditor
164
+ expr={transform || { op: '$add', args: [`$${fieldPath.join('.')}`] }}
165
+ onChange={(newTransform) => {
166
+ setTransform(newTransform);
167
+ // Effacer la valeur si c'est une transformation pure
168
+ if (!operator) {
169
+ setValue(undefined);
170
+ }
171
+ }}
172
+ />
173
+ {operator && operator !== '$exists' && (
174
+ <input
175
+ placeholder={t('value', 'Valeur')}
176
+ value={value || ''}
177
+ onChange={(e) => setValue(e.target.value)}
178
+ className="condition-value-input"
179
+ />
180
+ )}
181
+ </>
182
+ );
183
+ }
184
+
185
+ return (
186
+ <input
187
+ placeholder={t('value', 'Valeur')}
188
+ value={value}
189
+ onChange={(e) => setValue(e.target.value)}
190
+ className="condition-value-input"
191
+ />
192
+ );
193
+ };
194
+ const renderFieldSelectors = () => {
195
+ const selectors = [];
196
+ let currentFieldsForLevel = modelFields;
197
+
198
+ for (let i = 0; ; i++) {
199
+ const currentPathSegment = fieldPath[i] || '';
200
+ if (!currentFieldsForLevel || currentFieldsForLevel.length === 0) break;
201
+
202
+ selectors.push(
203
+ <span key={i} className="field-selector">
204
+ {i > 0 && <FaChevronRight className="path-separator" />}
205
+ <select
206
+ value={currentPathSegment}
207
+ onChange={(e) => updatePath(i, e.target.value)}
208
+ className="condition-field-select"
209
+ >
210
+ <option value="">{t('selectField', 'Champ...')}</option>
211
+ <option value="_id">_id</option>
212
+ {currentFieldsForLevel.map(f => (
213
+ <option key={f.name} value={f.name}>
214
+ {t(`field_${currentModel}_${f.name}`, f.name)}
215
+ {f.type === 'relation' ? ' (...)' : ''}
216
+ </option>
217
+ ))}
218
+ </select>
219
+ </span>
220
+ );
221
+
222
+ if (!currentPathSegment) break;
223
+ const fieldDef = getFieldDefinition(currentFieldsForLevel, currentPathSegment);
224
+ if (fieldDef?.type === 'relation') {
225
+ currentFieldsForLevel = getModelFields(fieldDef.relation, allModels, user);
226
+ } else {
227
+ currentFieldsForLevel = [];
228
+ }
229
+ }
230
+ return selectors;
231
+ };
232
+ return (
233
+ <div className="condition-node condition-simple">
234
+ {renderFieldSelectors()}
235
+ {fieldPath.length > 0 && (
236
+ <>
237
+ <select
238
+ value={operator || ''}
239
+ onChange={(e) => {
240
+ setOperator(e.target.value || undefined);
241
+ if (!e.target.value) {
242
+ setValue(undefined);
243
+ }
244
+ }}
245
+ className="condition-operator-select"
246
+ >
247
+ <option value="">{t('noComparison', 'Transformation seule')}</option>
248
+ {mongoOperators.map(o => (
249
+ <option key={o.value} value={o.value}>{o.label}</option>
250
+ ))}
251
+ </select>
252
+ {renderValueInput()}
253
+ </>
254
+ )}
255
+ <button onClick={onRemove} className="delete-button" title={t('conditionBuilder.deleteCondition')}>
256
+ <FaTrashAlt />
257
+ </button>
258
+ </div>
259
+ );
260
+ };
261
+ const ConditionGroup = ({ node, onChange, allModels, currentModel, user }) => {
262
+ const { t } = useTranslation();
263
+ const [type, setType] = useState(node.$and ? '$and' : '$or');
264
+ const [children, setChildren] = useState(node[type] || []);
265
+
266
+ useEffect(() => {
267
+ onChange({ [type]: children });
268
+ }, [type, children]);
269
+
270
+ const updateChild = (index, newChild) => {
271
+ const newChildren = [...children];
272
+ newChildren[index] = newChild;
273
+ setChildren(newChildren);
274
+ };
275
+
276
+ const removeChild = (index) => {
277
+ setChildren(children.filter((_, i) => i !== index));
278
+ };
279
+
280
+ const addCondition = () => {
281
+ gtag('event', '[cb] add condition');
282
+ setChildren([...children, { path: [], op: '$eq', value: '' }]);
283
+ };
284
+
285
+ const addGroup = (groupType) => {
286
+ gtag('event', '[cb] add group');
287
+ setChildren([...children, { [groupType]: [] }]);
288
+ };
289
+
290
+ return (
291
+ <div className={`condition-node condition-group ${type === '$and' ? 'condition-and' : 'condition-or'}`}>
292
+ <div className="group-header">
293
+ <select value={type} onChange={(e) => setType(e.target.value)}>
294
+ <option value="$and">{t('conditionBuilder.logical.and', 'ET')}</option>
295
+ <option value="$or">{t('conditionBuilder.logical.or', 'OU')}</option>
296
+ </select>
297
+ </div>
298
+ <div className="group-content">
299
+ {children.map((child, index) => (
300
+ child.path ? (
301
+ <ConditionRow
302
+ key={index}
303
+ condition={child}
304
+ onChange={(updated) => updateChild(index, updated)}
305
+ onRemove={() => removeChild(index)}
306
+ allModels={allModels}
307
+ currentModel={currentModel}
308
+ user={user}
309
+ />
310
+ ) : (
311
+ <ConditionGroup
312
+ key={index}
313
+ node={child}
314
+ onChange={(updated) => updateChild(index, updated)}
315
+ allModels={allModels}
316
+ currentModel={currentModel}
317
+ user={user}
318
+ />
319
+ )
320
+ ))}
321
+ <div className="add-buttons">
322
+ <button type={"button"} onClick={addCondition}><FaPlus /> <Trans i18nKey="addCondition">Condition</Trans></button>
323
+ <button type={"button"} onClick={() => addGroup('$and')}><FaProjectDiagram /> <Trans i18nKey="conditionBuilder.andGroup">Groupe ET</Trans></button>
324
+ <button type={"button"} onClick={() => addGroup('$or')}><FaProjectDiagram /> <Trans i18nKey="conditionBuilder.orGroup">Groupe OU</Trans></button>
325
+ </div>
326
+ </div>
327
+ </div>
328
+ );
329
+ };
330
+
331
+ const CalculationEditor = ({ expr, onChange }) => {
332
+ const { t } = useTranslation();
333
+ const [currentOp, setCurrentOp] = useState(expr?.op || '$add');
334
+ const [args, setArgs] = useState(expr?.args || []);
335
+ const isConstant = currentOp === '$const';
336
+ const isConverter = MONGO_CALC_OPERATORS[currentOp]?.converter;
337
+
338
+ useEffect(() => {
339
+ if (isConstant && args.length === 0) {
340
+ setArgs(['']); // Initialiser avec une valeur vide pour les constantes
341
+ }
342
+ onChange?.({ op: currentOp, args });
343
+ }, [currentOp, args]);
344
+
345
+ const updateArg = (index, value) => {
346
+ const updated = [...args];
347
+ updated[index] = value;
348
+ setArgs(updated);
349
+ };
350
+
351
+ const addArg = () => {
352
+ if (isConstant) return; // Pas d'ajout d'argument pour les constantes
353
+ setArgs([...args, '']);
354
+ };
355
+
356
+ const removeArg = (index) => {
357
+ if (isConstant) return; // Pas de suppression pour les constantes
358
+ setArgs(args.filter((_, i) => i !== index));
359
+ };
360
+
361
+ const toggleArgType = (index) => {
362
+ const arg = args[index];
363
+ const isObject = typeof arg === 'object' && arg !== null;
364
+
365
+ if (isObject) {
366
+ // Convertir en valeur simple
367
+ updateArg(index, '');
368
+ } else {
369
+ // Convertir en expression (par défaut $add)
370
+ updateArg(index, { op: '$add', args: [arg || ''] });
371
+ }
372
+ };
373
+
374
+ const renderConstantInput = () => {
375
+ const value = args[0] ?? '';
376
+ return (
377
+ <div className="constant-input">
378
+ <select
379
+ value={typeof value}
380
+ onChange={(e) => {
381
+ // Convertir le type de la constante
382
+ const newValue = e.target.value === 'boolean' ? true :
383
+ e.target.value === 'number' ? 0 : '';
384
+ updateArg(0, newValue);
385
+ }}
386
+ >
387
+ <option value="string">String</option>
388
+ <option value="number">Nombre</option>
389
+ <option value="boolean">Booléen</option>
390
+ </select>
391
+
392
+ {typeof value === 'boolean' ? (
393
+ <select
394
+ value={String(value)}
395
+ onChange={(e) => updateArg(0, e.target.value === 'true')}
396
+ >
397
+ <option value="true">Vrai</option>
398
+ <option value="false">Faux</option>
399
+ </select>
400
+ ) : (
401
+ <input
402
+ type={typeof value === 'number' ? 'number' : 'text'}
403
+ value={value}
404
+ onChange={(e) => {
405
+ const val = typeof value === 'number' ?
406
+ Number(e.target.value) : e.target.value;
407
+ updateArg(0, val);
408
+ }}
409
+ placeholder="Valeur constante"
410
+ />
411
+ )}
412
+ </div>
413
+ );
414
+ };
415
+
416
+ const renderArgInput = (arg, index) => {
417
+ const isObject = typeof arg === 'object' && arg !== null;
418
+
419
+ return isObject ? (
420
+ <div className="nested-calc-editor">
421
+ <CalculationEditor
422
+ expr={arg}
423
+ onChange={(val) => updateArg(index, val)}
424
+ />
425
+ <div className="arg-actions">
426
+ <button
427
+ type="button"
428
+ onClick={() => toggleArgType(index)}
429
+ className="icon-btn"
430
+ title={t('convertToValue', 'Convertir en valeur simple')}
431
+ >
432
+ <FaCode />
433
+ </button>
434
+ <FaMinusCircle
435
+ onClick={() => removeArg(index)}
436
+ className="icon-btn"
437
+ title={t('removeArg', 'Supprimer cet argument')}
438
+ />
439
+ </div>
440
+ </div>
441
+ ) : (
442
+ <div className="simple-arg">
443
+ <input
444
+ placeholder={t('value', 'Valeur')}
445
+ value={arg}
446
+ onChange={(e) => updateArg(index, e.target.value)}
447
+ />
448
+ <div className="arg-actions">
449
+ <button
450
+ type="button"
451
+ onClick={() => toggleArgType(index)}
452
+ className="icon-btn"
453
+ title={t('convertToExpression', 'Convertir en expression')}
454
+ >
455
+ <FaProjectDiagram />
456
+ </button>
457
+ <FaMinusCircle
458
+ onClick={() => removeArg(index)}
459
+ className="icon-btn"
460
+ title={t('removeArg', 'Supprimer cet argument')}
461
+ />
462
+ </div>
463
+ </div>
464
+ );
465
+ };
466
+
467
+ useEffect(() => {
468
+ if ((isConverter || isConstant) && args.length === 0) {
469
+ setArgs(['']); // Initialiser avec une valeur vide
470
+ }
471
+ onChange?.({ op: currentOp, args });
472
+ }, [currentOp, args]);
473
+
474
+
475
+ const renderConverterInput = () => {
476
+ return (
477
+ <div className="converter-input">
478
+ {args[0] && typeof args[0] === 'object' && args[0].op ? (
479
+ <CalculationEditor
480
+ expr={args[0]}
481
+ onChange={(val) => updateArg(0, val)}
482
+ />
483
+ ) : (
484
+ <input
485
+ placeholder="Expression ou valeur"
486
+ value={args[0] || ''}
487
+ onChange={(e) => updateArg(0, e.target.value)}
488
+ />
489
+ )}
490
+ <button
491
+ type="button"
492
+ onClick={() => toggleArgType(0)}
493
+ className="icon-btn"
494
+ title={t('toggleArgType', 'Basculer entre valeur et expression')}
495
+ >
496
+ <FaExchangeAlt />
497
+ </button>
498
+ </div>
499
+ );
500
+ };
501
+ return (
502
+ <div className="calc-editor">
503
+ <select
504
+ value={currentOp}
505
+ onChange={(e) => {
506
+ setCurrentOp(e.target.value);
507
+ const isNewConverter = MONGO_CALC_OPERATORS[e.target.value]?.converter;
508
+ if (isNewConverter || e.target.value === '$const') {
509
+ setArgs(['']);
510
+ }
511
+ }}
512
+ className="calc-operator-select"
513
+ >
514
+ <option value="$const">Constante</option>
515
+ <optgroup label="Conversions">
516
+ <option value="$toBool">toBool</option>
517
+ <option value="$toString">toString</option>
518
+ <option value="$toInt">toInt</option>
519
+ <option value="$toDouble">toDouble</option>
520
+ </optgroup>
521
+ <optgroup label="Opérations">
522
+ {Object.entries(MONGO_CALC_OPERATORS)
523
+ .filter(([_, def]) => !def.converter)
524
+ .map(([key, def]) => (
525
+ <option key={key} value={key}>{def.label}</option>
526
+ ))}
527
+ </optgroup>
528
+ </select>
529
+
530
+ {isConstant ? (
531
+ renderConstantInput()
532
+ ) : isConverter ? (
533
+ renderConverterInput()
534
+ ) : (
535
+ <>
536
+ <div className="calc-args">
537
+ {args.map((arg, index) => (
538
+ <div key={index} className="calc-arg-wrapper">
539
+ {renderArgInput(arg, index)}
540
+ </div>
541
+ ))}
542
+ </div>
543
+
544
+ <button
545
+ type="button"
546
+ onClick={addArg}
547
+ className="add-arg-button"
548
+ disabled={isConstant || isConverter}
549
+ >
550
+ <FaPlus /> {t('addArg', 'Ajouter un argument')}
551
+ </button>
552
+ </>
553
+ )}
554
+ </div>
555
+ );
556
+ };
557
+
558
+ const ConditionBuilder = ({ initialValue = null, onChange, models, model = null, label = null}) => {
559
+ const { t } = useTranslation();
560
+ const [root, setRoot] = useState(initialValue);
561
+ const [showJsonEditor, setShowJsonEditor] = useState(false);
562
+ const [jsonError, setJsonError] = useState('');
563
+ const { me:user } = useAuthContext()
564
+
565
+ const handleJsonChange = (e) => {
566
+ const p = typeof(e.value) === 'string' ? JSON.parse(e.value) : e.value;
567
+ setRoot(p);
568
+ onChange(p);
569
+ };
570
+ // C:/Dev/hackersonline-engine/client/src/ConditionBuilder.jsx
571
+ useEffect(() => {
572
+ let parsedValue = {}; // Valeur par défaut si tout échoue
573
+
574
+ if (typeof initialValue === 'string' && initialValue.trim().startsWith('{')) {
575
+ try {
576
+ // Tenter de parser la chaîne en objet JSON
577
+ parsedValue = JSON.parse(initialValue);
578
+ } catch (e) {
579
+ console.error("ConditionBuilder: Impossible de parser la chaîne JSON initialValue.", initialValue, e);
580
+ parsedValue = {}; // Revenir à un état sûr en cas d'erreur
581
+ }
582
+ } else if (typeof initialValue === 'object' && initialValue !== null) {
583
+ // Si c'est déjà un objet, on le prend tel quel
584
+ parsedValue = initialValue;
585
+ }
586
+
587
+ setRoot(parsedValue);
588
+ }, [initialValue]); // <-- Très important : réagir aux changements de la prop
589
+
590
+
591
+ return (
592
+ <div className="condition-builder">
593
+ {label && <label>{label}</label>}
594
+ <button type="button" onClick={() => {
595
+ setShowJsonEditor(!showJsonEditor);
596
+ gtag('event', '[cb] show ' + (showJsonEditor ? 'CB' : 'JSON'));
597
+ }} className="condition-builder-toggle-view">
598
+ {showJsonEditor ? <FaListAlt title={t('conditionBuilder.showBuilderTooltip', 'Afficher le constructeur')} /> : <FaCode title={t('conditionBuilder.showJsonTooltip', 'Afficher le JSON')} />}
599
+ </button>
600
+ {showJsonEditor ? (
601
+ <div className="condition-builder-json">
602
+ <CodeField
603
+ name="conditionJsonEditor"
604
+ language="json"
605
+ value={JSON.stringify(root, null, 2)}
606
+ onChange={handleJsonChange}
607
+ />
608
+ {jsonError && <p className="condition-builder-json-error">{jsonError}</p>}
609
+ </div>
610
+ ) : (
611
+ <ConditionBuilder2
612
+ initialValue={root}
613
+ onChange={onChange}
614
+ models={models}
615
+ model={model}
616
+ />
617
+ )}
618
+ </div>
619
+ );
620
+ };
621
+
622
+ export default ConditionBuilder;