data-primals-engine 1.4.1 → 1.4.3
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 +37 -22
- package/client/package-lock.json +33 -0
- package/client/package.json +1 -0
- package/client/src/App.scss +12 -4
- package/client/src/AssistantChat.jsx +48 -5
- package/client/src/AssistantChat.scss +0 -1
- package/client/src/ChartConfigModal.jsx +34 -9
- package/client/src/ConditionBuilder.jsx +1 -1
- package/client/src/ConditionBuilder2.jsx +2 -1
- package/client/src/Dashboard.jsx +396 -349
- package/client/src/DashboardChart.jsx +91 -12
- package/client/src/DataEditor.jsx +376 -368
- package/client/src/DataLayout.jsx +3 -2
- package/client/src/DataTable.jsx +60 -31
- package/client/src/Field.jsx +1788 -1782
- package/client/src/GeolocationField.jsx +94 -0
- package/client/src/ModelCreatorField.jsx +1 -0
- package/client/src/RelationField.jsx +2 -2
- package/client/src/constants.js +2 -2
- package/client/src/contexts/UIContext.jsx +5 -2
- package/client/src/filter.js +0 -155
- package/client/src/translations.js +16828 -16750
- package/package.json +10 -2
- package/src/config.js +2 -2
- package/src/constants.js +262 -4
- package/src/core.js +21 -3
- package/src/defaultModels.js +12 -12
- package/src/engine.js +7 -1
- package/src/events.js +4 -3
- package/src/filter.js +272 -260
- package/src/i18n.js +187 -177
- package/src/middlewares/middleware-mongodb.js +3 -1
- package/src/modules/assistant/assistant.js +131 -42
- package/src/modules/bucket.js +3 -2
- package/src/modules/data/data.backup.js +374 -0
- package/src/modules/data/data.core.js +2 -1
- package/src/modules/data/data.history.js +11 -8
- package/src/modules/data/data.js +190 -4551
- package/src/modules/data/data.operations.js +3000 -0
- package/src/modules/data/data.relations.js +687 -0
- package/src/modules/data/data.routes.js +132 -38
- package/src/modules/data/data.scheduling.js +274 -0
- package/src/modules/data/data.validation.js +248 -0
- package/src/modules/data/index.js +29 -1
- package/src/modules/user.js +2 -1
- package/src/modules/workflow.js +3 -2
- package/src/services/stripe.js +3 -2
- package/swagger-en.yml +133 -0
- package/test/model.integration.test.js +377 -221
|
@@ -109,8 +109,9 @@ function DataLayout({refreshUI}) {
|
|
|
109
109
|
}, []);
|
|
110
110
|
|
|
111
111
|
useEffect(() =>{
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const m = models.find(f => f.name === mod);
|
|
113
|
+
setSelectedModel(m);
|
|
114
|
+
setEditionMode(!m);
|
|
114
115
|
}, [mod, models, searchParams])
|
|
115
116
|
|
|
116
117
|
|
package/client/src/DataTable.jsx
CHANGED
|
@@ -71,7 +71,8 @@ const Header = ({
|
|
|
71
71
|
filterActive,
|
|
72
72
|
onChangeFilterValue,
|
|
73
73
|
setFilterValues,
|
|
74
|
-
filterValues
|
|
74
|
+
filterValues,
|
|
75
|
+
advanced=true
|
|
75
76
|
}) => {
|
|
76
77
|
|
|
77
78
|
const {t} = useTranslation()
|
|
@@ -92,13 +93,13 @@ const Header = ({
|
|
|
92
93
|
setAdvancedFilterVisible(true);
|
|
93
94
|
}
|
|
94
95
|
return <><tr className={reversed ? ' reversed' : ''}>
|
|
95
|
-
<th className={"mini"}>
|
|
96
|
+
{advanced && (<th className={"mini"}>
|
|
96
97
|
<div className="flex flex-row">
|
|
97
98
|
|
|
98
99
|
<Button onClick={handleFilter} className={filterActive ? ' active' : ''}><FaFilter/></Button>
|
|
99
100
|
{filterActive && <Button onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
|
|
100
|
-
<CheckboxField checked={checkedItems
|
|
101
|
-
if (checkedItems
|
|
101
|
+
<CheckboxField checkbox={true} checked={checkedItems?.length === data.length} onChange={e => {
|
|
102
|
+
if (checkedItems?.length === data.length) {
|
|
102
103
|
setCheckedItems([]);
|
|
103
104
|
} else {
|
|
104
105
|
setCheckedItems(data);
|
|
@@ -126,8 +127,8 @@ const Header = ({
|
|
|
126
127
|
)}
|
|
127
128
|
</DialogProvider>
|
|
128
129
|
</div>
|
|
129
|
-
</th>
|
|
130
|
-
{model
|
|
130
|
+
</th>)}
|
|
131
|
+
{(model?.fields || []).map(field => {
|
|
131
132
|
if (field.type === 'password')
|
|
132
133
|
return <></>;
|
|
133
134
|
if( field.type === 'relation' && !models.find(f => f.name === field.relation && f._user === me?.username ))
|
|
@@ -143,11 +144,11 @@ const Header = ({
|
|
|
143
144
|
</th>;
|
|
144
145
|
|
|
145
146
|
totalCol++;
|
|
146
|
-
return <FilterField reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
|
|
147
|
+
return <FilterField advanced={advanced} reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
|
|
147
148
|
model={model} field={field} active={filterActive}
|
|
148
149
|
onChangeFilterValue={onChangeFilterValue}/>;
|
|
149
150
|
})}
|
|
150
|
-
<th><Trans i18nKey="actions">Actions</Trans>
|
|
151
|
+
{advanced && (<th><Trans i18nKey="actions">Actions</Trans>
|
|
151
152
|
{Object.keys(pagedFilters[model.name] || {})
|
|
152
153
|
.filter(f=> Object.keys(pagedFilters[model.name]?.[f] || {}).length > 0).length > 0 && <div>
|
|
153
154
|
<button onClick={() => {
|
|
@@ -155,7 +156,7 @@ const Header = ({
|
|
|
155
156
|
setPagedFilters(pagedFilters => ({...pagedFilters, [model.name]: {}}));
|
|
156
157
|
}}><FaEraser />
|
|
157
158
|
</button>
|
|
158
|
-
</div>}</th>
|
|
159
|
+
</div>}</th>)}
|
|
159
160
|
</tr>
|
|
160
161
|
{reversed && (
|
|
161
162
|
<tr>
|
|
@@ -237,14 +238,17 @@ const RichText = ({ value, initialLang }) => {
|
|
|
237
238
|
export function DataTable({
|
|
238
239
|
model,
|
|
239
240
|
checkedItems,
|
|
240
|
-
setCheckedItems,
|
|
241
|
+
setCheckedItems = () => {},
|
|
241
242
|
onEdit,
|
|
242
243
|
onAddData,
|
|
243
244
|
onDuplicateData,
|
|
244
245
|
onDelete,
|
|
245
246
|
onShowAPI,
|
|
246
247
|
filterValues,
|
|
247
|
-
setFilterValues
|
|
248
|
+
setFilterValues = () => {},
|
|
249
|
+
data: propData, // NOUVEAU: Accepter les données via les props
|
|
250
|
+
advanced=true
|
|
251
|
+
|
|
248
252
|
}) {
|
|
249
253
|
const {
|
|
250
254
|
models,
|
|
@@ -260,15 +264,15 @@ export function DataTable({
|
|
|
260
264
|
const {t, i18n} = useTranslation();
|
|
261
265
|
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
262
266
|
const queryClient = useQueryClient();
|
|
263
|
-
const {me} = useAuthContext()
|
|
267
|
+
const {me} = useAuthContext();
|
|
264
268
|
|
|
269
|
+
// Si des données sont passées en props, on les utilise, sinon on prend celles du contexte.
|
|
270
|
+
const data = propData || paginatedDataByModel[model?.name] || [];
|
|
265
271
|
|
|
266
272
|
const isDataLoaded = true;
|
|
267
273
|
const [importVisible, setImportVisible] = useState(false);
|
|
268
274
|
const [filterActive, setFilterActive] = useState(false)
|
|
269
275
|
|
|
270
|
-
const data = paginatedDataByModel[model?.name] || [];
|
|
271
|
-
|
|
272
276
|
const [showPackGallery, setShowPackGallery] = useState(false);
|
|
273
277
|
const [showExportDialog, setExportDialogVisible] = useState(false);
|
|
274
278
|
|
|
@@ -367,7 +371,14 @@ export function DataTable({
|
|
|
367
371
|
body,
|
|
368
372
|
headers: {"Content-Type": "application/json"}
|
|
369
373
|
})
|
|
370
|
-
.then(resp =>
|
|
374
|
+
.then(async resp => {
|
|
375
|
+
if( resp.status === 200 )
|
|
376
|
+
return resp.blob();
|
|
377
|
+
else {
|
|
378
|
+
const res = await resp.json();
|
|
379
|
+
throw new Error(res.error || 'something went wrong')
|
|
380
|
+
}
|
|
381
|
+
})
|
|
371
382
|
.then((blob) => {
|
|
372
383
|
const url = window.URL.createObjectURL(blob);
|
|
373
384
|
const a = document.createElement('a');
|
|
@@ -380,11 +391,10 @@ export function DataTable({
|
|
|
380
391
|
window.URL.revokeObjectURL(url);
|
|
381
392
|
})
|
|
382
393
|
.then(e => {
|
|
383
|
-
|
|
384
394
|
const notificationData = {
|
|
385
|
-
title: t('dataimporter.success', 'Exportation de ' + model.name + ' réussie'),
|
|
386
|
-
icon: <FaInfo
|
|
387
|
-
status: 'completed'
|
|
395
|
+
title: e.success ? t('dataimporter.success', 'Exportation de ' + model.name + ' réussie'): e.error,
|
|
396
|
+
icon: e.success ? <FaInfo/> : null,
|
|
397
|
+
status: e.success ? 'completed' : 'error'
|
|
388
398
|
};
|
|
389
399
|
addNotification(notificationData);
|
|
390
400
|
return e;
|
|
@@ -469,7 +479,7 @@ export function DataTable({
|
|
|
469
479
|
};
|
|
470
480
|
return (
|
|
471
481
|
<div className={`datatable${filterActive ? ' filter-active' : ''}`}>
|
|
472
|
-
{<div className="flex actions flex-left">
|
|
482
|
+
{advanced && <div className="flex actions flex-left">
|
|
473
483
|
<Button onClick={() => onAddData(model)}><FaPlus/><Trans i18nKey="btns.addData">Ajouter une
|
|
474
484
|
donnée</Trans></Button>
|
|
475
485
|
<Button onClick={handleImport} title={t("btns.import")}><FaFileImport/><Trans
|
|
@@ -494,7 +504,7 @@ export function DataTable({
|
|
|
494
504
|
onShowAPI(selectedModel);
|
|
495
505
|
}}><FaBook/> {t('btns.api', 'API')}</Button>
|
|
496
506
|
</div>}
|
|
497
|
-
<div className="table-wrapper">
|
|
507
|
+
<div className={"table-wrapper"}>
|
|
498
508
|
<Tooltip id={"tooltipFile"} clickable={true} />
|
|
499
509
|
<Tooltip id={"tooltipActions"} />
|
|
500
510
|
<Lightbox
|
|
@@ -511,7 +521,7 @@ export function DataTable({
|
|
|
511
521
|
{isDataLoaded && (
|
|
512
522
|
<table>
|
|
513
523
|
<thead>
|
|
514
|
-
<Header model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>
|
|
524
|
+
<Header advanced={advanced} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>
|
|
515
525
|
</thead>
|
|
516
526
|
<tbody>
|
|
517
527
|
{(data || []).map((item) => (
|
|
@@ -526,9 +536,9 @@ export function DataTable({
|
|
|
526
536
|
setCheckedItems(items => items.filter(i => i._id !== item._id));
|
|
527
537
|
}
|
|
528
538
|
}}>
|
|
529
|
-
<td className={"mini"}>
|
|
530
|
-
<CheckboxField className={"input-ref"}
|
|
531
|
-
checked={checkedItems
|
|
539
|
+
{advanced && (<td className={"mini"}>
|
|
540
|
+
<CheckboxField checkbox={true} className={"input-ref"}
|
|
541
|
+
checked={checkedItems?.some(i => i._id === item._id)}
|
|
532
542
|
onChange={(e) => {
|
|
533
543
|
if (e) {
|
|
534
544
|
setCheckedItems(items => {
|
|
@@ -537,10 +547,10 @@ export function DataTable({
|
|
|
537
547
|
} else {
|
|
538
548
|
setCheckedItems(items => items.filter(i => i._id !== item._id));
|
|
539
549
|
}
|
|
540
|
-
}}/></td>
|
|
541
|
-
{model
|
|
550
|
+
}}/></td>)}
|
|
551
|
+
{(model?.fields ||[]).map(field => {
|
|
542
552
|
|
|
543
|
-
if( !isConditionMet(model, field.condition, item, models, me)){
|
|
553
|
+
if( !isConditionMet(model, field.condition, item, models, me, false)){
|
|
544
554
|
return <td className={"notmet"} key={item._id + field.name}></td>; // Do not render the header cell if the condition isn't met
|
|
545
555
|
}
|
|
546
556
|
|
|
@@ -607,6 +617,25 @@ export function DataTable({
|
|
|
607
617
|
if (field.type === 'model') {
|
|
608
618
|
return <td key={field.name} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}} className={isLightColor(field.color)?"lighted":""}>{hiddenable(item[field.name] ? `${t('model_'+item[field.name], item[field.name])} (${item[field.name]})`:'')}</td>;
|
|
609
619
|
}
|
|
620
|
+
if (field.type === 'geolocation') {
|
|
621
|
+
const geoData = item[field.name];
|
|
622
|
+
console.log({geoData})
|
|
623
|
+
if (geoData && geoData.coordinates && geoData.coordinates.length === 2) {
|
|
624
|
+
const [lng, lat] = geoData.coordinates;
|
|
625
|
+
const coordinatesText = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
|
|
626
|
+
const mapUrl = `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}#map=16/${lat}/${lng}`;
|
|
627
|
+
return (
|
|
628
|
+
<td key={field.name} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}} className={isLightColor(field.color)?"lighted":""}>
|
|
629
|
+
{hiddenable(
|
|
630
|
+
<a href={mapUrl} target="_blank" rel="noopener noreferrer" style={{color: 'inherit'}}>
|
|
631
|
+
{coordinatesText}
|
|
632
|
+
</a>
|
|
633
|
+
)}
|
|
634
|
+
</td>
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return <td key={field.name} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}} className={isLightColor(field.color)?"lighted":""}>{hiddenable('')}</td>;
|
|
638
|
+
}
|
|
610
639
|
if (field.type === 'password') {
|
|
611
640
|
return <></>;
|
|
612
641
|
}
|
|
@@ -738,7 +767,7 @@ export function DataTable({
|
|
|
738
767
|
return <td key={field.name} className={isLightColor(field.color)?"lighted":""} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}}>
|
|
739
768
|
{hiddenable(item[field.name])}</td>;
|
|
740
769
|
})}
|
|
741
|
-
<td>
|
|
770
|
+
{advanced && (<td>
|
|
742
771
|
<button data-tooltip-id="tooltipActions"
|
|
743
772
|
data-tooltip-content={t('btns.edit', 'Modifier')}
|
|
744
773
|
onClick={() => handleEdit(item)}><FaPencil/></button>
|
|
@@ -760,7 +789,7 @@ export function DataTable({
|
|
|
760
789
|
<button data-tooltip-id="tooltipActions"
|
|
761
790
|
data-tooltip-content={t('btns.delete', 'Supprimer')}
|
|
762
791
|
onClick={() => handleDelete(item)}><FaTrash/></button>
|
|
763
|
-
</td>
|
|
792
|
+
</td>)}
|
|
764
793
|
</tr>
|
|
765
794
|
</DialogProvider></>
|
|
766
795
|
))}
|
|
@@ -768,7 +797,7 @@ export function DataTable({
|
|
|
768
797
|
</tbody>
|
|
769
798
|
|
|
770
799
|
<tfoot>
|
|
771
|
-
{data.length > 10 && (<Header reversed={true} model={model} setCheckedItems={setCheckedItems}
|
|
800
|
+
{data.length > 10 && (<Header advanced={advanced} reversed={true} model={model} setCheckedItems={setCheckedItems}
|
|
772
801
|
filterValues={filterValues} data={data}
|
|
773
802
|
setFilterValues={setFilterValues}
|
|
774
803
|
onChangeFilterValue={onChangeFilterValue}
|