data-primals-engine 1.4.3 → 1.5.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.
Files changed (77) hide show
  1. package/README.md +913 -867
  2. package/client/package-lock.json +49 -0
  3. package/client/package.json +1 -0
  4. package/client/src/AddWidgetTypeModal.jsx +47 -43
  5. package/client/src/App.jsx +3 -7
  6. package/client/src/App.scss +25 -3
  7. package/client/src/AssistantChat.jsx +363 -323
  8. package/client/src/AssistantChat.scss +30 -12
  9. package/client/src/Dashboard.jsx +480 -396
  10. package/client/src/Dashboard.scss +1 -1
  11. package/client/src/DashboardHtmlViewItem.jsx +147 -0
  12. package/client/src/DashboardView.jsx +104 -19
  13. package/client/src/DataEditor.jsx +12 -5
  14. package/client/src/DataLayout.jsx +805 -762
  15. package/client/src/DataLayout.scss +14 -0
  16. package/client/src/DataTable.jsx +63 -77
  17. package/client/src/Dialog.scss +1 -1
  18. package/client/src/Field.jsx +591 -322
  19. package/client/src/FlexDataRenderer.jsx +2 -0
  20. package/client/src/FlexTreeUtils.js +1 -1
  21. package/client/src/FlexViewCard.jsx +44 -0
  22. package/client/src/HistoryDialog.jsx +47 -14
  23. package/client/src/HtmlViewBuilderModal.jsx +91 -0
  24. package/client/src/HtmlViewBuilderModal.scss +18 -0
  25. package/client/src/HtmlViewCard.jsx +44 -0
  26. package/client/src/HtmlViewCard.scss +35 -0
  27. package/client/src/KPIDialog.jsx +11 -1
  28. package/client/src/KanbanCard.jsx +1 -2
  29. package/client/src/ModelCreator.jsx +6 -6
  30. package/client/src/ModelCreatorField.jsx +74 -31
  31. package/client/src/ModelList.jsx +93 -54
  32. package/client/src/Notification.jsx +136 -136
  33. package/client/src/Notification.scss +0 -18
  34. package/client/src/Pagination.jsx +5 -3
  35. package/client/src/RelationField.jsx +354 -258
  36. package/client/src/RelationSelectorWidget.jsx +173 -0
  37. package/client/src/constants.js +1 -1
  38. package/client/src/contexts/ModelContext.jsx +10 -1
  39. package/client/src/contexts/UIContext.jsx +72 -63
  40. package/client/src/filter.js +262 -212
  41. package/client/src/hooks/useTutorials.jsx +62 -65
  42. package/client/src/hooks/useValidation.js +75 -0
  43. package/client/src/translations.js +26 -24
  44. package/package.json +3 -1
  45. package/perf/README.md +147 -0
  46. package/perf/artillery-hooks.js +37 -0
  47. package/perf/perf-shot-hardwork.yml +84 -0
  48. package/perf/perf-shot-search.yml +45 -0
  49. package/perf/setup.yml +26 -0
  50. package/server.js +1 -1
  51. package/src/constants.js +3 -28
  52. package/src/core.js +15 -1
  53. package/src/data.js +1 -1
  54. package/src/defaultModels.js +63 -7
  55. package/src/email.js +5 -2
  56. package/src/engine.js +5 -3
  57. package/src/filter.js +5 -3
  58. package/src/i18n.js +710 -10
  59. package/src/modules/assistant/assistant.js +151 -19
  60. package/src/modules/bucket.js +14 -16
  61. package/src/modules/data/data.backup.js +11 -8
  62. package/src/modules/data/data.core.js +118 -92
  63. package/src/modules/data/data.history.js +531 -492
  64. package/src/modules/data/data.js +9 -56
  65. package/src/modules/data/data.operations.js +3282 -2999
  66. package/src/modules/data/data.relations.js +686 -686
  67. package/src/modules/data/data.routes.js +118 -24
  68. package/src/modules/data/data.scheduling.js +2 -1
  69. package/src/modules/data/data.validation.js +85 -3
  70. package/src/modules/file.js +247 -236
  71. package/src/modules/user.js +4 -1
  72. package/src/modules/workflow.js +9 -10
  73. package/src/openai.jobs.js +2 -0
  74. package/src/packs.js +5482 -5461
  75. package/src/providers.js +22 -7
  76. package/test/data.integration.test.js +136 -2
  77. package/test/import_export.integration.test.js +1 -1
@@ -0,0 +1,14 @@
1
+
2
+
3
+ /* website*/
4
+ #content .datalayout {
5
+ padding: 0;
6
+
7
+ h1 {
8
+ margin: 24px 8px;
9
+ }
10
+
11
+ h2 {
12
+ margin: 20px 4px 0 4px;
13
+ }
14
+ }
@@ -11,7 +11,7 @@ import {Event} from "../../src/events.js";
11
11
  import {
12
12
  FaBook,
13
13
  FaCopy,
14
- FaDatabase, FaEraser,
14
+ FaDatabase, FaEraser, FaEye,
15
15
  FaFileExport,
16
16
  FaFileImport,
17
17
  FaFilter, FaHistory,
@@ -50,16 +50,15 @@ import {Zoom} from "yet-another-react-lightbox/plugins";
50
50
  import Lightbox from "yet-another-react-lightbox";
51
51
 
52
52
  import "./DataTable.scss"
53
- import useLocalStorage from "./hooks/useLocalStorage.js";
54
53
  import TutorialsMenu from "../src/TutorialsMenu.jsx";
55
54
  import {useTutorials} from "./hooks/useTutorials.jsx";
56
- import PackGallery from "./PackGallery.jsx";
57
55
  import {HiddenableCell} from "./HiddenableCell.jsx";
58
56
  import ConditionBuilder from "./ConditionBuilder.jsx";
59
57
  import {pagedFilterToMongoConds} from "./filter.js";
60
58
  import {isConditionMet} from "../../src/filter";
61
59
  import {DataImporter} from "./DataImporter.jsx";
62
60
  import {HistoryDialog} from "./HistoryDialog.jsx";
61
+ import {Config} from "../../src/config.js";
63
62
 
64
63
  const Header = ({
65
64
  reversed = false,
@@ -72,7 +71,8 @@ const Header = ({
72
71
  onChangeFilterValue,
73
72
  setFilterValues,
74
73
  filterValues,
75
- advanced=true
74
+ advanced=true,
75
+ selectionMode=false
76
76
  }) => {
77
77
 
78
78
  const {t} = useTranslation()
@@ -92,12 +92,26 @@ const Header = ({
92
92
  const handleAdvancedFilter = () => {
93
93
  setAdvancedFilterVisible(true);
94
94
  }
95
+
96
+ const [iconFilterActive, setIconFilterActive] = useState(true);
97
+ useEffect(() => {
98
+ if (pagedFilters && selectedModel?.name) {
99
+ const modelFilters = pagedFilters[selectedModel.name] || {};
100
+ const isActive = Object.keys(modelFilters)
101
+ .some(fieldKey => {
102
+ const fieldFilter = modelFilters[fieldKey];
103
+ return fieldFilter && Object.keys(fieldFilter).length > 0;
104
+ });
105
+ setIconFilterActive(isActive);
106
+ }
107
+ }, [pagedFilters, selectedModel?.name]);
108
+
95
109
  return <><tr className={reversed ? ' reversed' : ''}>
96
110
  {advanced && (<th className={"mini"}>
97
111
  <div className="flex flex-row">
98
112
 
99
- <Button onClick={handleFilter} className={filterActive ? ' active' : ''}><FaFilter/></Button>
100
- {filterActive && <Button onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
113
+ <Button type={"button"} onClick={handleFilter} className={iconFilterActive ? ' active' : ''}><FaFilter/></Button>
114
+ {filterActive && <Button type={"button"} onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
101
115
  <CheckboxField checkbox={true} checked={checkedItems?.length === data.length} onChange={e => {
102
116
  if (checkedItems?.length === data.length) {
103
117
  setCheckedItems([]);
@@ -117,7 +131,7 @@ const Header = ({
117
131
  setPagedFilters(pagedFilters => ({
118
132
  ...pagedFilters,
119
133
  [model.name]: c || pagedFilters[model.name] || {} }));
120
- }} initialValue={{ $and: pagedFilterToMongoConds(pagedFilters, model)}} models={models} model={model.name} checkedItems={checkedItems} setCheckedItems={setCheckedItems} data={data} filterActive={filterActive} onChangeFilterValue={onChangeFilterValue} setFilterValues={setFilterValues}/>
134
+ }} initialValue={{ $and: pagedFilterToMongoConds(pagedFilters, model)}} models={models} model={model} checkedItems={checkedItems} setCheckedItems={setCheckedItems} data={data} filterActive={filterActive} onChangeFilterValue={onChangeFilterValue} setFilterValues={setFilterValues}/>
121
135
  <Button onClick={() =>{
122
136
  setPagedFilters(pagedFilters => ({
123
137
  ...pagedFilters,
@@ -144,11 +158,11 @@ const Header = ({
144
158
  </th>;
145
159
 
146
160
  totalCol++;
147
- return <FilterField advanced={advanced} reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
161
+ return <FilterField key={"datatable-th-"+model.name+":"+field.name} advanced={advanced} reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
148
162
  model={model} field={field} active={filterActive}
149
163
  onChangeFilterValue={onChangeFilterValue}/>;
150
164
  })}
151
- {advanced && (<th><Trans i18nKey="actions">Actions</Trans>
165
+ {advanced && !selectionMode && (<th><Trans i18nKey="actions">Actions</Trans>
152
166
  {Object.keys(pagedFilters[model.name] || {})
153
167
  .filter(f=> Object.keys(pagedFilters[model.name]?.[f] || {}).length > 0).length > 0 && <div>
154
168
  <button onClick={() => {
@@ -246,9 +260,9 @@ export function DataTable({
246
260
  onShowAPI,
247
261
  filterValues,
248
262
  setFilterValues = () => {},
249
- data: propData, // NOUVEAU: Accepter les données via les props
250
- advanced=true
251
-
263
+ data: propData,
264
+ advanced= true,
265
+ selectionMode= false
252
266
  }) {
253
267
  const {
254
268
  models,
@@ -272,20 +286,9 @@ export function DataTable({
272
286
  const isDataLoaded = true;
273
287
  const [importVisible, setImportVisible] = useState(false);
274
288
  const [filterActive, setFilterActive] = useState(false)
275
-
276
- const [showPackGallery, setShowPackGallery] = useState(false);
277
289
  const [showExportDialog, setExportDialogVisible] = useState(false);
278
290
 
279
-
280
291
  const [selectedRow, setSelectedRow] = useState(null);
281
-
282
- const [showAddPack, setAddPackVisible] = useState(false);
283
- const handleAddPack = () => {
284
- setAddPackVisible(!showAddPack);
285
- }
286
- const handleShowPacks = () => {
287
- setShowPackGallery(true);
288
- };
289
292
  const handleDelete = async (item) => {
290
293
  if (model && item && item._id) { // Assurez-vous d'avoir un identifiant unique pour chaque élément
291
294
  try {
@@ -355,7 +358,9 @@ export function DataTable({
355
358
  params.append("_user", getUserId(me));
356
359
 
357
360
  // Cas de la table de données : requête paginée
358
- params.append('limit', maxRequestData + '');
361
+
362
+ const m = Config.Get('maxRequestData', maxRequestData);
363
+ params.append('limit', m + '');
359
364
  params.append('attachment', '1');
360
365
  params.append("depth", data.depth+"");
361
366
  if( data.withModels )
@@ -446,17 +451,12 @@ export function DataTable({
446
451
  setFilterActive(!filterActive);
447
452
  }
448
453
 
449
-
450
454
  const [lightboxIndex, setLightboxIndex] = useState(0);
451
455
  const [lightboxOpened, setLightboxOpened] = useState(false);
452
456
  const [lightboxSlides, setLightboxSlides] = useState([]);
453
- const [tutorialDialogVisible, setTutorialDialogVisible] = useState(false);
454
457
 
455
458
  const [newPackName, setNewPackName] = useState(''); // pack
456
459
 
457
- const handleShowTutorialMenu = () => {
458
- setTutorialDialogVisible(!tutorialDialogVisible);
459
- }
460
460
 
461
461
  if (!model)
462
462
  return <></>;
@@ -477,32 +477,23 @@ export function DataTable({
477
477
 
478
478
  onDuplicateData(dataToDuplicate);
479
479
  };
480
+
481
+ const desc = t(`model_description_${selectedModel.name}`, selectedModel.description);
480
482
  return (
481
483
  <div className={`datatable${filterActive ? ' filter-active' : ''}`}>
482
- {advanced && <div className="flex actions flex-left">
483
- <Button onClick={() => onAddData(model)}><FaPlus/><Trans i18nKey="btns.addData">Ajouter une
484
- donnée</Trans></Button>
484
+ {advanced && !selectionMode && <div className="flex actions">
485
+ {t(`model_${selectedModel?.name}`, selectedModel?.name) !== selectedModel?.name && (
486
+ <span className="badge"><strong>model</strong> : {selectedModel?.name}</span>)}
487
+ {selectedModel.name === 'dashboard' && <Button className={"btn"} onClick={() => {
488
+ nav('/user/'+getUserHash(me)+'/dashboards');
489
+ }}><FaEye /> Tableaux de bord</Button> }
490
+ {desc && <p className="model-desc hint">{desc}</p>}
485
491
  <Button onClick={handleImport} title={t("btns.import")}><FaFileImport/><Trans
486
492
  i18nKey="btns.import">Importer</Trans></Button>
487
- <Button disabled={isLoading} onClick={handleExport} title={t("btns.export")}><FaFileExport/><Trans
488
- i18nKey="btns.export">Exporter</Trans></Button>
489
- <Button className="tourStep-import-datapack" onClick={handleShowPacks} title={t("btns.addPack")}><FaPlus/><Trans
490
- i18nKey="btns.addPack">Packs...</Trans></Button>
491
-
492
- <Button className={"tourStep-tutorials " + (/^demo[0-9]{1,2}$/.test(me.username) ? "btn-primary" : "btn")} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaPlus/><Trans
493
- i18nKey="btns.showTutos">Tutoriels</Trans></Button>
493
+ <Button disabled={isLoading} onClick={handleExport} title={t("btns.export")}><FaFileExport/><Trans i18nKey="btns.export">Exporter</Trans></Button>
494
494
  {!/^demo[0-9]{1,2}$/.test(me.username) && (<Button onClick={handleBackup} title={t("btns.backup")}><FaDatabase/><Trans
495
495
  i18nKey="btns.backup">Backup</Trans></Button>)}
496
- <DialogProvider>
497
- {tutorialDialogVisible && (
498
- <Dialog isClosable={true} isModal={true} onClose={() => setTutorialDialogVisible(false)}>
499
- <TutorialsMenu />
500
- </Dialog>
501
- )}
502
- </DialogProvider>
503
- <Button className="btn btn-primary" onClick={() => {
504
- onShowAPI(selectedModel);
505
- }}><FaBook/> {t('btns.api', 'API')}</Button>
496
+
506
497
  </div>}
507
498
  <div className={"table-wrapper"}>
508
499
  <Tooltip id={"tooltipFile"} clickable={true} />
@@ -521,32 +512,29 @@ export function DataTable({
521
512
  {isDataLoaded && (
522
513
  <table>
523
514
  <thead>
524
- <Header advanced={advanced} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>
515
+ <Header advanced={advanced} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter} selectionMode={selectionMode}/>
525
516
  </thead>
526
517
  <tbody>
527
518
  {(data || []).map((item) => (
528
519
  <><DialogProvider>
529
520
  <tr key={item._id} onDoubleClick={() => onEdit(item)} onClick={(e) => {
530
- const checked = (!e.target.closest('tr').querySelector('td:first-child input')?.checked);
531
- if (checked) {
532
- setCheckedItems(items => {
533
- return [...items, item];
534
- });
521
+ // Empêche le déclenchement du clic sur la ligne si on clique sur un bouton ou un lien
522
+ if (e.target.closest('button, a')) {
523
+ return;
524
+ }
525
+ const isCurrentlyChecked = checkedItems?.some(i => i?._id === item._id);
526
+ if (!isCurrentlyChecked) {
527
+ setCheckedItems([...(checkedItems || []), item]);
535
528
  } else {
536
- setCheckedItems(items => items.filter(i => i._id !== item._id));
529
+ setCheckedItems((checkedItems || []).filter(i => i._id !== item._id));
537
530
  }
538
531
  }}>
539
532
  {advanced && (<td className={"mini"}>
540
533
  <CheckboxField checkbox={true} className={"input-ref"}
541
- checked={checkedItems?.some(i => i._id === item._id)}
542
- onChange={(e) => {
543
- if (e) {
544
- setCheckedItems(items => {
545
- return [...items, item];
546
- });
547
- } else {
548
- setCheckedItems(items => items.filter(i => i._id !== item._id));
549
- }
534
+ checked={checkedItems?.some(i => i?._id === item._id)}
535
+ onChange={() => {
536
+ // La logique est gérée par le onClick de la <tr>
537
+ // pour permettre de cliquer n'importe où sur la ligne.
550
538
  }}/></td>)}
551
539
  {(model?.fields ||[]).map(field => {
552
540
 
@@ -619,7 +607,6 @@ export function DataTable({
619
607
  }
620
608
  if (field.type === 'geolocation') {
621
609
  const geoData = item[field.name];
622
- console.log({geoData})
623
610
  if (geoData && geoData.coordinates && geoData.coordinates.length === 2) {
624
611
  const [lng, lat] = geoData.coordinates;
625
612
  const coordinatesText = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
@@ -651,14 +638,18 @@ export function DataTable({
651
638
  })));
652
639
  e.preventDefault();
653
640
  };
654
- t = (item[field.name] || []).map((it,i) => {
641
+
642
+ if( !Array.isArray(item[field.name]))
643
+ return <td key={field.name}>
644
+ </td>
645
+ t = (item[field.name] ||[]).map((it,i) => {
655
646
 
656
647
  const r = `
657
648
  <strong>filename</strong> : ${it.filename}<br />
658
649
  <strong>guid</strong> : ${it.guid}<br />
659
650
  <strong>type</strong> : ${it.mimeType}<br />
660
651
  <strong>size</strong> : ${it.size} bytes<br />
661
- <strong>timestamp</strong> : ${it.timestamp ? new Date(it.timestamp).toLocaleString(lang) : ''}
652
+ <strong>timestamp</strong> : ${it.createdAt ? new Date(it.createdAts).toLocaleString(lang) : ''}
662
653
  `;
663
654
  return <a key={it.guid} href={`/resources/${it.guid}`} target="_blank"
664
655
  data-tooltip-id={"tooltipFile"} data-tooltip-html={r}
@@ -693,7 +684,7 @@ export function DataTable({
693
684
  <strong>guid</strong> : ${item[field.name].guid}<br />
694
685
  <strong>type</strong> : ${item[field.name].mimeType}<br />
695
686
  <strong>size</strong> : ${item[field.name].size} bytes<br />
696
- <strong>timestamp</strong> : ${item[field.name].timestamp.toLocaleString(lang)}
687
+ <strong>timestamp</strong> : ${new Date(item[field.name].createdAt)?.toLocaleString(lang)}
697
688
  `;
698
689
  if (['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/svg+xml', 'image/webp', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/x-windows-bmp'].includes(item[field.name].mimeType))
699
690
  return <td key={field.name}>{hiddenable(<a
@@ -767,7 +758,7 @@ export function DataTable({
767
758
  return <td key={field.name} className={isLightColor(field.color)?"lighted":""} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}}>
768
759
  {hiddenable(item[field.name])}</td>;
769
760
  })}
770
- {advanced && (<td>
761
+ {advanced && !selectionMode && (<td>
771
762
  <button data-tooltip-id="tooltipActions"
772
763
  data-tooltip-content={t('btns.edit', 'Modifier')}
773
764
  onClick={() => handleEdit(item)}><FaPencil/></button>
@@ -799,7 +790,7 @@ export function DataTable({
799
790
  <tfoot>
800
791
  {data.length > 10 && (<Header advanced={advanced} reversed={true} model={model} setCheckedItems={setCheckedItems}
801
792
  filterValues={filterValues} data={data}
802
- setFilterValues={setFilterValues}
793
+ setFilterValues={setFilterValues} selectionMode={selectionMode}
803
794
  onChangeFilterValue={onChangeFilterValue}
804
795
  checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>)}
805
796
  </tfoot>
@@ -821,14 +812,9 @@ export function DataTable({
821
812
  />
822
813
  <ExportDialog isOpen={showExportDialog} onClose={() => {
823
814
  setExportDialogVisible(false);
824
- }} availableModels={models} currentModel={selectedModel.name} hasSelection={true} onExport={(data)=>{
815
+ }} availableModels={models} currentModel={selectedModel?.name} hasSelection={true} onExport={(data)=>{
825
816
  exportMutation(data);
826
817
  }} />
827
- {showPackGallery && (
828
- <Dialog isClosable={true} isModal={true} onClose={() => setShowPackGallery(false)}>
829
- <PackGallery />
830
- </Dialog>
831
- )}
832
818
  </DialogProvider>
833
819
  </div>
834
820
  </div>
@@ -73,7 +73,7 @@
73
73
  word-wrap: break-word;
74
74
  max-height: 85vh;
75
75
  overflow-y: auto;
76
- > .field {
76
+ .field {
77
77
  margin: 0;
78
78
  }
79
79
  }