data-primals-engine 1.3.3 → 1.4.0

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 (36) hide show
  1. package/README.md +797 -782
  2. package/client/README.md +20 -0
  3. package/client/package-lock.json +717 -151
  4. package/client/package.json +37 -36
  5. package/client/src/App.jsx +9 -10
  6. package/client/src/App.scss +42 -1
  7. package/client/src/Dashboard.jsx +349 -208
  8. package/client/src/DashboardView.jsx +569 -547
  9. package/client/src/DataEditor.jsx +20 -2
  10. package/client/src/DataLayout.jsx +54 -13
  11. package/client/src/DataTable.jsx +807 -760
  12. package/client/src/DataTable.scss +187 -90
  13. package/client/src/Dialog.scss +0 -3
  14. package/client/src/Field.jsx +1783 -1583
  15. package/client/src/ModelCreator.jsx +25 -3
  16. package/client/src/ModelCreatorField.jsx +906 -804
  17. package/client/src/ModelList.jsx +20 -2
  18. package/client/src/constants.js +16 -4
  19. package/client/src/contexts/UIContext.jsx +19 -10
  20. package/client/src/translations.js +265 -3
  21. package/package.json +4 -3
  22. package/server.js +1 -0
  23. package/src/core.js +18 -0
  24. package/src/defaultModels.js +70 -10
  25. package/src/email.js +2 -1
  26. package/src/filter.js +260 -256
  27. package/src/i18n.js +503 -30
  28. package/src/modules/data/data.core.js +5 -1
  29. package/src/modules/data/data.history.js +489 -489
  30. package/src/modules/data/data.js +76 -10
  31. package/src/modules/data/data.routes.js +3 -20
  32. package/src/modules/user.js +19 -0
  33. package/src/modules/workflow.js +1808 -1817
  34. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  35. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  36. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
@@ -8,10 +8,10 @@ import RelationField from "./RelationField.jsx";
8
8
  import {
9
9
  CheckboxField,
10
10
  CodeField,
11
- ColorField, EmailField,
11
+ ColorField, DurationField, EmailField,
12
12
  EnumField,
13
13
  FileField, ModelField, NumberField,
14
- PhoneField,
14
+ PhoneField, RangeField,
15
15
  SelectField,
16
16
  TextField
17
17
  } from "./Field.jsx";
@@ -212,6 +212,24 @@ export const DataEditor = forwardRef(function MyDataEditor({
212
212
  }}/></>;
213
213
  }
214
214
  case "number":
215
+ if (field.type === 'number' && field.gauge) {
216
+ return <RangeField
217
+ name={field.name}
218
+ value={value}
219
+ onChange={(value) => handleChange({name: field.name, value})}
220
+ min={field.min}
221
+ max={field.max}
222
+ percent={field.percent}
223
+ step={field.step || 1}
224
+ />;
225
+ }
226
+ if (field.delay) {
227
+ return <DurationField
228
+ {...inputProps}
229
+ // DurationField's onChange provides an object: { name, value }
230
+ onChange={({ value }) => handleChange({name: field.name, value})}
231
+ />;
232
+ }
215
233
  inputProps["step"] = field.step || 0.1;
216
234
  if( field.min )
217
235
  inputProps["min"] = field.min;
@@ -9,9 +9,10 @@ import {Pagination} from "./Pagination.jsx";
9
9
  import {Event} from "../../src/events.js";
10
10
 
11
11
  import {
12
+ FaEye,
12
13
  FaFilter, FaInfo,
13
14
  } from "react-icons/fa";
14
- import {getDefaultForType, getUserId} from "../../src/data.js";
15
+ import {getDefaultForType, getUserHash, getUserId} from "../../src/data.js";
15
16
  import {Trans, useTranslation} from "react-i18next";
16
17
 
17
18
  import {getObjectHash} from "../../src/core.js";
@@ -32,6 +33,8 @@ import KanbanConfigModal from "./KanbanConfigModal.jsx";
32
33
  import CalendarConfigModal from "./CalendarConfigModal.jsx";
33
34
  import KanbanView from "./KanbanView.jsx";
34
35
  import CalendarView from "./CalendarView.jsx";
36
+ import {useLocation, useParams, useSearchParams} from "react-router-dom";
37
+ import { useNavigate } from "react-router-dom";
35
38
 
36
39
 
37
40
  const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
@@ -44,7 +47,8 @@ const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
44
47
  </div>
45
48
  );
46
49
 
47
- function DataLayout() {
50
+ function DataLayout({refreshUI}) {
51
+ const [ searchParams, setSearchParams ] = useSearchParams();
48
52
  const [viewSettings, setViewSettings] = useLocalStorage('viewSettings', {});
49
53
 
50
54
  const [isCalendarModalOpen, setCalendarModalOpen] = useState(false);
@@ -68,7 +72,8 @@ function DataLayout() {
68
72
  pagedFilters, pagedSort,
69
73
  elementsPerPage,
70
74
  setRelations,
71
- generatedModels
75
+ generatedModels,
76
+ models
72
77
  } = useModelContext(); // Utilisez le contexte
73
78
  const queryClient = useQueryClient();
74
79
 
@@ -86,6 +91,28 @@ function DataLayout() {
86
91
  const [editionMode, setEditionMode] = useState(false);
87
92
  const [showDataEditor, setDataEditorVisible] = useState(false);
88
93
  const [showAPIInfo, setAPIInfoVisible] = useState(false);
94
+ const nav = useNavigate();
95
+ const mod = searchParams.get('model');
96
+ const loc = useLocation();
97
+ useEffect(() =>{
98
+ if (selectedModel?.name) {
99
+ nav('/user/' + getUserHash(me) + '/?model=' + selectedModel?.name);
100
+ }
101
+ }, [selectedModel?.name])
102
+
103
+
104
+ useEffect(() => {
105
+ setSelectedModel(null)
106
+ setDataEditorVisible(false);
107
+ setAPIInfoVisible(false);
108
+ setEditionMode(true);
109
+ }, []);
110
+
111
+ useEffect(() =>{
112
+ setSelectedModel(models.find(f => f.name === mod));
113
+ setEditionMode(false);
114
+ }, [mod, models, searchParams])
115
+
89
116
 
90
117
  // La vue courante est dérivée du modèle sélectionné et des préférences stockées.
91
118
  const currentView = useMemo(() => {
@@ -426,13 +453,6 @@ function DataLayout() {
426
453
  queryClient.invalidateQueries(['api/data', model.name, r]);
427
454
  }
428
455
 
429
- useEffect(() => {
430
- setSelectedModel(null)
431
- setDataEditorVisible(false);
432
- setAPIInfoVisible(false);
433
- setEditionMode(true);
434
- }, []);
435
-
436
456
  const deleteMutation = useMutation((selectedModels) => {
437
457
  return fetch('/api/data/'+checkedItems.map(m => m._id).join(',')+'?lang='+lang+'&_user='+encodeURIComponent(getUserId(me)), {
438
458
  method: 'DELETE', headers: {
@@ -530,7 +550,7 @@ function DataLayout() {
530
550
 
531
551
 
532
552
  const [currentProfile, setCurrentProfile] = useLocalStorage('profile', null);
533
- const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour} = useUI();
553
+ const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour, addLaunchedTour} = useUI();
534
554
 
535
555
  const startTour = () => {
536
556
  setIsTourOpen(true);
@@ -538,12 +558,28 @@ function DataLayout() {
538
558
 
539
559
  const closeTour = (completedTourName) => {
540
560
  // On génère le nom du tour de démo pour le comparer
561
+ // This function is called when ANY tour is closed.
562
+ // It needs to persist the fact that the tour has been seen.
563
+
564
+ // 1. Add the tour's unique name to the list of launched tours.
565
+ // This list is managed by the UI context and stored in localStorage.
566
+ if (addLaunchedTour) { // Check if the function exists to be safe
567
+ addLaunchedTour(completedTourName);
568
+ }
569
+
570
+ // 2. Close the tour's UI.
571
+ setIsTourOpen(false);
572
+
573
+ // 3. Specific logic for the very first "demo" tour:
574
+ // We also set the user's profile to mark that they are no longer a brand new user.
575
+ // This prevents the demo tour from trying to launch on every page load.
541
576
  const demoTourName = `tour_${getObjectHash({steps: allTourSteps.demo || []})}`;
542
577
 
543
578
  // Si le tour qui vient de se terminer est le tour de démo
544
579
  if (completedTourName === demoTourName) {
545
580
 
546
581
  setIsTourOpen(false);
582
+ setCurrentProfile({ lastSeen: new Date().toISOString() });
547
583
  }
548
584
  };
549
585
 
@@ -660,10 +696,15 @@ function DataLayout() {
660
696
  onConfigureView={handleConfigureCurrentView}
661
697
  />}
662
698
  <h2>{t(`model_${selectedModel?.name}`, selectedModel?.name)} <>({countByModel?.[selectedModel?.name]})</></h2>
663
- {t(`model_${selectedModel?.name}`, selectedModel?.name) !== selectedModel?.name && (
699
+
700
+ <div className={"flex"}>
701
+ {t(`model_${selectedModel?.name}`, selectedModel?.name) !== selectedModel?.name && (
664
702
  <span className="badge"><strong>model</strong> : {selectedModel?.name}</span>)}
703
+ {selectedModel.name === 'dashboard' && <button onClick={() => {
704
+ nav('/user/'+getUserHash(me)+'/dashboards');
705
+ }}><FaEye /> Afficher les tableaux de bord</button> }
706
+ </div>
665
707
  <p className="model-desc hint">{t(`model_description_${selectedModel.name}`, selectedModel.description)}</p>
666
-
667
708
  {renderCurrentView()}
668
709
 
669
710
  {isDataLoaded && currentView === 'table' && (<>