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