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.
@@ -1,6 +1,6 @@
1
1
  import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
2
2
 
3
- import { FaUndo, FaRedo } from 'react-icons/fa';
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
- setEditionMode(false);
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 (currentView === 'calendar' && (!modelSettings.calendar?.titleField || !modelSettings.calendar?.startField || !modelSettings.calendar?.endField)) {
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 actions">
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/> {t('btns.api', 'API')}</Button>
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.some(g => g.name === selectedModel?.name) && (<div className="datas">
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
- #content .datalayout {
4
+ .datalayout {
5
5
  padding: 0;
6
6
 
7
- h1 {
8
- margin: 24px 8px;
9
- }
7
+ }
8
+
9
+ .workflow-selector-list {
10
+ list-style: none;
11
+ padding: 0;
12
+ margin: 0;
10
13
 
11
- h2 {
12
- margin: 20px 4px 0 4px;
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
  }
@@ -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, queryClient
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 actions">
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')}