data-primals-engine 1.6.0 → 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 -924
- 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 +19 -12
- package/client/src/ModelCreator.jsx +12 -12
- package/client/src/ModelCreator.scss +1 -1
- package/client/src/ModelImporter.jsx +4 -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 +12 -2
- package/src/i18n.js +35 -3
- package/src/index.js +1 -0
- package/src/modules/data/data.operations.js +91 -5
- package/src/modules/user.js +14 -9
- package/src/modules/workflow.js +1 -4
- package/src/packs.js +239 -24
- package/test/data.integration.test.js +75 -0
- package/test/user.test.js +106 -1
|
@@ -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
|
|
|
441
|
-
const
|
|
444
|
+
const nav = useNavigate();
|
|
445
|
+
|
|
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')}
|
|
@@ -57,13 +57,15 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
57
57
|
setFields([...(initialModel.fields || []).map(m => ({...m}))]);
|
|
58
58
|
setModelHistory(initialModel.history);
|
|
59
59
|
setModelIcon(initialModel.icon);
|
|
60
|
+
setUseAI(false);
|
|
61
|
+
setPrompt(null);
|
|
62
|
+
setHomePrompt(null);
|
|
60
63
|
} else {
|
|
61
64
|
// Mode création : on réinitialise tout pour une nouvelle génération
|
|
62
65
|
setModelName('');
|
|
63
66
|
setModelDescription('');
|
|
64
67
|
setFields([]);
|
|
65
68
|
setUseAI(true); // On active l'IA par défaut
|
|
66
|
-
setModelVisible(false);
|
|
67
69
|
setModelHistory(false);
|
|
68
70
|
setModelIcon(null);
|
|
69
71
|
}
|
|
@@ -363,7 +365,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
363
365
|
}
|
|
364
366
|
|
|
365
367
|
const [useAI, setUseAI] = useState(true);
|
|
366
|
-
const [showModel, setModelVisible] = useState(
|
|
368
|
+
const [showModel, setModelVisible] = useState(false);
|
|
367
369
|
const [prompt, setPrompt] = useState('');
|
|
368
370
|
|
|
369
371
|
const [homePrompt, setHomePrompt] = useLocalStorage("ai_model_prompt", null);
|
|
@@ -471,7 +473,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
471
473
|
return c && (
|
|
472
474
|
<><p className="ws-pre-line">{c}</p></>)
|
|
473
475
|
}} />
|
|
474
|
-
<h2>
|
|
476
|
+
<h2 className={"field-bg p-2"}>
|
|
475
477
|
{!initialModel && <Trans i18nKey={"btns.createModel"}>Créer un modèle</Trans>}
|
|
476
478
|
{!!initialModel && <><Trans i18nKey={"btns.editModel"}>Editer le modèle</Trans> "{t(`model_${modelName}`, modelName)}"</>}
|
|
477
479
|
</h2>
|
|
@@ -517,7 +519,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
517
519
|
)}
|
|
518
520
|
|
|
519
521
|
{/* Layout principal pour afficher la liste et le formulaire côte à côte APRES génération */}
|
|
520
|
-
{showModel &&
|
|
522
|
+
{useAI && showModel && !initialModel && (
|
|
521
523
|
<div className="flex model-generation-layout">
|
|
522
524
|
{/* Colonne de gauche: Liste des modèles générés */}
|
|
523
525
|
{generatedModels.some(g => models.find(f => f.name === g.name && f._user === g._user)) && (
|
|
@@ -558,15 +560,13 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
558
560
|
<div className="field">
|
|
559
561
|
<div className="checkbox-label flex flex-1">
|
|
560
562
|
<CheckboxField
|
|
561
|
-
label={<Trans i18nKey={"history
|
|
562
|
-
|
|
563
|
-
|
|
563
|
+
label={<Trans i18nKey={"history"}>Historique</Trans>}
|
|
564
|
+
help={t('modelcreator.field.history', '')}
|
|
565
|
+
disabled={modelLocked}
|
|
566
|
+
checked={!!modelHistory}
|
|
564
567
|
onChange={(e) => {
|
|
565
|
-
|
|
566
|
-
newFields[index].history = e ? { enabled: true } : undefined;
|
|
567
|
-
setFields(newFields);
|
|
568
|
+
setModelHistory(e? { enabled: true }: false);
|
|
568
569
|
}}
|
|
569
|
-
help={field.required && t('modelcreator.history.hint')}
|
|
570
570
|
/>
|
|
571
571
|
</div>
|
|
572
572
|
</div>
|
|
@@ -587,7 +587,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
587
587
|
)}
|
|
588
588
|
|
|
589
589
|
{/* Affichage du formulaire en mode manuel ou édition */}
|
|
590
|
-
{(!useAI || initialModel) && (
|
|
590
|
+
{(!useAI || !!initialModel) && (
|
|
591
591
|
<div className="model-form-container">
|
|
592
592
|
<div className="flex field-bg">
|
|
593
593
|
<label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, {useState, useEffect} from 'react';
|
|
2
2
|
import { useModelContext } from './contexts/ModelContext.jsx';
|
|
3
3
|
import { useAuthContext } from './contexts/AuthContext.jsx';
|
|
4
|
-
import {Trans, useTranslation} from '
|
|
4
|
+
import {Trans, useTranslation} from '../../src/i18n.js';
|
|
5
5
|
import {FaArrowDown, FaInfo} from 'react-icons/fa';
|
|
6
6
|
import { CheckboxField } from './Field.jsx';
|
|
7
7
|
import Button from './Button.jsx';
|
|
@@ -94,6 +94,9 @@ const ModelImporterItem = ({ model, onUnselect, onSelect, selected }) => {
|
|
|
94
94
|
useEffect(() => {
|
|
95
95
|
setSelected(selected);
|
|
96
96
|
}, [selected]);
|
|
97
|
+
|
|
98
|
+
if( !model )
|
|
99
|
+
return;
|
|
97
100
|
const deps = [...new Set((model.fields || []).filter(f=> f.type === 'relation' && f.relation !== model?.name).map(f=> t(`model_${f.relation}`, f.relation)))];
|
|
98
101
|
return <div className={`model-importer-item ${selected ? 'active' : ''}`} onClick={() => {
|
|
99
102
|
setSelected(!selected);
|
|
@@ -30,7 +30,7 @@ const ViewSwitcher = ({ currentView, onViewChange, configuredViews, onConfigureV
|
|
|
30
30
|
title={t(view.labelKey, view.defaultLabel)}
|
|
31
31
|
>
|
|
32
32
|
{view.icon}
|
|
33
|
-
|
|
33
|
+
{/*<span className="hidden md:inline-block ml-2">{t(view.labelKey, view.defaultLabel)}</span>*/}
|
|
34
34
|
</Button>
|
|
35
35
|
{/* AJOUT : Bouton de configuration pour la vue active */}
|
|
36
36
|
{isActive && view.id !== 'table' && (
|