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.
Files changed (40) hide show
  1. package/README.md +856 -797
  2. package/client/src/AssistantChat.jsx +48 -5
  3. package/client/src/AssistantChat.scss +0 -1
  4. package/client/src/ChartConfigModal.jsx +34 -9
  5. package/client/src/Dashboard.jsx +396 -349
  6. package/client/src/DashboardChart.jsx +91 -12
  7. package/client/src/DataTable.jsx +817 -807
  8. package/client/src/Field.jsx +1784 -1782
  9. package/client/src/PackGallery.jsx +391 -290
  10. package/client/src/PackGallery.scss +231 -210
  11. package/client/src/contexts/UIContext.jsx +5 -2
  12. package/client/src/translations.js +188 -0
  13. package/package.json +19 -8
  14. package/src/config.js +2 -2
  15. package/src/core.js +21 -3
  16. package/src/engine.js +2 -1
  17. package/src/events.js +4 -3
  18. package/src/gameObject.js +1 -1
  19. package/src/middlewares/middleware-mongodb.js +3 -1
  20. package/src/modules/assistant/assistant.js +131 -42
  21. package/src/modules/auth-google/index.js +51 -0
  22. package/src/modules/auth-microsoft/index.js +82 -0
  23. package/src/modules/auth-saml/index.js +90 -0
  24. package/src/modules/bucket.js +3 -2
  25. package/src/modules/data/data.backup.js +374 -0
  26. package/src/modules/data/data.history.js +11 -8
  27. package/src/modules/data/data.js +190 -4543
  28. package/src/modules/data/data.operations.js +2790 -0
  29. package/src/modules/data/data.relations.js +687 -0
  30. package/src/modules/data/data.routes.js +1785 -1646
  31. package/src/modules/data/data.scheduling.js +274 -0
  32. package/src/modules/data/data.validation.js +245 -0
  33. package/src/modules/data/index.js +29 -1
  34. package/src/modules/user.js +2 -1
  35. package/src/modules/workflow.js +3 -2
  36. package/src/providers.js +110 -1
  37. package/src/services/stripe.js +3 -2
  38. package/src/sso.js +194 -0
  39. package/test/data.integration.test.js +3 -0
  40. 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 // Pour stocker les détails de l'action à afficher
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 (botMessage.text) {
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 && (
@@ -125,7 +125,6 @@ $input-height: 50px;
125
125
  gap: 12px;
126
126
 
127
127
  .message {
128
- max-width: 80%;
129
128
  p {
130
129
  padding: 4px 8px;
131
130
  border-radius: 18px;
@@ -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
- <SelectField
325
- label={t('chartConfigModal.xAxis', 'Axe X')}
326
- value={xAxisField}
327
- onChange={(item) => setXAxisField(item.value)}
328
- items={[{label: t('selectPlaceholder', 'Choisir...'), value: ''}, ...xAxisOptions]}
329
- required={!isGroupingChart}
330
- disabled={xAxisOptions.length === 0}
331
- hint={xAxisOptions.length === 0 ? t('chartConfigModal.noXAxisFields', 'Aucun champ utilisable pour l\'axe X') : ''}
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é) */}