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.
- package/README.md +797 -782
- package/client/README.md +20 -0
- package/client/package-lock.json +717 -151
- package/client/package.json +37 -36
- package/client/src/App.jsx +9 -10
- package/client/src/App.scss +42 -1
- package/client/src/Dashboard.jsx +349 -208
- package/client/src/DashboardView.jsx +569 -547
- package/client/src/DataEditor.jsx +20 -2
- package/client/src/DataLayout.jsx +54 -13
- package/client/src/DataTable.jsx +807 -760
- package/client/src/DataTable.scss +187 -90
- package/client/src/Dialog.scss +0 -3
- package/client/src/Field.jsx +1783 -1583
- package/client/src/ModelCreator.jsx +25 -3
- package/client/src/ModelCreatorField.jsx +906 -804
- package/client/src/ModelList.jsx +20 -2
- package/client/src/constants.js +16 -4
- package/client/src/contexts/UIContext.jsx +19 -10
- package/client/src/translations.js +265 -3
- package/package.json +4 -3
- package/server.js +1 -0
- package/src/core.js +18 -0
- package/src/defaultModels.js +70 -10
- package/src/email.js +2 -1
- package/src/filter.js +260 -256
- package/src/i18n.js +503 -30
- package/src/modules/data/data.core.js +5 -1
- package/src/modules/data/data.history.js +489 -489
- package/src/modules/data/data.js +76 -10
- package/src/modules/data/data.routes.js +3 -20
- package/src/modules/user.js +19 -0
- package/src/modules/workflow.js +1808 -1817
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- 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
|
-
|
|
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' && (<>
|