data-primals-engine 1.1.3 → 1.1.5

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 (52) hide show
  1. package/README.md +5 -1
  2. package/client/package-lock.json +11580 -11664
  3. package/client/package.json +69 -69
  4. package/client/src/APIInfo.jsx +2 -5
  5. package/client/src/App.jsx +12 -7
  6. package/client/src/CalculationBuilder.jsx +2 -2
  7. package/client/src/ConditionBuilder.scss +24 -0
  8. package/client/src/ConditionBuilder2.jsx +56 -0
  9. package/client/src/Dashboard.jsx +2 -2
  10. package/client/src/DashboardChart.jsx +1 -1
  11. package/client/src/DashboardFlexViewItem.jsx +1 -1
  12. package/client/src/DashboardView.jsx +1 -1
  13. package/client/src/DataEditor.jsx +3 -3
  14. package/client/src/DataLayout.jsx +23 -20
  15. package/client/src/DataTable.jsx +80 -13
  16. package/client/src/Dialog.scss +1 -0
  17. package/client/src/DisplayFlexNodeRenderer.jsx +2 -2
  18. package/client/src/Field.jsx +2 -2
  19. package/client/src/FlexBuilder.jsx +1 -1
  20. package/client/src/KPIWidget.jsx +1 -1
  21. package/client/src/KanbanCard.jsx +1 -1
  22. package/client/src/ModelCreator.jsx +3 -3
  23. package/client/src/ModelCreatorField.jsx +2 -2
  24. package/client/src/ModelImporter.jsx +1 -1
  25. package/client/src/PackGallery.jsx +1 -1
  26. package/client/src/RTETrans.jsx +1 -1
  27. package/client/src/RelationField.jsx +1 -1
  28. package/client/src/RelationValue.jsx +1 -1
  29. package/client/src/TourSpotlight.jsx +14 -10
  30. package/client/src/TutorialsMenu.scss +0 -1
  31. package/client/src/Webpage.jsx +2 -2
  32. package/client/src/constants.js +2 -1
  33. package/client/src/contexts/ModelContext.jsx +5 -11
  34. package/client/src/core/dom.js +26 -0
  35. package/client/src/filter.js +40 -1
  36. package/client/src/hooks/data.js +1 -1
  37. package/client/src/hooks/useTutorials.jsx +1 -1
  38. package/package.json +4 -6
  39. package/server.js +3 -2
  40. package/src/constants.js +3 -0
  41. package/src/engine.js +18 -13
  42. package/src/i18n.js +77 -3
  43. package/src/modules/assistant.js +1 -1
  44. package/src/modules/bucket.js +1 -1
  45. package/src/modules/data.js +14 -3
  46. package/src/modules/file.js +1 -1
  47. package/src/modules/user.js +1 -1
  48. package/src/modules/workflow.js +1 -1
  49. package/src/packs.js +4 -3
  50. package/src/setenv.js +1 -1
  51. package/test/data.integration.test.js +4 -10
  52. package/test/workflow.robustness.test.js +1 -1
@@ -4,18 +4,18 @@ import {Trans, useTranslation} from "react-i18next";
4
4
  import {useMutation, useQuery, useQueryClient} from "react-query";
5
5
  import {useAuthContext} from "./contexts/AuthContext.jsx";
6
6
  import React, {useEffect, useMemo, useRef, useState} from "react";
7
- import {getUserId} from "data-primals-engine/data";
7
+ import {getUserId} from "../../src/data.js";
8
8
  import cronstrue from 'cronstrue/i18n';
9
9
  import {
10
10
  FaBook,
11
11
  FaCopy,
12
- FaDatabase,
12
+ FaDatabase, FaEraser,
13
13
  FaFileExport,
14
14
  FaFileImport,
15
15
  FaFilter,
16
16
  FaInfo,
17
17
  FaPlus,
18
- FaTrash
18
+ FaTrash, FaWrench
19
19
  } from "react-icons/fa";
20
20
  import {useNotificationContext} from "./NotificationProvider.jsx";
21
21
  import {
@@ -24,7 +24,7 @@ import {
24
24
  maxBytesPerSecondThrottleData,
25
25
  maxFileSize,
26
26
  maxRequestData
27
- } from "data-primals-engine/constants";
27
+ } from "../../src/constants.js";
28
28
  import Button from "./Button.jsx";
29
29
  import {Dialog, DialogProvider} from "./Dialog.jsx";
30
30
  import {
@@ -38,9 +38,9 @@ import {
38
38
  TextField
39
39
  } from "./Field.jsx";
40
40
  import RelationValue from "./RelationValue.jsx";
41
- import {FaPencil, FaTriangleExclamation} from "react-icons/fa6";
41
+ import {FaGear, FaPencil, FaTriangleExclamation} from "react-icons/fa6";
42
42
  import RestoreConfirmationModal from "./RestoreConfirmationModal.jsx";
43
- import {event_trigger, isLightColor} from "data-primals-engine/core";
43
+ import {event_trigger, isLightColor} from "../../src/core.js";
44
44
  import {isConditionMet} from "./DataEditor.jsx";
45
45
  import {Tooltip} from "react-tooltip";
46
46
  import ExportDialog from "./ExportDialog.jsx";
@@ -54,6 +54,8 @@ import TutorialsMenu from "../src/TutorialsMenu.jsx";
54
54
  import {useTutorials} from "./hooks/useTutorials.jsx";
55
55
  import PackGallery from "./PackGallery.jsx";
56
56
  import {HiddenableCell} from "./HiddenableCell.jsx";
57
+ import ConditionBuilder from "./ConditionBuilder.jsx";
58
+ import {pagedFilterToMongoConds} from "./filter.js";
57
59
 
58
60
  // Ajoutez cette constante pour la clé de sessionStorage
59
61
  const SESSION_STORAGE_IMPORT_JOBS_KEY = 'activeImportJobs';
@@ -71,19 +73,29 @@ const Header = ({
71
73
  filterValues
72
74
  }) => {
73
75
 
76
+ const {t} = useTranslation()
74
77
  const { me } = useAuthContext()
75
78
  const {
76
79
  models,
77
80
  countByModel,
78
81
  selectedModel,
79
82
  setPagedFilters,
83
+ pagedFilters,
80
84
  page
81
85
  } = useModelContext();
82
86
  let totalCol = 0;
87
+
88
+ const [advancedFilterVisible, setAdvancedFilterVisible] = useState(false);
89
+
90
+ const handleAdvancedFilter = () => {
91
+ setAdvancedFilterVisible(true);
92
+ }
83
93
  return <><tr className={reversed ? ' reversed' : ''}>
84
94
  <th className={"mini"}>
85
95
  <div className="flex flex-row">
96
+
86
97
  <Button onClick={handleFilter} className={filterActive ? ' active' : ''}><FaFilter/></Button>
98
+ {filterActive && <Button onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
87
99
  <CheckboxField checked={checkedItems.length === data.length} onChange={e => {
88
100
  if (checkedItems.length === data.length) {
89
101
  setCheckedItems([]);
@@ -91,6 +103,27 @@ const Header = ({
91
103
  setCheckedItems(data);
92
104
  }
93
105
  }}/>
106
+
107
+ <DialogProvider>
108
+ {advancedFilterVisible && (
109
+ <Dialog title={t("datatable.advancedFilter.title")} isClosable={true} onClose={() => setAdvancedFilterVisible(false)}>
110
+ <h3>Edit filter</h3>
111
+ <div className="msg">
112
+ <Trans i18nKey={"datatable.advancedFilter.desc"}>{t("datatable.advancedFilter.desc")}</Trans>
113
+ </div>
114
+ <ConditionBuilder onChange={(c) => {
115
+ setPagedFilters(pagedFilters => ({
116
+ ...pagedFilters,
117
+ [model.name]: c || pagedFilters[model.name] || {} }));
118
+ }} initialValue={{ $and: pagedFilterToMongoConds(pagedFilters, model)}} models={models} model={model.name} checkedItems={checkedItems} setCheckedItems={setCheckedItems} data={data} filterActive={filterActive} onChangeFilterValue={onChangeFilterValue} setFilterValues={setFilterValues}/>
119
+ <Button onClick={() =>{
120
+ setPagedFilters(pagedFilters => ({
121
+ ...pagedFilters,
122
+ [model.name]: {}}));
123
+ }}><Trans i18nKey={"btns.reset"}>Reset</Trans></Button>
124
+ </Dialog>
125
+ )}
126
+ </DialogProvider>
94
127
  </div>
95
128
  </th>
96
129
  {model.fields.map(field => {
@@ -114,11 +147,12 @@ const Header = ({
114
147
  onChangeFilterValue={onChangeFilterValue}/>;
115
148
  })}
116
149
  <th><Trans i18nKey="actions">Actions</Trans>
117
- {filterActive && <div>
150
+ {Object.keys(pagedFilters[model.name] || {})
151
+ .filter(f=> Object.keys(pagedFilters[model.name]?.[f] || {}).length > 0).length > 0 && <div>
118
152
  <button onClick={() => {
119
153
  setFilterValues({});
120
154
  setPagedFilters(pagedFilters => ({...pagedFilters, [model.name]: {}}));
121
- }}>x
155
+ }}><FaEraser />
122
156
  </button>
123
157
  </div>}</th>
124
158
  </tr>
@@ -137,6 +171,7 @@ export function DataTable({
137
171
  setCheckedItems,
138
172
  onEdit,
139
173
  onAddData,
174
+ onDuplicateData,
140
175
  onDelete,
141
176
  onShowAPI,
142
177
  filterValues,
@@ -229,7 +264,7 @@ export function DataTable({
229
264
 
230
265
  const onChangeFilterValue = (field, value, tr) => {
231
266
  setPagedFilters(pagedFilters => ({
232
- [model.name]: {...pagedFilters[model.name] || {}, [field.name]: value || undefined}
267
+ ...pagedFilters, [model.name]: {...pagedFilters[model.name] || {}, [field.name]: value || pagedFilters[model.name]?.[field.name] || undefined}
233
268
  }));
234
269
  queryClient.invalidateQueries(['api/data', model.name, 'page', page, elementsPerPage, pagedFilters[model.name], pagedSort[model.name]]);
235
270
  }
@@ -344,6 +379,22 @@ export function DataTable({
344
379
  if (!model)
345
380
  return <></>;
346
381
 
382
+ // NOUVEAU : La fonction qui gère la duplication
383
+ const handleDuplicate = (originalData) => {
384
+ // 1. Créer une copie superficielle des données de la ligne
385
+ const dataToDuplicate = { ...originalData };
386
+
387
+ // 2. TRÈS IMPORTANT : Supprimer les champs qui ne doivent pas être copiés.
388
+ // - L'_id doit être supprimé pour que le système sache qu'il s'agit d'une NOUVELLE entrée.
389
+ // - Le _hash sera recalculé par le backend.
390
+ // - Les dates de création/mise à jour seront gérées par le backend.
391
+ delete dataToDuplicate._id;
392
+ delete dataToDuplicate._hash;
393
+ delete dataToDuplicate.createdAt; // si ce champ existe
394
+ delete dataToDuplicate.updatedAt; // si ce champ existe
395
+
396
+ onDuplicateData(dataToDuplicate);
397
+ };
347
398
  return (
348
399
  <div className={`datatable${filterActive ? ' filter-active' : ''}`}>
349
400
  {<div className="flex actions flex-left">
@@ -356,7 +407,7 @@ export function DataTable({
356
407
  <Button className="tourStep-import-datapack" onClick={handleShowPacks} title={t("btns.addPack")}><FaPlus/><Trans
357
408
  i18nKey="btns.addPack">Packs...</Trans></Button>
358
409
 
359
- <Button className={/^demo[0-9]{1,2}$/.test(me.username) ? "btn-primary" : "btn"} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaPlus/><Trans
410
+ <Button className={"tourStep-tutorials " + (/^demo[0-9]{1,2}$/.test(me.username) ? "btn-primary" : "btn")} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaPlus/><Trans
360
411
  i18nKey="btns.showTutos">Tutoriels</Trans></Button>
361
412
  {!/^demo[0-9]{1,2}$/.test(me.username) && (<Button onClick={handleBackup} title={t("btns.backup")}><FaDatabase/><Trans
362
413
  i18nKey="btns.backup">Backup</Trans></Button>)}
@@ -373,6 +424,7 @@ export function DataTable({
373
424
  </div>}
374
425
  <div className="table-wrapper">
375
426
  <Tooltip id={"tooltipFile"} clickable={true} />
427
+ <Tooltip id={"tooltipActions"} />
376
428
  <Lightbox
377
429
  inline={{
378
430
  style: { width: "100%", maxWidth: "900px", aspectRatio: "3 / 2" },
@@ -591,8 +643,19 @@ export function DataTable({
591
643
  {hiddenable(item[field.name])}</td>;
592
644
  })}
593
645
  <td>
594
- <button onClick={() => handleEdit(item)}><FaPencil/></button>
595
- <button onClick={() => handleDelete(item)}><FaTrash/></button>
646
+ <button data-tooltip-id="tooltipActions"
647
+ data-tooltip-content={t('btns.edit', 'Modifier')}
648
+ onClick={() => handleEdit(item)}><FaPencil/></button>
649
+ <button
650
+ onClick={() => handleDuplicate(item)}
651
+ data-tooltip-id="tooltipActions"
652
+ data-tooltip-content={t('btns.duplicate', 'Dupliquer')}
653
+ >
654
+ <FaCopy/>
655
+ </button>
656
+ <button data-tooltip-id="tooltipActions"
657
+ data-tooltip-content={t('btns.delete', 'Supprimer')}
658
+ onClick={() => handleDelete(item)}><FaTrash/></button>
596
659
  </td>
597
660
  </tr>
598
661
  </DialogProvider></>
@@ -600,7 +663,11 @@ export function DataTable({
600
663
  </tbody>
601
664
 
602
665
  <tfoot>
603
- {data.length > 10 && (<Header reversed={true} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>)}
666
+ {data.length > 10 && (<Header reversed={true} model={model} setCheckedItems={setCheckedItems}
667
+ filterValues={filterValues} data={data}
668
+ setFilterValues={setFilterValues}
669
+ onChangeFilterValue={onChangeFilterValue}
670
+ checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>)}
604
671
  </tfoot>
605
672
 
606
673
  </table>)}
@@ -64,6 +64,7 @@
64
64
  &.btn-close {
65
65
  position: absolute;
66
66
  right: 0px;
67
+ z-index: 100;
67
68
  }
68
69
  }
69
70
  }
@@ -1,9 +1,9 @@
1
1
  // C:/Dev/hackersonline-engine/client/src/DisplayFlexNodeRenderer.jsx
2
2
  import {useTranslation} from "react-i18next";
3
- import {cssProps} from "data-primals-engine/core";
3
+ import {cssProps} from "../../src/core.js";
4
4
  import FlexDataRenderer from "./FlexDataRenderer.jsx";
5
5
  import React, {useState} from "react";
6
- import {getFieldPathValue} from "data-primals-engine/data";
6
+ import {getFieldPathValue} from "../../src/data.js";
7
7
  import {getFieldDefinitionFromPath} from "./core/data.js";
8
8
  import {Dialog, DialogProvider} from "./Dialog.jsx";
9
9
  import {FaPlay} from "react-icons/fa";
@@ -11,8 +11,8 @@ import { recursiveMap, useRefs } from "./Utils.jsx";
11
11
  import {useTranslation} from "react-i18next";
12
12
  import { CodeiumEditor } from "@codeium/react-code-editor";
13
13
 
14
- import {debounce, escapeRegExp, isGUID, isLightColor} from "data-primals-engine/core";
15
- import {mainFieldsTypes, maxFileSize} from "data-primals-engine/constants";
14
+ import {debounce, escapeRegExp, isGUID, isLightColor} from "../../src/core.js";
15
+ import {mainFieldsTypes, maxFileSize} from "../../src/constants.js";
16
16
  import {useModelContext} from "./contexts/ModelContext.jsx";
17
17
  import {useQueryClient} from "react-query";
18
18
  import {
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import './FlexBuilder.scss';
4
4
  import { useAuthContext } from "./contexts/AuthContext.jsx";
5
5
  import { useTranslation } from "react-i18next";
6
- import { applyFilter } from "data-primals-engine/data";
6
+ import { applyFilter } from "../../src/data.js";
7
7
  import { FieldSelectorModal } from "./FlexNode.jsx";
8
8
  import { deepInsertNode, deepRemoveNode } from "./FlexBuilderUtils.js";
9
9
  import useDragAndDrop from './hooks/useDragAndDrop.js';
@@ -3,7 +3,7 @@ import { useQuery } from 'react-query';
3
3
  import * as FaIcons from 'react-icons/fa'; // Importer toutes les icônes Fa
4
4
  import * as Fa6Icons from 'react-icons/fa6'; // Importer Fa6
5
5
  import { FaInfo, FaTimes } from 'react-icons/fa';
6
- import { isLightColor } from "data-primals-engine/core"; // Pour le bouton supprimer
6
+ import { isLightColor } from "../../src/core.js"; // Pour le bouton supprimer
7
7
  import { Tooltip } from 'react-tooltip';
8
8
 
9
9
  // Fonction pour récupérer la valeur calculée d'un KPI (MODIFIÉE)
@@ -3,7 +3,7 @@
3
3
  import React from 'react';
4
4
  import {useTranslation} from "react-i18next";
5
5
  import {useModelContext} from "./contexts/ModelContext.jsx";
6
- import {getDataAsString} from "data-primals-engine/data";
6
+ import {getDataAsString} from "../../src/data.js";
7
7
 
8
8
 
9
9
  const KanbanCard = ({ card, model, subItemsField }) => {
@@ -14,15 +14,15 @@ import {
14
14
  mainFieldsTypes, maxFileSize,
15
15
  maxModelNameLength,
16
16
  maxRequestData
17
- } from "data-primals-engine/constants";
17
+ } from "../../src/constants.js";
18
18
  import {useAuthContext} from "./contexts/AuthContext.jsx";
19
- import {getUserHash, getUserId, isLocalUser} from "data-primals-engine/data";
19
+ import {getUserHash, getUserId, isLocalUser} from "../../src/data.js";
20
20
  import {useNotificationContext} from "./NotificationProvider.jsx";
21
21
  import ConditionBuilder from './ConditionBuilder.jsx';
22
22
  import './ConditionBuilder.scss';
23
23
  import {Tooltip} from "react-tooltip";
24
24
  import {FaCircleInfo} from "react-icons/fa6";
25
- import i18n from "data-primals-engine/i18n";
25
+ import i18n from "../../src/i18n.js";
26
26
  import Draggable from "./Draggable.jsx";
27
27
  import FlexBuilder from "./FlexBuilder.jsx";
28
28
  import {NavLink} from "react-router";
@@ -1,7 +1,7 @@
1
1
  import React, {useState} from "react";
2
2
  import {useModelContext} from "./contexts/ModelContext.jsx";
3
3
  import {useAuthContext} from "./contexts/AuthContext.jsx";
4
- import {isLocalUser} from "data-primals-engine/data";
4
+ import {isLocalUser} from "../../src/data.js";
5
5
  import {Trans, useTranslation} from "react-i18next";
6
6
  import {FaCircleInfo} from "react-icons/fa6";
7
7
  import {CheckboxField, CodeField, ColorField, NumberField, SelectField, TextField} from "./Field.jsx";
@@ -10,7 +10,7 @@ import {FaArrowDown, FaArrowUp, FaEdit, FaMinus, FaPlus, FaTrash} from "react-ic
10
10
  import CalculationBuilder from "./CalculationBuilder.jsx";
11
11
  import ConditionBuilder from "./ConditionBuilder.jsx";
12
12
  import Draggable from "./Draggable.jsx";
13
- import {mainFieldsTypes} from "data-primals-engine/constants";
13
+ import {mainFieldsTypes} from "../../src/constants.js";
14
14
 
15
15
  function RelationModelSelector({relation, onChange, ...rest}) {
16
16
  const {models} = useModelContext()
@@ -7,7 +7,7 @@ import { CheckboxField } from './Field.jsx';
7
7
  import Button from './Button.jsx';
8
8
  import { useQueryClient } from 'react-query';
9
9
  import { useNotificationContext } from './NotificationProvider.jsx';
10
- import {metaModels} from "data-primals-engine/constants";
10
+ import {metaModels} from "../../src/constants.js";
11
11
 
12
12
 
13
13
  const ModelImporter = ({ onImport }) => {
@@ -7,7 +7,7 @@ import { useNotificationContext } from './NotificationProvider.jsx';
7
7
  import { useModelContext } from './contexts/ModelContext.jsx';
8
8
 
9
9
  import './PackGallery.scss';
10
- import {elementsPerPage} from "data-primals-engine/constants";
10
+ import {elementsPerPage} from "../../src/constants.js";
11
11
 
12
12
  // --- API Fetching Functions ---
13
13
  const fetchPacks = async (sortBy, lang) => {
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
4
4
  import { RTE } from './RTE.jsx';
5
5
  import {useModelContext} from "./contexts/ModelContext.jsx";
6
6
  import {useQuery} from "react-query";
7
- import {getUserId} from "data-primals-engine/data";
7
+ import {getUserId} from "../../src/data.js";
8
8
  import {useAuthContext} from "./contexts/AuthContext.jsx";
9
9
 
10
10
  const RTETrans = ({ value, onChange, field }) => {
@@ -6,7 +6,7 @@ import { useAuthContext } from './contexts/AuthContext.jsx';
6
6
  import {conditionToApiSearchFilter, getDataAsString, getUserId} from 'data-primals-engine/data';
7
7
  import { FaEdit, FaTrash } from 'react-icons/fa';
8
8
  import Button from './Button.jsx';
9
- import {mainFieldsTypes} from "data-primals-engine/constants";
9
+ import {mainFieldsTypes} from "../../src/constants.js";
10
10
  import Draggable from "./Draggable.jsx";
11
11
  import {useTranslation} from "react-i18next";
12
12
 
@@ -8,7 +8,7 @@ import {FaMagnifyingGlass} from "react-icons/fa6";
8
8
  import {useAuthContext} from "./contexts/AuthContext.jsx";
9
9
  import {CodeField, ColorField} from "./Field.jsx";
10
10
  import { randomColor } from "randomcolor";
11
- import {getObjectHash, getRand, isLightColor, setSeed} from "data-primals-engine/core";
11
+ import {getObjectHash, getRand, isLightColor, setSeed} from "../../src/core.js";
12
12
 
13
13
  const RelationValue = ({ field, data, align }) => {
14
14
  const { models, relations, setRelations, dataByModel, relationIds, setRelationIds, setDatasToLoad, datasToLoad } = useModelContext();
@@ -4,6 +4,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
4
4
  import './TourSpotlight.scss';
5
5
  import { useUI } from "./contexts/UIContext.jsx";
6
6
  import useLocalStorage from "./hooks/useLocalStorage.js";
7
+ import {waitForElement} from "./core/dom.js";
7
8
 
8
9
  function debounce(func, wait) {
9
10
  let timeout;
@@ -39,27 +40,30 @@ function TourSpotlight({ name, steps = [], isOpen, onComplete, onClose, initialS
39
40
  }
40
41
  }, [isOpen, initialStepIndex]);
41
42
 
42
- const calculateRectAndScroll = useCallback(() => {
43
+ const calculateRectAndScroll = useCallback( () => {
43
44
  if (!isOpen || !currentStep?.selector) {
44
45
  setTargetRect(null);
45
46
  return;
46
47
  }
47
48
 
48
- const element = document.querySelector(currentStep.selector);
49
- if (element) {
50
- const rect = element.getBoundingClientRect();
51
- if (!targetRect || rect.top !== targetRect.top || rect.left !== targetRect.left || rect.width !== targetRect.width || rect.height !== targetRect.height) {
52
- setTargetRect(rect);
49
+ waitForElement(currentStep.selector).then(element => {
50
+ if (element) {
51
+ const rect = element.getBoundingClientRect();
52
+ if (!targetRect || rect.top !== targetRect.top || rect.left !== targetRect.left || rect.width !== targetRect.width || rect.height !== targetRect.height) {
53
+ setTargetRect(rect);
54
+ }
55
+ } else {
56
+ console.warn(`[TourSpotlight] Élément cible non trouvé : ${currentStep.selector}`);
57
+ setTargetRect(null);
53
58
  }
54
- } else {
55
- console.warn(`[TourSpotlight] Élément cible non trouvé : ${currentStep.selector}`);
56
- setTargetRect(null);
57
- }
59
+ });
58
60
  }, [isOpen, currentStep?.selector, targetRect]);
59
61
 
60
62
  useEffect(() => {
61
63
  if (!isOpen || !name) return;
62
64
 
65
+
66
+ console.log({currentStep})
63
67
  if (!currentStep?.selector) {
64
68
  console.warn(`[TourSpotlight] Élément cible non défini pour le tour ${name}`);
65
69
  return;
@@ -35,7 +35,6 @@ $color-completed-accent: #28a745; // Vert plus vif
35
35
  .tutorial-item {
36
36
  background-color: $color-item-bg;
37
37
  border: 1px solid $color-border;
38
- z-index: -1;
39
38
  border-radius: 12px; // Coins plus arrondis pour un look moderne
40
39
  box-shadow: 0 4px 12px $color-shadow;
41
40
  padding: 1.5rem;
@@ -5,10 +5,10 @@ import {useQuery} from "react-query";
5
5
  import {useLocation} from "react-router-dom";
6
6
  import {Helmet} from "react-helmet";
7
7
  import {useTranslation} from "react-i18next";
8
- import {getObjectHash} from "data-primals-engine/core";
8
+ import {getObjectHash} from "../../src/core.js";
9
9
  import {useData} from "./hooks/data.js";
10
10
  import {useAuthContext} from "./contexts/AuthContext.jsx";
11
- import {getUserHash, getUserId} from "data-primals-engine/data";
11
+ import {getUserHash, getUserId} from "../../src/data.js";
12
12
 
13
13
 
14
14
  function Webpage({children}) {
@@ -1,3 +1,4 @@
1
+ import {host} from "../../src/constants.js";
1
2
 
2
3
  export const seoTitle = 'Self Data Hosting';
3
4
 
@@ -52,7 +53,7 @@ export const MONGO_OPERATORS = {
52
53
  export const profiles = {
53
54
  'personal': ['contact', 'location', 'imageGallery', 'budget', 'currency', 'taxonomy'], // budget,
54
55
  'developer': ['alert','endpoint','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role'],
55
- 'company': ['alert','request','location', 'campaign', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'messaging', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
56
+ 'company': ['alert','request','location', 'campaign', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'message', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
56
57
  'engineer': ['alert','endpoint','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
57
58
  }
58
59
 
@@ -1,19 +1,19 @@
1
1
  // ModelContext.jsx
2
2
  import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react';
3
3
  import { useQueries, useQuery } from 'react-query';
4
- import {elementsPerPage} from "data-primals-engine/constants";
5
- import {getObjectHash} from "data-primals-engine/core";
4
+ import {getObjectHash} from "../../../src/core.js";
6
5
  import {useAuthContext} from "./AuthContext.jsx";
7
- import {getUserHash, getUserId} from "data-primals-engine/data";
6
+ import {getUserHash, getUserId} from "../../../src/data.js";
8
7
  import {useTranslation} from "react-i18next";
9
8
  import useLocalStorage from "../hooks/useLocalStorage.js";
9
+ import {pagedFilterToMongoConds} from "../filter.js";
10
10
 
11
11
  const ModelContext = createContext(null);
12
12
 
13
13
  export const ModelProvider = ({ children }) => {
14
14
  const [models, setModels] = useState([]);
15
15
  const [datasToLoad, setDatasToLoad] = useState([]);
16
- const [pagedFilters,setPagedFilters] = useState({});
16
+ const [pagedFilters,setPagedFilters] = useLocalStorage('paged_filters', {});
17
17
  const [pagedDepth,setPagedDepth] = useState({});
18
18
  const [filteredDatasToLoad, setFilteredDatasToLoad] = useState([]);
19
19
  const [onSuccessCallbacks, setOnSuccessCallbacks] = useState({});
@@ -189,13 +189,7 @@ export const ModelProvider = ({ children }) => {
189
189
  if( sortParam.length )
190
190
  params.append("sort", sortParam.join(','));
191
191
 
192
- const c = [];
193
- Object.keys(pagedFilters[model.name] || {}).filter(f=>typeof(pagedFilters[model.name][f]) === 'object' && Object.keys(pagedFilters[model.name][f]).length > 0).forEach(obj =>{
194
- const f = model.fields.find(f=>f.name===obj);
195
- if(f !== null && f !== undefined){
196
- c.push(pagedFilters[model.name][obj]);
197
- }
198
- });
192
+ const c = pagedFilterToMongoConds(pagedFilters, model)
199
193
  const filter= JSON.stringify({filter:{$and:c}});
200
194
 
201
195
  return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', body: filter, headers: { "Content-Type": "application/json"}})
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Waits for an element to appear in the DOM.
3
+ * @param {string} selector - The CSS selector of the element.
4
+ * @param {number} timeout - The maximum time to wait in milliseconds.
5
+ * @returns {Promise<Element>} A promise that resolves with the element or rejects on timeout.
6
+ */
7
+ export function waitForElement(selector, timeout = 2000) {
8
+ return new Promise((resolve, reject) => {
9
+ const intervalTime = 100; // Check every 100ms
10
+ let elapsedTime = 0;
11
+
12
+ const interval = setInterval(() => {
13
+ const element = document.querySelector(selector);
14
+ if (element) {
15
+ clearInterval(interval);
16
+ resolve(element);
17
+ } else {
18
+ elapsedTime += intervalTime;
19
+ if (elapsedTime >= timeout) {
20
+ clearInterval(interval);
21
+ reject(new Error(`Element "${selector}" not found after ${timeout}ms`));
22
+ }
23
+ }
24
+ }, intervalTime);
25
+ });
26
+ }
@@ -7,6 +7,12 @@ export const MONGO_CALC_OPERATORS = {
7
7
  multi: false,
8
8
  isFindOperator: true // Nouvelle propriété pour identifier les opérateurs $find
9
9
  },
10
+ '$regexMatch': {
11
+ label: 'Find by regex',
12
+ description: 'Find a string matching a regular expression (Ecmascript)',
13
+ args: 2, // input et regex
14
+ specialStructure: true // Indique une structure spéciale
15
+ },
10
16
  $and: { label: 'Et (and)', description: 'Renvoie vrai si toutes les conditions sont vérifiées', args: 1, multi: true },
11
17
  $or: { label: 'Ou (or)', description: 'Renvoie vrai si au moins une des conditions est vérifiée.', args: 1, multi: true },
12
18
  $not: { label: 'Non (not)', description: 'Inverse la condition (ex: non égal à).', args: 1, multi: true },
@@ -57,7 +63,7 @@ export const MONGO_CALC_OPERATORS = {
57
63
  $toBool: { label: 'toBool', multi: false, converter: true },
58
64
  $toString: { label: 'toString', multi: false, converter: true },
59
65
  $toInt: { label: 'toInt', multi: false, converter: true },
60
- $toDouble: { label: 'toDouble', multi: false, converter: true },
66
+ $toDouble: { label: 'toDouble', multi: false, converter: true }
61
67
  };
62
68
 
63
69
  export const convertInputValue = (value) => {
@@ -219,4 +225,37 @@ export const buildRootFromExpr = (query) => {
219
225
  if (parsedRoot.$and || parsedRoot.$or) return parsedRoot;
220
226
 
221
227
  return { $and: [parsedRoot] };
228
+ };
229
+
230
+ export const pagedFilterToMongoConds = (pagedFilters, model) => {
231
+ const modelFilter = pagedFilters?.[model.name] || {};
232
+ const filterKeys = Object.keys(modelFilter);
233
+
234
+ // Si le filtre est vide, on retourne un tableau vide.
235
+ if (filterKeys.length === 0) {
236
+ return [];
237
+ }
238
+
239
+ // Détecte si c'est un filtre avancé (contient des opérateurs logiques de haut niveau comme $and, $or, etc.)
240
+ const isAdvancedFilter = filterKeys.some(key => key.startsWith('$'));
241
+
242
+ if (isAdvancedFilter) {
243
+ // C'est un filtre avancé du ConditionBuilder.
244
+ // Il est déjà une condition MongoDB complète. On le met dans un tableau pour l'insérer dans le $and global.
245
+ // Si le filtre avancé est vide (ex: { $and: [] }), on le retourne quand même pour que le ConditionBuilder puisse s'initialiser correctement.
246
+ return [modelFilter];
247
+ } else {
248
+ // C'est un filtre simple (par colonne).
249
+ // On transforme { champ1: cond1, champ2: cond2 } en [{ champ1: cond1 }, { champ2: cond2 }]
250
+ const c = [];
251
+ filterKeys.forEach(fieldName => {
252
+ if (model.fields.some(f => f.name === fieldName)) {
253
+ const condition = modelFilter[fieldName];
254
+ if (condition && typeof condition === 'object' && Object.keys(condition).length > 0) {
255
+ c.push(condition);
256
+ }
257
+ }
258
+ });
259
+ return c;
260
+ }
222
261
  };
@@ -1,7 +1,7 @@
1
1
  import {useQuery} from "react-query";
2
2
  import {getObjectHash} from "data-primals-engine/core";
3
3
  import {useAuthContext} from "../contexts/AuthContext.jsx";
4
- import {getUserHash, getUserId} from "data-primals-engine/data";
4
+ import {getUserHash, getUserId} from "../../../src/data";
5
5
  import {useTranslation} from "react-i18next";
6
6
  import {useEffect, useState} from "react";
7
7
  import {useUI} from "../contexts/UIContext.jsx";
@@ -6,7 +6,7 @@ import { tutorialsConfig } from '../tutorials.js';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { useCallback, useEffect, useMemo, useRef } from 'react';
8
8
  import useLocalStorage from "./useLocalStorage.js";
9
- import { event_off, event_on } from "data-primals-engine/core";
9
+ import { event_off, event_on } from "../../../src/core.js";
10
10
 
11
11
  /**
12
12
  * Hook pour gérer la logique des tutoriels multi-étapes.