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.
Files changed (49) hide show
  1. package/README.md +37 -22
  2. package/client/package-lock.json +33 -0
  3. package/client/package.json +1 -0
  4. package/client/src/App.scss +12 -4
  5. package/client/src/AssistantChat.jsx +48 -5
  6. package/client/src/AssistantChat.scss +0 -1
  7. package/client/src/ChartConfigModal.jsx +34 -9
  8. package/client/src/ConditionBuilder.jsx +1 -1
  9. package/client/src/ConditionBuilder2.jsx +2 -1
  10. package/client/src/Dashboard.jsx +396 -349
  11. package/client/src/DashboardChart.jsx +91 -12
  12. package/client/src/DataEditor.jsx +376 -368
  13. package/client/src/DataLayout.jsx +3 -2
  14. package/client/src/DataTable.jsx +60 -31
  15. package/client/src/Field.jsx +1788 -1782
  16. package/client/src/GeolocationField.jsx +94 -0
  17. package/client/src/ModelCreatorField.jsx +1 -0
  18. package/client/src/RelationField.jsx +2 -2
  19. package/client/src/constants.js +2 -2
  20. package/client/src/contexts/UIContext.jsx +5 -2
  21. package/client/src/filter.js +0 -155
  22. package/client/src/translations.js +16828 -16750
  23. package/package.json +10 -2
  24. package/src/config.js +2 -2
  25. package/src/constants.js +262 -4
  26. package/src/core.js +21 -3
  27. package/src/defaultModels.js +12 -12
  28. package/src/engine.js +7 -1
  29. package/src/events.js +4 -3
  30. package/src/filter.js +272 -260
  31. package/src/i18n.js +187 -177
  32. package/src/middlewares/middleware-mongodb.js +3 -1
  33. package/src/modules/assistant/assistant.js +131 -42
  34. package/src/modules/bucket.js +3 -2
  35. package/src/modules/data/data.backup.js +374 -0
  36. package/src/modules/data/data.core.js +2 -1
  37. package/src/modules/data/data.history.js +11 -8
  38. package/src/modules/data/data.js +190 -4551
  39. package/src/modules/data/data.operations.js +3000 -0
  40. package/src/modules/data/data.relations.js +687 -0
  41. package/src/modules/data/data.routes.js +132 -38
  42. package/src/modules/data/data.scheduling.js +274 -0
  43. package/src/modules/data/data.validation.js +248 -0
  44. package/src/modules/data/index.js +29 -1
  45. package/src/modules/user.js +2 -1
  46. package/src/modules/workflow.js +3 -2
  47. package/src/services/stripe.js +3 -2
  48. package/swagger-en.yml +133 -0
  49. package/test/model.integration.test.js +377 -221
@@ -109,8 +109,9 @@ function DataLayout({refreshUI}) {
109
109
  }, []);
110
110
 
111
111
  useEffect(() =>{
112
- setSelectedModel(models.find(f => f.name === mod));
113
- setEditionMode(false);
112
+ const m = models.find(f => f.name === mod);
113
+ setSelectedModel(m);
114
+ setEditionMode(!m);
114
115
  }, [mod, models, searchParams])
115
116
 
116
117
 
@@ -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.length === data.length} onChange={e => {
101
- if (checkedItems.length === data.length) {
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.fields.map(field => {
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 => resp.status === 200 ? resp.blob() : Promise.reject('something went wrong'))
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.some(i => i._id === item._id)}
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.fields.map(field => {
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}