data-primals-engine 1.3.0 → 1.3.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.
- package/CHANGELOG.md +440 -0
- package/README.md +782 -748
- package/client/src/App.scss +4 -0
- package/client/src/DataLayout.jsx +2 -2
- package/client/src/DataTable.jsx +19 -2
- package/client/src/Field.jsx +3 -2
- package/client/src/HistoryDialog.jsx +96 -0
- package/client/src/HistoryDialog.scss +73 -0
- package/client/src/ModelCreator.jsx +36 -15
- package/client/src/ModelCreatorField.jsx +5 -6
- package/client/src/hooks/useTutorials.jsx +1 -1
- package/client/src/translations.js +155 -1
- package/package.json +2 -2
- package/src/HistoryDialog.jsx +86 -0
- package/src/HistoryDialog.scss +77 -0
- package/src/email.js +1 -1
- package/src/engine.js +6 -6
- package/src/events.js +15 -5
- package/src/gameObject.js +0 -29
- package/src/modules/data/data.history.js +304 -0
- package/src/modules/data/data.js +4620 -4601
- package/src/modules/data/data.routes.js +4 -4
- package/src/modules/mongodb.js +17 -1
- package/src/modules/user.js +9 -3
- package/src/packs.js +2 -1
- package/src/services/stripe.js +67 -13
- package/src/workers/crypto-worker.js +3 -3
- package/test/data.backup.integration.test.js +5 -0
- package/test/data.history.integration.test.js +193 -0
- package/test/data.integration.test.js +79 -39
- package/test/events.test.js +27 -27
- package/test/globalTeardown.js +1 -0
- package/test/model.integration.test.js +5 -0
- package/test/workflow.actions.integration.test.js +2 -0
- package/test/workflow.integration.test.js +2 -0
- package/test/workflow.robustness.test.js +2 -0
package/client/src/App.scss
CHANGED
|
@@ -327,11 +327,11 @@ function DataLayout() {
|
|
|
327
327
|
};
|
|
328
328
|
addNotification(notificationData);
|
|
329
329
|
},
|
|
330
|
-
onSuccess: (data) => {
|
|
330
|
+
onSuccess: async (data) => {
|
|
331
331
|
|
|
332
332
|
console.log('Données enregistrées:', data, selectedModel);
|
|
333
333
|
|
|
334
|
-
Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
|
|
334
|
+
await Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
|
|
335
335
|
model: selectedModel.name,
|
|
336
336
|
});
|
|
337
337
|
|
package/client/src/DataTable.jsx
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
FaDatabase, FaEraser,
|
|
15
15
|
FaFileExport,
|
|
16
16
|
FaFileImport,
|
|
17
|
-
FaFilter,
|
|
17
|
+
FaFilter, FaHistory,
|
|
18
18
|
FaInfo,
|
|
19
19
|
FaPlus,
|
|
20
20
|
FaTrash, FaWrench
|
|
@@ -59,6 +59,7 @@ import ConditionBuilder from "./ConditionBuilder.jsx";
|
|
|
59
59
|
import {pagedFilterToMongoConds} from "./filter.js";
|
|
60
60
|
import {isConditionMet} from "../../src/filter";
|
|
61
61
|
import {DataImporter} from "./DataImporter.jsx";
|
|
62
|
+
import {HistoryDialog} from "./HistoryDialog.jsx";
|
|
62
63
|
|
|
63
64
|
const Header = ({
|
|
64
65
|
reversed = false,
|
|
@@ -238,6 +239,7 @@ export function DataTable({
|
|
|
238
239
|
const queryClient = useQueryClient();
|
|
239
240
|
const {me} = useAuthContext()
|
|
240
241
|
|
|
242
|
+
|
|
241
243
|
const isDataLoaded = true;
|
|
242
244
|
const [importVisible, setImportVisible] = useState(false);
|
|
243
245
|
const [filterActive, setFilterActive] = useState(false)
|
|
@@ -248,6 +250,8 @@ export function DataTable({
|
|
|
248
250
|
const [showExportDialog, setExportDialogVisible] = useState(false);
|
|
249
251
|
|
|
250
252
|
|
|
253
|
+
const [selectedRow, setSelectedRow] = useState(null);
|
|
254
|
+
|
|
251
255
|
const [showAddPack, setAddPackVisible] = useState(false);
|
|
252
256
|
const handleAddPack = () => {
|
|
253
257
|
setAddPackVisible(!showAddPack);
|
|
@@ -283,7 +287,7 @@ export function DataTable({
|
|
|
283
287
|
console.log('Données non trouvées');
|
|
284
288
|
}
|
|
285
289
|
|
|
286
|
-
Event.Trigger('API_DELETE_DATA', "custom", "data",{ model: item._model, id: item._id });
|
|
290
|
+
await Event.Trigger('API_DELETE_DATA', "custom", "data",{ model: item._model, id: item._id });
|
|
287
291
|
queryClient.invalidateQueries(['api/data', item._model, 'page', page, elementsPerPage, pagedFilters[item._model], pagedSort[item._model]]);
|
|
288
292
|
|
|
289
293
|
} else {
|
|
@@ -697,6 +701,14 @@ export function DataTable({
|
|
|
697
701
|
>
|
|
698
702
|
<FaCopy/>
|
|
699
703
|
</button>
|
|
704
|
+
{selectedModel?.history?.enabled && (<button
|
|
705
|
+
onClick={() => setSelectedRow(item._id)}
|
|
706
|
+
data-tooltip-id="tooltipActions"
|
|
707
|
+
data-tooltip-content={t('btns.showHistory', 'Voir l\'historique')}
|
|
708
|
+
>
|
|
709
|
+
<FaHistory />
|
|
710
|
+
</button>)}
|
|
711
|
+
|
|
700
712
|
<button data-tooltip-id="tooltipActions"
|
|
701
713
|
data-tooltip-content={t('btns.delete', 'Supprimer')}
|
|
702
714
|
onClick={() => handleDelete(item)}><FaTrash/></button>
|
|
@@ -704,6 +716,7 @@ export function DataTable({
|
|
|
704
716
|
</tr>
|
|
705
717
|
</DialogProvider></>
|
|
706
718
|
))}
|
|
719
|
+
|
|
707
720
|
</tbody>
|
|
708
721
|
|
|
709
722
|
<tfoot>
|
|
@@ -720,6 +733,10 @@ export function DataTable({
|
|
|
720
733
|
{importVisible && (<DataImporter onClose={() => {
|
|
721
734
|
setImportVisible(false);
|
|
722
735
|
}}/>)}
|
|
736
|
+
|
|
737
|
+
{selectedRow && (
|
|
738
|
+
<HistoryDialog onClose={() => setSelectedRow(null)} modelName={selectedModel?.name} recordId={selectedRow} />
|
|
739
|
+
)}
|
|
723
740
|
<RestoreConfirmationModal
|
|
724
741
|
isOpen={isBackupModalOpen}
|
|
725
742
|
onClose={() => setIsBackupModalOpen(false)}
|
package/client/src/Field.jsx
CHANGED
|
@@ -93,6 +93,7 @@ const TextField = forwardRef(function TextField(
|
|
|
93
93
|
searchable,
|
|
94
94
|
labelProps,
|
|
95
95
|
showErrors=false,
|
|
96
|
+
after,
|
|
96
97
|
...rest
|
|
97
98
|
},
|
|
98
99
|
ref,
|
|
@@ -186,7 +187,7 @@ const TextField = forwardRef(function TextField(
|
|
|
186
187
|
></textarea>
|
|
187
188
|
)}
|
|
188
189
|
|
|
189
|
-
<div className={"flex flex-
|
|
190
|
+
<div className={"flex flex-1 flex-no-gap flex-start"}>
|
|
190
191
|
{!mult && (
|
|
191
192
|
<input
|
|
192
193
|
ref={inputRef}
|
|
@@ -207,7 +208,7 @@ const TextField = forwardRef(function TextField(
|
|
|
207
208
|
{...rest}
|
|
208
209
|
/>
|
|
209
210
|
)}
|
|
210
|
-
|
|
211
|
+
{after}
|
|
211
212
|
</div>
|
|
212
213
|
</div>
|
|
213
214
|
{errors.length > 0 && (
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Dialog } from "./Dialog.jsx";
|
|
3
|
+
import { FaHistory } from "react-icons/fa";
|
|
4
|
+
import {Trans, useTranslation} from "react-i18next";
|
|
5
|
+
import "./HistoryDialog.scss";
|
|
6
|
+
|
|
7
|
+
// Sub-component for displaying a single history entry
|
|
8
|
+
const HistoryEntry = ({ entry }) => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
// The backend has transformed the data for us.
|
|
11
|
+
// _id: history doc id, _v: version, _op: operation type (i,u,d), _rid: original record id
|
|
12
|
+
const { _id, _v, _op, _updatedAt, _user, _rid, ...data } = entry; // ...data collects the remaining fields
|
|
13
|
+
|
|
14
|
+
const operationDetails = {
|
|
15
|
+
// The keys 'i', 'u', 'd' correspond to what the backend sends now.
|
|
16
|
+
'i': { label: t('history.op.create', 'Creation'), className: 'op-insert' },
|
|
17
|
+
'u': { label: t('history.op.update', 'Update'), className: 'op-update' },
|
|
18
|
+
'd': { label: t('history.op.delete', 'Deletion'), className: 'op-delete' },
|
|
19
|
+
};
|
|
20
|
+
const opInfo = operationDetails[_op] || { label: _op, className: '' };
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="history-entry">
|
|
24
|
+
<div className="history-entry-header">
|
|
25
|
+
<span className={`op-badge ${opInfo.className}`}>{opInfo.label}</span>
|
|
26
|
+
<span className="history-date">{t('history.datePrefix', 'On')} {new Date(_updatedAt).toLocaleString()}</span>
|
|
27
|
+
{_user && <span className="history-user">{t('history.byUser', 'by')} <strong>{_user}</strong></span>}
|
|
28
|
+
<span className="history-version">{t('history.version', 'Version:')} {_v}</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="history-entry-data">
|
|
31
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const HistoryDialog = ({ modelName, recordId, onClose }) => {
|
|
38
|
+
const [history, setHistory] = useState([]);
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
const [error, setError] = useState(null);
|
|
41
|
+
const { t, i18n } = useTranslation();
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!modelName || !recordId) return;
|
|
45
|
+
|
|
46
|
+
const fetchHistory = async () => {
|
|
47
|
+
setLoading(true);
|
|
48
|
+
setError(null);
|
|
49
|
+
try {
|
|
50
|
+
// Assuming the API endpoint for history is structured like this
|
|
51
|
+
const query = await fetch(`/api/data/history/${modelName}/${recordId}`, {
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const response = await query.json();
|
|
57
|
+
if (response.success) {
|
|
58
|
+
// L'API devrait retourner l'historique trié du plus récent au plus ancien
|
|
59
|
+
setHistory(response.data);
|
|
60
|
+
} else {
|
|
61
|
+
setError(response.error || i18n.t("history.error", "Could not load history."));
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
setError(i18n.t("history.error", "Could not load history."));
|
|
65
|
+
console.error(err);
|
|
66
|
+
} finally {
|
|
67
|
+
setLoading(false);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
fetchHistory();
|
|
72
|
+
}, [modelName, recordId]);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Dialog
|
|
76
|
+
title={<><FaHistory style={{ marginRight: '8px' }} /> <Trans i18nKey={"history.title"}>Historique de l'enregistrement</Trans></>}
|
|
77
|
+
onClose={onClose}
|
|
78
|
+
isClosable={true}
|
|
79
|
+
className="history-dialog"
|
|
80
|
+
>
|
|
81
|
+
<div className="history-dialog-content">
|
|
82
|
+
{loading && <div><Trans i18nKey={"history.loading"}>Chargement de l'historique..</Trans></div>}
|
|
83
|
+
{error && <div className="error-message">{error}</div>}
|
|
84
|
+
{!loading && !error && (
|
|
85
|
+
<div className="history-list">
|
|
86
|
+
{history.length > 0 ? (
|
|
87
|
+
history.map(entry => <HistoryEntry key={entry._id} entry={entry} />)
|
|
88
|
+
) : (
|
|
89
|
+
<div><Trans i18nKey={"history.noHistory"}>Aucun historique trouvé pour cet enregistrement.</Trans></div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</Dialog>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
.history-dialog {
|
|
2
|
+
|
|
3
|
+
.dialog-content {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
height: 100%;
|
|
7
|
+
padding: 0; // Le padding sera sur le content wrapper
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.history-dialog-content {
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
padding: 1rem;
|
|
14
|
+
background-color: #f4f4f4;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.history-list {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 1rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.history-entry {
|
|
24
|
+
border: 1px solid #ddd;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
background-color: #fff;
|
|
27
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
28
|
+
|
|
29
|
+
.history-entry-header {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 1rem;
|
|
33
|
+
padding: 0.5rem 1rem;
|
|
34
|
+
background-color: #f9f9f9;
|
|
35
|
+
border-bottom: 1px solid #eee;
|
|
36
|
+
font-size: 0.9em;
|
|
37
|
+
color: #333;
|
|
38
|
+
|
|
39
|
+
.op-badge {
|
|
40
|
+
padding: 0.2rem 0.6rem;
|
|
41
|
+
border-radius: 12px;
|
|
42
|
+
color: white;
|
|
43
|
+
font-weight: bold;
|
|
44
|
+
font-size: 0.8em;
|
|
45
|
+
text-transform: uppercase;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.op-insert { background-color: #28a745; } // green
|
|
49
|
+
.op-update { background-color: #007bff; } // blue
|
|
50
|
+
.op-delete { background-color: #dc3545; } // red
|
|
51
|
+
|
|
52
|
+
.history-version {
|
|
53
|
+
margin-left: auto;
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.history-entry-data {
|
|
59
|
+
padding: 1rem;
|
|
60
|
+
|
|
61
|
+
pre {
|
|
62
|
+
white-space: pre-wrap;
|
|
63
|
+
word-wrap: break-word;
|
|
64
|
+
background-color: #2d2d2d;
|
|
65
|
+
color: #f8f8f2;
|
|
66
|
+
padding: 1rem;
|
|
67
|
+
border-radius: 4px;
|
|
68
|
+
margin: 0;
|
|
69
|
+
font-family: 'Courier New', Courier, monospace;
|
|
70
|
+
font-size: 0.85em;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -41,6 +41,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
41
41
|
const [modelName, setModelName] = useState(initialModel?.name || ''); // Utilisation de initialModel
|
|
42
42
|
const [modelMaxRequestData, setModelMaxRequestData] = useState(initialModel?.maxRequestData || ''); // Utilisation de initialModel
|
|
43
43
|
const [modelDescription, setModelDescription] = useState(initialModel?.name ? t(`model_description_${initialModel?.name}`, initialModel?.description) : '');
|
|
44
|
+
const [modelHistory, setModelHistory] = useState(!!initialModel?.history || undefined);
|
|
44
45
|
const [fields, setFields] = useState([]);
|
|
45
46
|
const [changed, setChanged] = useState(false);
|
|
46
47
|
|
|
@@ -53,6 +54,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
53
54
|
setModelMaxRequestData(initialModel.maxRequestData || defaultMaxRequestData);
|
|
54
55
|
setModelDescription(initialModel.name ? t(`model_description_${initialModel.name}`, initialModel.description) : '');
|
|
55
56
|
setFields([...(initialModel.fields || []).map(m => ({...m}))]);
|
|
57
|
+
setModelHistory(initialModel.history);
|
|
56
58
|
} else {
|
|
57
59
|
// Mode création : on réinitialise tout pour une nouvelle génération
|
|
58
60
|
setModelName('');
|
|
@@ -60,6 +62,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
60
62
|
setFields([]);
|
|
61
63
|
setUseAI(true); // On active l'IA par défaut
|
|
62
64
|
setModelVisible(false);
|
|
65
|
+
setModelHistory(false);
|
|
63
66
|
}
|
|
64
67
|
}, [initialModel]);
|
|
65
68
|
|
|
@@ -130,6 +133,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
130
133
|
setModelDescription('');
|
|
131
134
|
setModelMaxRequestData(defaultMaxRequestData);
|
|
132
135
|
setFields([{ name: '', type: 'string' }]);
|
|
136
|
+
setModelHistory(undefined);
|
|
133
137
|
}
|
|
134
138
|
setDatasToLoad(datas=>[...datas, modelName]);
|
|
135
139
|
|
|
@@ -208,6 +212,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
208
212
|
name: modelName,
|
|
209
213
|
description: modelDescription,
|
|
210
214
|
maxRequestData: modelMaxRequestData,
|
|
215
|
+
history: modelHistory,
|
|
211
216
|
fields: (fields || []).map((field) => {
|
|
212
217
|
delete field['_isNewField'];
|
|
213
218
|
let otherFields = [];
|
|
@@ -312,8 +317,8 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
const handleRenameField = (fi, oldVal) => {
|
|
315
|
-
const prompted = prompt(t('core.renameFields', 'Renommer le champ'), oldVal);
|
|
316
|
-
if (prompted !== oldVal) {
|
|
320
|
+
const prompted = window.prompt(t('core.renameFields', 'Renommer le champ'), oldVal);
|
|
321
|
+
if (prompted && prompted !== oldVal) {
|
|
317
322
|
mutationRename.mutateAsync({oldName: oldVal, newName: prompted}).then(e =>{
|
|
318
323
|
const newFields = [...fields];
|
|
319
324
|
const field = newFields[fi];
|
|
@@ -542,21 +547,22 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
542
547
|
</div>
|
|
543
548
|
|
|
544
549
|
<div className="field">
|
|
545
|
-
<div className="flex
|
|
546
|
-
<
|
|
550
|
+
<div className="checkbox-label flex flex-1">
|
|
551
|
+
<CheckboxField
|
|
552
|
+
label={<Trans i18nKey={"history.title"}>Historique</Trans>}
|
|
553
|
+
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
554
|
+
checked={field.required}
|
|
555
|
+
onChange={(e) => {
|
|
556
|
+
const newFields = [...fields];
|
|
557
|
+
newFields[index].history = e ? { enabled: true } : undefined;
|
|
558
|
+
setFields(newFields);
|
|
559
|
+
}}
|
|
560
|
+
help={field.required && t('modelcreator.history.hint')}
|
|
561
|
+
/>
|
|
547
562
|
</div>
|
|
548
|
-
<TextField
|
|
549
|
-
multiline
|
|
550
|
-
help={t('modelcreator.field.description')}
|
|
551
|
-
id="modelDescription"
|
|
552
|
-
disabled={modelLocked}
|
|
553
|
-
value={modelDescription}
|
|
554
|
-
onChange={(e) => {
|
|
555
|
-
setModelDescription(e.target.value);
|
|
556
|
-
setChanged(true)
|
|
557
|
-
}}
|
|
558
|
-
/>
|
|
559
563
|
</div>
|
|
564
|
+
|
|
565
|
+
|
|
560
566
|
<h3><Trans i18nKey={"modelcreator.fields"}>Champs du modèle :</Trans></h3>
|
|
561
567
|
{fields.map((field, index) => <ModelCreatorField
|
|
562
568
|
key={initialModel?.name + '_field_' + index}
|
|
@@ -601,6 +607,21 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
601
607
|
setChanged(true)
|
|
602
608
|
}}
|
|
603
609
|
/>
|
|
610
|
+
|
|
611
|
+
<div className="flex flex-no-wrap">
|
|
612
|
+
<div className="checkbox-label flex flex-1">
|
|
613
|
+
<CheckboxField
|
|
614
|
+
label={<Trans i18nKey={"history"}>Historique</Trans>}
|
|
615
|
+
help={t('modelcreator.field.history', '')}
|
|
616
|
+
disabled={modelLocked}
|
|
617
|
+
checked={!!modelHistory}
|
|
618
|
+
onChange={(e) => {
|
|
619
|
+
setModelHistory(e? { enabled: true }: false);
|
|
620
|
+
}}
|
|
621
|
+
/>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
604
625
|
<h3><Trans i18nKey={"modelcreator.fields"}>Champs du modèle :</Trans></h3>
|
|
605
626
|
{fields.map((field, index) => <ModelCreatorField
|
|
606
627
|
key={initialModel?.name + '_field_' + index}
|
|
@@ -74,11 +74,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
74
74
|
}}
|
|
75
75
|
help={t('modelcreator.name.hint')}
|
|
76
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>)}
|
|
77
80
|
/>
|
|
78
|
-
{!(!modelLocked && isLocalUser(me) && field.locked) && !field._isNewField && (
|
|
79
|
-
<Button type={"button"} className={"btn-form btn-last"}
|
|
80
|
-
onClick={() => handleRenameField(index, field.name)}><FaEdit/></Button>)}
|
|
81
|
-
|
|
82
81
|
</div>
|
|
83
82
|
|
|
84
83
|
</div>
|
|
@@ -449,11 +448,11 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
449
448
|
</div>
|
|
450
449
|
|
|
451
450
|
{!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
|
|
452
|
-
className="flex flex-no-wrap
|
|
451
|
+
className="flex flex-no-wrap mg-item">
|
|
453
452
|
|
|
454
453
|
{['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
|
|
455
454
|
{hint('modelcreator.default.hint')}
|
|
456
|
-
<div className="flex flex-1">
|
|
455
|
+
<div className="flex flex-1 field-bg">
|
|
457
456
|
<TextField
|
|
458
457
|
className="flex-1"
|
|
459
458
|
value={field.default}
|
|
@@ -195,7 +195,7 @@ export const useTutorials = () => {
|
|
|
195
195
|
|
|
196
196
|
// Écoute les événements de modification de données pour vérifier la progression en arrière-plan.
|
|
197
197
|
useEffect(() => {
|
|
198
|
-
const handleDataChange = (payload) => {
|
|
198
|
+
const handleDataChange = async (payload) => {
|
|
199
199
|
console.log(`[Tutoriels] Événement de données reçu.`, payload);
|
|
200
200
|
if (me?.activeTutorial) {
|
|
201
201
|
triggerTutorialCheck();
|