data-primals-engine 1.0.11 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. package/vitest.config.js +2 -2
@@ -0,0 +1,810 @@
1
+ import React, {useState} from "react";
2
+ import {useModelContext} from "./contexts/ModelContext.jsx";
3
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
4
+ import {isLocalUser} from "data-primals-engine/data";
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 "data-primals-engine/constants";
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
+ const [focusedField, setFocusedField] = useState({});
47
+ return (
48
+ <div className="field-edit">
49
+
50
+ <div className="actions right">
51
+ {index < fields.length - 1 && (
52
+ <button title={"Déplacer vers le bas"} type="button"
53
+ onClick={() => handleDown(index)}>
54
+ <FaArrowDown/>
55
+ </button>)}
56
+ {index > 0 && (
57
+ <button title="Déplacer vers le haut" type="button"
58
+ onClick={() => handleUp(index)}>
59
+ <FaArrowUp/>
60
+ </button>)}
61
+ </div>
62
+ <div className="flex flex-row flex-stretch">
63
+
64
+ <div className="flex fieldName">{hint('modelcreator.name.hint')}
65
+ <div className="flex flex-no-gap flex-no-wrap flex-1">
66
+ <TextField
67
+ label={t('modelcreator.fieldName')}
68
+ className={!modelLocked && !field._isNewField && "input-fit"}
69
+ disabled={modelLocked || (!!model && !field._isNewField)}
70
+ value={field.name}
71
+ onChange={(e) => {
72
+ const newFields = [...fields];
73
+ newFields[index].name = e.target.value;
74
+ setFields(newFields);
75
+ }}
76
+ help={focusedField?.name === field.name && t('modelcreator.name.hint')}
77
+ onFocus={() => setFocusedField(field)}
78
+ onBlur={() => setFocusedField(null)}
79
+ required
80
+ />
81
+ {!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
82
+ <Button type={"button"} className={"btn-form btn-last"}
83
+ onClick={() => handleRenameField(index, field.name)}><FaEdit/></Button>)}
84
+
85
+ </div>
86
+
87
+ </div>
88
+
89
+ <div className="flex">
90
+ {hint('modelcreator.type.hint')}
91
+ <div className="flex flex-1 flex-stretch flex-no-gap">
92
+
93
+ <SelectField
94
+ label={t('modelcreator.type', 'Type de champ')}
95
+ className={"flex-1"}
96
+ value={field.type}
97
+ onChange={(e) => {
98
+ const newFields = [...fields];
99
+
100
+ const nf = newFields[index];
101
+ nf.type = e.value;
102
+
103
+ setFields(newFields);
104
+ gtag('event', 'model set field ' + e.value);
105
+ }}
106
+ items={[
107
+ {label: t("field.string"), value: "string"},
108
+ {label: t("field.string_t"), value: "string_t"},
109
+ {label: t("field.richtext"), value: "richtext"},
110
+ {label: t("field.richtext_t"), value: "richtext_t"},
111
+ {label: t("field.number"), value: "number"},
112
+ {label: t("field.boolean"), value: "boolean"},
113
+ {label: t("field.enum"), value: "enum"},
114
+ {label: t("field.date"), value: "date"},
115
+ {label: t("field.datetime"), value: "datetime"},
116
+ {label: t("field.password"), value: "password"},
117
+ {label: t("field.email"), value: "email"},
118
+ {label: t("field.phone"), value: "phone"},
119
+ {label: t("field.url"), value: "url"},
120
+ {label: t("field.color"), value: "color"},
121
+ {label: t("field.array"), value: "array"},
122
+ {label: t("field.relation"), value: "relation"},
123
+ {label: t("field.file"), value: "file"},
124
+ {label: t("field.code"), value: "code"},
125
+ {label: t("field.calculated", "calculated data"), value: "calculated"},
126
+ {label: t("field.cronSchedule", "task schedule"), value: "cronSchedule"},
127
+ ]}
128
+ />
129
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
130
+
131
+ {(field.type === 'relation' || field.type === 'array') && <div className={"div"}>
132
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
133
+ {field.type === 'relation' && (
134
+ <>
135
+ <RelationModelSelector
136
+ className={"flex-1 mg-v-1"}
137
+ relation={field.relation}
138
+ onChange={(relation) => {
139
+ const newFields = [...fields];
140
+ newFields[index].relation = relation;
141
+ setFields(newFields);
142
+
143
+ }}
144
+ />
145
+ </>
146
+ )}
147
+ {/* Afficher le sélecteur de modèle lié si le type est "relation" */}
148
+ {field.type === 'array' && (
149
+ <>
150
+ <select
151
+ className={"flex-1 mg-v-1"}
152
+ value={field.itemsType}
153
+ onChange={(e) => {
154
+ const newFields = [...fields];
155
+ newFields[index].itemsType = e.target.value;
156
+ setFields(newFields);
157
+
158
+ gtag('event', 'model set itemsType ' + e.target.value);
159
+ }}
160
+ >
161
+ <option value="string"><Trans i18nKey={"field.string"}>Texte</Trans></option>
162
+ <option value="string_t"><Trans i18nKey={"field.string_t"}>Texte traduit</Trans>
163
+ </option>
164
+ <option value="richtext"><Trans i18nKey={"field.richtext"}>Texte enrichi</Trans>
165
+ </option>
166
+ <option value="richtext_t"><Trans i18nKey={"field.richtext_t"}>Texte enrichi
167
+ traduit</Trans>
168
+ </option>
169
+ <option value="number"><Trans i18nKey={"field.number"}>Nombre</Trans></option>
170
+ <option value="boolean"><Trans i18nKey={"field.boolean"}>Booléen</Trans>
171
+ </option>
172
+ <option value="date"><Trans i18nKey={"field.date"}>Date</Trans></option>
173
+ <option value="datetime"><Trans i18nKey={"field.datetime"}>Date / Heure</Trans>
174
+ </option>
175
+ <option value="file"><Trans i18nKey={"field.file"}>Fichier</Trans>
176
+ </option>
177
+ <option value="email"><Trans i18nKey={"field.email"}>Email</Trans></option>
178
+ <option value="phone"><Trans i18nKey={"field.phone"}>Téléphone</Trans></option>
179
+ <option value="url"><Trans i18nKey={"field.url"}>URL</Trans></option>
180
+ <option value="color"><Trans i18nKey={"field.color"}>Couleur</Trans></option>
181
+ <option value="code"><Trans i18nKey={"field.code"}>Code</Trans></option>
182
+ <option value="calculated"><Trans i18nKey={"field.calculated"}>Calculated
183
+ data</Trans></option>
184
+ <option value="cronSchedule"><Trans i18nKey={"field.cronSchedule"}>Task
185
+ schedule</Trans></option>
186
+ {/* Ajoutez d'autres types de champs ici */}
187
+ </select>
188
+ </>
189
+ )}
190
+ </div>}
191
+ </div>
192
+ </div>
193
+
194
+ <div key={index + field.name}>
195
+ {field.type === 'calculated' && (
196
+ <>
197
+ <CalculationBuilder
198
+ // Clé : Assurez-vous que modelName est utilisé si model n'existe pas (nouveau modèle)
199
+ key={`${model?.name || modelName}-${field.name}-calc`}
200
+ // Prop AJOUTÉE : Nom du modèle en cours de création/modification
201
+ currentModelName={model?.name}
202
+ // Champs disponibles du modèle actuel (on filtre le champ calculé lui-même)
203
+ availableFields={fields.filter(f => f.name !== field.name)}
204
+ // Tous les modèles (pour les relations)
205
+ models={models} // Vient de useModelContext(), semble correct
206
+ initialSteps={field.calculation?.steps || []}
207
+ onCalculationChange={(calc) => {
208
+ const newFields = [...fields];
209
+ console.log({calc});
210
+ newFields[index].calculation = calc; // 'index' est l'index du champ calculé dans 'fields'
211
+ setFields(newFields);
212
+ }}
213
+ />
214
+ </>
215
+ )}
216
+ {field.type === "relation" && (
217
+
218
+ <div className="flex flex-no-wrap">
219
+ {hint('modelcreator.relationFilter.hint')}
220
+ <label className="checkbox-label flex flex-1"><Trans
221
+ i18nKey={"modelcreator.relationFilter"}>Filtre</Trans> :
222
+ <input
223
+ type="checkbox"
224
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
225
+ checked={field.relationFilter !== undefined}
226
+ onChange={(e) => {
227
+ const newFields = [...fields];
228
+ if (e.target.checked) {
229
+ newFields[index].relationFilter = null;
230
+ } else {
231
+ delete newFields[index].relationFilter;
232
+ }
233
+ setFields(newFields);
234
+ }}
235
+ />
236
+ </label>
237
+ </div>)}
238
+
239
+
240
+ {field.relationFilter !== undefined && Array.isArray(models) && (
241
+ <div className={"condition-details flex flex-start"}>
242
+ <ConditionBuilder
243
+ modelFields={models.find(f => f.name === field.relation)?.fields || []}
244
+ model={field.relation}
245
+ models={models}
246
+ selectableModels={false}
247
+ initialValue={field.relationFilter}
248
+ onChange={(newCondition) => {
249
+ const newFields = [...fields];
250
+ newFields[index].relationFilter = newCondition || {};
251
+ setFields(newFields);
252
+ }}
253
+ />
254
+ </div>
255
+ )}
256
+
257
+ {(field.itemsType || field.type) === 'file' && (
258
+ <>
259
+ <div className={"flex"}>
260
+ {hint('modelcreator.mimeTypes.hint')}
261
+ <label className={"flex"}>
262
+ <Trans i18nKey={"modelcreator.mimeTypes"}>Types de fichiers
263
+ acceptés
264
+ :</Trans>
265
+ <input
266
+ type="text"
267
+ value={field.mimeTypes}
268
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
269
+ placeholder={t('modelcreator.field.mimeTypes.ph', "image/png, image/jpeg...")}
270
+ onChange={(e) => {
271
+ const newFields = [...fields];
272
+ newFields[index].mimeTypes = e.target.value.split(',').map(m => m.trim());
273
+ setFields(newFields);
274
+ }}
275
+ />
276
+ </label></div>
277
+ </>
278
+ )}
279
+
280
+ {['string', 'string_t'].includes(field.itemsType || field.type) && (
281
+ <>
282
+ <div className="flex flex-no-wrap">
283
+ {hint('modelcreator.multiline.hint')}
284
+ <div className={"checkbox-label flex flex-1"}>
285
+ <CheckboxField
286
+ label={<Trans i18nKey={"modelcreator.multiline"}>Multi-lignes :</Trans>}
287
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
288
+ checked={field.multiline}
289
+ onChange={(e) => {
290
+ const newFields = [...fields];
291
+ newFields[index].multiline = e.target.checked;
292
+ setFields(newFields);
293
+ }}
294
+ help={field.multiline && t('modelcreator.multiline.hint')}
295
+ />
296
+ </div>
297
+ </div>
298
+ </>
299
+ )}
300
+
301
+ {['string', 'string_t', 'richtext', 'richtext_t', 'email', 'phone', 'url', 'password', 'code'].includes(field.itemsType || field.type) && (
302
+ <>
303
+ <div className={"flex flex-no-wrap"}>
304
+ {hint('modelcreator.maxlength.hint')}
305
+ <label className={"flex flex-1"}>
306
+ <Trans i18nKey={"modelcreator.maxlength"}>Longueur
307
+ maximale :</Trans>
308
+ <NumberField
309
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
310
+ step={1}
311
+ min={0}
312
+ className={"flex-1"}
313
+ value={field.maxlength}
314
+ onChange={(e) => {
315
+ const newFields = [...fields];
316
+ const val = parseInt(e, 10);
317
+ if (!val)
318
+ newFields[index].maxlength = undefined;
319
+ else
320
+ newFields[index].maxlength = val;
321
+ setFields(newFields);
322
+ }}
323
+ />
324
+ </label>
325
+ </div>
326
+ </>
327
+ )}
328
+ {(field.itemsType || field.type) === 'number' && (
329
+ <>
330
+ <div className={"flex flex-no-wrap"}>
331
+ {hint('modelcreator.precision.hint')}
332
+ <label className="flex">
333
+ <span><Trans
334
+ i18nKey={"modelcreator.precision"}>Précision :</Trans></span>
335
+ <div className="flex-1"><NumberField
336
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
337
+ value={field.step || 1}
338
+ step={0.1}
339
+ placeholder={t('modelcreator.field.step.ph', "Précision (1, 0.1...)")}
340
+ onChange={(e) => {
341
+ const newFields = [...fields];
342
+ newFields[index].step = e.target.value;//.replace('.', ','));
343
+ setFields(newFields);
344
+ }}
345
+ />
346
+ </div>
347
+ </label>
348
+ </div>
349
+ <div className="flex flex-no-wrap">
350
+ {hint('modelcreator.unit.hint')}
351
+ <label className="flex">
352
+ <span><Trans
353
+ i18nKey={"modelcreator.unit"}>Unité :</Trans></span>
354
+ <div className="flex-1"><input
355
+ type="string"
356
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
357
+ value={field.unit}
358
+ placeholder={t('modelcreator.field.unit.ph', "€, cm, kg...")}
359
+ onChange={(e) => {
360
+ const newFields = [...fields];
361
+ newFields[index].unit = e.target.value;
362
+ setFields(newFields);
363
+ }}
364
+ /></div>
365
+
366
+ </label>
367
+ </div>
368
+ </>
369
+ )}
370
+ {(field.itemsType || field.type) === "relation" && (
371
+ <div className="flex flex-no-wrap">
372
+ {hint('modelcreator.multiple.hint')}
373
+ <div className="checkbox-label flex flex-1">
374
+ <CheckboxField
375
+ label={<Trans i18nKey={"modelcreator.multiple"}>Multiple :</Trans>}
376
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
377
+ checked={field.multiple}
378
+ onChange={(e) => {
379
+ const newFields = [...fields];
380
+ newFields[index].multiple = e.target.checked;
381
+ setFields(newFields);
382
+ }}
383
+ /></div>
384
+ </div>)}
385
+
386
+ {(field.itemsType || field.type) === "enum" && (
387
+ <div className="values">
388
+ <p><Trans i18nKey={"modelcreator.enumValues"}>Valeurs possibles
389
+ :</Trans></p>
390
+ <Draggable items={field.items} renderItem={(v, i) => {
391
+ return <div className="flex flex-no-wrap">
392
+ <ValueField v={v}
393
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
394
+ setValue={(value) => {
395
+ const newFields = [...fields];
396
+ newFields[index].items[i] = value;
397
+ setFields(newFields);
398
+ }}/>
399
+ <button type="button"
400
+ onClick={() => handleRemoveValue(index, i)}>
401
+ <FaMinus/>
402
+ </button>
403
+ </div>
404
+ }} onChange={(arr) => {
405
+ const newFields = [...fields];
406
+ newFields[index].items = arr;
407
+ setFields(newFields);
408
+ }}/>
409
+ <button type="button" onClick={() => handleAddValue(index)}><FaPlus/>
410
+ </button>
411
+ </div>
412
+ )}
413
+
414
+ <details className="advanced-options-details">
415
+ <summary>
416
+ <Trans i18nKey={"modelcreator.advancedOptions"}>Advanced options</Trans>
417
+ </summary>
418
+ <>
419
+
420
+ <div className="flex flex-no-wrap">
421
+ {hint('modelcreator.required.hint')}
422
+ <div className="checkbox-label flex flex-1">
423
+ <CheckboxField
424
+ label={<Trans i18nKey={"modelcreator.required"}>Requis :</Trans>}
425
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
426
+ checked={field.required}
427
+ onChange={(e) => {
428
+ const newFields = [...fields];
429
+ newFields[index].required = e.target.checked;
430
+ setFields(newFields);
431
+ }}
432
+ help={field.required && t('modelcreator.required.hint')}
433
+ />
434
+ </div>
435
+ </div>
436
+
437
+ <div className="flex flex-no-wrap">
438
+ {hint('modelcreator.unique.hint')}
439
+ <div className="checkbox-label flex flex-1">
440
+
441
+ <CheckboxField
442
+ label={<Trans i18nKey={"modelcreator.unique"}>Unique :</Trans>}
443
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
444
+ checked={field.unique}
445
+ onChange={(e) => {
446
+ const newFields = [...fields];
447
+ newFields[index].unique = e.target.checked;
448
+ setFields(newFields);
449
+ }}
450
+ help={field.unique && t('modelcreator.unique.hint')}
451
+ />
452
+ </div>
453
+ </div>
454
+
455
+ {!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
456
+ className="flex flex-no-wrap">
457
+ {hint('modelcreator.default.hint')}
458
+
459
+ {['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
460
+ <label className="flex flex-1">
461
+ <Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>
462
+ <input
463
+ type="text"
464
+ className="flex-1"
465
+ value={field.default}
466
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
467
+ onChange={(e) => {
468
+ const newFields = [...fields];
469
+ newFields[index].default = e.target.value;
470
+ setFields(newFields);
471
+ }}
472
+ />
473
+ </label>
474
+ </>)}
475
+ {['number'].includes(field.type) && (<>
476
+ <NumberField
477
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
478
+ type="number"
479
+ className="flex-1"
480
+ value={field.default}
481
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
482
+ onChange={(e) => {
483
+ const newFields = [...fields];
484
+ const val = parseInt(e.target.value, 10);
485
+ if (!val)
486
+ newFields[index].default = undefined;
487
+ else
488
+ newFields[index].default = val;
489
+ setFields(newFields);
490
+ }}
491
+ />
492
+ </>)}
493
+ {['enum'].includes(field.type) && (<>
494
+ <SelectField
495
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
496
+ value={field.default}
497
+ className="flex-1"
498
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
499
+ items={(field.items || []).map(m => ({label: t(m, m), value: m}))}
500
+ onChange={(e) => {
501
+ const newFields = [...fields];
502
+ newFields[index].default = e.value;
503
+ setFields(newFields);
504
+ }}
505
+ />
506
+ </>)}
507
+ {['code'].includes(field.type) && (<>
508
+ <CodeField
509
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
510
+ value={field.language === 'json' ? JSON.stringify(field.default, 2, null) : field.default}
511
+ onChange={(e) => {
512
+ const newFields = [...fields];
513
+ newFields[index].default = e.value;
514
+ setFields(newFields);
515
+ }}
516
+ />
517
+ </>)}
518
+ {['boolean'].includes(field.type) && (<>
519
+ <div className="checkbox-label flex flex-1">
520
+ <CheckboxField
521
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
522
+ checked={field.default}
523
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
524
+ onChange={(e) => {
525
+ const newFields = [...fields];
526
+ newFields[index].default = e.target.checked;
527
+ setFields(newFields);
528
+ }}
529
+ />
530
+ </div>
531
+ </>)}
532
+ {['color'].includes(field.type) && (<>
533
+ <ColorField
534
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
535
+ value={field.default || null} name={field.name}
536
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
537
+ onChange={(e) => {
538
+ const newFields = [...fields];
539
+
540
+ newFields[index].default = e.value;
541
+ setFields(newFields);
542
+ }}
543
+ />
544
+ </>)}
545
+ {['date', 'datetime'].includes(field.type) && (<>
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">
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">
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">
605
+ {hint('modelcreator.condition.hint')}
606
+
607
+ <label className="checkbox-label flex flex-1"><Trans
608
+ i18nKey={"modelcreator.condition"}>Condition</Trans> :
609
+ <input
610
+ type="checkbox"
611
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
612
+ checked={field.condition !== undefined}
613
+ onChange={(e) => {
614
+ const newFields = [...fields];
615
+ if (e.target.checked) {
616
+ newFields[index].condition = null;
617
+ } else {
618
+ delete newFields[index].condition;
619
+ }
620
+ setFields(newFields);
621
+ }}
622
+ />
623
+ </label>
624
+ </div>
625
+ {field.condition !== undefined && (
626
+ <div className={"condition-details flex flex-start"}>
627
+
628
+ <p>{t('modelcreator.condition.hint')}</p>
629
+ <ConditionBuilder key="test"
630
+ onChange={(newCondition) => {
631
+ const newFields = [...fields];
632
+ newFields[index].condition = newCondition || {};
633
+ setFields(newFields);
634
+ }} initialValue={field.condition || {}}
635
+ model={model} models={models}/>
636
+
637
+ </div>
638
+ )}
639
+ {field.type === 'code' && (<>
640
+ <div className="flex">
641
+ {hint('modelcreator.language.hint')}
642
+ <label className="checkbox-label flex flex-1">
643
+ <Trans i18nKey={"modelcreator.language"}>Language :</Trans>
644
+ <TextField placeholder={"json, javascript..."}
645
+ value={field.language}
646
+ onChange={e => {
647
+ const newFields = [...fields];
648
+ newFields[index].language = e.target.value;
649
+ setFields(newFields);
650
+ }}/>
651
+ </label>
652
+ </div>
653
+ {field.language === 'json' && (<div className="flex flex-no-wrap">
654
+ {hint('modelcreator.conditionBuilder.hint')}
655
+ <label className="checkbox-label flex flex-1">
656
+ <Trans i18nKey={"modelcreator.conditionBuilder"}>Condition Builder
657
+ :</Trans>
658
+ <input
659
+ type="checkbox"
660
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
661
+ checked={field.conditionBuilder}
662
+ onChange={(e) => {
663
+ const newFields = [...fields];
664
+ newFields[index].conditionBuilder = e.target.checked ? true : undefined;
665
+ setFields(newFields);
666
+ }}
667
+ />
668
+ </label>
669
+ </div>)}
670
+
671
+ </>)}
672
+
673
+ {me.userPlan === 'premium' && (
674
+ <div className="flex flex-no-wrap"
675
+ 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.')}>
676
+ {hint('modelcreator.index.hint')}
677
+ <label className="checkbox-label flex flex-1">
678
+ <Trans i18nKey={"indexed_field"}>Champ indexé</Trans> :
679
+ <input
680
+ type="checkbox"
681
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
682
+ checked={!!field.index}
683
+ onChange={(e) => {
684
+ const newFields = [...fields];
685
+ newFields[index].index = e.target.checked;
686
+ setFields(newFields);
687
+ }}
688
+ />
689
+ </label>
690
+ </div>)}
691
+
692
+
693
+ {mainFieldsTypes.includes(field.itemsType || field.type) && (<div
694
+ className="flex flex-no-wrap"
695
+ title={t("modelcreator.field.asMain", "Une information principale sera affichée dans le titre de l'enregistrement")}>
696
+ {hint('modelcreator.asMain.hint')}
697
+ <label className="checkbox-label flex flex-1">
698
+
699
+ <CheckboxField
700
+ label={<Trans i18nKey={"modelcreator.asMain"}>Information principale :</Trans>}
701
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
702
+ checked={field.asMain}
703
+ onChange={(e) => {
704
+ const newFields = [...fields];
705
+ newFields[index].asMain = e.target.checked;
706
+ setFields(newFields);
707
+ }}
708
+ help={field.asMain && t('modelcreator.asMain.hint')}
709
+ />
710
+ </label>
711
+ </div>)}
712
+
713
+ <div className="flex flex-row flex-stretch">
714
+ <div className="flex">
715
+ {hint('modelcreator.description.hint')}
716
+ <label htmlFor={`textarea-model-description${field.name}`}><Trans
717
+ i18nKey={"modelcreator.fieldDesc"}>Description du champ :</Trans></label>
718
+ </div>
719
+ <div className="flex flex-1">
720
+ <textarea id={`textarea-model-description${field.name}`}
721
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
722
+ onChange={(e) => {
723
+ const newFields = [...fields];
724
+ newFields[index].hint = e.target.value;
725
+ setFields(newFields);
726
+ }}
727
+ defaultValue={field.hint || ''}/>
728
+ </div>
729
+ </div>
730
+
731
+
732
+ <label className="flex">
733
+ {hint('modelcreator.color.hint')}
734
+ <Trans i18nKey={"field.color"}>Color :</Trans>
735
+ <ColorField
736
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
737
+ value={field.color || '#FFFFFF'}
738
+ onChange={(e) => {
739
+ const newFields = [...fields];
740
+ newFields[index].color = e.value;
741
+ setFields(newFields);
742
+ }}
743
+ />
744
+ </label>
745
+
746
+ <div className="flex flex-no-wrap">
747
+ {hint('modelcreator.hiddenable.hint')}
748
+ <div className="checkbox-label flex flex-1">
749
+
750
+ <CheckboxField
751
+ label={<Trans i18nKey={"modelcreator.hiddenable"}>Dissimulable :</Trans>}
752
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
753
+ checked={field.hiddenable}
754
+ onChange={(e) => {
755
+ const newFields = [...fields];
756
+ newFields[index].hiddenable = e.target.checked;
757
+ setFields(newFields);
758
+ }}
759
+ help={field.unique && t('modelcreator.hiddenable.hint')}
760
+ />
761
+ </div>
762
+ </div>
763
+
764
+ <div className={"flex flex-no-wrap"}>
765
+ {hint('modelcreator.anonymized.hint')}
766
+ <label className={"checkbox-label flex flex-1"}>
767
+ <Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>
768
+ <input
769
+ type="checkbox"
770
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
771
+ checked={field.anonymized}
772
+ onChange={(e) => {
773
+ const newFields = [...fields];
774
+ newFields[index].anonymized = e.target.checked;
775
+ setFields(newFields);
776
+ }}
777
+ />
778
+ </label>
779
+ </div>
780
+
781
+
782
+ {false && (<label className="checkbox-label flex">
783
+ <Trans i18nKey={"modelcreator.locked"}>Locked :</Trans>
784
+ <input
785
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
786
+ type={"checkbox"}
787
+ checked={field.locked}
788
+ onChange={(e) => {
789
+ const newFields = [...fields];
790
+ newFields[index].locked = e.target.checked;
791
+ setFields(newFields);
792
+ }}
793
+ />
794
+ </label>)}
795
+ </>
796
+ </details>
797
+
798
+ <div className="flex actions">
799
+ <Button type="button" onClick={() => handleRemoveField(index)}>
800
+ <FaTrash/>
801
+ </Button>
802
+ </div>
803
+ </div>
804
+
805
+ </div>
806
+ </div>
807
+ );
808
+ }
809
+
810
+ export default ModelCreatorField;