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
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# data-primals-engine
|
|
2
2
|
[](https://github.com/anonympins/data-primals-engine/actions/workflows/node.js.yml)
|
|
3
3
|

|
|
4
4
|

|
|
@@ -33,15 +33,6 @@
|
|
|
33
33
|
- **📦 Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
|
|
34
34
|
- **📄Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
|
|
35
35
|
|
|
36
|
-
## ⚛️ Frontend Integration (React)
|
|
37
|
-
|
|
38
|
-
This engine is designed to work seamlessly with its dedicated client library, **`data-primals-engine/client`**. This library provides a complete set of React hooks and UI components to build a rich, data-centric user interface on top of the backend.
|
|
39
|
-
|
|
40
|
-
While this README focuses on the backend engine and its API, you can find detailed instructions on how to integrate the client part in your React application here:
|
|
41
|
-
|
|
42
|
-
➡️ **[View the Frontend Integration Guide](https://github.com/anonympins/data-primals-engine/tree/develop/client)**
|
|
43
|
-
|
|
44
|
-
|
|
45
36
|
## 🌟 Why Choose data-primals-engine?
|
|
46
37
|
|
|
47
38
|
- **Zero Boilerplate**: Focus on your business logic, not infrastructure
|
|
@@ -129,10 +120,11 @@ By default, the app runs on port **7633** : http://localhost:7633
|
|
|
129
120
|
|
|
130
121
|
The engine includes a pluggable system for user management. For development and testing, it uses a `DefaultUserProvider` which creates a volatile **`demo`** user. This is perfect for getting started quickly without setting up a user database.
|
|
131
122
|
|
|
132
|
-
For production environments, you should extend the base `UserProvider` class to connect to your actual user database (e.g., another MongoDB collection, a SQL database, or an external authentication service). This allows you to implement your own logic for finding
|
|
123
|
+
For production environments, you should use SSO providers as seen in the page below, or extend the base `UserProvider` class to connect to your actual user database (e.g., another MongoDB collection, a SQL database, or an external authentication service). This allows you to implement your own logic for finding and creating users.
|
|
124
|
+
|
|
125
|
+
### Model generation
|
|
126
|
+
Models are the way to handle structured data. They organize data and they can be declared in JSON.
|
|
133
127
|
|
|
134
|
-
### Models
|
|
135
|
-
Define schemas using JSON:
|
|
136
128
|
```json
|
|
137
129
|
{
|
|
138
130
|
"name": "product",
|
|
@@ -150,12 +142,8 @@ Define schemas using JSON:
|
|
|
150
142
|
]
|
|
151
143
|
}
|
|
152
144
|
```
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- For larger datasets, use intermediate collections
|
|
156
|
-
- Automatic indexing on key fields
|
|
157
|
-
- Custom indexing on fields
|
|
158
|
-
- Custom fields :
|
|
145
|
+
|
|
146
|
+
### Custom fields types
|
|
159
147
|
|
|
160
148
|
| Type | Description | Properties/Notes |
|
|
161
149
|
|:------------|:------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
|
|
@@ -180,6 +168,12 @@ Define schemas using JSON:
|
|
|
180
168
|
| model | Stores a model by name | – |
|
|
181
169
|
| modelField | Stores a model field path | – |
|
|
182
170
|
|
|
171
|
+
### Other model features
|
|
172
|
+
- Handles up to 2,000 direct relations by default (can be customized). For larger datasets, use intermediate collections
|
|
173
|
+
- Automatic indexing on key fields
|
|
174
|
+
- Custom indexing on fields
|
|
175
|
+
- Anonymizable fields (encrypted for API users)
|
|
176
|
+
|
|
183
177
|
### Model constraints
|
|
184
178
|
```javascript
|
|
185
179
|
{
|
|
@@ -250,6 +244,7 @@ data-primals-engine/
|
|
|
250
244
|
### 📁 Model Management
|
|
251
245
|
|
|
252
246
|
#### Create a model
|
|
247
|
+
> Defines a new data model (schema) in the system. The request body must contain the model's name and an array of field definitions.
|
|
253
248
|
```bash
|
|
254
249
|
curl -X POST http://localhost:7633/api/model?_user=demo \
|
|
255
250
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -265,6 +260,7 @@ curl -X POST http://localhost:7633/api/model?_user=demo \
|
|
|
265
260
|
```
|
|
266
261
|
|
|
267
262
|
#### Update a model
|
|
263
|
+
> Modifies an existing model's structure using its unique ID. This allows you to add, remove, or change fields.
|
|
268
264
|
```bash
|
|
269
265
|
curl -X PUT http://localhost:7633/api/model/60d0fe4f5311236168a109ca?_user=demo \
|
|
270
266
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -280,6 +276,7 @@ curl -X PUT http://localhost:7633/api/model/60d0fe4f5311236168a109ca?_user=demo
|
|
|
280
276
|
```
|
|
281
277
|
|
|
282
278
|
#### Delete a model
|
|
279
|
+
> Permanently deletes a model definition using its name. This action is irreversible.
|
|
283
280
|
```bash
|
|
284
281
|
curl -X DELETE "http://localhost:7633/api/model?_user=demo&name=newModel" \
|
|
285
282
|
-H "Authorization: Bearer demotoken"
|
|
@@ -288,6 +285,7 @@ curl -X DELETE "http://localhost:7633/api/model?_user=demo&name=newModel" \
|
|
|
288
285
|
### 🗂️ Data Management
|
|
289
286
|
|
|
290
287
|
#### Create a document
|
|
288
|
+
> Creates a single new document in the specified model. The `data` object must conform to the model's schema.
|
|
291
289
|
```bash
|
|
292
290
|
curl -X POST http://localhost:7633/api/data?_user=demo \
|
|
293
291
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -303,18 +301,22 @@ curl -X POST http://localhost:7633/api/data?_user=demo \
|
|
|
303
301
|
}'
|
|
304
302
|
```
|
|
305
303
|
|
|
304
|
+
|
|
306
305
|
#### Search documents
|
|
306
|
+
> Searches for documents using a MongoDB-style filter. This endpoint is ideal for complex queries, pagination, and sorting.
|
|
307
|
+
|
|
307
308
|
```bash
|
|
308
309
|
curl -X POST http://localhost:7633/api/data/search?_user=demo \
|
|
309
310
|
-H "Authorization: Bearer demotoken" \
|
|
310
311
|
-H "Content-Type: application/json" \
|
|
311
312
|
-d '{
|
|
312
313
|
"model": "product",
|
|
313
|
-
"filter": { "
|
|
314
|
+
"filter": { "$gt" : ["$price", 50] }
|
|
314
315
|
}'
|
|
315
316
|
```
|
|
316
317
|
|
|
317
318
|
#### Update a document by ID
|
|
319
|
+
> Updates a single document by its unique ID. The request body contains the fields to be modified.
|
|
318
320
|
```bash
|
|
319
321
|
curl -X PUT http://localhost:7633/api/data/64a31c123ef59d4c8d55aa99?_user=demo \
|
|
320
322
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -325,7 +327,9 @@ curl -X PUT http://localhost:7633/api/data/64a31c123ef59d4c8d55aa99?_user=demo \
|
|
|
325
327
|
}'
|
|
326
328
|
```
|
|
327
329
|
|
|
330
|
+
|
|
328
331
|
#### Bulk update
|
|
332
|
+
> Updates multiple documents matching a filter. This is efficient for applying changes to a batch of records, such as restocking all out-of-stock products.
|
|
329
333
|
```bash
|
|
330
334
|
curl -X PUT http://localhost:7633/api/data?_user=demo \
|
|
331
335
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -337,6 +341,7 @@ curl -X PUT http://localhost:7633/api/data?_user=demo \
|
|
|
337
341
|
```
|
|
338
342
|
|
|
339
343
|
#### Delete documents
|
|
344
|
+
> Deletes one or more documents. You can provide an array of `ids` to delete specific documents.
|
|
340
345
|
```bash
|
|
341
346
|
curl -X DELETE http://localhost:7633/api/data?_user=demo \
|
|
342
347
|
-H "Authorization: Bearer demotoken" \
|
|
@@ -811,7 +816,7 @@ Event.Listen("OnDataAdded", (engine, data) => {
|
|
|
811
816
|
| OnDataInsert | Triggered just before data insertion. It will use the overrided data. | System | internal | (data) |
|
|
812
817
|
| OnDataValidate | Triggered to override validation check. | System | internal | (value, field, data) |
|
|
813
818
|
| OnDataFilter | Triggered to override data filtering operation. | System | internal | (filteredValue, field, data) |
|
|
814
|
-
| OnEmailTemplate | Triggered to override custom email templates | System | internal | (
|
|
819
|
+
| OnEmailTemplate | Triggered to override custom email templates | System | internal | (templateData, lang) |
|
|
815
820
|
|
|
816
821
|
### Triggering events
|
|
817
822
|
|
|
@@ -826,6 +831,16 @@ Results are merged together if multiple events are triggered.
|
|
|
826
831
|
- arrays are concatenated
|
|
827
832
|
- objects are merged using spread operator
|
|
828
833
|
|
|
834
|
+
---
|
|
835
|
+
|
|
836
|
+
## ⚛️ Frontend Integration (React)
|
|
837
|
+
|
|
838
|
+
This engine is designed to work seamlessly with its dedicated client library, **`data-primals-engine/client`**. This library provides a complete set of React hooks and UI components to build a rich, data-centric user interface on top of the backend.
|
|
839
|
+
|
|
840
|
+
While this README focuses on the backend engine and its API, you can find detailed instructions on how to integrate the client part in your React application here:
|
|
841
|
+
|
|
842
|
+
➡️ **[View the Frontend Integration Guide](https://github.com/anonympins/data-primals-engine/tree/develop/client)**
|
|
843
|
+
|
|
829
844
|
|
|
830
845
|
---
|
|
831
846
|
|
|
@@ -849,4 +864,4 @@ Distributed under the **MIT License**. See `LICENSE` file.
|
|
|
849
864
|
|
|
850
865
|
---
|
|
851
866
|
|
|
852
|
-
## [🔼](
|
|
867
|
+
## [🔼](#data-primals-engine) Back to Top
|
package/client/package-lock.json
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"react": "^18.3.1",
|
|
34
34
|
"react-big-calendar": "^1.15.0",
|
|
35
35
|
"react-dom": "^18.3.1",
|
|
36
|
+
"react-leaflet": "^4.2.1",
|
|
36
37
|
"react-router": "^7.8.1",
|
|
37
38
|
"react-switch": "^7.1.0",
|
|
38
39
|
"uniqid": "^5.4.0",
|
|
@@ -1692,6 +1693,17 @@
|
|
|
1692
1693
|
"url": "https://opencollective.com/popperjs"
|
|
1693
1694
|
}
|
|
1694
1695
|
},
|
|
1696
|
+
"node_modules/@react-leaflet/core": {
|
|
1697
|
+
"version": "2.1.0",
|
|
1698
|
+
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
|
|
1699
|
+
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
|
|
1700
|
+
"license": "Hippocratic-2.1",
|
|
1701
|
+
"peerDependencies": {
|
|
1702
|
+
"leaflet": "^1.9.0",
|
|
1703
|
+
"react": "^18.0.0",
|
|
1704
|
+
"react-dom": "^18.0.0"
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1695
1707
|
"node_modules/@remirror/core-constants": {
|
|
1696
1708
|
"version": "3.0.0",
|
|
1697
1709
|
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
|
@@ -5157,6 +5169,13 @@
|
|
|
5157
5169
|
"json-buffer": "3.0.1"
|
|
5158
5170
|
}
|
|
5159
5171
|
},
|
|
5172
|
+
"node_modules/leaflet": {
|
|
5173
|
+
"version": "1.9.4",
|
|
5174
|
+
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
|
5175
|
+
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
|
5176
|
+
"license": "BSD-2-Clause",
|
|
5177
|
+
"peer": true
|
|
5178
|
+
},
|
|
5160
5179
|
"node_modules/levn": {
|
|
5161
5180
|
"version": "0.4.1",
|
|
5162
5181
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
|
@@ -6274,6 +6293,20 @@
|
|
|
6274
6293
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
|
6275
6294
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
|
6276
6295
|
},
|
|
6296
|
+
"node_modules/react-leaflet": {
|
|
6297
|
+
"version": "4.2.1",
|
|
6298
|
+
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
|
|
6299
|
+
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
|
|
6300
|
+
"license": "Hippocratic-2.1",
|
|
6301
|
+
"dependencies": {
|
|
6302
|
+
"@react-leaflet/core": "^2.1.0"
|
|
6303
|
+
},
|
|
6304
|
+
"peerDependencies": {
|
|
6305
|
+
"leaflet": "^1.9.0",
|
|
6306
|
+
"react": "^18.0.0",
|
|
6307
|
+
"react-dom": "^18.0.0"
|
|
6308
|
+
}
|
|
6309
|
+
},
|
|
6277
6310
|
"node_modules/react-lifecycles-compat": {
|
|
6278
6311
|
"version": "3.0.4",
|
|
6279
6312
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
package/client/package.json
CHANGED
package/client/src/App.scss
CHANGED
|
@@ -1039,10 +1039,18 @@ h2.shadow {
|
|
|
1039
1039
|
}
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
|
-
.mini
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1042
|
+
.mini {
|
|
1043
|
+
.field-checkbox.field-bg {
|
|
1044
|
+
background: none;
|
|
1045
|
+
}
|
|
1046
|
+
input[type=checkbox] {
|
|
1047
|
+
cursor: pointer;
|
|
1048
|
+
margin: 0 auto;
|
|
1049
|
+
background: none;
|
|
1050
|
+
width: 16px;
|
|
1051
|
+
height: 16px;
|
|
1052
|
+
display: block;
|
|
1053
|
+
}
|
|
1046
1054
|
}
|
|
1047
1055
|
|
|
1048
1056
|
.pagination-wrapper {
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
// client/src/components/AssistantChat.jsx
|
|
2
2
|
|
|
3
3
|
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
4
|
-
import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress } from 'react-icons/fa';
|
|
4
|
+
import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress, FaPlus } from 'react-icons/fa';
|
|
5
5
|
import './AssistantChat.scss';
|
|
6
6
|
import { useModelContext } from "./contexts/ModelContext.jsx";
|
|
7
7
|
import { Trans, useTranslation } from "react-i18next";
|
|
8
8
|
import Markdown from 'react-markdown';
|
|
9
9
|
import {useQueryClient} from "react-query";
|
|
10
10
|
import {providers} from "../../src/modules/assistant/constants.js";
|
|
11
|
+
import DashboardChart from "./DashboardChart.jsx";
|
|
12
|
+
import {useUI} from "./contexts/UIContext.jsx";
|
|
13
|
+
import Button from "./Button.jsx";
|
|
14
|
+
import {getUserHash, getUserId} from "../../src/data.js";
|
|
15
|
+
import {useNavigation} from "react-router";
|
|
16
|
+
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
17
|
+
import {useNavigate} from "react-router-dom";
|
|
18
|
+
import {DataTable} from "./DataTable.jsx";
|
|
11
19
|
|
|
12
20
|
const AssistantChat = ({ config }) => {
|
|
13
|
-
const { selectedModel } = useModelContext();
|
|
21
|
+
const { selectedModel, models } = useModelContext();
|
|
22
|
+
const { me } = useAuthContext();
|
|
23
|
+
const nav = useNavigate();
|
|
14
24
|
const { t } = useTranslation();
|
|
15
25
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
26
|
const [isMaximized, setIsMaximized] = useState(false);
|
|
@@ -19,6 +29,7 @@ const AssistantChat = ({ config }) => {
|
|
|
19
29
|
]);
|
|
20
30
|
const [input, setInput] = useState('');
|
|
21
31
|
const [isLoading, setIsLoading] = useState(false);
|
|
32
|
+
const { setChartToAdd } = useUI();
|
|
22
33
|
|
|
23
34
|
// NOUVEL ÉTAT : Stocke une action en attente de confirmation de l'utilisateur
|
|
24
35
|
const [pendingConfirmation, setPendingConfirmation] = useState(null);
|
|
@@ -74,7 +85,8 @@ const AssistantChat = ({ config }) => {
|
|
|
74
85
|
const botMessage = {
|
|
75
86
|
from: 'bot',
|
|
76
87
|
text: null,
|
|
77
|
-
actionDetails: null
|
|
88
|
+
actionDetails: null,
|
|
89
|
+
chartConfig: null// Pour stocker les détails de l'action à afficher
|
|
78
90
|
};
|
|
79
91
|
|
|
80
92
|
// Gérer le texte à afficher
|
|
@@ -104,7 +116,15 @@ const AssistantChat = ({ config }) => {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
// On ajoute le message à la liste uniquement s'il a du contenu textuel
|
|
107
|
-
if (
|
|
119
|
+
if (result.chartConfig) {
|
|
120
|
+
botMessage.chartConfig = result.chartConfig;
|
|
121
|
+
}
|
|
122
|
+
// Si des données tabulaires sont retournées
|
|
123
|
+
if (result.dataResult) {
|
|
124
|
+
botMessage.dataResult = result.dataResult;
|
|
125
|
+
}
|
|
126
|
+
// On ajoute le message à la liste uniquement s'il a du contenu textuel ou un graphique
|
|
127
|
+
if (botMessage.text || botMessage.chartConfig || botMessage.dataResult) {
|
|
108
128
|
setMessages(prev => [...prev, botMessage]);
|
|
109
129
|
}
|
|
110
130
|
|
|
@@ -225,7 +245,30 @@ const AssistantChat = ({ config }) => {
|
|
|
225
245
|
<div className="chat-messages">
|
|
226
246
|
{messages.map((msg, index) => (
|
|
227
247
|
<div key={index} className={`message ${msg.from}`}>
|
|
228
|
-
<Markdown>{msg.text}</Markdown>
|
|
248
|
+
{msg.text && <Markdown>{msg.text}</Markdown>}
|
|
249
|
+
{msg.chartConfig && (
|
|
250
|
+
<div className="chart-container">
|
|
251
|
+
<DashboardChart config={msg.chartConfig} />
|
|
252
|
+
<div className="chart-actions" style={{ marginTop: '8px', textAlign: 'right' }}>
|
|
253
|
+
<Button onClick={() => {
|
|
254
|
+
nav('/user/'+getUserHash(me)+'/dashboards');
|
|
255
|
+
setChartToAdd(msg.chartConfig);
|
|
256
|
+
}} title={t('assistant.addToDashboard', 'Ajouter au tableau de bord')}>
|
|
257
|
+
<FaPlus />
|
|
258
|
+
<span style={{ marginLeft: '8px' }}>
|
|
259
|
+
{t('assistant.addToDashboard', 'Ajouter au tableau de bord')}
|
|
260
|
+
</span>
|
|
261
|
+
</Button>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* NOUVEAU : Affichage des données tabulaires */}
|
|
267
|
+
{msg.dataResult && (
|
|
268
|
+
<div className="data-table-container">
|
|
269
|
+
<DataTable model={models.find(f => f.name === msg.dataResult.model)} advanced={false} data={msg.dataResult.data} />
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
229
272
|
{msg.actionDetails && (
|
|
230
273
|
<div className="action-details">
|
|
231
274
|
{msg.actionDetails.model && (
|
|
@@ -55,6 +55,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
55
55
|
const [chartType, setChartType] = useState('bar');
|
|
56
56
|
const [chartAggregationType, setChartAggregationType] = useState('count');
|
|
57
57
|
const [chartTitle, setChartTitle] = useState('');
|
|
58
|
+
const [timeUnit, setTimeUnit] = useState('day');
|
|
58
59
|
|
|
59
60
|
const currentModel = models.find(f => f.name === selectedModel && f._user === me.username);
|
|
60
61
|
|
|
@@ -74,6 +75,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
74
75
|
setColorField(initialConfig.chartBackgroundColor || null);
|
|
75
76
|
setGroupByLabelField(initialConfig.groupByLabelField || '');
|
|
76
77
|
setChartAggregationType(initialConfig.aggregationType || 'count');
|
|
78
|
+
setTimeUnit(initialConfig.timeUnit || 'day');
|
|
77
79
|
// Note: modelFields et relatedModelFields seront chargés par les autres useEffects
|
|
78
80
|
// déclenchés par le changement de selectedModel et groupByField.
|
|
79
81
|
} else {
|
|
@@ -87,6 +89,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
87
89
|
setGroupByField('');
|
|
88
90
|
setGroupByLabelField('');
|
|
89
91
|
setChartAggregationType('count');
|
|
92
|
+
setTimeUnit('day');
|
|
90
93
|
setModelFields([]);
|
|
91
94
|
setRelatedModelFields([]);
|
|
92
95
|
}
|
|
@@ -155,6 +158,9 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
155
158
|
const isRelationGroupBy = isGroupingChart && modelFields.find(f => f.name === groupByField)?.type === 'relation';
|
|
156
159
|
const isYAxisRequiredForValidation = chartAggregationType && !['count'].includes(chartAggregationType);
|
|
157
160
|
|
|
161
|
+
const xAxisFieldDefinition = modelFields.find(f => f.name === xAxisField);
|
|
162
|
+
const isTemporal = !isGroupingChart && xAxisFieldDefinition && ['date', 'datetime'].includes(xAxisFieldDefinition.type);
|
|
163
|
+
|
|
158
164
|
// handleSave (inchangé - envoie l'état actuel)
|
|
159
165
|
const handleSave = () => {
|
|
160
166
|
const isValid = selectedModel && chartType && chartTitle &&
|
|
@@ -178,6 +184,7 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
178
184
|
aggregationType: chartAggregationType,
|
|
179
185
|
chartBackgroundColor: colorField,
|
|
180
186
|
filter,
|
|
187
|
+
timeUnit: isTemporal ? timeUnit : undefined,
|
|
181
188
|
});
|
|
182
189
|
} else {
|
|
183
190
|
let errorMsg = t('chartConfigModal.fillFields', "Veuillez remplir tous les champs requis.");
|
|
@@ -321,15 +328,33 @@ const ChartConfigModal = ({ isOpen, onClose, onSave, initialConfig = null }) =>
|
|
|
321
328
|
)}
|
|
322
329
|
</>
|
|
323
330
|
) : (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
<>
|
|
332
|
+
<SelectField
|
|
333
|
+
label={t('chartConfigModal.xAxis', 'Axe X')}
|
|
334
|
+
value={xAxisField}
|
|
335
|
+
onChange={(item) => setXAxisField(item.value)}
|
|
336
|
+
items={[{label: t('selectPlaceholder', 'Choisir...'), value: ''}, ...xAxisOptions]}
|
|
337
|
+
required={!isGroupingChart}
|
|
338
|
+
disabled={xAxisOptions.length === 0}
|
|
339
|
+
hint={xAxisOptions.length === 0 ? t('chartConfigModal.noXAxisFields', 'Aucun champ utilisable pour l\'axe X') : ''}
|
|
340
|
+
/>
|
|
341
|
+
{isTemporal && (
|
|
342
|
+
<SelectField
|
|
343
|
+
label={t('charts.timeUnit', 'Échelle de temps')}
|
|
344
|
+
value={timeUnit}
|
|
345
|
+
onChange={(item) => setTimeUnit(item.value)}
|
|
346
|
+
items={[
|
|
347
|
+
{ value: 'minute', label: t('time.minute', 'Minute') },
|
|
348
|
+
{ value: 'hour', label: t('time.hour', 'Heure') },
|
|
349
|
+
{ value: 'day', label: t('time.day', 'Jour') },
|
|
350
|
+
{ value: 'week', label: t('time.week', 'Semaine') },
|
|
351
|
+
{ value: 'month', label: t('time.month', 'Mois') },
|
|
352
|
+
{ value: 'year', label: t('time.year', 'Année') },
|
|
353
|
+
]}
|
|
354
|
+
hint={t('charts.timeUnitHelp', "Définit l'unité de regroupement pour l'axe des dates.")}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
357
|
+
</>
|
|
333
358
|
)}
|
|
334
359
|
|
|
335
360
|
{/* Axe Y (inchangé) */}
|
|
@@ -14,9 +14,9 @@ import './ConditionBuilder.scss';
|
|
|
14
14
|
import { useTranslation, Trans } from "react-i18next";
|
|
15
15
|
import { CodeField } from "./Field.jsx";
|
|
16
16
|
import { useAuthContext } from "./contexts/AuthContext.jsx";
|
|
17
|
-
import { MONGO_CALC_OPERATORS} from "./filter.js";
|
|
18
17
|
import {ConditionBuilder2} from "./ConditionBuilder2.jsx";
|
|
19
18
|
import {mongoOperators} from "./constants.js";
|
|
19
|
+
import {MONGO_CALC_OPERATORS} from "../../src/constants";
|
|
20
20
|
|
|
21
21
|
const getFieldDefinition = (fields, fieldName) => fields?.find(f => f.name === fieldName);
|
|
22
22
|
const getModelFields = (modelName, allModels, user) => {
|
|
@@ -2,10 +2,11 @@ import React, {useEffect, useState} from 'react';
|
|
|
2
2
|
import { Tooltip } from 'react-tooltip';
|
|
3
3
|
import {FaPlus, FaProjectDiagram, FaTrash, FaInfoCircle, FaEdit, FaTags, FaCalendarAlt} from 'react-icons/fa';
|
|
4
4
|
import { Trans } from 'react-i18next';
|
|
5
|
-
import {convertInputValue
|
|
5
|
+
import {convertInputValue} from "./filter.js";
|
|
6
6
|
import i18n from "i18next";
|
|
7
7
|
import {FaRepeat} from "react-icons/fa6";
|
|
8
8
|
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
9
|
+
import {MONGO_CALC_OPERATORS} from "../../src/constants.js";
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
// Déterminer si le champ doit être une date
|