data-primals-engine 1.4.1 → 1.4.3

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 (49) hide show
  1. package/README.md +37 -22
  2. package/client/package-lock.json +33 -0
  3. package/client/package.json +1 -0
  4. package/client/src/App.scss +12 -4
  5. package/client/src/AssistantChat.jsx +48 -5
  6. package/client/src/AssistantChat.scss +0 -1
  7. package/client/src/ChartConfigModal.jsx +34 -9
  8. package/client/src/ConditionBuilder.jsx +1 -1
  9. package/client/src/ConditionBuilder2.jsx +2 -1
  10. package/client/src/Dashboard.jsx +396 -349
  11. package/client/src/DashboardChart.jsx +91 -12
  12. package/client/src/DataEditor.jsx +376 -368
  13. package/client/src/DataLayout.jsx +3 -2
  14. package/client/src/DataTable.jsx +60 -31
  15. package/client/src/Field.jsx +1788 -1782
  16. package/client/src/GeolocationField.jsx +94 -0
  17. package/client/src/ModelCreatorField.jsx +1 -0
  18. package/client/src/RelationField.jsx +2 -2
  19. package/client/src/constants.js +2 -2
  20. package/client/src/contexts/UIContext.jsx +5 -2
  21. package/client/src/filter.js +0 -155
  22. package/client/src/translations.js +16828 -16750
  23. package/package.json +10 -2
  24. package/src/config.js +2 -2
  25. package/src/constants.js +262 -4
  26. package/src/core.js +21 -3
  27. package/src/defaultModels.js +12 -12
  28. package/src/engine.js +7 -1
  29. package/src/events.js +4 -3
  30. package/src/filter.js +272 -260
  31. package/src/i18n.js +187 -177
  32. package/src/middlewares/middleware-mongodb.js +3 -1
  33. package/src/modules/assistant/assistant.js +131 -42
  34. package/src/modules/bucket.js +3 -2
  35. package/src/modules/data/data.backup.js +374 -0
  36. package/src/modules/data/data.core.js +2 -1
  37. package/src/modules/data/data.history.js +11 -8
  38. package/src/modules/data/data.js +190 -4551
  39. package/src/modules/data/data.operations.js +3000 -0
  40. package/src/modules/data/data.relations.js +687 -0
  41. package/src/modules/data/data.routes.js +132 -38
  42. package/src/modules/data/data.scheduling.js +274 -0
  43. package/src/modules/data/data.validation.js +248 -0
  44. package/src/modules/data/index.js +29 -1
  45. package/src/modules/user.js +2 -1
  46. package/src/modules/workflow.js +3 -2
  47. package/src/services/stripe.js +3 -2
  48. package/swagger-en.yml +133 -0
  49. package/test/model.integration.test.js +377 -221
@@ -13,7 +13,7 @@ import i18n from 'i18next';
13
13
 
14
14
  import 'chartjs-adapter-date-fns';
15
15
  import {useModelContext} from "./contexts/ModelContext.jsx";
16
- import {isDate} from "../../src/core.js";
16
+ import {isDate, stringToHslColor} from "../../src/core.js";
17
17
  import useWindowSize from "./hooks/useWindowSize.js";
18
18
  import useDebounce from "./hooks/useDebounce.js";
19
19
 
@@ -73,6 +73,8 @@ const formatDateLabel = (label, locale = 'fr-FR') => {
73
73
  return String(label);
74
74
  };
75
75
 
76
+
77
+
76
78
  const parsePotentiallyFormattedDate = (str) => {
77
79
  if (typeof str !== 'string') return null;
78
80
  let match = str.match(/^(\d{2})\/(\d{2})\/(\d{4})(?: (\d{2}):(\d{2}))?$/);
@@ -150,7 +152,7 @@ const processDataForChart = (inputData, config, t, timed, lang) => {
150
152
  barPercentage: timed && config.type === 'bar' ? 0.9 : undefined,
151
153
  categoryPercentage: timed && config.type === 'bar' ? 0.8 : undefined,
152
154
  backgroundColor: (type === 'pie' || type === 'doughnut')
153
- ? ['rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)', 'rgba(255, 206, 86, 0.7)', 'rgba(75, 192, 192, 0.7)', 'rgba(153, 102, 255, 0.7)', 'rgba(255, 159, 64, 0.7)', 'rgba(199, 199, 199, 0.7)', 'rgba(83, 102, 89, 0.7)']
155
+ ? labels.map(label => stringToHslColor(String(label))) // Génération dynamique
154
156
  : config.chartBackgroundColor,
155
157
  borderColor: (type === 'pie' || type === 'doughnut')
156
158
  ? '#fff'
@@ -195,6 +197,14 @@ const DashboardChart = ({ config }) => { // config peut maintenant contenir conf
195
197
  const fieldDefinition = modelDefinition?.fields.find(f => f.name === config.xAxis);
196
198
  const timed = fieldDefinition && ['datetime', 'date'].includes(fieldDefinition.type);
197
199
 
200
+ // État local pour gérer l'échelle de temps de manière interactive
201
+ const [localTimeUnit, setLocalTimeUnit] = useState(config.timeUnit || 'day');
202
+
203
+ // S'assurer que l'état local est synchronisé si la config du graphique change
204
+ useEffect(() => {
205
+ setLocalTimeUnit(config.timeUnit || 'day');
206
+ }, [config.timeUnit]);
207
+
198
208
  const isGroupingChart = config && ['pie', 'doughnut'].includes(config.type);
199
209
  const requiresYAxisForValidation = config && config.aggregationType && config.aggregationType !== 'count';
200
210
  const isValidConfig = config && config.model && config.type && config.title && config.aggregationType &&
@@ -262,25 +272,45 @@ const DashboardChart = ({ config }) => { // config peut maintenant contenir conf
262
272
  },
263
273
  };
264
274
 
275
+ const timeUnit = localTimeUnit; // On utilise l'état local interactif
276
+
277
+ const getTooltipFormat = (unit) => {
278
+ switch(unit) {
279
+ case 'year': return 'yyyy';
280
+ case 'month': return 'MMM yyyy';
281
+ case 'hour': return "dd MMM, HH'h'";
282
+ case 'minute': return 'dd MMM, HH:mm';
283
+ case 'day':
284
+ case 'week':
285
+ default: return 'dd MMM yyyy';
286
+ }
287
+ };
288
+
265
289
  const axisOptions = {
266
290
  ...baseOptions,
267
291
  scales: {
268
292
  x: {
269
293
  type: timed ? 'time' : 'category',
270
294
  time: timed ? {
271
- tooltipFormat: 'dd/MM/yyyy HH:mm',
272
- unit: 'minute',
295
+ tooltipFormat: getTooltipFormat(timeUnit),
296
+ unit: timeUnit,
273
297
  } : {},
274
298
  ticks: timed ? {
275
299
  autoSkip: true,
276
300
  maxTicksLimit: 20,
277
301
  callback: function (value) {
278
- const d = new Date();
279
- d.setTime(value);
280
- return d.toLocaleDateString(lang, {
281
- minute: '2-digit',
282
- hour: '2-digit'
283
- });
302
+ const d = new Date(value);
303
+ let options;
304
+ switch(timeUnit) {
305
+ case 'year': options = { year: 'numeric' }; break;
306
+ case 'month': options = { month: 'short', year: 'numeric' }; break;
307
+ case 'day':
308
+ case 'week': options = { day: 'numeric', month: 'short' }; break;
309
+ case 'hour':
310
+ case 'minute':
311
+ default: options = { hour: '2-digit', minute: '2-digit' }; break;
312
+ }
313
+ return new Intl.DateTimeFormat(lang, options).format(d);
284
314
  }
285
315
  } : {}
286
316
  },
@@ -289,7 +319,32 @@ const DashboardChart = ({ config }) => { // config peut maintenant contenir conf
289
319
  }
290
320
  },
291
321
  };
292
- const noAxisOptions = { ...baseOptions, scales: undefined };
322
+
323
+ const noAxisOptions = {
324
+ ...baseOptions,
325
+ scales: undefined,
326
+ plugins: {
327
+ ...baseOptions.plugins, // Conserve la légende et le titre de base
328
+ tooltip: {
329
+ callbacks: {
330
+ // Le titre de l'infobulle doit être le libellé de la section survolée (ex: "Catégorie A")
331
+ title: (tooltipItems) => tooltipItems[0]?.label || '',
332
+
333
+ // Le corps de l'infobulle doit afficher la valeur et son contexte (ex: "Count: 123")
334
+ // et non le titre général du graphique.
335
+ label: (tooltipItem) => {
336
+ const aggregationLabel = t('aggregation.' + config.aggregationType, config.aggregationType);
337
+ const value = tooltipItem.formattedValue || tooltipItem.raw;
338
+ if (config.yAxis && config.aggregationType !== 'count') {
339
+ const yAxisLabel = t(`field_${config.model}_${config.yAxis}`, config.yAxis);
340
+ return `${aggregationLabel} (${yAxisLabel}): ${value}`;
341
+ }
342
+ return `${aggregationLabel}: ${value}`;
343
+ }
344
+ }
345
+ }
346
+ }
347
+ };
293
348
 
294
349
  const data = !isResizing ? chartData : { labels: [], datasets: [] };
295
350
  try {
@@ -311,7 +366,7 @@ const DashboardChart = ({ config }) => { // config peut maintenant contenir conf
311
366
  </div>
312
367
  );
313
368
  }
314
- }, [config, t, chartData, isResizing]);
369
+ }, [config, t, chartData, isResizing, lang, localTimeUnit]);
315
370
 
316
371
  if (!isValidConfig) {
317
372
  return (
@@ -353,6 +408,30 @@ const DashboardChart = ({ config }) => { // config peut maintenant contenir conf
353
408
 
354
409
  return (
355
410
  <div className="dashboard-chart-container" style={containerStyle}>
411
+ {/* Ajout du sélecteur d'échelle de temps directement sur le graphique */}
412
+ {timed && (
413
+ <div style={{ position: 'absolute', top: '5px', right: '5px', zIndex: 10 }}>
414
+ <select
415
+ value={localTimeUnit}
416
+ onChange={(e) => setLocalTimeUnit(e.target.value)}
417
+ style={{
418
+ padding: '2px 4px',
419
+ borderRadius: '4px',
420
+ border: '1px solid #ccc',
421
+ backgroundColor: 'white',
422
+ fontSize: '0.8em'
423
+ }}
424
+ title={t('charts.timeUnit', 'Échelle de temps')}
425
+ >
426
+ <option value="minute">{t('time.minute', 'Minute')}</option>
427
+ <option value="hour">{t('time.hour', 'Heure')}</option>
428
+ <option value="day">{t('time.day', 'Jour')}</option>
429
+ <option value="week">{t('time.week', 'Semaine')}</option>
430
+ <option value="month">{t('time.month', 'Mois')}</option>
431
+ <option value="year">{t('time.year', 'Année')}</option>
432
+ </select>
433
+ </div>
434
+ )}
356
435
  {renderChart}
357
436
  </div>
358
437
  );