data-primals-engine 1.2.6 → 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 -712
- package/client/src/App.jsx +13 -20
- package/client/src/App.scss +4 -0
- package/client/src/AssistantChat.jsx +5 -4
- package/client/src/DataEditor.jsx +1 -1
- package/client/src/DataLayout.jsx +2 -2
- package/client/src/DataTable.jsx +66 -5
- package/client/src/ExportDialog.jsx +2 -2
- package/client/src/Field.jsx +5 -4
- package/client/src/HistoryDialog.jsx +96 -0
- package/client/src/HistoryDialog.scss +73 -0
- package/client/src/KanbanCard.jsx +4 -2
- package/client/src/KanbanConfigModal.jsx +5 -7
- package/client/src/ModelCreator.jsx +36 -15
- package/client/src/ModelCreatorField.jsx +14 -15
- package/client/src/PackGallery.jsx +89 -9
- package/client/src/PackGallery.scss +58 -4
- package/client/src/RTETrans.jsx +11 -0
- package/client/src/RelationValue.jsx +3 -4
- package/client/src/constants.js +1 -1
- package/client/src/core/data.js +2 -1
- package/client/src/hooks/useTutorials.jsx +1 -1
- package/client/src/translations.js +217 -0
- package/package.json +9 -2
- package/server.js +4 -4
- package/src/HistoryDialog.jsx +86 -0
- package/src/HistoryDialog.scss +77 -0
- package/src/constants.js +6 -0
- package/src/defaultModels.js +23 -10
- 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/i18n.js +1 -1
- package/src/modules/{assistant.js → assistant/assistant.js} +42 -27
- package/src/modules/assistant/constants.js +16 -0
- package/src/modules/data/data.history.js +304 -0
- package/src/modules/data/data.js +306 -238
- package/src/modules/data/data.routes.js +33 -7
- package/src/modules/mongodb.js +17 -1
- package/src/modules/user.js +21 -4
- package/src/modules/workflow.js +133 -48
- package/src/packs.js +1016 -9
- package/src/services/index.js +11 -0
- package/src/services/stripe.js +195 -0
- 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 +144 -41
- 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 +4 -2
- package/test/workflow.integration.test.js +3 -1
- package/test/workflow.robustness.test.js +2 -0
|
@@ -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>
|
|
@@ -284,7 +283,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
284
283
|
checked={field.multiline}
|
|
285
284
|
onChange={(e) => {
|
|
286
285
|
const newFields = [...fields];
|
|
287
|
-
newFields[index].multiline = e
|
|
286
|
+
newFields[index].multiline = e;
|
|
288
287
|
setFields(newFields);
|
|
289
288
|
}}
|
|
290
289
|
help={field.multiline && t('modelcreator.multiline.hint')}
|
|
@@ -373,7 +372,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
373
372
|
checked={field.multiple}
|
|
374
373
|
onChange={(e) => {
|
|
375
374
|
const newFields = [...fields];
|
|
376
|
-
newFields[index].multiple = e
|
|
375
|
+
newFields[index].multiple = e;
|
|
377
376
|
setFields(newFields);
|
|
378
377
|
}}
|
|
379
378
|
/></div>
|
|
@@ -422,7 +421,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
422
421
|
checked={field.required}
|
|
423
422
|
onChange={(e) => {
|
|
424
423
|
const newFields = [...fields];
|
|
425
|
-
newFields[index].required = e
|
|
424
|
+
newFields[index].required = e;
|
|
426
425
|
setFields(newFields);
|
|
427
426
|
}}
|
|
428
427
|
help={field.required && t('modelcreator.required.hint')}
|
|
@@ -440,7 +439,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
440
439
|
checked={field.unique}
|
|
441
440
|
onChange={(e) => {
|
|
442
441
|
const newFields = [...fields];
|
|
443
|
-
newFields[index].unique = e
|
|
442
|
+
newFields[index].unique = e;
|
|
444
443
|
setFields(newFields);
|
|
445
444
|
}}
|
|
446
445
|
help={field.unique && t('modelcreator.unique.hint')}
|
|
@@ -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}
|
|
@@ -522,7 +521,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
522
521
|
disabled={modelLocked || (isLocalUser(me) && field.locked)}
|
|
523
522
|
onChange={(e) => {
|
|
524
523
|
const newFields = [...fields];
|
|
525
|
-
newFields[index].default = e
|
|
524
|
+
newFields[index].default = e;
|
|
526
525
|
setFields(newFields);
|
|
527
526
|
}}
|
|
528
527
|
/>
|
|
@@ -612,7 +611,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
612
611
|
checked={field.condition !== undefined}
|
|
613
612
|
onChange={(e) => {
|
|
614
613
|
const newFields = [...fields];
|
|
615
|
-
if (e
|
|
614
|
+
if (e) {
|
|
616
615
|
newFields[index].condition = null;
|
|
617
616
|
} else {
|
|
618
617
|
delete newFields[index].condition;
|
|
@@ -701,7 +700,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
701
700
|
checked={field.asMain}
|
|
702
701
|
onChange={(e) => {
|
|
703
702
|
const newFields = [...fields];
|
|
704
|
-
newFields[index].asMain = e
|
|
703
|
+
newFields[index].asMain = e;
|
|
705
704
|
setFields(newFields);
|
|
706
705
|
}}
|
|
707
706
|
help={field.asMain && t('modelcreator.asMain.hint')}
|
|
@@ -752,7 +751,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
752
751
|
checked={field.hiddenable}
|
|
753
752
|
onChange={(e) => {
|
|
754
753
|
const newFields = [...fields];
|
|
755
|
-
newFields[index].hiddenable = e
|
|
754
|
+
newFields[index].hiddenable = e;
|
|
756
755
|
setFields(newFields);
|
|
757
756
|
}}
|
|
758
757
|
help={field.unique && t('modelcreator.hiddenable.hint')}
|
|
@@ -768,7 +767,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
|
|
|
768
767
|
checked={field.anonymized}
|
|
769
768
|
onChange={(e) => {
|
|
770
769
|
const newFields = [...fields];
|
|
771
|
-
newFields[index].anonymized = e
|
|
770
|
+
newFields[index].anonymized = e;
|
|
772
771
|
setFields(newFields);
|
|
773
772
|
}}
|
|
774
773
|
/>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import {useMutation, useQuery, useQueryClient} from 'react-query';
|
|
3
3
|
import { Trans, useTranslation } from 'react-i18next';
|
|
4
|
-
import {
|
|
4
|
+
import {FaBoxOpen, FaChevronLeft, FaDownload, FaPlus, FaStar, FaTag, FaUser} from 'react-icons/fa';
|
|
5
5
|
import Button from './Button.jsx';
|
|
6
6
|
import { useNotificationContext } from './NotificationProvider.jsx';
|
|
7
7
|
import { useModelContext } from './contexts/ModelContext.jsx';
|
|
8
8
|
|
|
9
9
|
import './PackGallery.scss';
|
|
10
10
|
import {elementsPerPage} from "../../src/constants.js";
|
|
11
|
+
import Markdown from "react-markdown";
|
|
12
|
+
import {Dialog, DialogProvider} from "./Dialog.jsx";
|
|
13
|
+
import {TextField} from "./Field.jsx";
|
|
11
14
|
|
|
12
15
|
// --- API Fetching Functions ---
|
|
13
16
|
const fetchPacks = async (sortBy, lang) => {
|
|
@@ -117,7 +120,8 @@ const PackDetail = ({ packId, onBack }) => {
|
|
|
117
120
|
</div>
|
|
118
121
|
<div className="pack-content">
|
|
119
122
|
<h2><Trans i18nKey="packs.description">Description</Trans></h2>
|
|
120
|
-
|
|
123
|
+
|
|
124
|
+
<div className="description-content"><Markdown>{pack.description}</Markdown></div>
|
|
121
125
|
|
|
122
126
|
<h2><Trans i18nKey="packs.modelsIncluded">Modèles inclus</Trans></h2>
|
|
123
127
|
<p>{renderModelsList(pack.models)}</p>
|
|
@@ -127,6 +131,8 @@ const PackDetail = ({ packId, onBack }) => {
|
|
|
127
131
|
<pre>{JSON.stringify(pack.data, null, 2)}</pre>
|
|
128
132
|
</div>
|
|
129
133
|
</div>
|
|
134
|
+
|
|
135
|
+
|
|
130
136
|
</div>
|
|
131
137
|
);
|
|
132
138
|
};
|
|
@@ -151,11 +157,15 @@ const installPackMutationFn = async ({packId, lang}) => {
|
|
|
151
157
|
const PackGallery = () => {
|
|
152
158
|
const { t, i18n } = useTranslation();
|
|
153
159
|
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
160
|
+
const { addNotification } = useNotificationContext();
|
|
154
161
|
|
|
162
|
+
const [showManualInstallDialog, setShowManualInstallDialog] = useState(false);
|
|
163
|
+
const [manualPackJson, setManualPackJson] = useState('');
|
|
155
164
|
const [view, setView] = useState('list'); // 'list' ou 'detail'
|
|
156
165
|
const [selectedPackId, setSelectedPackId] = useState(null);
|
|
157
166
|
const [sortBy, setSortBy] = useState({ field: '_updatedAt', order: -1 });
|
|
158
167
|
|
|
168
|
+
const queryClient = useQueryClient()
|
|
159
169
|
const { data: packs, isLoading, isError } = useQuery(['packs', sortBy], () => fetchPacks(sortBy, lang));
|
|
160
170
|
|
|
161
171
|
const handleSelectPack = (packId) => {
|
|
@@ -163,6 +173,40 @@ const PackGallery = () => {
|
|
|
163
173
|
setView('detail');
|
|
164
174
|
};
|
|
165
175
|
|
|
176
|
+
const handleManualInstall = async () => {
|
|
177
|
+
try {
|
|
178
|
+
const packData = JSON.parse(manualPackJson.trim());
|
|
179
|
+
addNotification({ status: 'info', title: t('packs.install.started', `Installation du pack personnalisé...`) });
|
|
180
|
+
|
|
181
|
+
const response = await fetch('/api/packs/install', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: {
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
},
|
|
186
|
+
body: JSON.stringify({ packData, lang }),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
throw new Error(await response.text());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
addNotification({
|
|
194
|
+
status: 'completed',
|
|
195
|
+
title: t('datatable.pack.install.success', 'Pack installé avec succès !')
|
|
196
|
+
});
|
|
197
|
+
setShowManualInstallDialog(false);
|
|
198
|
+
setManualPackJson('');
|
|
199
|
+
queryClient.invalidateQueries(['packs', sortBy]); // Rafraîchir la liste
|
|
200
|
+
|
|
201
|
+
} catch (error) {
|
|
202
|
+
addNotification({
|
|
203
|
+
status: 'error',
|
|
204
|
+
title: t('datatable.pack.install.error', `Échec de l'installation`),
|
|
205
|
+
message: error.message
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
166
210
|
const handleBackToList = () => {
|
|
167
211
|
setSelectedPackId(null);
|
|
168
212
|
setView('list');
|
|
@@ -182,16 +226,52 @@ const PackGallery = () => {
|
|
|
182
226
|
<>
|
|
183
227
|
<div className="gallery-header">
|
|
184
228
|
<h1><Trans i18nKey="packs.galleryTitle">Galerie de Packs</Trans></h1>
|
|
185
|
-
<div className="
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<
|
|
229
|
+
<div className="header-actions">
|
|
230
|
+
<div className="sort-options">
|
|
231
|
+
<span><Trans i18nKey="packs.sortBy">Trier par :</Trans></span>
|
|
232
|
+
<Button onClick={() => setSortBy({ field: '_updatedAt', order: -1 })} className={sortBy.field === '_updatedAt' ? 'active' : ''}>
|
|
233
|
+
<Trans i18nKey="packs.sort.lastUpdated">Derniers ajouts</Trans>
|
|
234
|
+
</Button>
|
|
235
|
+
<Button onClick={() => setSortBy({ field: 'stars', order: -1 })} className={sortBy.field === 'stars' ? 'active' : ''}>
|
|
236
|
+
<Trans i18nKey="packs.sort.mostStarred">Plus populaires</Trans>
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
<Button
|
|
240
|
+
onClick={() => setShowManualInstallDialog(true)}
|
|
241
|
+
className="add-pack-button"
|
|
242
|
+
>
|
|
243
|
+
<FaPlus /> <Trans i18nKey="packs.manualInstall">Importer</Trans>
|
|
192
244
|
</Button>
|
|
193
245
|
</div>
|
|
194
246
|
</div>
|
|
247
|
+
{showManualInstallDialog && (
|
|
248
|
+
<DialogProvider>
|
|
249
|
+
<Dialog
|
|
250
|
+
isModal={true}
|
|
251
|
+
onClose={() => setShowManualInstallDialog(false)}
|
|
252
|
+
title={t('packs.manualInstall.title', 'Installation manuelle de pack')}
|
|
253
|
+
>
|
|
254
|
+
<div className="manual-install-dialog">
|
|
255
|
+
<p>
|
|
256
|
+
<Trans i18nKey="packs.manualInstall.instructions">
|
|
257
|
+
Collez ici le JSON de configuration du pack que vous souhaitez installer.
|
|
258
|
+
</Trans>
|
|
259
|
+
</p>
|
|
260
|
+
<TextField
|
|
261
|
+
multiline={true}
|
|
262
|
+
value={manualPackJson}
|
|
263
|
+
onChange={(e) => setManualPackJson(e.target.value)}
|
|
264
|
+
placeholder={t('packs.manualInstall.placeholder', '{"name": "Mon Pack", "description": "...", "models": [...], "data": [...]}')}
|
|
265
|
+
rows={15}
|
|
266
|
+
/>
|
|
267
|
+
<div className="flex actions right">
|
|
268
|
+
<Button onClick={() => setShowManualInstallDialog(false)}><Trans i18nKey={"btns.cancel"}>Annuler</Trans></Button>
|
|
269
|
+
<Button disabled={!manualPackJson.trim()} className="btn-primary" onClick={() => handleManualInstall()}><Trans i18nKey={"packs.install"}>Installer</Trans></Button>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</Dialog>
|
|
273
|
+
</DialogProvider>
|
|
274
|
+
)}
|
|
195
275
|
<div className="pack-list">
|
|
196
276
|
{packs.map(pack => (
|
|
197
277
|
<PackCard key={pack._id} pack={pack} onSelect={handleSelectPack} />
|
|
@@ -7,20 +7,38 @@
|
|
|
7
7
|
|
|
8
8
|
.gallery-header {
|
|
9
9
|
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
gap: 1rem ;
|
|
10
12
|
justify-content: space-between;
|
|
11
13
|
align-items: center;
|
|
12
14
|
padding-bottom: 1rem;
|
|
13
15
|
border-bottom: 1px solid #e0e0e0;
|
|
14
16
|
margin-bottom: 1rem;
|
|
17
|
+
|
|
15
18
|
h1 {
|
|
16
19
|
margin: 0;
|
|
20
|
+
flex: 1; // Permet au titre de prendre l'espace disponible
|
|
21
|
+
min-width: 200px; // Empêche le titre de devenir trop petit
|
|
17
22
|
}
|
|
18
|
-
|
|
23
|
+
|
|
24
|
+
.header-actions {
|
|
19
25
|
display: flex;
|
|
20
|
-
gap:
|
|
26
|
+
gap: 1rem;
|
|
21
27
|
align-items: center;
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
|
|
29
|
+
.sort-options {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 0.5rem;
|
|
32
|
+
align-items: center;
|
|
33
|
+
flex-wrap: wrap; // Pour le responsive
|
|
34
|
+
|
|
35
|
+
.btn.active {
|
|
36
|
+
color: white;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.add-pack-button {
|
|
41
|
+
white-space: nowrap; // Empêche le texte du bouton de se casser
|
|
24
42
|
}
|
|
25
43
|
}
|
|
26
44
|
}
|
|
@@ -153,4 +171,40 @@
|
|
|
153
171
|
}
|
|
154
172
|
}
|
|
155
173
|
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// À ajouter à la fin de votre fichier CSS
|
|
177
|
+
.manual-install-dialog {
|
|
178
|
+
width: 100%;
|
|
179
|
+
|
|
180
|
+
p {
|
|
181
|
+
margin-top: 0;
|
|
182
|
+
color: #666;
|
|
183
|
+
font-size: 0.9em;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
textarea {
|
|
187
|
+
width: 100%;
|
|
188
|
+
padding: 1rem;
|
|
189
|
+
border: 1px solid #ddd;
|
|
190
|
+
border-radius: 4px;
|
|
191
|
+
font-family: monospace;
|
|
192
|
+
resize: vertical;
|
|
193
|
+
min-height: 300px;
|
|
194
|
+
background-color: #f9f9f9;
|
|
195
|
+
color: #333;
|
|
196
|
+
|
|
197
|
+
&:focus {
|
|
198
|
+
outline: none;
|
|
199
|
+
border-color: var(--primary-color);
|
|
200
|
+
box-shadow: 0 0 0 2px rgba(var(--primary-color-rgb), 0.2);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.dialog-actions {
|
|
205
|
+
display: flex;
|
|
206
|
+
justify-content: flex-end;
|
|
207
|
+
gap: 0.5rem;
|
|
208
|
+
margin-top: 1rem;
|
|
209
|
+
}
|
|
156
210
|
}
|
package/client/src/RTETrans.jsx
CHANGED
|
@@ -53,6 +53,16 @@ const RTETrans = ({ value, onChange, field }) => {
|
|
|
53
53
|
};
|
|
54
54
|
const languagesToAdd = availableLangs.filter(lang => !existingLangs.includes(lang.code));
|
|
55
55
|
|
|
56
|
+
const confirmDelete = (e, lang) => {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
if( !confirm(t('rte.confirmLangDeletion', 'Confirm lang deletion ?')))
|
|
60
|
+
return;
|
|
61
|
+
const val = {...value};
|
|
62
|
+
delete val[lang];
|
|
63
|
+
onChange(val);
|
|
64
|
+
setActiveTab(Object.keys(val)[0] || null);
|
|
65
|
+
}
|
|
56
66
|
return (
|
|
57
67
|
<div className="rte-trans-container">
|
|
58
68
|
<div className="tabs-container flex items-center border-b border-gray-200">
|
|
@@ -64,6 +74,7 @@ const RTETrans = ({ value, onChange, field }) => {
|
|
|
64
74
|
title={availableLangs.find(f=>f.code===lang)?.name.value || ''}
|
|
65
75
|
>
|
|
66
76
|
{lang.toUpperCase()}
|
|
77
|
+
<button onClick={(e) => confirmDelete(e, lang)}>x</button>
|
|
67
78
|
</button>
|
|
68
79
|
))}
|
|
69
80
|
{languagesToAdd.length > 0 && (
|
|
@@ -187,8 +187,7 @@ const RelationValue = ({ field, data, align }) => {
|
|
|
187
187
|
if (f.type === 'code') {
|
|
188
188
|
let t;
|
|
189
189
|
try {
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
t = f.language === 'json' ? JSON.stringify(intVal[f.name], null, 2) : t = intVal[f.name].toString();
|
|
192
191
|
} catch (e) {
|
|
193
192
|
t = intVal[f.name].toString();
|
|
194
193
|
}
|
|
@@ -196,8 +195,8 @@ const RelationValue = ({ field, data, align }) => {
|
|
|
196
195
|
<dt>{columnName} {span}</dt>
|
|
197
196
|
<dd>
|
|
198
197
|
<CodeField onChange={() => {
|
|
199
|
-
}} language={
|
|
200
|
-
name={f.name} value={
|
|
198
|
+
}} language={f.language || 'json'}
|
|
199
|
+
name={f.name} value={f.language === 'json' ? JSON.stringify(intVal[f.name], null, 2) : t} />
|
|
201
200
|
</dd>
|
|
202
201
|
</>;
|
|
203
202
|
}
|
package/client/src/constants.js
CHANGED
package/client/src/core/data.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MONGO_OPERATORS} from "../constants.js";
|
|
2
|
+
import {getHost} from "../../../src/constants";
|
|
2
3
|
|
|
3
4
|
const isProd = import.meta.env.MODE === 'production';
|
|
4
5
|
export const urlData = isProd ? 'https://'+getHost()+'/' : 'http://localhost:7633/';
|
|
@@ -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();
|