data-primals-engine 1.0.11 → 1.1.0

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 (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. package/vitest.config.js +2 -2
@@ -0,0 +1,68 @@
1
+
2
+ export const seoTitle = '';
3
+
4
+
5
+ // Dans ConditionBuilder.jsx ou un fichier de constantes partagé
6
+ export const mongoOperators = [
7
+ { value: '$eq', label: '==', inputType: 'text' }, // Égal (pour string, number, boolean...)
8
+ { value: '$ne', label: '!=', inputType: 'text' }, // Différent
9
+ { value: '$gt', label: '>', inputType: 'number' }, // Supérieur (nombre, date)
10
+ { value: '$gte', label: '>=', inputType: 'number' }, // Supérieur ou égal à
11
+ { value: '$lt', label: '<', inputType: 'number' }, // Inférieur à
12
+ { value: '$lte', label: '<=', inputType: 'number' }, // Inférieur ou égal à
13
+ { value: '$regex', label: 'contient (regex)', inputType: 'text' }, // Correspondance Regex (pour string)
14
+ { value: '$in', label: 'est dans (liste)', inputType: 'csv' }, // Dans un tableau (valeurs séparées par des virgules)
15
+ { value: '$nin', label: "n'est pas dans (liste)", inputType: 'csv' }, // Pas dans un tableau
16
+ { value: '$exists', label: 'existe', inputType: 'boolean' } // Le champ existe (true/false)
17
+ // { value: '$not', label: 'NON (condition)', inputType: 'condition' }, // Pourrait encapsuler une autre condition
18
+ // Ajoutez d'autres opérateurs MongoDB pertinents si besoin ($size, $type, $elemMatch...)
19
+ ];
20
+
21
+ export const MONGO_OPERATORS = {
22
+ ADD: { mongo: '$add', label: '+', type: 'numeric', multi: true },
23
+ SUBTRACT: { mongo: '$subtract', label: '-', type: 'numeric', multi: false },
24
+ MULTIPLY: { mongo: '$multiply', label: '*', type: 'numeric', multi: true },
25
+ DIVIDE: { mongo: '$divide', label: '/', type: 'numeric', multi: false },
26
+ MODULO: { mongo: '$mod', label: '%', type: 'numeric', multi: false },
27
+ POW: { mongo: '$pow', label: 'pow', type: 'numeric', multi: false },
28
+ SQRT: { mongo: '$sqrt', label: '√', type: 'numeric', multi: false, unary: true },
29
+ ABS: { mongo: '$abs', label: 'abs', type: 'numeric', multi: false, unary: true },
30
+ CEIL: { mongo: '$ceil', label: 'ceil', type: 'numeric', multi: false, unary: true },
31
+ FLOOR: { mongo: '$floor', label: 'floor', type: 'numeric', multi: false, unary: true },
32
+ COS: { mongo: '$cos', label: 'cos', type: 'numeric', multi: false, unary: true },
33
+ ACOS: { mongo: '$acos', label: 'acos', type: 'numeric', multi: false, unary: true },
34
+ SIN: { mongo: '$sin', label: 'sin', type: 'numeric', multi: false, unary: true },
35
+ ASIN: { mongo: '$asin', label: 'asin', type: 'numeric', multi: false, unary: true },
36
+ TAN: { mongo: '$tan', label: 'tan', type: 'numeric', multi: false, unary: true },
37
+ ATAN: { mongo: '$atan', label: 'atan', type: 'numeric', multi: false, unary: true },
38
+ LN: { mongo: '$ln', label: 'ln', type: 'numeric', multi: false, unary: true },
39
+ LOG: { mongo: '$log10', label: 'log', type: 'numeric', multi: false, unary: true },
40
+
41
+ CONCAT: { mongo: '$concat', label: 'concat', type: 'string', multi: true },
42
+
43
+ YEAR: { mongo: '$year', label: 'year', type: 'date', unary: true },
44
+ MONTH: { mongo: '$month', label: 'month', type: 'date', unary: true },
45
+ WEEK: { mongo: '$week', label: 'week', type: 'date', unary: true },
46
+ DAY_OF_MONTH: { mongo: '$dayOfMonth', label: 'day', type: 'date', unary: true },
47
+ HOUR: { mongo: '$hour', label: 'hour', type: 'date', unary: true },
48
+ MINUTE: { mongo: '$minute', label: 'min', type: 'date', unary: true },
49
+ SECOND: { mongo: '$second', label: 'sec', type: 'date', unary: true }
50
+ };
51
+
52
+ export const profiles = {
53
+ 'personal': ['contact', 'location', 'imageGallery', 'budget', 'currency', 'taxonomy'], // budget,
54
+ 'developer': ['alert','request','webpage', 'content', 'taxonomy', 'resource', 'translation', 'contact', 'location', 'channel', 'lang', 'token', 'message', 'ticket', 'user', 'permission', 'role'],
55
+ 'company': ['alert','request','location', 'campaign', 'order', 'currency', 'product', 'cart', 'cartItem', 'invoice', 'messaging', 'user', 'role', 'permission', 'token','translation', 'lang', 'webpage', 'content', 'taxonomy', 'contact', 'resource', 'accountingExercise', 'accountingLineItem', 'accountingEntry', 'employee', 'kpi', 'dashboard'],
56
+ 'engineer': ['alert','request','dashboard', 'kpi', 'user', 'role', 'token', 'permission', 'workflow', 'workflowRun', 'workflowStep', "channel", "message", 'workflowAction', 'workflowTrigger']
57
+ }
58
+
59
+ export const OPERAND_TYPES = {
60
+ FIELD: 'field',
61
+ CONSTANT: 'constant',
62
+ PREVIOUS_STEP: 'previousStep'
63
+ };
64
+
65
+
66
+ export const getHost = () => {
67
+ return process.env.HOST || 'localhost';
68
+ }
@@ -0,0 +1,28 @@
1
+ // ModelContext.jsx
2
+ import React, { createContext, useContext, useState } from 'react';
3
+
4
+ const AuthContext = createContext(null);
5
+
6
+ export const AuthProvider = ({ children }) => {
7
+ const [me, setMe] = useState(null);
8
+
9
+
10
+ const contextValue = {
11
+ me,
12
+ setMe
13
+ };
14
+
15
+ return (
16
+ <AuthContext.Provider value={contextValue}>
17
+ {children}
18
+ </AuthContext.Provider>
19
+ );
20
+ };
21
+
22
+ export const useAuthContext = () => {
23
+ const context = useContext(AuthContext);
24
+ if (context === null) {
25
+ throw new Error('useAuthContext doit être utilisé dans un AuthProvider');
26
+ }
27
+ return context;
28
+ };
@@ -0,0 +1,328 @@
1
+ // ModelContext.jsx
2
+ import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react';
3
+ import { useQueries, useQuery } from 'react-query';
4
+ import {elementsPerPage} from "data-primals-engine/constants";
5
+ import {getObjectHash} from "data-primals-engine/core";
6
+ import {useAuthContext} from "./AuthContext.jsx";
7
+ import {getUserHash, getUserId} from "data-primals-engine/data";
8
+ import {useTranslation} from "react-i18next";
9
+ import useLocalStorage from "../hooks/useLocalStorage.js";
10
+
11
+ const ModelContext = createContext(null);
12
+
13
+ export const ModelProvider = ({ children }) => {
14
+ const [models, setModels] = useState([]);
15
+ const [datasToLoad, setDatasToLoad] = useState([]);
16
+ const [pagedFilters,setPagedFilters] = useState({});
17
+ const [pagedDepth,setPagedDepth] = useState({});
18
+ const [filteredDatasToLoad, setFilteredDatasToLoad] = useState([]);
19
+ const [onSuccessCallbacks, setOnSuccessCallbacks] = useState({});
20
+ const [selectedModel, setSelectedModel] = useState(null);
21
+ const [relationFilters, setRelationFilters] = useState({});
22
+ const [relationIds, setRelationIds] = useState({});
23
+ const [page, setPage] = useState(1);
24
+ const [elementsPerPage, setElementsPerPage] = useState(20);
25
+ const [relationModels, setRelationModels] = useState([]);
26
+ const [pagedSort, setPagedSort] = useState({})
27
+ const [lockedColumns, setLockedColumns] = useState([])
28
+ const [generatedModels, setGeneratedModels] = useLocalStorage('ai_generated_models', []);
29
+
30
+ const updateRelationIds = (modelName, ids, cb) => {
31
+ setRelationIds((prevIds) => {
32
+ cb?.();
33
+ return {
34
+ ...prevIds,
35
+ [modelName]: ids,
36
+ };
37
+ });
38
+ };
39
+
40
+
41
+ const { me } = useAuthContext();
42
+ const { t, i18n } = useTranslation()
43
+ const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
44
+
45
+ const { data: dataModels = [], isFetched } = useQuery(['api/models', me], () => {
46
+ if(!me)
47
+ return Promise.reject();
48
+ return fetch(`/api/models?lang=${lang}&_user=${encodeURIComponent(getUserId(me))}`).then((res) => {
49
+ if( !res.ok ){
50
+ return [];
51
+ }
52
+ return res.json();
53
+ })}, {
54
+ onSuccess: (data, d) => {
55
+ if( typeof(data) === 'object' && data.success === false) {
56
+ return;
57
+ }
58
+ setModels(data);
59
+ }, onError: () => {
60
+ setModels([])
61
+ }, enabled: !!me});
62
+
63
+ const allModels = useMemo(() => {
64
+ // On filtre les modèles générés pour ne pas inclure ceux qui ont le même nom qu'un modèle déjà sauvegardé.
65
+ const uniqueGeneratedModels = generatedModels.filter(
66
+ genModel => !dataModels.some(apiModel => apiModel._user === genModel._user && apiModel.name === genModel.name)
67
+ );
68
+ return [...dataModels, ...uniqueGeneratedModels];
69
+ }, [dataModels, generatedModels]);
70
+
71
+ useEffect(() => {
72
+ if (selectedModel) {
73
+ const rels = selectedModel.fields
74
+ .filter((f) => f.type === 'relation')
75
+ .map((m) => m.relation);
76
+ setRelationModels(rels);
77
+ }
78
+ }, [selectedModel]);
79
+
80
+ let abortController = new AbortController();
81
+
82
+ const [relations, setRelations] = useState([]);
83
+
84
+ const rawQueries = models
85
+ ?.filter((f) => datasToLoad?.includes(f.name))
86
+ .map((model) => {
87
+ const rels = relationIds[model.name]?.filter(Boolean) || [];
88
+ if( rels.length === 0)
89
+ return Promise.resolve({data:[], count:0});
90
+ const params = new URLSearchParams();
91
+ params.append('model', model.name);
92
+ params.append('lang', lang);
93
+ params.append("_user", getUserId(me));
94
+ params.append("depth", '1')
95
+
96
+ let ids = [...new Set(rels.map(r => r._id || r))].join(',');
97
+ if (rels && rels.length > 0) {
98
+ // Cas des relations : requête par IDs, sans pagination
99
+ params.append('ids', ids);
100
+ }
101
+ return ({
102
+ queryKey: ['api/data', model.name, params.toString()], // Ajout de page et relationIds à la clé de requête
103
+ queryFn: ({signal}) => {
104
+ if(!me)return Promise.reject();
105
+ const rel = relations['api/data'+model.name+getObjectHash({ids})];
106
+ if( rel){
107
+ return Promise.resolve(rel);
108
+ }
109
+ return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', headers: { "Content-Type": "application/json"}})
110
+ .then((res) => res.json())
111
+ .then((e) => ({
112
+ count: e.count,
113
+ data: (e.data || []).map((m) => {
114
+
115
+ const mod = {...m, _model: model.name};
116
+ // Parcourir les champs pour normaliser les relations
117
+ model.fields.forEach(field => {
118
+ if (field.type === 'relation') {
119
+ const relationValue = mod[field.name];
120
+
121
+ if (field.multiple) {
122
+ // Pour les relations multiples, on s'assure d'avoir un tableau d'IDs.
123
+ if (Array.isArray(relationValue)) {
124
+ // On extrait l'_id de chaque objet, ou on garde la valeur si c'est déjà un ID.
125
+ mod[field.name] = relationValue.map(item => item?._id || item);
126
+ } else {
127
+ // Si ce n'est pas un tableau, on met un tableau vide pour éviter les erreurs.
128
+ mod[field.name] = [];
129
+ }
130
+ } else {
131
+ // Pour une relation simple, on veut juste l'ID ou null.
132
+ if (typeof relationValue === 'object' && relationValue !== null) {
133
+ // Gère les cas où la relation est un objet { _id: "..." } ou un tableau [ { _id: "..." } ]
134
+ const item = Array.isArray(relationValue) ? relationValue[0] : relationValue;
135
+ mod[field.name] = item?._id || null;
136
+ } else {
137
+ // La valeur est déjà un ID (string) ou null/undefined. On la garde telle quelle.
138
+ mod[field.name] = relationValue || null;
139
+ }
140
+ }
141
+ }
142
+ });
143
+ return mod;
144
+ })
145
+ }))
146
+ },
147
+ enabled: !!selectedModel && !!me,
148
+ onSuccess: (data)=>{
149
+ },
150
+ onSettled: (data)=>{
151
+ const dt = {...relations};
152
+ dt['api/data'+model.name+getObjectHash({ids})] = data;
153
+ setRelations(dt);
154
+ try{
155
+ if (onSuccessCallbacks?.['api/data/'+model.name])
156
+ Object.keys(onSuccessCallbacks?.['api/data/'+model.name]).forEach(cb => onSuccessCallbacks?.['api/data/'+model.name][cb](data));
157
+ } catch (e) {
158
+ console.error(e);
159
+ }
160
+ }
161
+ })
162
+ }) || [];
163
+
164
+ const rawQueriesPage = models
165
+ ?.filter((f) => filteredDatasToLoad?.includes(f.name))
166
+ .map((model) => ({
167
+ queryKey: ['api/data', model.name, 'page', page, elementsPerPage,pagedFilters[model.name], pagedSort[model.name], lang], // Ajout de page et relationIds à la clé de requête
168
+ queryFn: ({signal}) => {
169
+
170
+ const params = new URLSearchParams();
171
+ params.append('model', model.name);
172
+ params.append("_user", getUserId(me));
173
+
174
+ // Cas de la table de données : requête paginée
175
+ params.append('lang', lang);
176
+ params.append('limit', elementsPerPage+'');
177
+ params.append('page', page+'');
178
+ params.append("depth", '1');
179
+
180
+ const sortParam = [];
181
+
182
+ lockedColumns.forEach(s => {
183
+ sortParam.push(s + ':' + (pagedSort[model.name]?.[s] > 0 ? 'ASC' : 'DESC'));
184
+ });
185
+ Object.keys(pagedSort[model.name] || {}).forEach(s => {
186
+ if( !lockedColumns.includes(s))
187
+ sortParam.push(s + ':' + (pagedSort[model.name]?.[s] > 0 ? 'ASC' : 'DESC'));
188
+ })
189
+ if( sortParam.length )
190
+ params.append("sort", sortParam.join(','));
191
+
192
+ const c = [];
193
+ Object.keys(pagedFilters[model.name] || {}).filter(f=>typeof(pagedFilters[model.name][f]) === 'object' && Object.keys(pagedFilters[model.name][f]).length > 0).forEach(obj =>{
194
+ const f = model.fields.find(f=>f.name===obj);
195
+ if(f !== null && f !== undefined){
196
+ c.push(pagedFilters[model.name][obj]);
197
+ }
198
+ });
199
+ const filter= JSON.stringify({filter:{$and:c}});
200
+
201
+ return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', body: filter, headers: { "Content-Type": "application/json"}})
202
+ .then((res) => res.json())
203
+ .then((e) => {
204
+ return {
205
+ count: e.count || 0,
206
+ data: (e.data || []).map((m) => {
207
+ const mod = {...m, _model: model.name};
208
+ return mod;
209
+ }),
210
+ }
211
+ });
212
+ },
213
+ enabled: !!selectedModel,
214
+ onSettled: (data)=>{
215
+ try {
216
+ Object.keys(onSuccessCallbacks?.['api/data/' + model.name + '/paged']).forEach(cb => {
217
+ onSuccessCallbacks?.['api/data/' + model.name + '/paged'][cb](data)
218
+ });
219
+ } catch (e){
220
+ console.error(e)
221
+ }
222
+ },
223
+ onError: (e)=>{
224
+ console.error(e)
225
+ }
226
+ })) || [];
227
+
228
+ const modelQueries = useQueries(rawQueries);
229
+ const paginateQueries = useQueries(rawQueriesPage);
230
+ const countByModel = {};
231
+
232
+ const dataByModel = models.reduce((acc, model) => {
233
+ const query = modelQueries.find(
234
+ (q) => q.data?.data.length && q.data.data[0]._model === model.name
235
+ );
236
+ if (query?.isSuccess) {
237
+ acc[model.name] = query.data.data;
238
+ } else {
239
+ acc[model.name] = [];
240
+ }
241
+ return acc;
242
+ }, {});
243
+
244
+ const paginatedDataByModel = models.reduce((acc, model) => {
245
+ const query = paginateQueries.find(
246
+ (q) => q.data?.data.length && q.data.data[0]._model === model.name
247
+ );
248
+ if (query?.isSuccess) {
249
+ acc[model.name] = query.data.data;
250
+ countByModel[model.name] = query.data.count;
251
+ } else {
252
+ acc[model.name] = [];
253
+ countByModel[model.name] = 0;
254
+ }
255
+ return acc;
256
+ }, {});
257
+
258
+ const relatedData = {};
259
+ models.forEach((model) => {
260
+ model.fields.forEach((field) => {
261
+ if (field.type === 'relation') {
262
+ const relatedModelName = field.relation;
263
+ relatedData[relatedModelName] = dataByModel[relatedModelName];
264
+ }
265
+ });
266
+ });
267
+
268
+ const contextValue = {
269
+ models: allModels,
270
+ setModels,
271
+ page,
272
+ setPage,
273
+ countByModel,
274
+ selectedModel,
275
+ setSelectedModel,
276
+ datasToLoad,
277
+ setDatasToLoad,
278
+ pagedFilters,
279
+ setPagedFilters,
280
+ dataByModel,
281
+ paginatedDataByModel,
282
+ filteredDatasToLoad,
283
+ setFilteredDatasToLoad,
284
+ relatedData,
285
+ setRelationIds,
286
+ setRelationFilters,
287
+ relationIds,
288
+ updateRelationIds,
289
+ getObjectHash,
290
+ onSuccessCallbacks,
291
+ setOnSuccessCallbacks,
292
+ pagedSort,
293
+ setPagedSort,
294
+ lockedColumns,
295
+ setLockedColumns,
296
+ elementsPerPage,
297
+ setElementsPerPage,
298
+ relations,
299
+ setRelations,
300
+ generatedModels,
301
+ setGeneratedModels
302
+ };
303
+
304
+ const func = useCallback(() => {
305
+ setRelations({});
306
+ }, []);
307
+ useEffect(() => {
308
+ window.addEventListener('visibilitychange', func);
309
+ return () => window.removeEventListener('visibilitychanges', func);
310
+ }, []);
311
+ useEffect(() => {
312
+ if( selectedModel && page )
313
+ setRelations({});
314
+ }, [selectedModel, page, pagedFilters]);
315
+ return (
316
+ <ModelContext.Provider value={contextValue}>
317
+ {children}
318
+ </ModelContext.Provider>
319
+ );
320
+ };
321
+
322
+ export const useModelContext = () => {
323
+ const context = useContext(ModelContext);
324
+ if (context === null) {
325
+ throw new Error('useModelContext doit être utilisé dans un ModelProvider');
326
+ }
327
+ return context;
328
+ };
@@ -0,0 +1,52 @@
1
+ // ModelContext.jsx
2
+ import React, {createContext, useContext, useEffect, useMemo, useState} from 'react';
3
+ import useLocalStorage from "../hooks/useLocalStorage.js";
4
+
5
+ const UIContext = createContext(null);
6
+
7
+ export const UIProvider = ({ children }) => {
8
+
9
+ const [tourStepIndex, setTourStepIndex] = useState(0);
10
+ const [currentTourSteps, setCurrentTourSteps] = useState([]);
11
+ const [isTourOpen, setIsTourOpen] = useState(false);
12
+ const [allTourSteps, setAllTourSteps] = useState([]);
13
+ const [launchedTours, setLaunchedTours] = useState([]);
14
+
15
+ const [currentTour, setCurrentTour] = useLocalStorage("spotlight-tour", null);
16
+ const [finishedTours, setLSTours] = useLocalStorage('launchedTours', []);
17
+ useEffect(() =>{
18
+ if( launchedTours)
19
+ setLSTours(launchedTours);
20
+ }, [launchedTours])
21
+
22
+ const [isLocked, setLocked] = useState(false);
23
+ const contextValue = useMemo(() => ({
24
+ isLocked,
25
+ setLocked,
26
+ currentTourSteps, setCurrentTourSteps,
27
+ launchedTours,setLaunchedTours,
28
+ currentTour, setCurrentTour,
29
+ isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
30
+ tourStepIndex, setTourStepIndex
31
+ }), [isLocked,
32
+ setLocked,
33
+ currentTourSteps, setCurrentTourSteps,
34
+ launchedTours,setLaunchedTours,
35
+ currentTour, setCurrentTour,
36
+ isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
37
+ tourStepIndex, setTourStepIndex]);
38
+
39
+ return (
40
+ <UIContext.Provider value={contextValue}>
41
+ <div id={"ui"} className={`${isLocked ? 'ui-locked' : ''}`}>{children}</div>
42
+ </UIContext.Provider>
43
+ );
44
+ };
45
+
46
+ export const useUI = () => {
47
+ const context = useContext(UIContext);
48
+ if (context === null) {
49
+ throw new Error('useUI doit être utilisé dans un UIProvider');
50
+ }
51
+ return context;
52
+ };
@@ -0,0 +1,35 @@
1
+ import {getHost, MONGO_OPERATORS} from "../constants.js";
2
+
3
+ const isProd = import.meta.env.MODE === 'production';
4
+ export const urlData = isProd ? 'https://'+getHost()+'/' : 'http://localhost:7633/';
5
+
6
+
7
+ // Fonction pour vérifier si un opérateur est "atomique" (date, 1 seul opérande)
8
+ export const isAtomicDateOperator = (operator) => {
9
+ return Object.keys(MONGO_OPERATORS).some(f => MONGO_OPERATORS[f].mongo === operator && (MONGO_OPERATORS[f].isDate || MONGO_OPERATORS[f].isAtomic));
10
+ };
11
+
12
+ // --- Helper to get the definition of a field, possibly nested ---
13
+ export const getFieldDefinitionFromPath = (fieldPath, startModelName, allModels) => {
14
+ if (!fieldPath || !startModelName || !allModels) return null;
15
+ const parts = fieldPath.split('.');
16
+ let currentModel = allModels.find(m => m.name === startModelName);
17
+ if (!currentModel) return null;
18
+
19
+ let fieldDef = null;
20
+ for (let i = 0; i < parts.length; i++) {
21
+ const partName = parts[i];
22
+ if (!currentModel || !currentModel.fields) return null;
23
+ fieldDef = currentModel.fields.find(f => f.name === partName);
24
+ if (!fieldDef) return null;
25
+
26
+ if (i < parts.length - 1) { // This is a relation part
27
+ if (fieldDef.type === 'relation' && fieldDef.relation) {
28
+ currentModel = allModels.find(m => m.name === fieldDef.relation);
29
+ } else {
30
+ return null; // Path broken, intermediate part is not a valid relation
31
+ }
32
+ }
33
+ }
34
+ return fieldDef; // Definition of the final field in the path
35
+ };
@@ -0,0 +1,15 @@
1
+ import './index.css'
2
+ import React from 'react'
3
+ import ReactDOM from 'react-dom/client'
4
+ import App from './App'
5
+ import {BrowserRouter} from "react-router-dom";
6
+ import dns from "dns";
7
+
8
+ dns.setDefaultResultOrder("verbatim");
9
+
10
+ ReactDOM.hydrateRoot(
11
+ document.getElementById('root'),
12
+ <BrowserRouter>
13
+ <App />
14
+ </BrowserRouter>
15
+ )
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import ReactDOMServer from 'react-dom/server'
3
+ import App from './App'
4
+ import {StaticRouter} from "react-router";
5
+
6
+ export function render() {
7
+ const html = ReactDOMServer.renderToString(
8
+ <StaticRouter location={"/"}>
9
+ <App />
10
+ </StaticRouter>
11
+ )
12
+ return { html }
13
+ }