data-primals-engine 1.4.3 → 1.5.1

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 (77) hide show
  1. package/README.md +913 -867
  2. package/client/package-lock.json +49 -0
  3. package/client/package.json +1 -0
  4. package/client/src/AddWidgetTypeModal.jsx +47 -43
  5. package/client/src/App.jsx +3 -7
  6. package/client/src/App.scss +25 -3
  7. package/client/src/AssistantChat.jsx +363 -323
  8. package/client/src/AssistantChat.scss +30 -12
  9. package/client/src/Dashboard.jsx +480 -396
  10. package/client/src/Dashboard.scss +1 -1
  11. package/client/src/DashboardHtmlViewItem.jsx +147 -0
  12. package/client/src/DashboardView.jsx +104 -19
  13. package/client/src/DataEditor.jsx +12 -5
  14. package/client/src/DataLayout.jsx +805 -762
  15. package/client/src/DataLayout.scss +14 -0
  16. package/client/src/DataTable.jsx +63 -77
  17. package/client/src/Dialog.scss +1 -1
  18. package/client/src/Field.jsx +591 -322
  19. package/client/src/FlexDataRenderer.jsx +2 -0
  20. package/client/src/FlexTreeUtils.js +1 -1
  21. package/client/src/FlexViewCard.jsx +44 -0
  22. package/client/src/HistoryDialog.jsx +47 -14
  23. package/client/src/HtmlViewBuilderModal.jsx +91 -0
  24. package/client/src/HtmlViewBuilderModal.scss +18 -0
  25. package/client/src/HtmlViewCard.jsx +44 -0
  26. package/client/src/HtmlViewCard.scss +35 -0
  27. package/client/src/KPIDialog.jsx +11 -1
  28. package/client/src/KanbanCard.jsx +1 -2
  29. package/client/src/ModelCreator.jsx +6 -6
  30. package/client/src/ModelCreatorField.jsx +74 -31
  31. package/client/src/ModelList.jsx +93 -54
  32. package/client/src/Notification.jsx +136 -136
  33. package/client/src/Notification.scss +0 -18
  34. package/client/src/Pagination.jsx +5 -3
  35. package/client/src/RelationField.jsx +354 -258
  36. package/client/src/RelationSelectorWidget.jsx +173 -0
  37. package/client/src/constants.js +1 -1
  38. package/client/src/contexts/ModelContext.jsx +10 -1
  39. package/client/src/contexts/UIContext.jsx +72 -63
  40. package/client/src/filter.js +262 -212
  41. package/client/src/hooks/useTutorials.jsx +62 -65
  42. package/client/src/hooks/useValidation.js +75 -0
  43. package/client/src/translations.js +26 -24
  44. package/package.json +3 -1
  45. package/perf/README.md +147 -0
  46. package/perf/artillery-hooks.js +37 -0
  47. package/perf/perf-shot-hardwork.yml +84 -0
  48. package/perf/perf-shot-search.yml +45 -0
  49. package/perf/setup.yml +26 -0
  50. package/server.js +1 -1
  51. package/src/constants.js +3 -28
  52. package/src/core.js +15 -1
  53. package/src/data.js +1 -1
  54. package/src/defaultModels.js +63 -7
  55. package/src/email.js +5 -2
  56. package/src/engine.js +5 -3
  57. package/src/filter.js +5 -3
  58. package/src/i18n.js +710 -10
  59. package/src/modules/assistant/assistant.js +151 -19
  60. package/src/modules/bucket.js +14 -16
  61. package/src/modules/data/data.backup.js +11 -8
  62. package/src/modules/data/data.core.js +118 -92
  63. package/src/modules/data/data.history.js +531 -492
  64. package/src/modules/data/data.js +9 -56
  65. package/src/modules/data/data.operations.js +3282 -2999
  66. package/src/modules/data/data.relations.js +686 -686
  67. package/src/modules/data/data.routes.js +118 -24
  68. package/src/modules/data/data.scheduling.js +2 -1
  69. package/src/modules/data/data.validation.js +85 -3
  70. package/src/modules/file.js +247 -236
  71. package/src/modules/user.js +4 -1
  72. package/src/modules/workflow.js +9 -10
  73. package/src/openai.jobs.js +2 -0
  74. package/src/packs.js +5482 -5461
  75. package/src/providers.js +22 -7
  76. package/test/data.integration.test.js +136 -2
  77. package/test/import_export.integration.test.js +1 -1
@@ -33,8 +33,10 @@ const formatDate = (value, type, t) => {
33
33
 
34
34
  const FlexDataRenderer = ({ value, fieldDefinition,data }) => {
35
35
  const { t, i18n } = useTranslation();
36
+ const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
36
37
  const {models} = useModelContext()
37
38
  const {me} = useAuthContext()
39
+
38
40
  // 'translatedLabel' is added by getFieldDefinitionByPath
39
41
  const field = fieldDefinition;
40
42
  const translatedLabel = t('model_'+field.name, field.name);
@@ -36,7 +36,7 @@ export const createNewNode = (type) => {
36
36
  return {
37
37
  ...baseNode,
38
38
  type: 'item',
39
- itemStyle: { width: '100px', height: '50px' },
39
+ itemStyle: { },
40
40
  content: { type: 'placeholder' }
41
41
  };
42
42
  }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { useModelContext } from './contexts/ModelContext.jsx';
3
+ import FlexDataRenderer from './FlexDataRenderer.jsx';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ /**
7
+ * Renders a flexible data card, typically generated by the AI assistant.
8
+ * It displays a set of fields for a single data record.
9
+ * @param {object} props
10
+ * @param {object} props.config - The configuration for the view.
11
+ * @param {string} props.config.title - The title of the card.
12
+ * @param {string} props.config.model - The name of the data model.
13
+ * @param {string[]} props.config.fields - An array of field names to display.
14
+ * @param {object} props.config.data - The data record to display.
15
+ */
16
+ const FlexViewCard = ({ config }) => {
17
+ const { t } = useTranslation();
18
+ const { models } = useModelContext();
19
+ const { title, model: modelName, fields: fieldNames, data } = config;
20
+
21
+ const model = models.find(m => m.name === modelName);
22
+
23
+ if (!model || !data) {
24
+ return <div className="flex-view-card-error">{t('assistant.flexView.loadingError', 'Erreur de chargement de la vue.')}</div>;
25
+ }
26
+
27
+ const fieldsToDisplay = model.fields.filter(field => fieldNames.includes(field.name));
28
+
29
+ return (
30
+ <div className="flex-view-card">
31
+ {title && <h4>{title}</h4>}
32
+ <div className="data-card-content">
33
+ {fieldsToDisplay.map(fieldDef => (
34
+ <div key={fieldDef.name} className="data-field-row">
35
+ <strong className="field-label" title={fieldDef.name}>{t(`field_${model.name}_${fieldDef.name}`, fieldDef.name)}:</strong>
36
+ <div className="field-value"><FlexDataRenderer value={data[fieldDef.name]} fieldDefinition={fieldDef} data={data} /></div>
37
+ </div>
38
+ ))}
39
+ </div>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export default FlexViewCard;
@@ -6,12 +6,20 @@ import {Trans, useTranslation} from "react-i18next";
6
6
  import "./HistoryDialog.scss";
7
7
  import {Tooltip} from "react-tooltip";
8
8
  import {CodeField} from "./Field.jsx";
9
+ import {Pagination} from "./Pagination.jsx";
10
+ import {useModelContext} from "./contexts/ModelContext.jsx";
9
11
 
10
12
  // Helper to render values in a friendly way for the summary list
11
- const renderSummaryValue = (value) => {
13
+ const renderSummaryValue = (value, field, t) => {
12
14
  if (value === undefined || value === null) {
13
15
  return <i className="value-empty">empty</i>;
14
16
  }
17
+ if (field?.type === 'string_t' && t) {
18
+ // For string_t, the stored value is the key. We display the translated value.
19
+ const key = (typeof value === 'object' && value?.key) ? value.key : String(value);
20
+ const displayValue = t(key, key);
21
+ return <span className="value-text" title={key}>{displayValue}</span>;
22
+ }
15
23
  if (typeof value === 'boolean') {
16
24
  return <code className="value-boolean">{value.toString()}</code>;
17
25
  }
@@ -31,16 +39,26 @@ const renderSummaryValue = (value) => {
31
39
  // New sub-component for rendering a single field change
32
40
  const ChangeDetail = ({ field, from, to }) => {
33
41
  const { t } = useTranslation();
42
+ const { selectedModel } = useModelContext();
43
+ const fieldObj = selectedModel?.fields?.find(f => f.name === field);
34
44
 
35
- const tt = typeof(to) === 'object' ? JSON.stringify(to,null,2):to;
36
- const f = typeof(from) === 'object' ? JSON.stringify(from,null,2):from;
45
+ const getTooltipText = (value, field) => {
46
+ if (field?.type === 'string_t') {
47
+ const key = (typeof value === 'object' && value?.key) ? value.key : String(value);
48
+ return t(key, key); // Affiche la valeur traduite dans la tooltip
49
+ }
50
+ return (typeof value === 'object' && value !== null) ? JSON.stringify(value, null, 2) : String(value);
51
+ };
52
+
53
+ const tt = getTooltipText(to, fieldObj);
54
+ const f = getTooltipText(from, fieldObj);
37
55
  return (
38
56
  <div className="change-detail">
39
57
  <strong className="change-field-name" title={field}>{field}</strong>
40
58
  <div className="change-values">
41
- <div className="value-from" data-tooltip-id={"changeTooltip"} data-tooltip-content={f}>{renderSummaryValue(from)}</div>
59
+ <div className="value-from" data-tooltip-id={"changeTooltip"} data-tooltip-content={f}>{renderSummaryValue(from, fieldObj, t)}</div>
42
60
  <div className="change-arrow">→</div>
43
- <div className="value-to" data-tooltip-id={"changeTooltip"} data-tooltip-content={tt}>{renderSummaryValue(to)}</div>
61
+ <div className="value-to" data-tooltip-id={"changeTooltip"} data-tooltip-content={tt}>{renderSummaryValue(to, fieldObj, t)}</div>
44
62
  </div>
45
63
  </div>
46
64
  );
@@ -49,6 +67,7 @@ const ChangeDetail = ({ field, from, to }) => {
49
67
  // Sub-component for displaying a single history entry
50
68
  const HistoryEntry = ({ entry, onPreview, isSelected }) => {
51
69
  const { t } = useTranslation();
70
+ const { selectedModel } = useModelContext();
52
71
  // The backend has transformed the data for us.
53
72
  // _id: history doc id, _v: version, _op: operation type (i,u,d), _rid: original record id
54
73
  const { _id, _v, _op, _updatedAt, _user, ...data } = entry; // ...data collects the remaining fields
@@ -83,12 +102,13 @@ const HistoryEntry = ({ entry, onPreview, isSelected }) => {
83
102
  }
84
103
  return (
85
104
  <div className="snapshot-list">
86
- {snapshotEntries.map(([key, value]) => (
87
- <div className="snapshot-item" key={key}>
105
+ {snapshotEntries.map(([key, value]) => {
106
+ const fieldObj = selectedModel?.fields?.find(f => f.name === key);
107
+ return (<div className="snapshot-item" key={key}>
88
108
  <strong className="snapshot-key" title={key}>{key}:</strong>
89
- <span className="snapshot-value">{renderSummaryValue(value)}</span>
90
- </div>
91
- ))}
109
+ <span className="snapshot-value">{renderSummaryValue(value, fieldObj, t)}</span>
110
+ </div>);
111
+ })}
92
112
  </div>
93
113
  );
94
114
  };
@@ -118,6 +138,9 @@ export const HistoryDialog = ({ modelName, recordId, onClose }) => {
118
138
  const [previewData, setPreviewData] = useState(null);
119
139
  const [previewLoading, setPreviewLoading] = useState(false);
120
140
  const [selectedVersion, setSelectedVersion] = useState(null);
141
+ const [page, setPage] = useState(1);
142
+ const [totalCount, setTotalCount] = useState(0);
143
+ const elementsPerPage = 10;
121
144
  const { t, i18n } = useTranslation();
122
145
 
123
146
  const fetchHistory = useCallback(async () => {
@@ -125,10 +148,12 @@ export const HistoryDialog = ({ modelName, recordId, onClose }) => {
125
148
  setLoading(true);
126
149
  setError(null);
127
150
  try {
128
- const query = await fetch(`/api/data/history/${modelName}/${recordId}`);
151
+ const params = new URLSearchParams({ page, limit: elementsPerPage });
152
+ const query = await fetch(`/api/data/history/${modelName}/${recordId}?${params.toString()}`);
129
153
  const response = await query.json();
130
154
  if (response.success) {
131
155
  setHistory(response.data);
156
+ setTotalCount(response.count || 0);
132
157
  } else {
133
158
  setError(response.error || i18n.t("history.error", "Could not load history."));
134
159
  }
@@ -138,7 +163,7 @@ export const HistoryDialog = ({ modelName, recordId, onClose }) => {
138
163
  } finally {
139
164
  setLoading(false);
140
165
  }
141
- }, [modelName, recordId, i18n]);
166
+ }, [modelName, recordId, i18n, page, elementsPerPage]);
142
167
 
143
168
  useEffect(() => {
144
169
  fetchHistory();
@@ -208,7 +233,7 @@ export const HistoryDialog = ({ modelName, recordId, onClose }) => {
208
233
  <div className="history-list-container">
209
234
  {loading && <div><Trans i18nKey={"history.loading"}>Chargement de l'historique..</Trans></div>}
210
235
  {error && <div className="error-message">{error}</div>}
211
- {!loading && !error && (
236
+ {!loading && !error && (<>
212
237
  <div className="history-list">
213
238
  {history.length > 0 ? (
214
239
  history.map(entry =>
@@ -222,7 +247,15 @@ export const HistoryDialog = ({ modelName, recordId, onClose }) => {
222
247
  <div><Trans i18nKey={"history.noHistory"}>Aucun historique trouvé pour cet enregistrement.</Trans></div>
223
248
  )}
224
249
  </div>
225
- )}
250
+ {totalCount > elementsPerPage && <Pagination
251
+ totalCount={totalCount}
252
+ page={page}
253
+ setPage={setPage}
254
+ elementsPerPage={elementsPerPage}
255
+ visibleItemsCount={5}
256
+ hasPreviousNext={true}
257
+ />}
258
+ </>)}
226
259
  </div>
227
260
  <div className="history-preview-container">
228
261
  <div className="preview-header">
@@ -0,0 +1,91 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Dialog } from './Dialog.jsx';
4
+ import {TextField, SelectField, NumberField, CodeField, ModelField} from './Field.jsx';
5
+ import Button from './Button.jsx';
6
+ import { FaSave, FaTimes } from 'react-icons/fa';
7
+ import './HtmlViewBuilderModal.scss';
8
+
9
+ const HtmlViewBuilderModal = ({ isOpen, onClose, onSave, models, initialConfig }) => {
10
+ const { t } = useTranslation();
11
+ const [config, setConfig] = useState({});
12
+ const [jsonError, setJsonError] = useState('');
13
+
14
+ useEffect(() => {
15
+ if (isOpen) {
16
+ setConfig(initialConfig || {
17
+ title: '',
18
+ model: '',
19
+ limit: 10,
20
+ filter: {},
21
+ template: '',
22
+ css: '',
23
+ ...initialConfig
24
+ });
25
+ setJsonError('');
26
+ }
27
+ }, [isOpen, initialConfig]);
28
+
29
+ const modelOptions = useMemo(() => {
30
+ return (models || []).map(m => ({ label: m.name, value: m.name }));
31
+ }, [models]);
32
+
33
+ const handleChange = (e) => {
34
+ const { name, value } = e.target || e;
35
+ setConfig(prev => ({ ...prev, [name]: value }));
36
+ };
37
+
38
+ const handleCodeChange = (name, value) => {
39
+ setConfig(prev => ({ ...prev, [name]: value }));
40
+ };
41
+
42
+ const handleFilterChange = (codeValue) => {
43
+ try {
44
+ const parsed = JSON.parse(codeValue);
45
+ setConfig(prev => ({ ...prev, filter: parsed }));
46
+ setJsonError('');
47
+ } catch (e) {
48
+ setJsonError(t('dashboards.invalidJson', 'JSON invalide'));
49
+ }
50
+ };
51
+
52
+ const handleSave = () => {
53
+ if (!config.model || !config.template) {
54
+ console.error("Model and template are required.");
55
+ return;
56
+ }
57
+ if (jsonError) return;
58
+ onSave(config);
59
+ };
60
+
61
+ if (!isOpen) return null;
62
+
63
+ return (
64
+ <Dialog
65
+ isModal={true}
66
+ isClosable={true}
67
+ onClose={onClose}
68
+ title={initialConfig?.id ? t('dashboards.editHtmlViewTitle', 'Modifier la Vue HTML') : t('dashboards.addHtmlViewTitle', 'Ajouter une Vue HTML')}
69
+ className="html-view-builder-modal"
70
+ >
71
+ <div className="form-grid">
72
+ <TextField name="title" label={t('dashboards.widgetTitle', 'Titre du widget')} value={config.title || ''} onChange={handleChange} />
73
+ <ModelField name="model" label={t('model', 'Modèle')} value={config.model || ''} onChange={(option) => handleChange({ name: 'model', value: option?.value })} required />
74
+ <NumberField name="limit" label={t('dashboards.dataLimit', 'Limite de données')} value={config.limit ?? 10} onChange={handleChange} min={1} max={100} />
75
+ <CodeField name="filter" label={t('filter', 'Filtre (JSON)')} value={typeof config.filter === 'string' ? config.filter : JSON.stringify(config.filter || {}, null, 2)} language="json" onChange={(e) => handleFilterChange(e.value)} error={jsonError} />
76
+ <CodeField name="template" label={t('dashboards.htmlTemplate', 'Template HTML')} value={config.template || ''} language="html" onChange={(e) => handleCodeChange('template', e.value)} required height="200px" />
77
+ <CodeField name="css" label={t('dashboards.cssStyles', 'Styles CSS')} value={config.css || ''} language="css" onChange={(e) => handleCodeChange('css', e.value)} height="150px" />
78
+ </div>
79
+ <div className="flex actions right">
80
+ <Button onClick={onClose} className="btn-secondary">
81
+ <FaTimes /> {t('btns.cancel', 'Annuler')}
82
+ </Button>
83
+ <Button onClick={handleSave} className="btn-primary" disabled={!!jsonError}>
84
+ <FaSave /> {t('btns.save', 'Enregistrer')}
85
+ </Button>
86
+ </div>
87
+ </Dialog>
88
+ );
89
+ };
90
+
91
+ export default HtmlViewBuilderModal;
@@ -0,0 +1,18 @@
1
+ .html-view-builder-modal {
2
+ .form-grid {
3
+ display: grid;
4
+ grid-template-columns: 1fr;
5
+ gap: 1rem;
6
+ padding-bottom: 1rem;
7
+
8
+ .code-editor-container {
9
+ grid-column: 1 / -1;
10
+ }
11
+ }
12
+
13
+ .actions {
14
+ margin-top: 1rem;
15
+ padding-top: 1rem;
16
+ border-top: 1px solid var(--border-color);
17
+ }
18
+ }
@@ -0,0 +1,44 @@
1
+ import React, {useEffect, useMemo} from 'react';
2
+ import './HtmlViewCard.scss';
3
+ import {renderHtmlTemplate} from "./DashboardHtmlViewItem.jsx";
4
+
5
+ /**
6
+ * Affiche une vue HTML personnalisée, généralement générée par l'assistant IA.
7
+ * @param {object} props
8
+ * @param {object} props.config - La configuration de la vue.
9
+ * @param {string} props.config.title - Le titre de la carte.
10
+ * @param {string} props.config.template - La chaîne de template HTML Handlebars.
11
+ * @param {Array<object>} props.config.data - Le tableau d'enregistrements de données à afficher.
12
+ */
13
+ const HtmlViewCard = ({ config }) => {
14
+ const { title, template, data, css } = config;
15
+
16
+ // Génère un ID unique et stable pour ce composant
17
+ const containerId = useMemo(() => `html-view-${Math.random().toString(36).substring(2, 9)}`, []);
18
+
19
+ // Effet pour injecter et nettoyer le CSS
20
+ useEffect(() => {
21
+ if (!css) return;
22
+
23
+ const styleElement = document.createElement('style');
24
+ // Remplace le placeholder par notre ID unique pour scoper le CSS
25
+ styleElement.innerHTML = css.replace(/\{\{containerId\}\}/g, containerId);
26
+ document.head.appendChild(styleElement);
27
+
28
+ // Fonction de nettoyage : retire le style quand le composant est démonté
29
+ return () => {
30
+ document.head.removeChild(styleElement);
31
+ };
32
+ }, [css, containerId]);
33
+
34
+ const renderedHtml = renderHtmlTemplate(template, data);
35
+
36
+ return (
37
+ <div id={containerId} className="html-view-card">
38
+ {title && <h4>{title}</h4>}
39
+ <div className="html-view-content" dangerouslySetInnerHTML={{ __html: renderedHtml }} />
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export default HtmlViewCard;
@@ -0,0 +1,35 @@
1
+ .html-view-card {
2
+ background-color: var(--bg-color-dark);
3
+ border: 1px solid var(--border-color);
4
+ border-radius: 8px;
5
+ margin-top: 12px;
6
+ font-size: 0.9rem;
7
+
8
+ h4 {
9
+ margin-top: 0;
10
+ margin-bottom: 12px;
11
+ color: var(--text-color-light);
12
+ border-bottom: 1px solid var(--border-color-light);
13
+ padding-bottom: 8px;
14
+ }
15
+
16
+ .html-view-content {
17
+ ul, ol {
18
+ padding-left: 20px;
19
+ margin-top: 8px;
20
+ margin-bottom: 8px;
21
+ }
22
+ li { margin-bottom: 4px; }
23
+ a {
24
+ color: var(--primary-color);
25
+ text-decoration: none;
26
+ &:hover { text-decoration: underline; }
27
+ }
28
+ h1, h2, h3 {
29
+ margin-top: 1em;
30
+ margin-bottom: 0.5em;
31
+ color: var(--text-color);
32
+ }
33
+ p { line-height: 1.5; }
34
+ }
35
+ }
@@ -1,11 +1,16 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Dialog } from './Dialog.jsx'; // Assure-toi que Dialog est importé
3
3
  import { useTranslation } from 'react-i18next';
4
+ import {FaAd, FaPlus} from "react-icons/fa";
5
+ import {getUserHash} from "../../src/data.js";
6
+ import {useNavigate} from "react-router-dom";
7
+ import {useAuthContext} from "./contexts/AuthContext.jsx";
4
8
 
5
9
  function KPIDialog({ availableKpis = [], onAddKpi, onClose }) {
6
10
  const { t } = useTranslation();
7
11
  const [searchTerm, setSearchTerm] = useState('');
8
-
12
+ const nav = useNavigate()
13
+ const {me} = useAuthContext()
9
14
  const filteredKpis = (availableKpis || []).filter(kpi =>
10
15
  kpi.name.value.toLowerCase().includes(searchTerm.toLowerCase())
11
16
  );
@@ -32,6 +37,11 @@ function KPIDialog({ availableKpis = [], onAddKpi, onClose }) {
32
37
  <li>{t('dashboards.no_kpi_found', 'Aucun KPI disponible ou correspondant.')}</li>
33
38
  )}
34
39
  </ul>
40
+ <div className={"flex mg-2"}>
41
+ <button onClick={() => {
42
+ nav(`/user/${getUserHash(me)}/?model=kpi`);
43
+ }} className={"btn"}><FaPlus />{t('dashboards.addKpi', 'Nouvel indicateur KPI')}</button>
44
+ </div>
35
45
  </Dialog>
36
46
  );
37
47
  }
@@ -14,7 +14,6 @@ const KanbanCard = ({ card, model, subItemsField }) => {
14
14
  // Logique du Drag & Drop natif pour la carte
15
15
  const lang = (tr.i18n.resolvedLanguage || tr.i18n.language).split(/[-_]/)?.[0];
16
16
 
17
- console.log(subItems)
18
17
  const getfield = (model, data) => {
19
18
  const v = getDataAsString(model, data, tr, models);
20
19
  if(!v){
@@ -26,7 +25,7 @@ const KanbanCard = ({ card, model, subItemsField }) => {
26
25
  <strong>guid</strong> : ${it.guid}<br />
27
26
  <strong>type</strong> : ${it.mimeType}<br />
28
27
  <strong>size</strong> : ${it.size} bytes<br />
29
- <strong>timestamp</strong> : ${it.timestamp ? new Date(it.timestamp).toLocaleString(lang) : ''}
28
+ <strong>timestamp</strong> : ${it.createdAt ? new Date(it.createdAt).toLocaleString(lang) : ''}
30
29
  `;
31
30
  return <a key={it.guid} href={`/resources/${it.guid}`} target="_blank"
32
31
  data-tooltip-id={"tooltipFile"} data-tooltip-html={r}
@@ -225,7 +225,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
225
225
  delete field['_isNewField'];
226
226
  let otherFields = [];
227
227
  // Check for specific field types
228
- switch (field.type) {
228
+ switch ((field.type)) {
229
229
  case 'relation':
230
230
  otherFields = ['relation', 'multiple', 'relationFilter'];
231
231
  break;
@@ -245,10 +245,11 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
245
245
  case 'phone':
246
246
  case 'password':
247
247
  case 'code':
248
- if (field.type === 'code')
248
+ const t = (field.itemsType || field.type);
249
+ if (t === 'code')
249
250
  otherFields = ['maxlength', 'language', 'conditionBuilder'];
250
- else if( ['string_t', 'string'].includes(field.type))
251
- otherFields = ['maxlength', 'multiline'];
251
+ else if( ['string_t', 'string'].includes(t))
252
+ otherFields = ['maxlength', 'multiline', 'mask', 'replacement'];
252
253
  else
253
254
  otherFields = ['maxlength'];
254
255
  break;
@@ -660,11 +661,10 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
660
661
  {/* Boutons d'action, visibles si un modèle est affiché ou en mode manuel */}
661
662
  {(showModel || !useAI || initialModel) && (
662
663
  <div className="actions flex">
663
- {initialModel && (
664
+
664
665
  <Button type="button" onClick={handleAddField}>
665
666
  <FaPlus /> <Trans i18nKey={"btns.addField"}>Ajouter un champ</Trans>
666
667
  </Button>
667
- )}
668
668
 
669
669
  <Button type="submit">
670
670
  <FaSave />