data-primals-engine 1.5.2 → 1.6.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 (63) hide show
  1. package/README.md +924 -915
  2. package/client/README.md +136 -136
  3. package/client/index.js +0 -1
  4. package/client/public/doc/API.postman_collection.json +213 -213
  5. package/client/public/react.svg +9 -9
  6. package/client/src/AddWidgetTypeModal.jsx +47 -47
  7. package/client/src/App.css +42 -42
  8. package/client/src/App.jsx +92 -150
  9. package/client/src/App.scss +6 -0
  10. package/client/src/AssistantChat.jsx +362 -363
  11. package/client/src/Button.jsx +12 -12
  12. package/client/src/Dashboard.jsx +480 -480
  13. package/client/src/DashboardHtmlViewItem.jsx +146 -146
  14. package/client/src/DashboardView.jsx +654 -654
  15. package/client/src/DataEditor.jsx +383 -383
  16. package/client/src/DataLayout.jsx +779 -808
  17. package/client/src/DataTable.jsx +782 -822
  18. package/client/src/DataTable.scss +186 -186
  19. package/client/src/GeolocationField.jsx +93 -93
  20. package/client/src/HistoryDialog.jsx +307 -307
  21. package/client/src/HistoryDialog.scss +319 -319
  22. package/client/src/HtmlViewBuilderModal.jsx +90 -90
  23. package/client/src/HtmlViewBuilderModal.scss +17 -17
  24. package/client/src/HtmlViewCard.jsx +43 -43
  25. package/client/src/ModelCreatorField.jsx +950 -950
  26. package/client/src/ModelList.jsx +280 -280
  27. package/client/src/Notification.jsx +136 -136
  28. package/client/src/PackGallery.jsx +391 -391
  29. package/client/src/PackGallery.scss +231 -231
  30. package/client/src/Pagination.jsx +143 -143
  31. package/client/src/RelationField.jsx +354 -354
  32. package/client/src/RelationSelectorWidget.jsx +172 -172
  33. package/client/src/RelationValue.jsx +2 -1
  34. package/client/src/contexts/CommandContext.jsx +260 -0
  35. package/client/src/contexts/ModelContext.jsx +4 -1
  36. package/client/src/contexts/UIContext.jsx +72 -72
  37. package/client/src/filter.js +263 -263
  38. package/client/src/index.css +90 -90
  39. package/package.json +6 -5
  40. package/perf/artillery-hooks.js +37 -37
  41. package/perf/setup.yml +25 -25
  42. package/src/constants.js +4 -0
  43. package/src/engine.js +335 -335
  44. package/src/events.js +232 -137
  45. package/src/filter.js +274 -274
  46. package/src/modules/assistant/assistant.js +225 -83
  47. package/src/modules/auth-google/index.js +50 -50
  48. package/src/modules/auth-microsoft/index.js +81 -81
  49. package/src/modules/data/data.core.js +118 -118
  50. package/src/modules/data/data.history.js +555 -555
  51. package/src/modules/data/data.operations.js +3401 -3381
  52. package/src/modules/data/data.relations.js +686 -686
  53. package/src/modules/data/data.routes.js +1879 -1879
  54. package/src/modules/data/data.validation.js +2 -2
  55. package/src/modules/file.js +247 -247
  56. package/src/packs.js +2 -2
  57. package/src/providers.js +2 -2
  58. package/src/services/stripe.js +196 -196
  59. package/src/sso.js +193 -193
  60. package/test/data.history.integration.test.js +264 -264
  61. package/test/data.integration.test.js +1206 -1206
  62. package/test/events.test.js +108 -10
  63. package/test/model.integration.test.js +377 -377
@@ -1,951 +1,951 @@
1
- import React, {useEffect, useState} from "react";
2
- import {useModelContext} from "./contexts/ModelContext.jsx";
3
- import {useAuthContext} from "./contexts/AuthContext.jsx";
4
- import {isLocalUser} from "../../src/data.js";
5
- import {Trans, useTranslation} from "react-i18next";
6
- import {FaCircleInfo} from "react-icons/fa6";
7
- import {CheckboxField, CodeField, ColorField, NumberField, SelectField, TextField} from "./Field.jsx";
8
- import Button from "./Button.jsx";
9
- import {FaArrowDown, FaArrowUp, FaEdit, FaMinus, FaPlus, FaTrash} from "react-icons/fa";
10
- import CalculationBuilder from "./CalculationBuilder.jsx";
11
- import ConditionBuilder from "./ConditionBuilder.jsx";
12
- import Draggable from "./Draggable.jsx";
13
- import {mainFieldsTypes} from "../../src/constants.js";
14
-
15
- function RelationModelSelector({relation, onChange, ...rest}) {
16
- const {models} = useModelContext()
17
- const {me} = useAuthContext()
18
- const {t} = useTranslation();
19
- return (
20
- <select {...rest} onChange={(e) => onChange(e.target.value)}>
21
- <option value=""><Trans i18nKey={"relation.selectModel"}>Sélectionner un modèle</Trans></option>
22
- {models.filter(f => f._user === me?.username).map((model) => (
23
- <option selected={model.name === relation} key={model.name} value={model.name}>
24
- {t(`model_${model.name}`, model.name)}
25
- </option>
26
- ))}
27
- </select>
28
- );
29
- }
30
-
31
-
32
- const ValueField = ({v, setValue, disabled}) => {
33
- const {t} = useTranslation();
34
- return <TextField value={v} placeholder={t('modelcreator.field.value', 'Valeur')} disabled={disabled} onChange={(e) => setValue(e.target.value)} />
35
- }
36
-
37
- const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleUp, handleDown, handleRemoveValue, handleAddValue, setFields, fields, field, index}) => {
38
-
39
- const {models} = useModelContext()
40
- const {me } = useAuthContext()
41
- const modelLocked = !!model && (!model?._user ? true : isLocalUser(me) && model?.locked);
42
- const { t, i18n } = useTranslation();
43
- const [showMore, setMoreVisible] = useState(false);
44
- const hint = (description) => t(description, '') && <div className="hint-icon"><FaCircleInfo data-tooltip-id={`tooltipHint`} data-tooltip-content={description} /></div>
45
-
46
- useEffect(() => {
47
- // S'assure que replacement est défini si un masque existe, avec une valeur par défaut correcte.
48
- // La chaîne "\\d" en JS devient "\d" dans l'objet, ce qui est correct pour new RegExp().
49
- if( field.mask && !field.replacement )
50
- field.replacement = { "_": "\\d" };
51
- }, [field]);
52
-
53
- return (
54
- <div className="field-edit">
55
-
56
- <div className="actions right">
57
- {index < fields.length - 1 && (
58
- <button title={"Déplacer vers le bas"} type="button"
59
- onClick={() => handleDown(index)}>
60
- <FaArrowDown/>
61
- </button>)}
62
- {index > 0 && (
63
- <button title="Déplacer vers le haut" type="button"
64
- onClick={() => handleUp(index)}>
65
- <FaArrowUp/>
66
- </button>)}
67
- </div>
68
- <div className="flex flex-row flex-stretch">
69
-
70
- <div className="flex fieldName field-bg">{hint('modelcreator.name.hint')}
71
- <div className="flex flex-no-gap flex-no-wrap flex-1">
72
- <TextField
73
- label={t('modelcreator.fieldName')}
74
- className={!modelLocked && !field._isNewField && "input-fit"}
75
- disabled={modelLocked || (!!model && !field._isNewField)}
76
- value={field.name}
77
- onChange={(e) => {
78
- const newFields = [...fields];
79
- newFields[index].name = e.target.value;
80
- setFields(newFields);
81
- }}
82
- required
83
- after={!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
84
- <Button type={"button"} className={"btn-form btn-last"}
85
- onClick={() => handleRenameField(index, field.name)}><FaEdit/></Button>)}
86
- />
87
- </div>
88
-
89
- </div>
90
-
91
- <div className="flex">
92
-
93
- <div className="flex flex-1 flex-stretch field-bg flex-no-gap">
94
-
95
- <SelectField
96
- label={<div className={"flex"}>{hint('modelcreator.type.hint')}{t('modelcreator.type', 'Type de champ')}</div>}
97
- className={"flex-1"}
98
- value={field.type}
99
- onChange={(e) => {
100
- const newFields = [...fields];
101
-
102
- const nf = newFields[index];
103
- nf.type = e.value;
104
-
105
- setFields(newFields);
106
- gtag('event', 'model set field ' + e.value);
107
- }}
108
- items={[
109
- {label: t("field.string"), value: "string"},
110
- {label: t("field.string_t"), value: "string_t"},
111
- {label: t("field.richtext"), value: "richtext"},
112
- {label: t("field.richtext_t"), value: "richtext_t"},
113
- {label: t("field.number"), value: "number"},
114
- {label: t("field.boolean"), value: "boolean"},
115
- {label: t("field.enum"), value: "enum"},
116
- {label: t("field.date"), value: "date"},
117
- {label: t("field.datetime"), value: "datetime"},
118
- {label: t("field.password"), value: "password"},
119
- {label: t("field.email"), value: "email"},
120
- {label: t("field.phone"), value: "phone"},
121
- {label: t("field.url"), value: "url"},
122
- {label: t("field.color"), value: "color"},
123
- {label: t("field.array"), value: "array"},
124
- {label: t("field.relation"), value: "relation"},
125
- {label: t("field.file"), value: "file"},
126
- {label: t("field.geolocation"), value: "geolocation"},
127
- {label: t("field.code"), value: "code"},
128
- {label: t("field.calculated", "calculated data"), value: "calculated"},
129
- {label: t("field.cronSchedule", "task schedule"), value: "cronSchedule"},
130
- ]}
131
- />
132
- {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
133
-
134
- {(field.type === 'relation' || field.type === 'array') && <div className={"div"}>
135
- {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
136
- {field.type === 'relation' && (
137
- <>
138
- <RelationModelSelector
139
- className={"flex-1 mg-v-1"}
140
- relation={field.relation}
141
- onChange={(relation) => {
142
- const newFields = [...fields];
143
- newFields[index].relation = relation;
144
- setFields(newFields);
145
-
146
- }}
147
- />
148
- </>
149
- )}
150
- {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
151
- {field.type === 'array' && (
152
- <>
153
- <select
154
- className={"flex-1 mg-v-1"}
155
- value={field.itemsType}
156
- onChange={(e) => {
157
- const newFields = [...fields];
158
- newFields[index].itemsType = e.target.value;
159
- setFields(newFields);
160
-
161
- gtag('event', 'model set itemsType ' + e.target.value);
162
- }}
163
- >
164
- <option value="string"><Trans i18nKey={"field.string"}>Texte</Trans></option>
165
- <option value="string_t"><Trans i18nKey={"field.string_t"}>Texte traduit</Trans>
166
- </option>
167
- <option value="richtext"><Trans i18nKey={"field.richtext"}>Texte enrichi</Trans>
168
- </option>
169
- <option value="richtext_t"><Trans i18nKey={"field.richtext_t"}>Texte enrichi
170
- traduit</Trans>
171
- </option>
172
- <option value="number"><Trans i18nKey={"field.number"}>Nombre</Trans></option>
173
- <option value="boolean"><Trans i18nKey={"field.boolean"}>Booléen</Trans>
174
- </option>
175
- <option value="date"><Trans i18nKey={"field.date"}>Date</Trans></option>
176
- <option value="datetime"><Trans i18nKey={"field.datetime"}>Date / Heure</Trans>
177
- </option>
178
- <option value="file"><Trans i18nKey={"field.file"}>Fichier</Trans>
179
- </option>
180
- <option value="email"><Trans i18nKey={"field.email"}>Email</Trans></option>
181
- <option value="phone"><Trans i18nKey={"field.phone"}>Téléphone</Trans></option>
182
- <option value="url"><Trans i18nKey={"field.url"}>URL</Trans></option>
183
- <option value="color"><Trans i18nKey={"field.color"}>Couleur</Trans></option>
184
- <option value="code"><Trans i18nKey={"field.code"}>Code</Trans></option>
185
- <option value="calculated"><Trans i18nKey={"field.calculated"}>Calculated
186
- data</Trans></option>
187
- <option value="cronSchedule"><Trans i18nKey={"field.cronSchedule"}>Task
188
- schedule</Trans></option>
189
- {/* Ajoutez d'autres types de champs ici */}
190
- </select>
191
- </>
192
- )}
193
- </div>}
194
- </div>
195
- </div>
196
-
197
- {field.type === 'calculated' && (
198
- <>
199
- <CalculationBuilder
200
- // Clé : Assurez-vous que modelName est utilisé si model n'existe pas (nouveau modèle)
201
- key={`${model?.name || modelName}-${field.name}-calc`}
202
- // Prop AJOUTÉE : Nom du modèle en cours de création/modification
203
- currentModelName={model?.name}
204
- // Champs disponibles du modèle actuel (on filtre le champ calculé lui-même)
205
- availableFields={fields.filter(f => f.name !== field.name)}
206
- // Tous les modèles (pour les relations)
207
- models={models} // Vient de useModelContext(), semble correct
208
- initialSteps={field.calculation?.steps || []}
209
- onCalculationChange={(calc) => {
210
- const newFields = [...fields];
211
- newFields[index].calculation = calc; // 'index' est l'index du champ calculé dans 'fields'
212
- setFields(newFields);
213
- }}
214
- />
215
- </>
216
- )}
217
- {field.type === "relation" && (
218
-
219
- <div className="flex flex-no-wrap">
220
- {hint('modelcreator.relationFilter.hint')}
221
- <div className="checkbox-label flex flex-1">
222
- <CheckboxField
223
- label={<Trans
224
- i18nKey={"modelcreator.relationFilter"}>Filtre</Trans>}
225
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
226
- checked={field.relationFilter !== undefined}
227
- onChange={(e) => {
228
- const newFields = [...fields];
229
- if (e.target.checked) {
230
- newFields[index].relationFilter = null;
231
- } else {
232
- delete newFields[index].relationFilter;
233
- }
234
- setFields(newFields);
235
- }}
236
- />
237
- </div>
238
- </div>)}
239
-
240
-
241
- {field.relationFilter !== undefined && Array.isArray(models) && (
242
- <div className={"condition-details flex flex-start"}>
243
- <ConditionBuilder
244
- modelFields={models.find(f => f.name === field.relation)?.fields || []}
245
- model={field.relation}
246
- models={models}
247
- selectableModels={false}
248
- initialValue={field.relationFilter}
249
- onChange={(newCondition) => {
250
- const newFields = [...fields];
251
- newFields[index].relationFilter = newCondition || {};
252
- setFields(newFields);
253
- }}
254
- />
255
- </div>
256
- )}
257
-
258
- {(field.itemsType || field.type) === 'file' && (
259
- <>
260
- <div className={"flex"}>
261
- {hint('modelcreator.mimeTypes.hint')}
262
- <label className={"flex"}>
263
- <Trans i18nKey={"modelcreator.mimeTypes"}>Types de fichiers
264
- acceptés
265
- :</Trans>
266
- <input
267
- type="text"
268
- value={field.mimeTypes}
269
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
270
- placeholder={t('modelcreator.field.mimeTypes.ph', "image/png, image/jpeg...")}
271
- onChange={(e) => {
272
- const newFields = [...fields];
273
- newFields[index].mimeTypes = e.target.value.split(',').map(m => m.trim());
274
- setFields(newFields);
275
- }}
276
- />
277
- </label></div>
278
- </>
279
- )}
280
-
281
- {['string', 'string_t'].includes(field.itemsType || field.type) && (
282
- <>
283
- <div className="flex flex-no-wrap">
284
- {hint('modelcreator.multiline.hint')}
285
- <div className={"checkbox-label flex flex-1"}>
286
- <CheckboxField
287
- label={<Trans i18nKey={"modelcreator.multiline"}>Multi-lignes :</Trans>}
288
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
289
- checked={field.multiline}
290
- onChange={(e) => {
291
- const newFields = [...fields];
292
- newFields[index].multiline = e;
293
- setFields(newFields);
294
- }}
295
- />
296
- </div>
297
- </div>
298
-
299
- </>
300
- )}
301
-
302
- {['string', 'string_t', 'richtext', 'richtext_t', 'email', 'phone', 'url', 'password', 'code'].includes(field.itemsType || field.type) && (
303
- <>
304
- <div className={"flex flex-no-wrap field-bg"}>
305
- {hint('modelcreator.maxlength.hint')}
306
- <div className={"flex-1"}><NumberField
307
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
308
- step={1}
309
- min={0}
310
- label={<Trans i18nKey={"modelcreator.maxlength"}>Longueur
311
- maximale :</Trans>}
312
- className={"flex-1"}
313
- value={field.maxlength}
314
- onChange={(e) => {
315
- const newFields = [...fields];
316
- const val = parseInt(e.target.value, 10);
317
- if (isNaN(val))
318
- newFields[index].maxlength = undefined;
319
- else
320
- newFields[index].maxlength = val;
321
- setFields(newFields);
322
- }}
323
- /></div>
324
- </div>
325
- </>
326
- )}
327
- {(field.itemsType || field.type) === 'number' && (
328
- <>
329
- <div className={"flex flex-no-wrap field-bg"}>
330
- {hint('modelcreator.precision.hint')}
331
- <div className="flex-1"><NumberField
332
- label={<Trans
333
- i18nKey={"modelcreator.precision"}>Précision :</Trans>}
334
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
335
- value={field.step || 1}
336
- step={0.1}
337
- className={"flex-1"}
338
- placeholder={t('modelcreator.field.step.ph', "Précision (1, 0.1...)")}
339
- onChange={(e) => {
340
- const newFields = [...fields];
341
- newFields[index].step = e.target.value;//.replace('.', ','));
342
- setFields(newFields);
343
- }}
344
- />
345
- </div>
346
- </div>
347
- <div className="flex flex-no-wrap field-bg">
348
- {hint('modelcreator.unit.hint')}
349
- <div className="flex-1"><TextField
350
- label={<Trans
351
- i18nKey={"modelcreator.unit"}>Unité :</Trans>}
352
- type="string"
353
- className={"flex-1"}
354
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
355
- value={field.unit}
356
- placeholder={t('modelcreator.field.unit.ph', "€, cm, kg...")}
357
- onChange={(e) => {
358
- const newFields = [...fields];
359
- newFields[index].unit = e.target.value;
360
- setFields(newFields);
361
- }}
362
- /></div>
363
- </div>
364
- <div className="flex flex-no-wrap">
365
- {hint('modelcreator.delay.hint')}
366
- <div className="checkbox-label flex flex-1">
367
- <CheckboxField
368
- label={<Trans i18nKey={"modelcreator.delay"}>Délai ?</Trans>}
369
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
370
- checked={field.delay}
371
- onChange={(e) => {
372
- const newFields = [...fields];
373
- newFields[index].delay = e;
374
- setFields(newFields);
375
- }}
376
- />
377
- </div>
378
- </div>
379
-
380
-
381
- </>
382
- )}
383
- {(field.itemsType || field.type) === "relation" && (
384
- <div className="flex flex-no-wrap">
385
- {hint('modelcreator.multiple.hint')}
386
- <div className="checkbox-label flex flex-1">
387
- <CheckboxField
388
- label={<Trans i18nKey={"modelcreator.multiple"}>Multiple :</Trans>}
389
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
390
- checked={field.multiple}
391
- onChange={(e) => {
392
- const newFields = [...fields];
393
- newFields[index].multiple = e;
394
- setFields(newFields);
395
- }}
396
- /></div>
397
- </div>)}
398
-
399
- {(field.itemsType || field.type) === "enum" && (
400
- <div className="values">
401
- <p><Trans i18nKey={"modelcreator.enumValues"}>Valeurs possibles
402
- :</Trans></p>
403
- <Draggable items={field.items} renderItem={(v, i) => {
404
- return <div className="flex flex-no-wrap">
405
- <ValueField v={v}
406
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
407
- setValue={(value) => {
408
- const newFields = [...fields];
409
- newFields[index].items[i] = value;
410
- setFields(newFields);
411
- }}/>
412
- <button type="button"
413
- onClick={() => handleRemoveValue(index, i)}>
414
- <FaMinus/>
415
- </button>
416
- </div>
417
- }} onChange={(arr) => {
418
- const newFields = [...fields];
419
- newFields[index].items = arr;
420
- setFields(newFields);
421
- }}/>
422
- <button type="button" onClick={() => handleAddValue(index)}><FaPlus/>
423
- </button>
424
- </div>
425
- )}
426
-
427
- <details className="advanced-options-details">
428
- <summary>
429
- <Trans i18nKey={"modelcreator.advancedOptions"}>Advanced options</Trans>
430
- </summary>
431
- <>
432
-
433
- <div className="flex flex-no-wrap">
434
- {hint('modelcreator.required.hint')}
435
- <div className="checkbox-label flex flex-1">
436
- <CheckboxField
437
- label={<Trans i18nKey={"modelcreator.required"}>Requis :</Trans>}
438
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
439
- checked={field.required}
440
- onChange={(e) => {
441
- const newFields = [...fields];
442
- newFields[index].required = e;
443
- setFields(newFields);
444
- }}
445
- />
446
- </div>
447
- </div>
448
-
449
- <div className="flex flex-no-wrap">
450
- {hint('modelcreator.unique.hint')}
451
- <div className="checkbox-label flex flex-1">
452
-
453
- <CheckboxField
454
- label={<Trans i18nKey={"modelcreator.unique"}>Unique :</Trans>}
455
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
456
- checked={field.unique}
457
- onChange={(e) => {
458
- const newFields = [...fields];
459
- newFields[index].unique = e;
460
- setFields(newFields);
461
- }}
462
- />
463
- </div>
464
- </div>
465
-
466
- {!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
467
- className="flex flex-no-wrap mg-item">
468
-
469
- {['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
470
- {hint('modelcreator.default.hint')}
471
- <div className="flex flex-1 field-bg">
472
- <TextField
473
- className="flex-1"
474
- value={field.default}
475
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
476
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
477
- onChange={(e) => {
478
- const newFields = [...fields];
479
- newFields[index].default = e.target.value;
480
- setFields(newFields);
481
- }}
482
- />
483
- </div>
484
- </>)}
485
- {['number'].includes(field.type) && (<>
486
- {hint('modelcreator.default.hint')}
487
- <div className="flex flex-1 field-bg">
488
- <NumberField
489
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
490
- type="number"
491
- className="flex-1"
492
- value={field.default}
493
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
494
- onChange={(e) => {
495
- const newFields = [...fields];
496
- const val = parseInt(e.target.value, 10);
497
- if (!val)
498
- newFields[index].default = undefined;
499
- else
500
- newFields[index].default = val;
501
- setFields(newFields);
502
- }}
503
- />
504
- </div>
505
- </>)}
506
- {['enum'].includes(field.type) && (<>
507
- {hint('modelcreator.default.hint')}
508
- <div className="flex flex-1 field-bg">
509
- <SelectField
510
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
511
- value={field.default}
512
- className="flex-1"
513
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
514
- items={(field.items || []).map(m => ({label: t(m, m), value: m}))}
515
- onChange={(e) => {
516
- const newFields = [...fields];
517
- newFields[index].default = e.value;
518
- setFields(newFields);
519
- }}
520
- /></div>
521
- </>)}
522
- {['code'].includes(field.type) && (<>
523
- {hint('modelcreator.default.hint')}
524
- <div className="flex flex-1 field-bg">
525
- <CodeField
526
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
527
- value={field.language === 'json' ? JSON.stringify(field.default, 2, null) : field.default}
528
- onChange={(e) => {
529
- const newFields = [...fields];
530
- newFields[index].default = e.value;
531
- setFields(newFields);
532
- }}
533
- />
534
- </div>
535
- </>)}
536
- {['boolean'].includes(field.type) && (<>
537
- {hint('modelcreator.default.hint')}
538
- <div className="checkbox-label flex flex-1">
539
- <CheckboxField
540
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
541
- checked={field.default}
542
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
543
- onChange={(e) => {
544
- const newFields = [...fields];
545
- newFields[index].default = e;
546
- setFields(newFields);
547
- }}
548
- />
549
- </div>
550
- </>)}
551
- {['color'].includes(field.type) && (<>
552
- {hint('modelcreator.default.hint')}
553
- <div className="flex flex-1 field-bg">
554
- <ColorField
555
- label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
556
- value={field.default || null} name={field.name}
557
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
558
- onChange={(e) => {
559
- const newFields = [...fields];
560
- newFields[index].default = e.value;
561
- setFields(newFields);
562
- }}
563
- />
564
- </div>
565
- </>)}
566
- {['date', 'datetime'].includes(field.type) && (<>
567
- {hint('modelcreator.default.hint')}
568
-
569
- <div className="flex flex-1 field-bg">
570
- <label className="flex flex-1">
571
- <span className={"flex-1"}><Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans></span>
572
- <input
573
- value={field.default}
574
- className="flex-1"
575
- type={field.type === 'date' ? 'date' : 'datetime-local'}
576
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
577
- onChange={(e) => {
578
- const newFields = [...fields];
579
- newFields[index].default = e.target.value;
580
- setFields(newFields);
581
- }}
582
- />
583
- </label>
584
- </div>
585
- </>)}
586
- </div>)}
587
-
588
- {["string_t", "string"].includes(field.itemsType || field.type) && (
589
- <>
590
-
591
- <div className={"flex flex-no-wrap field-bg"}>
592
- {hint('modelcreator.mask.hint')}
593
- <div className={"flex-1"}>
594
- <TextField
595
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
596
- label={<Trans i18nKey={"modelcreator.mask"}>Masque de saisie</Trans>}
597
- className={"flex-1"}
598
- value={field.mask}
599
- placeholder={t('modelcreator.field.mask.ph', "mask_")}
600
- onChange={(e) => {
601
- const newFields = [...fields];
602
- newFields[index].mask = e.target.value;
603
- setFields(newFields);
604
- }}
605
- />
606
- </div>
607
- </div>
608
- <div className={"flex flex-no-wrap field-bg"}>
609
- {hint('modelcreator.replacement.hint')}
610
- <div className={"flex-1"}>
611
- <CodeField
612
- name="replacement"
613
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
614
- label={<Trans i18nKey={"modelcreator.replacement"}>Règles de remplacement (JSON)</Trans>}
615
- language="json"
616
- value={field.replacement ? JSON.stringify(field.replacement, null, 2) : ''}
617
- onChange={(e) => {
618
- const newFields = [...fields];
619
- newFields[index].replacement = e.value;
620
- setFields(newFields);
621
- }}
622
- />
623
- </div>
624
- </div>
625
- </>
626
- )}
627
-
628
- {(field.itemsType || field.type) === 'number' && (
629
- <>
630
- <div
631
- className="flex flex-no-wrap mg-item">{hint('modelcreator.min.hint')}
632
- <div className="flex field-bg flex-1">
633
- <NumberField
634
- label={<Trans
635
- className={"flex-1"}
636
- i18nKey={"modelcreator.min"}>Valeur minimale :</Trans>}
637
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
638
- value={(field.min + '').replace('.', ',')}
639
- onChange={(e) => {
640
- const newFields = [...fields];
641
- newFields[index].min = parseInt(e.target.value, 10);
642
- setFields(newFields);
643
- }}
644
- />
645
- </div>
646
- </div>
647
- <div
648
- className="flex flex-no-wrap mg-item">{hint('modelcreator.max.hint')}
649
- <div className="flex field-bg flex-1">
650
- <NumberField
651
- label={<Trans
652
- className={"flex-1"}
653
- i18nKey={"modelcreator.max"}>Valeur maximale :</Trans>}
654
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
655
- value={(field.max + '').replace('.', ',')}
656
- onChange={(e) => {
657
- const newFields = [...fields];
658
- newFields[index].max = parseInt(e.target.value, 10);
659
- setFields(newFields);
660
- }}
661
- />
662
- </div>
663
- </div>
664
- </>
665
- )}
666
-
667
- {(['datetime', 'date'].includes(field.itemsType || field.type)) && (
668
- <>
669
- <div
670
- className="flex flex-no-wrap mg-item">
671
- {hint('modelcreator.min.hint')}
672
- <div className="flex field-bg flex-1">
673
- <label className={"flex-1"}><Trans i18nKey={"modelcreator.min"}>Valeur minimale :</Trans></label>
674
- <input
675
- className={"flex-1"}
676
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
677
- type={field.type === 'datetime' ? 'datetime-local' : field.type}
678
- value={field.type === "number" ? (field.min + '').replace('.', ',') : field.min}
679
- onChange={(e) => {
680
- const newFields = [...fields];
681
- if (['datetime', 'date'].includes(field.itemsType || field.type)) {
682
- newFields[index].min = e.target.value;
683
- } else {
684
- newFields[index].min = parseInt(e.target.value, 10);
685
- }
686
- setFields(newFields);
687
- }}
688
- />
689
- </div>
690
- </div>
691
-
692
- <div className="flex flex-no-wrap mg-item">
693
- {hint('modelcreator.max.hint')}
694
- <div className="flex field-bg flex-1">
695
- <label className={"flex-1"}><Trans i18nKey={"modelcreator.max"}>Valeur maximale :</Trans></label>
696
- <input
697
- className={"flex-1"}
698
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
699
- type={field.type === 'datetime' ? 'datetime-local' : field.type}
700
- value={field.type === "number" ? (field.max + '').replace('.', ',') : field.max}
701
- onChange={(e) => {
702
- const newFields = [...fields];
703
- if (['datetime', 'date'].includes(field.itemsType || field.type)) {
704
- newFields[index].max = e.target.value;
705
- } else {
706
- newFields[index].max = parseInt(e.target.value, 10);
707
- }
708
- setFields(newFields);
709
- }}
710
- /></div>
711
- </div>
712
- </>
713
- )}
714
- <div className="flex flex-no-wrap mg-item">
715
- {hint('modelcreator.condition.hint')}
716
-
717
- <CheckboxField
718
- label={<Trans
719
- i18nKey={"modelcreator.condition"}>Condition</Trans>}
720
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
721
- checked={field.condition !== undefined}
722
- onChange={(e) => {
723
- const newFields = [...fields];
724
- if (e) {
725
- newFields[index].condition = null;
726
- } else {
727
- delete newFields[index].condition;
728
- }
729
- setFields(newFields);
730
- }}
731
- />
732
- </div>
733
-
734
-
735
- {(field.itemsType || field.type)=== 'number'&&(
736
- <div className="flex flex-no-wrap">
737
- {hint('modelcreator.gauge.hint')}
738
- <div className="checkbox-label flex flex-1">
739
- <CheckboxField
740
- label={<Trans i18nKey={"modelcreator.gauge"}>Jauge ?</Trans>}
741
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
742
- checked={field.gauge}
743
- onChange={(e) => {
744
- const newFields = [...fields];
745
- newFields[index].gauge = e;
746
- if (!e) {
747
- delete newFields[index].percent;
748
- }
749
- setFields(newFields);
750
- }}
751
- />
752
- </div>
753
- {field.gauge && (
754
- <div className="flex flex-no-wrap">
755
- {hint('modelcreator.percent.hint')}
756
- <div className="checkbox-label flex flex-1">
757
- <CheckboxField
758
- label={<Trans i18nKey={"modelcreator.percent"}>Mode pourcentage ?</Trans>}
759
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
760
- checked={field.percent}
761
- onChange={(e) => {
762
- const newFields = [...fields];
763
- newFields[index].percent = e;
764
- setFields(newFields);
765
- }}
766
- />
767
- </div>
768
- </div>)}
769
- </div>
770
- )}
771
-
772
- {field.condition !== undefined && (
773
- <div className={"condition-details flex flex-start"}>
774
-
775
- <p>{t('modelcreator.condition.hint')}</p>
776
- <ConditionBuilder key="test"
777
- onChange={(newCondition) => {
778
- const newFields = [...fields];
779
- newFields[index].condition = newCondition || {};
780
- setFields(newFields);
781
- }} initialValue={field.condition || {}}
782
- model={model} models={models}/>
783
-
784
- </div>
785
- )}
786
- {(field.itemsType || field.type) === 'code' && (<>
787
- <div className="flex">
788
- {hint('modelcreator.language.hint')}
789
- <label className="checkbox-label flex flex-1">
790
- <Trans i18nKey={"modelcreator.language"}>Language :</Trans>
791
- <TextField placeholder={"json, javascript..."}
792
- value={field.language}
793
- onChange={e => {
794
- const newFields = [...fields];
795
- newFields[index].language = e.target.value;
796
- setFields(newFields);
797
- }}/>
798
- </label>
799
- </div>
800
- {field.language === 'json' && (<div className="flex flex-no-wrap">
801
- {hint('modelcreator.conditionBuilder.hint')}
802
- <label className="checkbox-label flex flex-1">
803
- <Trans i18nKey={"modelcreator.conditionBuilder"}>Condition Builder
804
- :</Trans>
805
- <input
806
- type="checkbox"
807
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
808
- checked={field.conditionBuilder}
809
- onChange={(e) => {
810
- const newFields = [...fields];
811
- newFields[index].conditionBuilder = e.target.checked ? true : undefined;
812
- setFields(newFields);
813
- }}
814
- />
815
- </label>
816
- </div>)}
817
-
818
- </>)}
819
-
820
- {me.userPlan === 'premium' && (
821
- <div className="flex flex-no-wrap"
822
- title={t('index_field_info', 'Améliore les performances sur la recherche mais demande un espace de stockage plus élevé et diminue les performances sur l\'insertion de données.')}>
823
- {hint('modelcreator.index.hint')}
824
- <label className="checkbox-label flex flex-1">
825
- <Trans i18nKey={"indexed_field"}>Champ indexé</Trans> :
826
- <input
827
- type="checkbox"
828
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
829
- checked={!!field.index}
830
- onChange={(e) => {
831
- const newFields = [...fields];
832
- newFields[index].index = e.target.checked;
833
- setFields(newFields);
834
- }}
835
- />
836
- </label>
837
- </div>)}
838
-
839
-
840
- {mainFieldsTypes.includes(field.itemsType || field.type) && (<div
841
- className="flex flex-no-wrap mg-item"
842
- title={t("modelcreator.field.asMain", "Une information principale sera affichée dans le titre de l'enregistrement")}>
843
- {hint('modelcreator.asMain.hint')}
844
- <div className="flex flex-1">
845
-
846
- <CheckboxField
847
- label={<Trans i18nKey={"modelcreator.asMain"}>Information principale :</Trans>}
848
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
849
- checked={field.asMain}
850
- onChange={(e) => {
851
- const newFields = [...fields];
852
- newFields[index].asMain = e;
853
- setFields(newFields);
854
- }}
855
- />
856
- </div>
857
- </div>)}
858
-
859
- <div className="flex flex-row flex-stretch">
860
- <div className="flex">
861
- {hint('modelcreator.description.hint')}
862
- <label className={"flex-1 field-bg"} htmlFor={`textarea-model-description${field.name}`}><Trans
863
- i18nKey={"modelcreator.fieldDesc"}>Description du champ :</Trans></label>
864
- </div>
865
- <div className="flex flex-1">
866
- <textarea id={`textarea-model-description${field.name}`}
867
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
868
- onChange={(e) => {
869
- const newFields = [...fields];
870
- newFields[index].hint = e.target.value;
871
- setFields(newFields);
872
- }}
873
- defaultValue={field.hint || ''}/>
874
- </div>
875
- </div>
876
-
877
-
878
- <label className="flex mg-item ">
879
- {hint('modelcreator.color.hint')}
880
- <Trans i18nKey={"field.color"}>Color :</Trans>
881
- <ColorField
882
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
883
- value={field.color || '#FFFFFF'}
884
- onChange={(e) => {
885
- const newFields = [...fields];
886
- newFields[index].color = e.value;
887
- setFields(newFields);
888
- }}
889
- />
890
- </label>
891
-
892
- <div className="flex flex-no-wrap">
893
- {hint('modelcreator.hiddenable.hint')}
894
- <div className="checkbox-label flex flex-1">
895
-
896
- <CheckboxField
897
- label={<Trans i18nKey={"modelcreator.hiddenable"}>Dissimulable :</Trans>}
898
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
899
- checked={field.hiddenable}
900
- onChange={(e) => {
901
- const newFields = [...fields];
902
- newFields[index].hiddenable = e;
903
- setFields(newFields);
904
- }}
905
- />
906
- </div>
907
- </div>
908
-
909
- <div className={"flex flex-no-wrap"}>
910
- {hint('modelcreator.anonymized.hint')}
911
- <CheckboxField
912
- label={<Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>}
913
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
914
- checked={field.anonymized}
915
- onChange={(e) => {
916
- const newFields = [...fields];
917
- newFields[index].anonymized = e;
918
- setFields(newFields);
919
- }}
920
- />
921
- </div>
922
-
923
-
924
- {false && (<label className="checkbox-label flex">
925
- <Trans i18nKey={"modelcreator.locked"}>Locked :</Trans>
926
- <input
927
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
928
- type={"checkbox"}
929
- checked={field.locked}
930
- onChange={(e) => {
931
- const newFields = [...fields];
932
- newFields[index].locked = e.target.checked;
933
- setFields(newFields);
934
- }}
935
- />
936
- </label>)}
937
- </>
938
- </details>
939
-
940
- <div className="flex actions">
941
- <Button type="button" onClick={() => handleRemoveField(index)}>
942
- <FaTrash/>
943
- </Button>
944
- </div>
945
- </div>
946
-
947
- </div>
948
- );
949
- }
950
-
1
+ import React, {useEffect, useState} from "react";
2
+ import {useModelContext} from "./contexts/ModelContext.jsx";
3
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
4
+ import {isLocalUser} from "../../src/data.js";
5
+ import {Trans, useTranslation} from "react-i18next";
6
+ import {FaCircleInfo} from "react-icons/fa6";
7
+ import {CheckboxField, CodeField, ColorField, NumberField, SelectField, TextField} from "./Field.jsx";
8
+ import Button from "./Button.jsx";
9
+ import {FaArrowDown, FaArrowUp, FaEdit, FaMinus, FaPlus, FaTrash} from "react-icons/fa";
10
+ import CalculationBuilder from "./CalculationBuilder.jsx";
11
+ import ConditionBuilder from "./ConditionBuilder.jsx";
12
+ import Draggable from "./Draggable.jsx";
13
+ import {mainFieldsTypes} from "../../src/constants.js";
14
+
15
+ function RelationModelSelector({relation, onChange, ...rest}) {
16
+ const {models} = useModelContext()
17
+ const {me} = useAuthContext()
18
+ const {t} = useTranslation();
19
+ return (
20
+ <select {...rest} onChange={(e) => onChange(e.target.value)}>
21
+ <option value=""><Trans i18nKey={"relation.selectModel"}>Sélectionner un modèle</Trans></option>
22
+ {models.filter(f => f._user === me?.username).map((model) => (
23
+ <option selected={model.name === relation} key={model.name} value={model.name}>
24
+ {t(`model_${model.name}`, model.name)}
25
+ </option>
26
+ ))}
27
+ </select>
28
+ );
29
+ }
30
+
31
+
32
+ const ValueField = ({v, setValue, disabled}) => {
33
+ const {t} = useTranslation();
34
+ return <TextField value={v} placeholder={t('modelcreator.field.value', 'Valeur')} disabled={disabled} onChange={(e) => setValue(e.target.value)} />
35
+ }
36
+
37
+ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleUp, handleDown, handleRemoveValue, handleAddValue, setFields, fields, field, index}) => {
38
+
39
+ const {models} = useModelContext()
40
+ const {me } = useAuthContext()
41
+ const modelLocked = !!model && (!model?._user ? true : isLocalUser(me) && model?.locked);
42
+ const { t, i18n } = useTranslation();
43
+ const [showMore, setMoreVisible] = useState(false);
44
+ const hint = (description) => t(description, '') && <div className="hint-icon"><FaCircleInfo data-tooltip-id={`tooltipHint`} data-tooltip-content={description} /></div>
45
+
46
+ useEffect(() => {
47
+ // S'assure que replacement est défini si un masque existe, avec une valeur par défaut correcte.
48
+ // La chaîne "\\d" en JS devient "\d" dans l'objet, ce qui est correct pour new RegExp().
49
+ if( field.mask && !field.replacement )
50
+ field.replacement = { "_": "\\d" };
51
+ }, [field]);
52
+
53
+ return (
54
+ <div className="field-edit">
55
+
56
+ <div className="actions right">
57
+ {index < fields.length - 1 && (
58
+ <button title={"Déplacer vers le bas"} type="button"
59
+ onClick={() => handleDown(index)}>
60
+ <FaArrowDown/>
61
+ </button>)}
62
+ {index > 0 && (
63
+ <button title="Déplacer vers le haut" type="button"
64
+ onClick={() => handleUp(index)}>
65
+ <FaArrowUp/>
66
+ </button>)}
67
+ </div>
68
+ <div className="flex flex-row flex-stretch">
69
+
70
+ <div className="flex fieldName field-bg">{hint('modelcreator.name.hint')}
71
+ <div className="flex flex-no-gap flex-no-wrap flex-1">
72
+ <TextField
73
+ label={t('modelcreator.fieldName')}
74
+ className={!modelLocked && !field._isNewField && "input-fit"}
75
+ disabled={modelLocked || (!!model && !field._isNewField)}
76
+ value={field.name}
77
+ onChange={(e) => {
78
+ const newFields = [...fields];
79
+ newFields[index].name = e.target.value;
80
+ setFields(newFields);
81
+ }}
82
+ required
83
+ after={!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
84
+ <Button type={"button"} className={"btn-form btn-last"}
85
+ onClick={() => handleRenameField(index, field.name)}><FaEdit/></Button>)}
86
+ />
87
+ </div>
88
+
89
+ </div>
90
+
91
+ <div className="flex">
92
+
93
+ <div className="flex flex-1 flex-stretch field-bg flex-no-gap">
94
+
95
+ <SelectField
96
+ label={<div className={"flex"}>{hint('modelcreator.type.hint')}{t('modelcreator.type', 'Type de champ')}</div>}
97
+ className={"flex-1"}
98
+ value={field.type}
99
+ onChange={(e) => {
100
+ const newFields = [...fields];
101
+
102
+ const nf = newFields[index];
103
+ nf.type = e.value;
104
+
105
+ setFields(newFields);
106
+ gtag('event', 'model set field ' + e.value);
107
+ }}
108
+ items={[
109
+ {label: t("field.string"), value: "string"},
110
+ {label: t("field.string_t"), value: "string_t"},
111
+ {label: t("field.richtext"), value: "richtext"},
112
+ {label: t("field.richtext_t"), value: "richtext_t"},
113
+ {label: t("field.number"), value: "number"},
114
+ {label: t("field.boolean"), value: "boolean"},
115
+ {label: t("field.enum"), value: "enum"},
116
+ {label: t("field.date"), value: "date"},
117
+ {label: t("field.datetime"), value: "datetime"},
118
+ {label: t("field.password"), value: "password"},
119
+ {label: t("field.email"), value: "email"},
120
+ {label: t("field.phone"), value: "phone"},
121
+ {label: t("field.url"), value: "url"},
122
+ {label: t("field.color"), value: "color"},
123
+ {label: t("field.array"), value: "array"},
124
+ {label: t("field.relation"), value: "relation"},
125
+ {label: t("field.file"), value: "file"},
126
+ {label: t("field.geolocation"), value: "geolocation"},
127
+ {label: t("field.code"), value: "code"},
128
+ {label: t("field.calculated", "calculated data"), value: "calculated"},
129
+ {label: t("field.cronSchedule", "task schedule"), value: "cronSchedule"},
130
+ ]}
131
+ />
132
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
133
+
134
+ {(field.type === 'relation' || field.type === 'array') && <div className={"div"}>
135
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
136
+ {field.type === 'relation' && (
137
+ <>
138
+ <RelationModelSelector
139
+ className={"flex-1 mg-v-1"}
140
+ relation={field.relation}
141
+ onChange={(relation) => {
142
+ const newFields = [...fields];
143
+ newFields[index].relation = relation;
144
+ setFields(newFields);
145
+
146
+ }}
147
+ />
148
+ </>
149
+ )}
150
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
151
+ {field.type === 'array' && (
152
+ <>
153
+ <select
154
+ className={"flex-1 mg-v-1"}
155
+ value={field.itemsType}
156
+ onChange={(e) => {
157
+ const newFields = [...fields];
158
+ newFields[index].itemsType = e.target.value;
159
+ setFields(newFields);
160
+
161
+ gtag('event', 'model set itemsType ' + e.target.value);
162
+ }}
163
+ >
164
+ <option value="string"><Trans i18nKey={"field.string"}>Texte</Trans></option>
165
+ <option value="string_t"><Trans i18nKey={"field.string_t"}>Texte traduit</Trans>
166
+ </option>
167
+ <option value="richtext"><Trans i18nKey={"field.richtext"}>Texte enrichi</Trans>
168
+ </option>
169
+ <option value="richtext_t"><Trans i18nKey={"field.richtext_t"}>Texte enrichi
170
+ traduit</Trans>
171
+ </option>
172
+ <option value="number"><Trans i18nKey={"field.number"}>Nombre</Trans></option>
173
+ <option value="boolean"><Trans i18nKey={"field.boolean"}>Booléen</Trans>
174
+ </option>
175
+ <option value="date"><Trans i18nKey={"field.date"}>Date</Trans></option>
176
+ <option value="datetime"><Trans i18nKey={"field.datetime"}>Date / Heure</Trans>
177
+ </option>
178
+ <option value="file"><Trans i18nKey={"field.file"}>Fichier</Trans>
179
+ </option>
180
+ <option value="email"><Trans i18nKey={"field.email"}>Email</Trans></option>
181
+ <option value="phone"><Trans i18nKey={"field.phone"}>Téléphone</Trans></option>
182
+ <option value="url"><Trans i18nKey={"field.url"}>URL</Trans></option>
183
+ <option value="color"><Trans i18nKey={"field.color"}>Couleur</Trans></option>
184
+ <option value="code"><Trans i18nKey={"field.code"}>Code</Trans></option>
185
+ <option value="calculated"><Trans i18nKey={"field.calculated"}>Calculated
186
+ data</Trans></option>
187
+ <option value="cronSchedule"><Trans i18nKey={"field.cronSchedule"}>Task
188
+ schedule</Trans></option>
189
+ {/* Ajoutez d'autres types de champs ici */}
190
+ </select>
191
+ </>
192
+ )}
193
+ </div>}
194
+ </div>
195
+ </div>
196
+
197
+ {field.type === 'calculated' && (
198
+ <>
199
+ <CalculationBuilder
200
+ // Clé : Assurez-vous que modelName est utilisé si model n'existe pas (nouveau modèle)
201
+ key={`${model?.name || modelName}-${field.name}-calc`}
202
+ // Prop AJOUTÉE : Nom du modèle en cours de création/modification
203
+ currentModelName={model?.name}
204
+ // Champs disponibles du modèle actuel (on filtre le champ calculé lui-même)
205
+ availableFields={fields.filter(f => f.name !== field.name)}
206
+ // Tous les modèles (pour les relations)
207
+ models={models} // Vient de useModelContext(), semble correct
208
+ initialSteps={field.calculation?.steps || []}
209
+ onCalculationChange={(calc) => {
210
+ const newFields = [...fields];
211
+ newFields[index].calculation = calc; // 'index' est l'index du champ calculé dans 'fields'
212
+ setFields(newFields);
213
+ }}
214
+ />
215
+ </>
216
+ )}
217
+ {field.type === "relation" && (
218
+
219
+ <div className="flex flex-no-wrap">
220
+ {hint('modelcreator.relationFilter.hint')}
221
+ <div className="checkbox-label flex flex-1">
222
+ <CheckboxField
223
+ label={<Trans
224
+ i18nKey={"modelcreator.relationFilter"}>Filtre</Trans>}
225
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
226
+ checked={field.relationFilter !== undefined}
227
+ onChange={(e) => {
228
+ const newFields = [...fields];
229
+ if (e.target.checked) {
230
+ newFields[index].relationFilter = null;
231
+ } else {
232
+ delete newFields[index].relationFilter;
233
+ }
234
+ setFields(newFields);
235
+ }}
236
+ />
237
+ </div>
238
+ </div>)}
239
+
240
+
241
+ {field.relationFilter !== undefined && Array.isArray(models) && (
242
+ <div className={"condition-details flex flex-start"}>
243
+ <ConditionBuilder
244
+ modelFields={models.find(f => f.name === field.relation)?.fields || []}
245
+ model={field.relation}
246
+ models={models}
247
+ selectableModels={false}
248
+ initialValue={field.relationFilter}
249
+ onChange={(newCondition) => {
250
+ const newFields = [...fields];
251
+ newFields[index].relationFilter = newCondition || {};
252
+ setFields(newFields);
253
+ }}
254
+ />
255
+ </div>
256
+ )}
257
+
258
+ {(field.itemsType || field.type) === 'file' && (
259
+ <>
260
+ <div className={"flex"}>
261
+ {hint('modelcreator.mimeTypes.hint')}
262
+ <label className={"flex"}>
263
+ <Trans i18nKey={"modelcreator.mimeTypes"}>Types de fichiers
264
+ acceptés
265
+ :</Trans>
266
+ <input
267
+ type="text"
268
+ value={field.mimeTypes}
269
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
270
+ placeholder={t('modelcreator.field.mimeTypes.ph', "image/png, image/jpeg...")}
271
+ onChange={(e) => {
272
+ const newFields = [...fields];
273
+ newFields[index].mimeTypes = e.target.value.split(',').map(m => m.trim());
274
+ setFields(newFields);
275
+ }}
276
+ />
277
+ </label></div>
278
+ </>
279
+ )}
280
+
281
+ {['string', 'string_t'].includes(field.itemsType || field.type) && (
282
+ <>
283
+ <div className="flex flex-no-wrap">
284
+ {hint('modelcreator.multiline.hint')}
285
+ <div className={"checkbox-label flex flex-1"}>
286
+ <CheckboxField
287
+ label={<Trans i18nKey={"modelcreator.multiline"}>Multi-lignes :</Trans>}
288
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
289
+ checked={field.multiline}
290
+ onChange={(e) => {
291
+ const newFields = [...fields];
292
+ newFields[index].multiline = e;
293
+ setFields(newFields);
294
+ }}
295
+ />
296
+ </div>
297
+ </div>
298
+
299
+ </>
300
+ )}
301
+
302
+ {['string', 'string_t', 'richtext', 'richtext_t', 'email', 'phone', 'url', 'password', 'code'].includes(field.itemsType || field.type) && (
303
+ <>
304
+ <div className={"flex flex-no-wrap field-bg"}>
305
+ {hint('modelcreator.maxlength.hint')}
306
+ <div className={"flex-1"}><NumberField
307
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
308
+ step={1}
309
+ min={0}
310
+ label={<Trans i18nKey={"modelcreator.maxlength"}>Longueur
311
+ maximale :</Trans>}
312
+ className={"flex-1"}
313
+ value={field.maxlength}
314
+ onChange={(e) => {
315
+ const newFields = [...fields];
316
+ const val = parseInt(e.target.value, 10);
317
+ if (isNaN(val))
318
+ newFields[index].maxlength = undefined;
319
+ else
320
+ newFields[index].maxlength = val;
321
+ setFields(newFields);
322
+ }}
323
+ /></div>
324
+ </div>
325
+ </>
326
+ )}
327
+ {(field.itemsType || field.type) === 'number' && (
328
+ <>
329
+ <div className={"flex flex-no-wrap field-bg"}>
330
+ {hint('modelcreator.precision.hint')}
331
+ <div className="flex-1"><NumberField
332
+ label={<Trans
333
+ i18nKey={"modelcreator.precision"}>Précision :</Trans>}
334
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
335
+ value={field.step || 1}
336
+ step={0.1}
337
+ className={"flex-1"}
338
+ placeholder={t('modelcreator.field.step.ph', "Précision (1, 0.1...)")}
339
+ onChange={(e) => {
340
+ const newFields = [...fields];
341
+ newFields[index].step = e.target.value;//.replace('.', ','));
342
+ setFields(newFields);
343
+ }}
344
+ />
345
+ </div>
346
+ </div>
347
+ <div className="flex flex-no-wrap field-bg">
348
+ {hint('modelcreator.unit.hint')}
349
+ <div className="flex-1"><TextField
350
+ label={<Trans
351
+ i18nKey={"modelcreator.unit"}>Unité :</Trans>}
352
+ type="string"
353
+ className={"flex-1"}
354
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
355
+ value={field.unit}
356
+ placeholder={t('modelcreator.field.unit.ph', "€, cm, kg...")}
357
+ onChange={(e) => {
358
+ const newFields = [...fields];
359
+ newFields[index].unit = e.target.value;
360
+ setFields(newFields);
361
+ }}
362
+ /></div>
363
+ </div>
364
+ <div className="flex flex-no-wrap">
365
+ {hint('modelcreator.delay.hint')}
366
+ <div className="checkbox-label flex flex-1">
367
+ <CheckboxField
368
+ label={<Trans i18nKey={"modelcreator.delay"}>Délai ?</Trans>}
369
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
370
+ checked={field.delay}
371
+ onChange={(e) => {
372
+ const newFields = [...fields];
373
+ newFields[index].delay = e;
374
+ setFields(newFields);
375
+ }}
376
+ />
377
+ </div>
378
+ </div>
379
+
380
+
381
+ </>
382
+ )}
383
+ {(field.itemsType || field.type) === "relation" && (
384
+ <div className="flex flex-no-wrap">
385
+ {hint('modelcreator.multiple.hint')}
386
+ <div className="checkbox-label flex flex-1">
387
+ <CheckboxField
388
+ label={<Trans i18nKey={"modelcreator.multiple"}>Multiple :</Trans>}
389
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
390
+ checked={field.multiple}
391
+ onChange={(e) => {
392
+ const newFields = [...fields];
393
+ newFields[index].multiple = e;
394
+ setFields(newFields);
395
+ }}
396
+ /></div>
397
+ </div>)}
398
+
399
+ {(field.itemsType || field.type) === "enum" && (
400
+ <div className="values">
401
+ <p><Trans i18nKey={"modelcreator.enumValues"}>Valeurs possibles
402
+ :</Trans></p>
403
+ <Draggable items={field.items} renderItem={(v, i) => {
404
+ return <div className="flex flex-no-wrap">
405
+ <ValueField v={v}
406
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
407
+ setValue={(value) => {
408
+ const newFields = [...fields];
409
+ newFields[index].items[i] = value;
410
+ setFields(newFields);
411
+ }}/>
412
+ <button type="button"
413
+ onClick={() => handleRemoveValue(index, i)}>
414
+ <FaMinus/>
415
+ </button>
416
+ </div>
417
+ }} onChange={(arr) => {
418
+ const newFields = [...fields];
419
+ newFields[index].items = arr;
420
+ setFields(newFields);
421
+ }}/>
422
+ <button type="button" onClick={() => handleAddValue(index)}><FaPlus/>
423
+ </button>
424
+ </div>
425
+ )}
426
+
427
+ <details className="advanced-options-details">
428
+ <summary>
429
+ <Trans i18nKey={"modelcreator.advancedOptions"}>Advanced options</Trans>
430
+ </summary>
431
+ <>
432
+
433
+ <div className="flex flex-no-wrap">
434
+ {hint('modelcreator.required.hint')}
435
+ <div className="checkbox-label flex flex-1">
436
+ <CheckboxField
437
+ label={<Trans i18nKey={"modelcreator.required"}>Requis :</Trans>}
438
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
439
+ checked={field.required}
440
+ onChange={(e) => {
441
+ const newFields = [...fields];
442
+ newFields[index].required = e;
443
+ setFields(newFields);
444
+ }}
445
+ />
446
+ </div>
447
+ </div>
448
+
449
+ <div className="flex flex-no-wrap">
450
+ {hint('modelcreator.unique.hint')}
451
+ <div className="checkbox-label flex flex-1">
452
+
453
+ <CheckboxField
454
+ label={<Trans i18nKey={"modelcreator.unique"}>Unique :</Trans>}
455
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
456
+ checked={field.unique}
457
+ onChange={(e) => {
458
+ const newFields = [...fields];
459
+ newFields[index].unique = e;
460
+ setFields(newFields);
461
+ }}
462
+ />
463
+ </div>
464
+ </div>
465
+
466
+ {!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
467
+ className="flex flex-no-wrap mg-item">
468
+
469
+ {['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
470
+ {hint('modelcreator.default.hint')}
471
+ <div className="flex flex-1 field-bg">
472
+ <TextField
473
+ className="flex-1"
474
+ value={field.default}
475
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
476
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
477
+ onChange={(e) => {
478
+ const newFields = [...fields];
479
+ newFields[index].default = e.target.value;
480
+ setFields(newFields);
481
+ }}
482
+ />
483
+ </div>
484
+ </>)}
485
+ {['number'].includes(field.type) && (<>
486
+ {hint('modelcreator.default.hint')}
487
+ <div className="flex flex-1 field-bg">
488
+ <NumberField
489
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
490
+ type="number"
491
+ className="flex-1"
492
+ value={field.default}
493
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
494
+ onChange={(e) => {
495
+ const newFields = [...fields];
496
+ const val = parseInt(e.target.value, 10);
497
+ if (!val)
498
+ newFields[index].default = undefined;
499
+ else
500
+ newFields[index].default = val;
501
+ setFields(newFields);
502
+ }}
503
+ />
504
+ </div>
505
+ </>)}
506
+ {['enum'].includes(field.type) && (<>
507
+ {hint('modelcreator.default.hint')}
508
+ <div className="flex flex-1 field-bg">
509
+ <SelectField
510
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
511
+ value={field.default}
512
+ className="flex-1"
513
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
514
+ items={(field.items || []).map(m => ({label: t(m, m), value: m}))}
515
+ onChange={(e) => {
516
+ const newFields = [...fields];
517
+ newFields[index].default = e.value;
518
+ setFields(newFields);
519
+ }}
520
+ /></div>
521
+ </>)}
522
+ {['code'].includes(field.type) && (<>
523
+ {hint('modelcreator.default.hint')}
524
+ <div className="flex flex-1 field-bg">
525
+ <CodeField
526
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
527
+ value={field.language === 'json' ? JSON.stringify(field.default, 2, null) : field.default}
528
+ onChange={(e) => {
529
+ const newFields = [...fields];
530
+ newFields[index].default = e.value;
531
+ setFields(newFields);
532
+ }}
533
+ />
534
+ </div>
535
+ </>)}
536
+ {['boolean'].includes(field.type) && (<>
537
+ {hint('modelcreator.default.hint')}
538
+ <div className="checkbox-label flex flex-1">
539
+ <CheckboxField
540
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
541
+ checked={field.default}
542
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
543
+ onChange={(e) => {
544
+ const newFields = [...fields];
545
+ newFields[index].default = e;
546
+ setFields(newFields);
547
+ }}
548
+ />
549
+ </div>
550
+ </>)}
551
+ {['color'].includes(field.type) && (<>
552
+ {hint('modelcreator.default.hint')}
553
+ <div className="flex flex-1 field-bg">
554
+ <ColorField
555
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
556
+ value={field.default || null} name={field.name}
557
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
558
+ onChange={(e) => {
559
+ const newFields = [...fields];
560
+ newFields[index].default = e.value;
561
+ setFields(newFields);
562
+ }}
563
+ />
564
+ </div>
565
+ </>)}
566
+ {['date', 'datetime'].includes(field.type) && (<>
567
+ {hint('modelcreator.default.hint')}
568
+
569
+ <div className="flex flex-1 field-bg">
570
+ <label className="flex flex-1">
571
+ <span className={"flex-1"}><Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans></span>
572
+ <input
573
+ value={field.default}
574
+ className="flex-1"
575
+ type={field.type === 'date' ? 'date' : 'datetime-local'}
576
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
577
+ onChange={(e) => {
578
+ const newFields = [...fields];
579
+ newFields[index].default = e.target.value;
580
+ setFields(newFields);
581
+ }}
582
+ />
583
+ </label>
584
+ </div>
585
+ </>)}
586
+ </div>)}
587
+
588
+ {["string_t", "string"].includes(field.itemsType || field.type) && (
589
+ <>
590
+
591
+ <div className={"flex flex-no-wrap field-bg"}>
592
+ {hint('modelcreator.mask.hint')}
593
+ <div className={"flex-1"}>
594
+ <TextField
595
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
596
+ label={<Trans i18nKey={"modelcreator.mask"}>Masque de saisie</Trans>}
597
+ className={"flex-1"}
598
+ value={field.mask}
599
+ placeholder={t('modelcreator.field.mask.ph', "mask_")}
600
+ onChange={(e) => {
601
+ const newFields = [...fields];
602
+ newFields[index].mask = e.target.value;
603
+ setFields(newFields);
604
+ }}
605
+ />
606
+ </div>
607
+ </div>
608
+ <div className={"flex flex-no-wrap field-bg"}>
609
+ {hint('modelcreator.replacement.hint')}
610
+ <div className={"flex-1"}>
611
+ <CodeField
612
+ name="replacement"
613
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
614
+ label={<Trans i18nKey={"modelcreator.replacement"}>Règles de remplacement (JSON)</Trans>}
615
+ language="json"
616
+ value={field.replacement ? JSON.stringify(field.replacement, null, 2) : ''}
617
+ onChange={(e) => {
618
+ const newFields = [...fields];
619
+ newFields[index].replacement = e.value;
620
+ setFields(newFields);
621
+ }}
622
+ />
623
+ </div>
624
+ </div>
625
+ </>
626
+ )}
627
+
628
+ {(field.itemsType || field.type) === 'number' && (
629
+ <>
630
+ <div
631
+ className="flex flex-no-wrap mg-item">{hint('modelcreator.min.hint')}
632
+ <div className="flex field-bg flex-1">
633
+ <NumberField
634
+ label={<Trans
635
+ className={"flex-1"}
636
+ i18nKey={"modelcreator.min"}>Valeur minimale :</Trans>}
637
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
638
+ value={(field.min + '').replace('.', ',')}
639
+ onChange={(e) => {
640
+ const newFields = [...fields];
641
+ newFields[index].min = parseInt(e.target.value, 10);
642
+ setFields(newFields);
643
+ }}
644
+ />
645
+ </div>
646
+ </div>
647
+ <div
648
+ className="flex flex-no-wrap mg-item">{hint('modelcreator.max.hint')}
649
+ <div className="flex field-bg flex-1">
650
+ <NumberField
651
+ label={<Trans
652
+ className={"flex-1"}
653
+ i18nKey={"modelcreator.max"}>Valeur maximale :</Trans>}
654
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
655
+ value={(field.max + '').replace('.', ',')}
656
+ onChange={(e) => {
657
+ const newFields = [...fields];
658
+ newFields[index].max = parseInt(e.target.value, 10);
659
+ setFields(newFields);
660
+ }}
661
+ />
662
+ </div>
663
+ </div>
664
+ </>
665
+ )}
666
+
667
+ {(['datetime', 'date'].includes(field.itemsType || field.type)) && (
668
+ <>
669
+ <div
670
+ className="flex flex-no-wrap mg-item">
671
+ {hint('modelcreator.min.hint')}
672
+ <div className="flex field-bg flex-1">
673
+ <label className={"flex-1"}><Trans i18nKey={"modelcreator.min"}>Valeur minimale :</Trans></label>
674
+ <input
675
+ className={"flex-1"}
676
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
677
+ type={field.type === 'datetime' ? 'datetime-local' : field.type}
678
+ value={field.type === "number" ? (field.min + '').replace('.', ',') : field.min}
679
+ onChange={(e) => {
680
+ const newFields = [...fields];
681
+ if (['datetime', 'date'].includes(field.itemsType || field.type)) {
682
+ newFields[index].min = e.target.value;
683
+ } else {
684
+ newFields[index].min = parseInt(e.target.value, 10);
685
+ }
686
+ setFields(newFields);
687
+ }}
688
+ />
689
+ </div>
690
+ </div>
691
+
692
+ <div className="flex flex-no-wrap mg-item">
693
+ {hint('modelcreator.max.hint')}
694
+ <div className="flex field-bg flex-1">
695
+ <label className={"flex-1"}><Trans i18nKey={"modelcreator.max"}>Valeur maximale :</Trans></label>
696
+ <input
697
+ className={"flex-1"}
698
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
699
+ type={field.type === 'datetime' ? 'datetime-local' : field.type}
700
+ value={field.type === "number" ? (field.max + '').replace('.', ',') : field.max}
701
+ onChange={(e) => {
702
+ const newFields = [...fields];
703
+ if (['datetime', 'date'].includes(field.itemsType || field.type)) {
704
+ newFields[index].max = e.target.value;
705
+ } else {
706
+ newFields[index].max = parseInt(e.target.value, 10);
707
+ }
708
+ setFields(newFields);
709
+ }}
710
+ /></div>
711
+ </div>
712
+ </>
713
+ )}
714
+ <div className="flex flex-no-wrap mg-item">
715
+ {hint('modelcreator.condition.hint')}
716
+
717
+ <CheckboxField
718
+ label={<Trans
719
+ i18nKey={"modelcreator.condition"}>Condition</Trans>}
720
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
721
+ checked={field.condition !== undefined}
722
+ onChange={(e) => {
723
+ const newFields = [...fields];
724
+ if (e) {
725
+ newFields[index].condition = null;
726
+ } else {
727
+ delete newFields[index].condition;
728
+ }
729
+ setFields(newFields);
730
+ }}
731
+ />
732
+ </div>
733
+
734
+
735
+ {(field.itemsType || field.type)=== 'number'&&(
736
+ <div className="flex flex-no-wrap">
737
+ {hint('modelcreator.gauge.hint')}
738
+ <div className="checkbox-label flex flex-1">
739
+ <CheckboxField
740
+ label={<Trans i18nKey={"modelcreator.gauge"}>Jauge ?</Trans>}
741
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
742
+ checked={field.gauge}
743
+ onChange={(e) => {
744
+ const newFields = [...fields];
745
+ newFields[index].gauge = e;
746
+ if (!e) {
747
+ delete newFields[index].percent;
748
+ }
749
+ setFields(newFields);
750
+ }}
751
+ />
752
+ </div>
753
+ {field.gauge && (
754
+ <div className="flex flex-no-wrap">
755
+ {hint('modelcreator.percent.hint')}
756
+ <div className="checkbox-label flex flex-1">
757
+ <CheckboxField
758
+ label={<Trans i18nKey={"modelcreator.percent"}>Mode pourcentage ?</Trans>}
759
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
760
+ checked={field.percent}
761
+ onChange={(e) => {
762
+ const newFields = [...fields];
763
+ newFields[index].percent = e;
764
+ setFields(newFields);
765
+ }}
766
+ />
767
+ </div>
768
+ </div>)}
769
+ </div>
770
+ )}
771
+
772
+ {field.condition !== undefined && (
773
+ <div className={"condition-details flex flex-start"}>
774
+
775
+ <p>{t('modelcreator.condition.hint')}</p>
776
+ <ConditionBuilder key="test"
777
+ onChange={(newCondition) => {
778
+ const newFields = [...fields];
779
+ newFields[index].condition = newCondition || {};
780
+ setFields(newFields);
781
+ }} initialValue={field.condition || {}}
782
+ model={model} models={models}/>
783
+
784
+ </div>
785
+ )}
786
+ {(field.itemsType || field.type) === 'code' && (<>
787
+ <div className="flex">
788
+ {hint('modelcreator.language.hint')}
789
+ <label className="checkbox-label flex flex-1">
790
+ <Trans i18nKey={"modelcreator.language"}>Language :</Trans>
791
+ <TextField placeholder={"json, javascript..."}
792
+ value={field.language}
793
+ onChange={e => {
794
+ const newFields = [...fields];
795
+ newFields[index].language = e.target.value;
796
+ setFields(newFields);
797
+ }}/>
798
+ </label>
799
+ </div>
800
+ {field.language === 'json' && (<div className="flex flex-no-wrap">
801
+ {hint('modelcreator.conditionBuilder.hint')}
802
+ <label className="checkbox-label flex flex-1">
803
+ <Trans i18nKey={"modelcreator.conditionBuilder"}>Condition Builder
804
+ :</Trans>
805
+ <input
806
+ type="checkbox"
807
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
808
+ checked={field.conditionBuilder}
809
+ onChange={(e) => {
810
+ const newFields = [...fields];
811
+ newFields[index].conditionBuilder = e.target.checked ? true : undefined;
812
+ setFields(newFields);
813
+ }}
814
+ />
815
+ </label>
816
+ </div>)}
817
+
818
+ </>)}
819
+
820
+ {me.userPlan === 'premium' && (
821
+ <div className="flex flex-no-wrap"
822
+ title={t('index_field_info', 'Améliore les performances sur la recherche mais demande un espace de stockage plus élevé et diminue les performances sur l\'insertion de données.')}>
823
+ {hint('modelcreator.index.hint')}
824
+ <label className="checkbox-label flex flex-1">
825
+ <Trans i18nKey={"indexed_field"}>Champ indexé</Trans> :
826
+ <input
827
+ type="checkbox"
828
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
829
+ checked={!!field.index}
830
+ onChange={(e) => {
831
+ const newFields = [...fields];
832
+ newFields[index].index = e.target.checked;
833
+ setFields(newFields);
834
+ }}
835
+ />
836
+ </label>
837
+ </div>)}
838
+
839
+
840
+ {mainFieldsTypes.includes(field.itemsType || field.type) && (<div
841
+ className="flex flex-no-wrap mg-item"
842
+ title={t("modelcreator.field.asMain", "Une information principale sera affichée dans le titre de l'enregistrement")}>
843
+ {hint('modelcreator.asMain.hint')}
844
+ <div className="flex flex-1">
845
+
846
+ <CheckboxField
847
+ label={<Trans i18nKey={"modelcreator.asMain"}>Information principale :</Trans>}
848
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
849
+ checked={field.asMain}
850
+ onChange={(e) => {
851
+ const newFields = [...fields];
852
+ newFields[index].asMain = e;
853
+ setFields(newFields);
854
+ }}
855
+ />
856
+ </div>
857
+ </div>)}
858
+
859
+ <div className="flex flex-row flex-stretch">
860
+ <div className="flex">
861
+ {hint('modelcreator.description.hint')}
862
+ <label className={"flex-1 field-bg"} htmlFor={`textarea-model-description${field.name}`}><Trans
863
+ i18nKey={"modelcreator.fieldDesc"}>Description du champ :</Trans></label>
864
+ </div>
865
+ <div className="flex flex-1">
866
+ <textarea id={`textarea-model-description${field.name}`}
867
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
868
+ onChange={(e) => {
869
+ const newFields = [...fields];
870
+ newFields[index].hint = e.target.value;
871
+ setFields(newFields);
872
+ }}
873
+ defaultValue={field.hint || ''}/>
874
+ </div>
875
+ </div>
876
+
877
+
878
+ <label className="flex mg-item ">
879
+ {hint('modelcreator.color.hint')}
880
+ <Trans i18nKey={"field.color"}>Color :</Trans>
881
+ <ColorField
882
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
883
+ value={field.color || '#FFFFFF'}
884
+ onChange={(e) => {
885
+ const newFields = [...fields];
886
+ newFields[index].color = e.value;
887
+ setFields(newFields);
888
+ }}
889
+ />
890
+ </label>
891
+
892
+ <div className="flex flex-no-wrap">
893
+ {hint('modelcreator.hiddenable.hint')}
894
+ <div className="checkbox-label flex flex-1">
895
+
896
+ <CheckboxField
897
+ label={<Trans i18nKey={"modelcreator.hiddenable"}>Dissimulable :</Trans>}
898
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
899
+ checked={field.hiddenable}
900
+ onChange={(e) => {
901
+ const newFields = [...fields];
902
+ newFields[index].hiddenable = e;
903
+ setFields(newFields);
904
+ }}
905
+ />
906
+ </div>
907
+ </div>
908
+
909
+ <div className={"flex flex-no-wrap"}>
910
+ {hint('modelcreator.anonymized.hint')}
911
+ <CheckboxField
912
+ label={<Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>}
913
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
914
+ checked={field.anonymized}
915
+ onChange={(e) => {
916
+ const newFields = [...fields];
917
+ newFields[index].anonymized = e;
918
+ setFields(newFields);
919
+ }}
920
+ />
921
+ </div>
922
+
923
+
924
+ {false && (<label className="checkbox-label flex">
925
+ <Trans i18nKey={"modelcreator.locked"}>Locked :</Trans>
926
+ <input
927
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
928
+ type={"checkbox"}
929
+ checked={field.locked}
930
+ onChange={(e) => {
931
+ const newFields = [...fields];
932
+ newFields[index].locked = e.target.checked;
933
+ setFields(newFields);
934
+ }}
935
+ />
936
+ </label>)}
937
+ </>
938
+ </details>
939
+
940
+ <div className="flex actions">
941
+ <Button type="button" onClick={() => handleRemoveField(index)}>
942
+ <FaTrash/>
943
+ </Button>
944
+ </div>
945
+ </div>
946
+
947
+ </div>
948
+ );
949
+ }
950
+
951
951
  export default ModelCreatorField;