data-primals-engine 1.4.0 → 1.4.2
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/README.md +856 -797
- package/client/src/AssistantChat.jsx +48 -5
- package/client/src/AssistantChat.scss +0 -1
- package/client/src/ChartConfigModal.jsx +34 -9
- package/client/src/Dashboard.jsx +396 -349
- package/client/src/DashboardChart.jsx +91 -12
- package/client/src/DataTable.jsx +817 -807
- package/client/src/Field.jsx +1784 -1782
- package/client/src/PackGallery.jsx +391 -290
- package/client/src/PackGallery.scss +231 -210
- package/client/src/contexts/UIContext.jsx +5 -2
- package/client/src/translations.js +188 -0
- package/package.json +19 -8
- package/src/config.js +2 -2
- package/src/core.js +21 -3
- package/src/engine.js +2 -1
- package/src/events.js +4 -3
- package/src/gameObject.js +1 -1
- package/src/middlewares/middleware-mongodb.js +3 -1
- package/src/modules/assistant/assistant.js +131 -42
- package/src/modules/auth-google/index.js +51 -0
- package/src/modules/auth-microsoft/index.js +82 -0
- package/src/modules/auth-saml/index.js +90 -0
- package/src/modules/bucket.js +3 -2
- package/src/modules/data/data.backup.js +374 -0
- package/src/modules/data/data.history.js +11 -8
- package/src/modules/data/data.js +190 -4543
- package/src/modules/data/data.operations.js +2790 -0
- package/src/modules/data/data.relations.js +687 -0
- package/src/modules/data/data.routes.js +1785 -1646
- package/src/modules/data/data.scheduling.js +274 -0
- package/src/modules/data/data.validation.js +245 -0
- package/src/modules/data/index.js +29 -1
- package/src/modules/user.js +2 -1
- package/src/modules/workflow.js +3 -2
- package/src/providers.js +110 -1
- package/src/services/stripe.js +3 -2
- package/src/sso.js +194 -0
- package/test/data.integration.test.js +3 -0
- package/test/user.test.js +1 -1
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
// client/src/components/AssistantChat.jsx
|
|
2
2
|
|
|
3
3
|
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
4
|
-
import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress } from 'react-icons/fa';
|
|
4
|
+
import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress, FaPlus } from 'react-icons/fa';
|
|
5
5
|
import './AssistantChat.scss';
|
|
6
6
|
import { useModelContext } from "./contexts/ModelContext.jsx";
|
|
7
7
|
import { Trans, useTranslation } from "react-i18next";
|
|
8
8
|
import Markdown from 'react-markdown';
|
|
9
9
|
import {useQueryClient} from "react-query";
|
|
10
10
|
import {providers} from "../../src/modules/assistant/constants.js";
|
|
11
|
+
import DashboardChart from "./DashboardChart.jsx";
|
|
12
|
+
import {useUI} from "./contexts/UIContext.jsx";
|
|
13
|
+
import Button from "./Button.jsx";
|
|
14
|
+
import {getUserHash, getUserId} from "../../src/data.js";
|
|
15
|
+
import {useNavigation} from "react-router";
|
|
16
|
+
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
17
|
+
import {useNavigate} from "react-router-dom";
|
|
18
|
+
import {DataTable} from "./DataTable.jsx";
|
|
11
19
|
|
|
12
20
|
const AssistantChat = ({ config }) => {
|
|
13
|
-
const { selectedModel } = useModelContext();
|
|
21
|
+
const { selectedModel, models } = useModelContext();
|
|
22
|
+
const { me } = useAuthContext();
|
|
23
|
+
const nav = useNavigate();
|
|
14
24
|
const { t } = useTranslation();
|
|
15
25
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
26
|
const [isMaximized, setIsMaximized] = useState(false);
|
|
@@ -19,6 +29,7 @@ const AssistantChat = ({ config }) => {
|
|
|
19
29
|
]);
|
|
20
30
|
const [input, setInput] = useState('');
|
|
21
31
|
const [isLoading, setIsLoading] = useState(false);
|
|
32
|
+
const { setChartToAdd } = useUI();
|
|
22
33
|
|
|
23
34
|
// NOUVEL ÉTAT : Stocke une action en attente de confirmation de l'utilisateur
|
|
24
35
|
const [pendingConfirmation, setPendingConfirmation] = useState(null);
|
|
@@ -74,7 +85,8 @@ const AssistantChat = ({ config }) => {
|
|
|
74
85
|
const botMessage = {
|
|
75
86
|
from: 'bot',
|
|
76
87
|
text: null,
|
|
77
|
-
actionDetails: null
|
|
88
|
+
actionDetails: null,
|
|
89
|
+
chartConfig: null// Pour stocker les détails de l'action à afficher
|
|
78
90
|
};
|
|
79
91
|
|
|
80
92
|
// Gérer le texte à afficher
|
|
@@ -104,7 +116,15 @@ const AssistantChat = ({ config }) => {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
// On ajoute le message à la liste uniquement s'il a du contenu textuel
|
|
107
|
-
if (
|
|
119
|
+
if (result.chartConfig) {
|
|
120
|
+
botMessage.chartConfig = result.chartConfig;
|
|
121
|
+
}
|
|
122
|
+
// Si des données tabulaires sont retournées
|
|
123
|
+
if (result.dataResult) {
|
|
124
|
+
botMessage.dataResult = result.dataResult;
|
|
125
|
+
}
|
|
126
|
+
// On ajoute le message à la liste uniquement s'il a du contenu textuel ou un graphique
|
|
127
|
+
if (botMessage.text || botMessage.chartConfig || botMessage.dataResult) {
|
|
108
128
|
setMessages(prev => [...prev, botMessage]);
|
|
109
129
|
}
|
|
110
130
|
|
|
@@ -225,7 +245,30 @@ const AssistantChat = ({ config }) => {
|
|
|
225
245
|
<div className="chat-messages">
|
|
226
246
|
{messages.map((msg, index) => (
|
|
227
247
|
<div key={index} className={`message ${msg.from}`}>
|
|
228
|
-
<Markdown>{msg.text}</Markdown>
|
|
248
|
+
{msg.text && <Markdown>{msg.text}</Markdown>}
|
|
249
|
+
{msg.chartConfig && (
|
|
250
|
+
<div className="chart-container">
|
|
251
|
+
<DashboardChart config={msg.chartConfig} />
|
|
252
|
+
<div className="chart-actions" style={{ marginTop: '8px', textAlign: 'right' }}>
|
|
253
|
+
<Button onClick={() => {
|
|
254
|
+
nav('/user/'+getUserHash(me)+'/dashboards');
|
|
255
|
+
setChartToAdd(msg.chartConfig);
|
|
256
|
+
}} title={t('assistant.addToDashboard', 'Ajouter au tableau de bord')}>
|
|
257
|
+
<FaPlus />
|
|
258
|
+
<span style={{ marginLeft: '8px' }}>
|
|
259
|
+
{t('assistant.addToDashboard', 'Ajouter au tableau de bord')}
|
|
260
|
+
</span>
|
|
261
|
+
</Button>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* NOUVEAU : Affichage des données tabulaires */}
|
|
267
|
+
{msg.dataResult && (
|
|
268
|
+
<div className="data-table-container">
|
|
269
|
+
<DataTable model={models.find(f => f.name === msg.dataResult.model)} advanced={false} data={msg.dataResult.data} />
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
229
272
|
{msg.actionDetails && (
|
|
230
273
|
<div className="action-details">
|
|
231
274
|
{msg.actionDetails.model && (
|
|
@@ -55,6 +55,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
55
55
|
const [chartType, setChartType] = useState('bar');
|
|
56
56
|
const [chartAggregationType, setChartAggregationType] = useState('count');
|
|
57
57
|
const [chartTitle, setChartTitle] = useState('');
|
|
58
|
+
const [timeUnit, setTimeUnit] = useState('day');
|
|
58
59
|
|
|
59
60
|
const currentModel = models.find(f => f.name === selectedModel && f._user === me.username);
|
|
60
61
|
|
|
@@ -74,6 +75,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
74
75
|
setColorField(initialConfig.chartBackgroundColor || null);
|
|
75
76
|
setGroupByLabelField(initialConfig.groupByLabelField || '');
|
|
76
77
|
setChartAggregationType(initialConfig.aggregationType || 'count');
|
|
78
|
+
setTimeUnit(initialConfig.timeUnit || 'day');
|
|
77
79
|
// Note: modelFields et relatedModelFields seront chargés par les autres useEffects
|
|
78
80
|
// déclenchés par le changement de selectedModel et groupByField.
|
|
79
81
|
} else {
|
|
@@ -87,6 +89,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
87
89
|
setGroupByField('');
|
|
88
90
|
setGroupByLabelField('');
|
|
89
91
|
setChartAggregationType('count');
|
|
92
|
+
setTimeUnit('day');
|
|
90
93
|
setModelFields([]);
|
|
91
94
|
setRelatedModelFields([]);
|
|
92
95
|
}
|
|
@@ -155,6 +158,9 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
155
158
|
const isRelationGroupBy = isGroupingChart && modelFields.find(f => f.name === groupByField)?.type === 'relation';
|
|
156
159
|
const isYAxisRequiredForValidation = chartAggregationType && !['count'].includes(chartAggregationType);
|
|
157
160
|
|
|
161
|
+
const xAxisFieldDefinition = modelFields.find(f => f.name === xAxisField);
|
|
162
|
+
const isTemporal = !isGroupingChart && xAxisFieldDefinition && ['date', 'datetime'].includes(xAxisFieldDefinition.type);
|
|
163
|
+
|
|
158
164
|
// handleSave (inchangé - envoie l'état actuel)
|
|
159
165
|
const handleSave = () => {
|
|
160
166
|
const isValid = selectedModel && chartType && chartTitle &&
|
|
@@ -178,6 +184,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
178
184
|
aggregationType: chartAggregationType,
|
|
179
185
|
chartBackgroundColor: colorField,
|
|
180
186
|
filter,
|
|
187
|
+
timeUnit: isTemporal ? timeUnit : undefined,
|
|
181
188
|
});
|
|
182
189
|
} else {
|
|
183
190
|
let errorMsg = t('chartConfigModal.fillFields', "Veuillez remplir tous les champs requis.");
|
|
@@ -321,15 +328,33 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
321
328
|
)}
|
|
322
329
|
</>
|
|
323
330
|
) : (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
<>
|
|
332
|
+
<SelectField
|
|
333
|
+
label={t('chartConfigModal.xAxis', 'Axe X')}
|
|
334
|
+
value={xAxisField}
|
|
335
|
+
onChange={(item) => setXAxisField(item.value)}
|
|
336
|
+
items={[{label: t('selectPlaceholder', 'Choisir...'), value: ''}, ...xAxisOptions]}
|
|
337
|
+
required={!isGroupingChart}
|
|
338
|
+
disabled={xAxisOptions.length === 0}
|
|
339
|
+
hint={xAxisOptions.length === 0 ? t('chartConfigModal.noXAxisFields', 'Aucun champ utilisable pour l\'axe X') : ''}
|
|
340
|
+
/>
|
|
341
|
+
{isTemporal && (
|
|
342
|
+
<SelectField
|
|
343
|
+
label={t('charts.timeUnit', 'Échelle de temps')}
|
|
344
|
+
value={timeUnit}
|
|
345
|
+
onChange={(item) => setTimeUnit(item.value)}
|
|
346
|
+
items={[
|
|
347
|
+
{ value: 'minute', label: t('time.minute', 'Minute') },
|
|
348
|
+
{ value: 'hour', label: t('time.hour', 'Heure') },
|
|
349
|
+
{ value: 'day', label: t('time.day', 'Jour') },
|
|
350
|
+
{ value: 'week', label: t('time.week', 'Semaine') },
|
|
351
|
+
{ value: 'month', label: t('time.month', 'Mois') },
|
|
352
|
+
{ value: 'year', label: t('time.year', 'Année') },
|
|
353
|
+
]}
|
|
354
|
+
hint={t('charts.timeUnitHelp', "Définit l'unité de regroupement pour l'axe des dates.")}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
357
|
+
</>
|
|
333
358
|
)}
|
|
334
359
|
|
|
335
360
|
{/* Axe Y (inchangé) */}
|