data-primals-engine 1.6.1 → 1.6.2-rc1
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 +968 -938
- package/client/package-lock.json +524 -5
- package/client/package.json +2 -1
- package/client/src/App.scss +14 -1
- package/client/src/Dashboard.jsx +4 -0
- package/client/src/DataLayout.jsx +85 -20
- package/client/src/DataLayout.scss +32 -6
- package/client/src/DataTable.jsx +18 -11
- package/client/src/ModelCreator.jsx +686 -683
- package/client/src/ModelImporter.jsx +1 -1
- package/client/src/ViewSwitcher.jsx +1 -1
- package/client/src/WorkflowEditor.jsx +317 -0
- package/client/src/WorkflowEditor.scss +16 -0
- package/package.json +142 -142
- package/src/defaultModels.js +5 -2
- package/src/i18n.js +35 -3
- package/src/modules/data/data.operations.js +3487 -3484
- package/src/modules/workflow.js +1 -4
- package/src/packs.js +239 -24
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {FaUndo, FaRedo, FaSitemap} from 'react-icons/fa';
|
|
4
4
|
import "./App.scss";
|
|
5
5
|
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
6
6
|
import ModelCreator from "./ModelCreator.jsx";
|
|
@@ -11,7 +11,7 @@ import {Event} from "../../src/events.js";
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
FaBook,
|
|
14
|
-
FaBoxOpen, FaDatabase,
|
|
14
|
+
FaBoxOpen, FaDatabase, FaProjectDiagram,
|
|
15
15
|
FaEye, FaFileImport,
|
|
16
16
|
FaFilter, FaInfo, FaPlus,
|
|
17
17
|
} from "react-icons/fa";
|
|
@@ -46,6 +46,7 @@ import {AssistantChat, NotificationList} from "../index.js";
|
|
|
46
46
|
import { useCommand } from './contexts/CommandContext.jsx';
|
|
47
47
|
|
|
48
48
|
import "./DataLayout.scss"
|
|
49
|
+
import WorkflowEditor from "./WorkflowEditor.jsx";
|
|
49
50
|
|
|
50
51
|
const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
|
|
51
52
|
<div className="p-4 border border-dashed rounded-md mt-4 text-center bg-gray-50">
|
|
@@ -57,12 +58,52 @@ const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
|
|
|
57
58
|
</div>
|
|
58
59
|
);
|
|
59
60
|
|
|
61
|
+
const WorkflowSelectorModal = ({ onClose, onSelectWorkflow }) => {
|
|
62
|
+
const { t } = useTranslation();
|
|
63
|
+
const { searchData } = useModelContext();
|
|
64
|
+
const { me } = useAuthContext();
|
|
65
|
+
const { data: activeWorkflows, isLoading } = useQuery(
|
|
66
|
+
'activeWorkflowsList',
|
|
67
|
+
() => fetch('/api/data/search?_user='+me.username, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
model: 'workflow',
|
|
72
|
+
sort: 'name:asc',limit: 5
|
|
73
|
+
})
|
|
74
|
+
}).then(res => res.json()),
|
|
75
|
+
{
|
|
76
|
+
select: (data) => data?.data || []
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Dialog title={t('workflow.select.title', 'Sélectionner un workflow')} isClosable={true} onClose={onClose}>
|
|
82
|
+
{isLoading && <p>{t('loading', 'Chargement...')}</p>}
|
|
83
|
+
{!isLoading && (!activeWorkflows || activeWorkflows.length === 0) && (
|
|
84
|
+
<p>{t('workflow.select.noActive', 'Aucun workflow actif trouvé.')}</p>
|
|
85
|
+
)}
|
|
86
|
+
<ul className="workflow-selector-list">
|
|
87
|
+
{activeWorkflows?.map(wf => (
|
|
88
|
+
<li key={wf._id} onClick={() => onSelectWorkflow(wf._id)}>
|
|
89
|
+
<FaSitemap />
|
|
90
|
+
<span className="font-bold">{wf.name.value}</span>
|
|
91
|
+
<span className="text-sm text-gray-500 ml-2">{wf.description}</span>
|
|
92
|
+
</li>
|
|
93
|
+
))}
|
|
94
|
+
</ul>
|
|
95
|
+
</Dialog>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
60
99
|
function DataLayout({refreshUI}) {
|
|
61
100
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
62
101
|
const [viewSettings, setViewSettings] = useLocalStorage('viewSettings', {});
|
|
63
102
|
|
|
64
103
|
const [isCalendarModalOpen, setCalendarModalOpen] = useState(false);
|
|
65
104
|
const [isKanbanModalOpen, setKanbanModalOpen] = useState(false);
|
|
105
|
+
const [isWorkflowListModalOpen, setWorkflowListModalOpen] = useState(false);
|
|
106
|
+
|
|
66
107
|
const [showPackGallery, setShowPackGallery] = useState(false);
|
|
67
108
|
const [checkedItems, setCheckedItems] = useState([])
|
|
68
109
|
|
|
@@ -115,6 +156,11 @@ function DataLayout({refreshUI}) {
|
|
|
115
156
|
const nav = useNavigate();
|
|
116
157
|
const mod = searchParams.get('model');
|
|
117
158
|
const loc = useLocation();
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// --- AJOUT : Récupérer le paramètre de l'URL pour l'édition de workflow ---
|
|
162
|
+
const workflowToEditId = searchParams.get('edit-workflow');
|
|
163
|
+
|
|
118
164
|
useEffect(() =>{
|
|
119
165
|
if (selectedModel?.name) {
|
|
120
166
|
nav('/user/' + getUserHash(me) + '/?model=' + selectedModel?.name);
|
|
@@ -129,13 +175,6 @@ function DataLayout({refreshUI}) {
|
|
|
129
175
|
setEditionMode(true);
|
|
130
176
|
}, []);
|
|
131
177
|
|
|
132
|
-
useEffect(() =>{
|
|
133
|
-
const m = models.find(f => f.name === mod);
|
|
134
|
-
setSelectedModel(m);
|
|
135
|
-
setEditionMode(!m);
|
|
136
|
-
}, [mod, models, searchParams])
|
|
137
|
-
|
|
138
|
-
|
|
139
178
|
// La vue courante est dérivée du modèle sélectionné et des préférences stockées.
|
|
140
179
|
const currentView = useMemo(() => {
|
|
141
180
|
if (!selectedModel) return 'table';
|
|
@@ -247,6 +286,10 @@ function DataLayout({refreshUI}) {
|
|
|
247
286
|
return configuredViews.kanban
|
|
248
287
|
? <KanbanView settings={currentModelViewSettings.kanban} model={selectedModel} />
|
|
249
288
|
: <NotConfiguredPlaceholder type="kanban" onConfigure={() => setKanbanModalOpen(true)} />;
|
|
289
|
+
case 'workflow':
|
|
290
|
+
return workflowToEditId
|
|
291
|
+
? <WorkflowEditor workflowId={workflowToEditId} />
|
|
292
|
+
: <NotConfiguredPlaceholder type="kanban" onConfigure={() => setKanbanModalOpen(true)} />;
|
|
250
293
|
case 'table':
|
|
251
294
|
default:
|
|
252
295
|
// Le DataTable existant est retourné par défaut
|
|
@@ -281,7 +324,7 @@ function DataLayout({refreshUI}) {
|
|
|
281
324
|
setRelationFilters({});
|
|
282
325
|
setCheckedItems([])
|
|
283
326
|
setFilterValues({});
|
|
284
|
-
|
|
327
|
+
|
|
285
328
|
if (!model) {
|
|
286
329
|
setSelectedModel(null);
|
|
287
330
|
return;
|
|
@@ -289,7 +332,9 @@ function DataLayout({refreshUI}) {
|
|
|
289
332
|
|
|
290
333
|
// Maintient la vue actuelle si elle est configurée pour le nouveau modèle, sinon revient à la vue "table"
|
|
291
334
|
const modelSettings = viewSettings[model.name] || {};
|
|
292
|
-
if
|
|
335
|
+
if( currentView === 'workflow') {
|
|
336
|
+
setCurrentView('table')
|
|
337
|
+
}else if (currentView === 'calendar' && (!modelSettings.calendar?.titleField || !modelSettings.calendar?.startField || !modelSettings.calendar?.endField)) {
|
|
293
338
|
setCurrentView('table');
|
|
294
339
|
} else if (currentView === 'kanban' && !modelSettings.kanban?.groupByField) {
|
|
295
340
|
setCurrentView('table');
|
|
@@ -586,7 +631,7 @@ function DataLayout({refreshUI}) {
|
|
|
586
631
|
isOpen={isTourOpen}
|
|
587
632
|
onClose={closeTour}
|
|
588
633
|
/>)}</>
|
|
589
|
-
<div className="flex
|
|
634
|
+
<div className="flex mg-1">
|
|
590
635
|
|
|
591
636
|
{<ViewSwitcher
|
|
592
637
|
currentView={currentView}
|
|
@@ -594,6 +639,9 @@ function DataLayout({refreshUI}) {
|
|
|
594
639
|
configuredViews={configuredViews}
|
|
595
640
|
onConfigureView={handleConfigureCurrentView}
|
|
596
641
|
/>}
|
|
642
|
+
<Button onClick={() => setWorkflowListModalOpen(true)} className={currentView === 'workflow' ? 'active' : ''} title={t('views.workflow', 'Workflow')}>
|
|
643
|
+
<FaProjectDiagram />
|
|
644
|
+
</Button>
|
|
597
645
|
<div className="flex items-center gap-1 p-1 bg-gray-200 rounded-md">
|
|
598
646
|
<Button onClick={undo} disabled={!canUndo} title={t('btns.undo', 'Annuler')}>
|
|
599
647
|
<FaUndo />
|
|
@@ -604,14 +652,14 @@ function DataLayout({refreshUI}) {
|
|
|
604
652
|
</div>
|
|
605
653
|
<Button onClick={() => {
|
|
606
654
|
setImportModalVisible(true);
|
|
607
|
-
}} className="btn tourStep-import-model"><FaFileImport/><Trans
|
|
608
|
-
i18nKey="btns.importModels">Modèles</Trans></Button>
|
|
655
|
+
}} className="btn tourStep-import-model"><FaFileImport/><span className={"no-mobile-text"}> <Trans
|
|
656
|
+
i18nKey="btns.importModels">Modèles</Trans></span></Button>
|
|
609
657
|
<Button onClick={() => {
|
|
610
658
|
setShowPackGallery(true);
|
|
611
|
-
}} className="btn tourStep-import-pack"><FaBoxOpen/><Trans
|
|
612
|
-
i18nKey="btns.importPacks">Packs</Trans></Button>
|
|
613
|
-
<Button className={"tourStep-tutorials btn"} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaBookAtlas/><Trans
|
|
614
|
-
i18nKey="btns.showTutos">Tutoriels</Trans></Button>
|
|
659
|
+
}} className="btn tourStep-import-pack"><FaBoxOpen/><span className={"no-mobile-text"}><Trans
|
|
660
|
+
i18nKey="btns.importPacks">Packs</Trans></span></Button>
|
|
661
|
+
<Button className={"tourStep-tutorials btn"} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaBookAtlas/><span className={"no-mobile-text"}><Trans
|
|
662
|
+
i18nKey="btns.showTutos">Tutoriels</Trans></span></Button>
|
|
615
663
|
|
|
616
664
|
<DialogProvider>
|
|
617
665
|
{tutorialDialogVisible && (
|
|
@@ -619,12 +667,24 @@ function DataLayout({refreshUI}) {
|
|
|
619
667
|
<TutorialsMenu />
|
|
620
668
|
</Dialog>
|
|
621
669
|
)}
|
|
670
|
+
{isWorkflowListModalOpen && (
|
|
671
|
+
<WorkflowSelectorModal
|
|
672
|
+
onClose={() => setWorkflowListModalOpen(false)}
|
|
673
|
+
onSelectWorkflow={(id) => {
|
|
674
|
+
nav(`?edit-workflow=${id}`);
|
|
675
|
+
setEditionMode(false)
|
|
676
|
+
mainPartRef.current.scrollIntoView();
|
|
677
|
+
setCurrentView('workflow'); // Ajout pour forcer le changement de vue
|
|
678
|
+
setWorkflowListModalOpen(false);
|
|
679
|
+
}}
|
|
680
|
+
/>
|
|
681
|
+
)}
|
|
622
682
|
</DialogProvider>
|
|
623
683
|
<Button className="btn" onClick={() => {
|
|
624
684
|
setAPIInfoVisible(true);
|
|
625
685
|
setDataEditorVisible(false);
|
|
626
686
|
setEditionMode(false);
|
|
627
|
-
}}><FaBook
|
|
687
|
+
}}><FaBook/><span className={"no-mobile-text"}>{t('btns.api', 'API')}</span></Button>
|
|
628
688
|
</div>
|
|
629
689
|
|
|
630
690
|
<div className="datalayout flex flex-start">
|
|
@@ -655,6 +715,7 @@ function DataLayout({refreshUI}) {
|
|
|
655
715
|
setDataEditorVisible(false);
|
|
656
716
|
setAPIInfoVisible(false);
|
|
657
717
|
setEditionMode(true);
|
|
718
|
+
|
|
658
719
|
mainPartRef.current.scrollIntoView({behavior: 'smooth'});
|
|
659
720
|
gtag("event", "select_content", {
|
|
660
721
|
content_type: "edit_model",
|
|
@@ -684,6 +745,10 @@ function DataLayout({refreshUI}) {
|
|
|
684
745
|
handleModelSelect(model);
|
|
685
746
|
}}/>)}
|
|
686
747
|
|
|
748
|
+
{/* --- AJOUT : Affichage conditionnel de l'éditeur de workflow --- */}
|
|
749
|
+
{workflowToEditId && (
|
|
750
|
+
<WorkflowEditor workflowId={workflowToEditId} />
|
|
751
|
+
)}
|
|
687
752
|
<div className="hidden-anchor" ref={mainPartRef}></div>
|
|
688
753
|
|
|
689
754
|
{showDataEditor && (<DataEditor
|
|
@@ -700,7 +765,7 @@ function DataLayout({refreshUI}) {
|
|
|
700
765
|
|
|
701
766
|
|
|
702
767
|
{selectedModel && showAPIInfo && <APIInfo/>}
|
|
703
|
-
{selectedModel && !showAPIInfo && !generatedModels.
|
|
768
|
+
{selectedModel && !showAPIInfo && !workflowToEditId && !generatedModels.find(g => g.name === selectedModel?.name) && (<div className="datas">
|
|
704
769
|
|
|
705
770
|
<h2 className={"field-bg p-2"}>{t(`model_${selectedModel?.name}`, selectedModel?.name)} <>({countByModel?.[selectedModel?.name]})</></h2>
|
|
706
771
|
|
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
/* website*/
|
|
4
|
-
|
|
4
|
+
.datalayout {
|
|
5
5
|
padding: 0;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.workflow-selector-list {
|
|
10
|
+
list-style: none;
|
|
11
|
+
padding: 0;
|
|
12
|
+
margin: 0;
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
li {
|
|
15
|
+
padding: 12px 15px;
|
|
16
|
+
border-bottom: 1px solid #eee;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
transition: background-color 0.2s ease-in-out;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 10px;
|
|
22
|
+
|
|
23
|
+
&:hover {
|
|
24
|
+
background-color: #f5f5f5;
|
|
25
|
+
}
|
|
13
26
|
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.properties-panel {
|
|
30
|
+
position: relative; /* Nécessaire pour positionner le resizer */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.resizer {
|
|
34
|
+
width: 5px;
|
|
35
|
+
height: 100%;
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
cursor: col-resize;
|
|
14
40
|
}
|
package/client/src/DataTable.jsx
CHANGED
|
@@ -3,8 +3,8 @@ import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
|
3
3
|
import {Trans, useTranslation} from "react-i18next";
|
|
4
4
|
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
5
5
|
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
6
|
-
import React, {useEffect, useMemo, useRef, useState} from "react";
|
|
7
|
-
import {getUserId} from "../../src/data.js";
|
|
6
|
+
import React, {useEffect, useMemo, useRef, useState} from "react";
|
|
7
|
+
import {getUserHash, getUserId} from "../../src/data.js";
|
|
8
8
|
import cronstrue from 'cronstrue/i18n';
|
|
9
9
|
import {Event} from "../../src/events.js";
|
|
10
10
|
|
|
@@ -43,7 +43,7 @@ import RelationValue from "./RelationValue.jsx";
|
|
|
43
43
|
import {FaGear, FaPencil, FaTriangleExclamation} from "react-icons/fa6";
|
|
44
44
|
import RestoreConfirmationModal from "./RestoreConfirmationModal.jsx";
|
|
45
45
|
import {event_trigger, isLightColor} from "../../src/core.js";
|
|
46
|
-
import {Tooltip} from "react-tooltip";
|
|
46
|
+
import {Tooltip} from "react-tooltip";
|
|
47
47
|
import ExportDialog from "./ExportDialog.jsx";
|
|
48
48
|
import Captions from "yet-another-react-lightbox/plugins/captions";
|
|
49
49
|
import {Zoom} from "yet-another-react-lightbox/plugins";
|
|
@@ -59,7 +59,9 @@ import {isConditionMet} from "../../src/filter";
|
|
|
59
59
|
import {DataImporter} from "./DataImporter.jsx";
|
|
60
60
|
import {HistoryDialog} from "./HistoryDialog.jsx";
|
|
61
61
|
import { useCommand } from './contexts/CommandContext.jsx';
|
|
62
|
-
import {Config} from "../../src/config.js";
|
|
62
|
+
import {Config} from "../../src/config.js";
|
|
63
|
+
import {useNavigate} from "react-router-dom";
|
|
64
|
+
import {NavLink} from "react-router";
|
|
63
65
|
|
|
64
66
|
const Header = ({
|
|
65
67
|
reversed = false,
|
|
@@ -261,7 +263,7 @@ export function DataTable({
|
|
|
261
263
|
filterValues,
|
|
262
264
|
setFilterValues = () => {},
|
|
263
265
|
data: propData,
|
|
264
|
-
advanced= true, selectionMode= false, deleteApiCall
|
|
266
|
+
advanced= true, selectionMode= false, deleteApiCall
|
|
265
267
|
}) {
|
|
266
268
|
const {
|
|
267
269
|
models,
|
|
@@ -279,6 +281,7 @@ export function DataTable({
|
|
|
279
281
|
const {me} = useAuthContext();
|
|
280
282
|
const { execute, DeleteCommand } = useCommand();
|
|
281
283
|
|
|
284
|
+
const queryClient = useQueryClient();
|
|
282
285
|
// Si des données sont passées en props, on les utilise, sinon on prend celles du contexte.
|
|
283
286
|
const data = propData || paginatedDataByModel[model?.name] || [];
|
|
284
287
|
|
|
@@ -438,15 +441,12 @@ export function DataTable({
|
|
|
438
441
|
onDuplicateData(dataToDuplicate);
|
|
439
442
|
};
|
|
440
443
|
|
|
444
|
+
const nav = useNavigate();
|
|
445
|
+
|
|
441
446
|
const desc = t(`model_description_${selectedModel?.name}`, selectedModel?.description || '');
|
|
442
447
|
return (
|
|
443
448
|
<div className={`datatable${filterActive ? ' filter-active' : ''}`}>
|
|
444
|
-
{advanced && !selectionMode && <div className="flex
|
|
445
|
-
{t(`model_${selectedModel?.name}`, selectedModel?.name) !== selectedModel?.name && (
|
|
446
|
-
<span className="badge"><strong>model</strong> : {selectedModel?.name}</span>)}
|
|
447
|
-
{selectedModel.name === 'dashboard' && <Button className={"btn"} onClick={() => {
|
|
448
|
-
nav('/user/'+getUserHash(me)+'/dashboards');
|
|
449
|
-
}}><FaEye /> Tableaux de bord</Button> }
|
|
449
|
+
{advanced && !selectionMode && <div className="flex">
|
|
450
450
|
{desc && <p className="model-desc hint">{desc}</p>}
|
|
451
451
|
<Button onClick={handleImport} title={t("btns.import")}><FaFileImport/><Trans
|
|
452
452
|
i18nKey="btns.import">Importer</Trans></Button>
|
|
@@ -736,6 +736,13 @@ export function DataTable({
|
|
|
736
736
|
>
|
|
737
737
|
<FaHistory />
|
|
738
738
|
</button>)}
|
|
739
|
+
{/* AJOUT : Bouton pour ouvrir l'éditeur de workflow */}
|
|
740
|
+
{model.name === 'workflow' && (
|
|
741
|
+
<NavLink to={`?edit-workflow=${item._id}`} className="datatable-action-btn workflow-edit"
|
|
742
|
+
data-tooltip-id="tooltipActions" data-tooltip-content={t('workflow.editor.title', "Éditeur de Workflow")}>
|
|
743
|
+
<FaGear />
|
|
744
|
+
</NavLink>
|
|
745
|
+
)}
|
|
739
746
|
|
|
740
747
|
<button data-tooltip-id="tooltipActions"
|
|
741
748
|
data-tooltip-content={t('btns.delete', 'Supprimer')}
|