data-primals-engine 1.5.0 → 1.5.2
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 -0
- package/client/src/AddWidgetTypeModal.jsx +47 -43
- package/client/src/App.jsx +2 -6
- package/client/src/App.scss +13 -1
- package/client/src/AssistantChat.jsx +363 -323
- package/client/src/AssistantChat.scss +27 -10
- package/client/src/Dashboard.jsx +480 -396
- package/client/src/Dashboard.scss +1 -1
- package/client/src/DashboardHtmlViewItem.jsx +147 -0
- package/client/src/DashboardView.jsx +654 -569
- package/client/src/DataEditor.jsx +10 -3
- package/client/src/DataLayout.jsx +807 -755
- package/client/src/DataLayout.scss +14 -0
- package/client/src/DataTable.jsx +39 -75
- package/client/src/Dialog.scss +1 -1
- package/client/src/Field.jsx +2057 -1825
- package/client/src/FlexViewCard.jsx +44 -0
- package/client/src/HistoryDialog.jsx +69 -14
- package/client/src/HtmlViewBuilderModal.jsx +91 -0
- package/client/src/HtmlViewBuilderModal.scss +18 -0
- package/client/src/HtmlViewCard.jsx +44 -0
- package/client/src/HtmlViewCard.scss +35 -0
- package/client/src/KanbanCard.jsx +1 -2
- package/client/src/ModelCreator.jsx +5 -4
- package/client/src/ModelCreatorField.jsx +51 -4
- package/client/src/ModelList.jsx +280 -236
- package/client/src/Notification.jsx +136 -136
- package/client/src/Notification.scss +0 -18
- package/client/src/Pagination.jsx +5 -3
- package/client/src/RelationField.jsx +354 -258
- package/client/src/RelationSelectorWidget.jsx +173 -0
- package/client/src/contexts/ModelContext.jsx +10 -1
- package/client/src/contexts/UIContext.jsx +72 -63
- package/client/src/filter.js +263 -212
- package/client/src/hooks/useValidation.js +75 -0
- package/client/src/translations.js +24 -24
- package/package.json +7 -6
- package/src/constants.js +1 -1
- package/src/core.js +8 -1
- package/src/defaultModels.js +1596 -1544
- package/src/engine.js +85 -43
- package/src/events.js +137 -113
- package/src/i18n.js +710 -10
- package/src/index.js +3 -0
- package/src/modules/assistant/assistant.js +253 -134
- package/src/modules/assistant/constants.js +2 -1
- package/src/modules/bucket.js +2 -1
- package/src/modules/data/data.core.js +118 -92
- package/src/modules/data/data.history.js +555 -492
- package/src/modules/data/data.js +3 -53
- package/src/modules/data/data.operations.js +3381 -3231
- package/src/modules/data/data.relations.js +686 -686
- package/src/modules/data/data.routes.js +1879 -1821
- package/src/modules/data/data.validation.js +81 -2
- package/src/modules/file.js +247 -238
- package/src/modules/user.js +1 -0
- package/src/modules/workflow.js +2 -2
- package/src/openai.jobs.js +3 -2
- package/src/packs.js +5482 -5478
- package/src/sso.js +2 -2
- package/src/workers/import-export-worker.js +1 -1
- package/test/data.history.integration.test.js +264 -192
- package/test/data.integration.test.js +149 -3
|
@@ -1,756 +1,808 @@
|
|
|
1
|
-
import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
2
|
-
|
|
3
|
-
import "./App.scss";
|
|
4
|
-
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
5
|
-
import ModelCreator from "./ModelCreator.jsx";
|
|
6
|
-
import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
7
|
-
import {Dialog, DialogProvider} from "./Dialog.jsx";
|
|
8
|
-
import {Pagination} from "./Pagination.jsx";
|
|
9
|
-
import {Event} from "../../src/events.js";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import
|
|
33
|
-
import
|
|
34
|
-
import
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const [
|
|
63
|
-
|
|
64
|
-
const [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const [
|
|
91
|
-
|
|
92
|
-
const [
|
|
93
|
-
const [
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
214
|
-
}, [viewSettings, selectedModel]);
|
|
215
|
-
|
|
216
|
-
// ---
|
|
217
|
-
const
|
|
218
|
-
if (!selectedModel) return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
model={selectedModel}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (!r[field.relation]
|
|
440
|
-
r[field.relation]
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (!r[field.relation]
|
|
448
|
-
r[field.relation]
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
case '
|
|
503
|
-
|
|
504
|
-
break;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
break;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
/>
|
|
621
|
-
|
|
622
|
-
<
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
<
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1
|
+
import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
2
|
+
|
|
3
|
+
import "./App.scss";
|
|
4
|
+
import {useMutation, useQuery, useQueryClient} from "react-query";
|
|
5
|
+
import ModelCreator from "./ModelCreator.jsx";
|
|
6
|
+
import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
7
|
+
import {Dialog, DialogProvider} from "./Dialog.jsx";
|
|
8
|
+
import {Pagination} from "./Pagination.jsx";
|
|
9
|
+
import {Event} from "../../src/events.js";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
FaBook,
|
|
13
|
+
FaBoxOpen, FaDatabase,
|
|
14
|
+
FaEye, FaFileImport,
|
|
15
|
+
FaFilter, FaInfo, FaPlus,
|
|
16
|
+
} from "react-icons/fa";
|
|
17
|
+
import {getDefaultForType, getUserHash, getUserId} from "../../src/data.js";
|
|
18
|
+
import {Trans, useTranslation} from "react-i18next";
|
|
19
|
+
|
|
20
|
+
import {getObjectHash} from "../../src/core.js";
|
|
21
|
+
import Button from "./Button.jsx";
|
|
22
|
+
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
23
|
+
import APIInfo from "./APIInfo.jsx";
|
|
24
|
+
import {useNotificationContext} from "./NotificationProvider.jsx";
|
|
25
|
+
import {ModelImporter} from "./ModelImporter.jsx";
|
|
26
|
+
import {DataTable} from "./DataTable.jsx";
|
|
27
|
+
import {ModelList} from "./ModelList.jsx";
|
|
28
|
+
import {DataEditor} from "./DataEditor.jsx";
|
|
29
|
+
import TourSpotlight from "./TourSpotlight.jsx";
|
|
30
|
+
import useLocalStorage from "./hooks/useLocalStorage.js";
|
|
31
|
+
import {useUI} from "./contexts/UIContext.jsx";
|
|
32
|
+
import {useTutorials} from "./hooks/useTutorials.jsx";
|
|
33
|
+
import ViewSwitcher from "./ViewSwitcher.jsx";
|
|
34
|
+
import KanbanConfigModal from "./KanbanConfigModal.jsx";
|
|
35
|
+
import CalendarConfigModal from "./CalendarConfigModal.jsx";
|
|
36
|
+
import KanbanView from "./KanbanView.jsx";
|
|
37
|
+
import CalendarView from "./CalendarView.jsx";
|
|
38
|
+
import {useLocation, useParams, useSearchParams} from "react-router-dom";
|
|
39
|
+
import { useNavigate } from "react-router-dom";
|
|
40
|
+
import {Tooltip} from "react-tooltip";
|
|
41
|
+
import PackGallery from "./PackGallery.jsx";
|
|
42
|
+
import TutorialsMenu from "./TutorialsMenu.jsx";
|
|
43
|
+
import {FaBookAtlas} from "react-icons/fa6";
|
|
44
|
+
import {AssistantChat, NotificationList} from "../index.js";
|
|
45
|
+
|
|
46
|
+
import "./DataLayout.scss"
|
|
47
|
+
|
|
48
|
+
const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
|
|
49
|
+
<div className="p-4 border border-dashed rounded-md mt-4 text-center bg-gray-50">
|
|
50
|
+
<h4><Trans i18nKey="dataview.notConfiguredTitle" values={{ type }}>Vue {{type}} non configurée</Trans></h4>
|
|
51
|
+
<p className="text-sm text-gray-600"><Trans i18nKey="dataview.notConfiguredText">Veuillez configurer cette vue pour l'utiliser.</Trans></p>
|
|
52
|
+
<Button onClick={onConfigure} className="mt-2">
|
|
53
|
+
<Trans i18nKey="dataview.configureButton" values={{ type }}>Configurer {{type}}</Trans>
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
function DataLayout({refreshUI}) {
|
|
59
|
+
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
60
|
+
const [viewSettings, setViewSettings] = useLocalStorage('viewSettings', {});
|
|
61
|
+
|
|
62
|
+
const [isCalendarModalOpen, setCalendarModalOpen] = useState(false);
|
|
63
|
+
const [isKanbanModalOpen, setKanbanModalOpen] = useState(false);
|
|
64
|
+
const [showPackGallery, setShowPackGallery] = useState(false);
|
|
65
|
+
|
|
66
|
+
const { triggerTutorialCheck } = useTutorials();
|
|
67
|
+
const { t, i18n } = useTranslation();
|
|
68
|
+
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
69
|
+
|
|
70
|
+
// Stocke la vue sélectionnée pour chaque modèle. Ex: { "contacts": "calendar", "tasks": "kanban" }
|
|
71
|
+
const [viewsByModel, setViewsByModel] = useLocalStorage('dataLayout_viewsByModel', {});
|
|
72
|
+
|
|
73
|
+
const [filterValues, setFilterValues] = useState({});
|
|
74
|
+
const { dataByModel,paginatedDataByModel,
|
|
75
|
+
setRelationFilters, setSelectedModel, selectedModel,
|
|
76
|
+
setFilteredDatasToLoad,
|
|
77
|
+
setRelationIds,
|
|
78
|
+
setOnSuccessCallbacks,
|
|
79
|
+
datasToLoad,
|
|
80
|
+
setDatasToLoad,page, setPage, countByModel, relationIds,
|
|
81
|
+
pagedFilters, pagedSort,
|
|
82
|
+
elementsPerPage,
|
|
83
|
+
setRelations,
|
|
84
|
+
generatedModels,
|
|
85
|
+
models
|
|
86
|
+
} = useModelContext(); // Utilisez le contexte
|
|
87
|
+
const queryClient = useQueryClient();
|
|
88
|
+
|
|
89
|
+
const isDataLoaded = true;
|
|
90
|
+
const [checkedItems, setCheckedItems] = useState([])
|
|
91
|
+
|
|
92
|
+
const [refreshTime, setRefreshTime] = useState(0);
|
|
93
|
+
const [formData, setFormData] = useState({});
|
|
94
|
+
const [recordToEdit, setRecordToEdit] = useState(null); // New state for record to edit
|
|
95
|
+
|
|
96
|
+
const mainPartRef = useRef();
|
|
97
|
+
const modelCreatorRef = useRef();
|
|
98
|
+
|
|
99
|
+
const [tutorialDialogVisible, setTutorialDialogVisible] = useState(false);
|
|
100
|
+
const [importModalVisible, setImportModalVisible] = useState(false);
|
|
101
|
+
const [editionMode, setEditionMode] = useState(false);
|
|
102
|
+
const [showDataEditor, setDataEditorVisible] = useState(false);
|
|
103
|
+
const [showAPIInfo, setAPIInfoVisible] = useState(false);
|
|
104
|
+
const nav = useNavigate();
|
|
105
|
+
const mod = searchParams.get('model');
|
|
106
|
+
const loc = useLocation();
|
|
107
|
+
useEffect(() =>{
|
|
108
|
+
if (selectedModel?.name) {
|
|
109
|
+
nav('/user/' + getUserHash(me) + '/?model=' + selectedModel?.name);
|
|
110
|
+
}
|
|
111
|
+
}, [selectedModel?.name])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
setSelectedModel(null)
|
|
116
|
+
setDataEditorVisible(false);
|
|
117
|
+
setAPIInfoVisible(false);
|
|
118
|
+
setEditionMode(true);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
useEffect(() =>{
|
|
122
|
+
const m = models.find(f => f.name === mod);
|
|
123
|
+
setSelectedModel(m);
|
|
124
|
+
setEditionMode(!m);
|
|
125
|
+
}, [mod, models, searchParams])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
// La vue courante est dérivée du modèle sélectionné et des préférences stockées.
|
|
129
|
+
const currentView = useMemo(() => {
|
|
130
|
+
if (!selectedModel) return 'table';
|
|
131
|
+
return viewsByModel[selectedModel.name] || 'table';
|
|
132
|
+
}, [selectedModel, viewsByModel]);
|
|
133
|
+
|
|
134
|
+
// Met à jour la vue pour le modèle actuellement sélectionné.
|
|
135
|
+
const setCurrentView = (viewName) => {
|
|
136
|
+
if (!selectedModel) return;
|
|
137
|
+
setViewsByModel(prev => ({
|
|
138
|
+
...prev,
|
|
139
|
+
[selectedModel.name]: viewName,
|
|
140
|
+
}));
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// --- MODIFICATION : Logique de changement de vue mise à jour ---
|
|
144
|
+
const handleSwitchView = (viewName) => {
|
|
145
|
+
if (viewName === 'table') {
|
|
146
|
+
setCurrentView('table');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!selectedModel) {
|
|
151
|
+
addNotification({ title: t('datalayout.selectModelFirst', 'Veuillez d\'abord sélectionner un modèle.'), status: 'warning' });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const modelSettings = viewSettings[selectedModel.name] || {};
|
|
156
|
+
|
|
157
|
+
if (viewName === 'calendar') {
|
|
158
|
+
if (modelSettings.calendar?.titleField && modelSettings.calendar?.startField && modelSettings.calendar?.endField) {
|
|
159
|
+
setCurrentView('calendar');
|
|
160
|
+
} else {
|
|
161
|
+
setCalendarModalOpen(true);
|
|
162
|
+
}
|
|
163
|
+
} else if (viewName === 'kanban') {
|
|
164
|
+
if (modelSettings.kanban?.groupByField) {
|
|
165
|
+
setCurrentView('kanban');
|
|
166
|
+
} else {
|
|
167
|
+
setKanbanModalOpen(true);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// --- MODIFICATION : Sauvegarde dans localStorage ---
|
|
173
|
+
const handleSaveCalendarConfig = (config) => {
|
|
174
|
+
if (!selectedModel) return;
|
|
175
|
+
const isConfigValid = config && config.titleField && config.startField && config.endField;
|
|
176
|
+
|
|
177
|
+
if (isConfigValid) {
|
|
178
|
+
setViewSettings(prev => ({
|
|
179
|
+
...prev,
|
|
180
|
+
[selectedModel.name]: {
|
|
181
|
+
...(prev[selectedModel.name] || {}),
|
|
182
|
+
calendar: config,
|
|
183
|
+
},
|
|
184
|
+
}));
|
|
185
|
+
setCalendarModalOpen(false);
|
|
186
|
+
setCurrentView('calendar');
|
|
187
|
+
} else {
|
|
188
|
+
// Si la configuration n'est pas valide, afficher une notification et garder la modale ouverte
|
|
189
|
+
addNotification({
|
|
190
|
+
title: t('datalayout.invalidConfigTitle', 'Configuration invalide'),
|
|
191
|
+
message: t('datalayout.invalidConfigMessage', 'Veuillez vous assurer que les champs pour le titre, la date de début et la date de fin sont tous sélectionnés.'),
|
|
192
|
+
status: 'error'
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const handleSaveKanbanConfig = (config) => {
|
|
198
|
+
if (!selectedModel) return;
|
|
199
|
+
setViewSettings(prev => ({
|
|
200
|
+
...prev,
|
|
201
|
+
[selectedModel.name]: {
|
|
202
|
+
...(prev[selectedModel.name] || {}),
|
|
203
|
+
kanban: config,
|
|
204
|
+
},
|
|
205
|
+
}));
|
|
206
|
+
setKanbanModalOpen(false);
|
|
207
|
+
setCurrentView('kanban');
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// --- MODIFICATION : Dérive les settings du modèle courant depuis l'at global ---
|
|
211
|
+
const currentModelViewSettings = useMemo(() => {
|
|
212
|
+
if (!selectedModel) return {};
|
|
213
|
+
return viewSettings[selectedModel.name] || {};
|
|
214
|
+
}, [viewSettings, selectedModel]);
|
|
215
|
+
|
|
216
|
+
// --- MODIFICATION : Vérifie si les vues sont configurées pour le modèle courant ---
|
|
217
|
+
const configuredViews = useMemo(() => {
|
|
218
|
+
if (!selectedModel) return { calendar: false, kanban: false };
|
|
219
|
+
const modelSettings = viewSettings[selectedModel.name] || {};
|
|
220
|
+
return {
|
|
221
|
+
calendar: !!modelSettings.calendar?.titleField && !!modelSettings.calendar?.startField && !!modelSettings.calendar?.endField,
|
|
222
|
+
kanban: !!modelSettings.kanban?.groupByField,
|
|
223
|
+
};
|
|
224
|
+
}, [viewSettings, selectedModel]);
|
|
225
|
+
|
|
226
|
+
// --- AJOUT : Logique de rendu de la vue courante ---
|
|
227
|
+
const renderCurrentView = () => {
|
|
228
|
+
if (!selectedModel) return null;
|
|
229
|
+
|
|
230
|
+
switch (currentView) {
|
|
231
|
+
case 'calendar':
|
|
232
|
+
return configuredViews.calendar
|
|
233
|
+
? <CalendarView settings={currentModelViewSettings.calendar} onEditData={(model, data) => handleAddData(model,data)} model={selectedModel} />
|
|
234
|
+
: <NotConfiguredPlaceholder type="calendar" onConfigure={() => setCalendarModalOpen(true)} />;
|
|
235
|
+
case 'kanban':
|
|
236
|
+
return configuredViews.kanban
|
|
237
|
+
? <KanbanView settings={currentModelViewSettings.kanban} model={selectedModel} />
|
|
238
|
+
: <NotConfiguredPlaceholder type="kanban" onConfigure={() => setKanbanModalOpen(true)} />;
|
|
239
|
+
case 'table':
|
|
240
|
+
default:
|
|
241
|
+
// Le DataTable existant est retourné par défaut
|
|
242
|
+
return <DataTable
|
|
243
|
+
checkedItems={checkedItems}
|
|
244
|
+
setCheckedItems={setCheckedItems}
|
|
245
|
+
filterValues={filterValues}
|
|
246
|
+
setFilterValues={setFilterValues}
|
|
247
|
+
model={selectedModel}
|
|
248
|
+
onAddData={(model) => {
|
|
249
|
+
mainPartRef.current.scrollIntoView({behavior: "smooth"});
|
|
250
|
+
handleAddData(model);
|
|
251
|
+
}}
|
|
252
|
+
onDuplicateData={(data) => {
|
|
253
|
+
mainPartRef.current.scrollIntoView({behavior: "smooth"});
|
|
254
|
+
handleAddData(selectedModel, data);
|
|
255
|
+
}}
|
|
256
|
+
onEdit={(item) => {
|
|
257
|
+
mainPartRef.current.scrollIntoView({behavior: "smooth"});
|
|
258
|
+
setRecordToEdit(item);
|
|
259
|
+
setFormData(item);
|
|
260
|
+
setDataEditorVisible(true);
|
|
261
|
+
}}
|
|
262
|
+
onDelete={(item) => {
|
|
263
|
+
queryClient.invalidateQueries(['api/data', selectedModel.name, 'page', page, elementsPerPage, pagedFilters[selectedModel.name], pagedSort[selectedModel.name]]);
|
|
264
|
+
}}
|
|
265
|
+
/>;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const handleModelSelect = (model) => {
|
|
270
|
+
setRelationFilters({});
|
|
271
|
+
setCheckedItems([])
|
|
272
|
+
setFilterValues({});
|
|
273
|
+
setEditionMode(false);
|
|
274
|
+
if (!model) {
|
|
275
|
+
setSelectedModel(null);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Maintient la vue actuelle si elle est configurée pour le nouveau modèle, sinon revient à la vue "table"
|
|
280
|
+
const modelSettings = viewSettings[model.name] || {};
|
|
281
|
+
if (currentView === 'calendar' && (!modelSettings.calendar?.titleField || !modelSettings.calendar?.startField || !modelSettings.calendar?.endField)) {
|
|
282
|
+
setCurrentView('table');
|
|
283
|
+
} else if (currentView === 'kanban' && !modelSettings.kanban?.groupByField) {
|
|
284
|
+
setCurrentView('table');
|
|
285
|
+
}
|
|
286
|
+
const dt = [];
|
|
287
|
+
const t = [...model.fields].reduce((acc, field, index) => {
|
|
288
|
+
if (field.type === "relation") {
|
|
289
|
+
dt.push(field.relation);
|
|
290
|
+
acc[field.name] = dataByModel[field.relation]?.length > 0 ? dataByModel[field.relation][0]._id : null;
|
|
291
|
+
} else {
|
|
292
|
+
acc[field.name] = getDefaultForType(field);
|
|
293
|
+
}
|
|
294
|
+
return acc;
|
|
295
|
+
}, {});
|
|
296
|
+
setPage(1);
|
|
297
|
+
setFormData(t);
|
|
298
|
+
setFilteredDatasToLoad([model.name]);
|
|
299
|
+
|
|
300
|
+
setRecordToEdit(null); // Clear record to edit when model changes
|
|
301
|
+
|
|
302
|
+
let tl = [];
|
|
303
|
+
model.fields.forEach(field => {
|
|
304
|
+
if (field.type === 'relation') {
|
|
305
|
+
if (!tl.includes(field.relation))
|
|
306
|
+
tl.push(field.relation);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
setDatasToLoad(tl);
|
|
310
|
+
|
|
311
|
+
const cb = ({data}) => {
|
|
312
|
+
|
|
313
|
+
updateRelationIds(model, data);
|
|
314
|
+
|
|
315
|
+
if( datasToLoad.length === 0 ) {
|
|
316
|
+
model.fields.forEach(field => {
|
|
317
|
+
if (field.type === 'relation') {
|
|
318
|
+
if (!tl.includes(field.relation))
|
|
319
|
+
tl.push(field.relation);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
setDatasToLoad(tl);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
// add new data
|
|
326
|
+
setOnSuccessCallbacks(cbs => {
|
|
327
|
+
const c = {...cbs};
|
|
328
|
+
if( !c['api/data/' + model.name + '/paged'])
|
|
329
|
+
c['api/data/' + model.name + '/paged'] = {};
|
|
330
|
+
c['api/data/' + model.name + '/paged'][model.name] = cb;
|
|
331
|
+
return c;
|
|
332
|
+
})
|
|
333
|
+
setSelectedModel(model);
|
|
334
|
+
|
|
335
|
+
gtag("event", "select_content", {
|
|
336
|
+
content_type: "model",
|
|
337
|
+
content_id: model.name
|
|
338
|
+
});
|
|
339
|
+
//queryClient.invalidateQueries(['api/data', model.name, 'page', page]);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const { me } = useAuthContext();
|
|
343
|
+
|
|
344
|
+
const { addNotification } = useNotificationContext();
|
|
345
|
+
|
|
346
|
+
const { mutate: insertOrUpdateMutation, isLoading} = useMutation(({formData,record}) => {
|
|
347
|
+
const method = record ? 'PUT' : 'POST'; // Determine method based on record
|
|
348
|
+
const url = record ? `/api/data/${record._id}` : `/api/data`; // Determine URL
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const fd = new FormData();
|
|
352
|
+
|
|
353
|
+
let obj = {};
|
|
354
|
+
for (const key in formData) {
|
|
355
|
+
if (formData[key] !== undefined)
|
|
356
|
+
obj[key] = formData[key];
|
|
357
|
+
}
|
|
358
|
+
Array.from(document.querySelectorAll('.field-file input[data-field]')).forEach(input =>{
|
|
359
|
+
const fieldName = input.dataset['field'];
|
|
360
|
+
for (let x = 0; x < input.files.length; x++) {
|
|
361
|
+
fd.append(`${fieldName}[${x}]`, input.files[x]);
|
|
362
|
+
}
|
|
363
|
+
obj[fieldName] = null;
|
|
364
|
+
});
|
|
365
|
+
fd.append("_data", JSON.stringify({...obj, _hash: undefined, _id: undefined}));
|
|
366
|
+
fd.append('model', selectedModel.name);
|
|
367
|
+
|
|
368
|
+
///fd.append("files", fd2);
|
|
369
|
+
return fetch(`${url}?lang=${lang}&_user=${encodeURIComponent(getUserId(me))}`, {
|
|
370
|
+
method,
|
|
371
|
+
body: fd
|
|
372
|
+
}).then(e => e.json());
|
|
373
|
+
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('Erreur lors de l\'enregistrement des données:', error);
|
|
376
|
+
// Handle error, e.g., display error message to the user
|
|
377
|
+
}
|
|
378
|
+
}, {
|
|
379
|
+
onError: (err)=>{
|
|
380
|
+
const notificationData = {
|
|
381
|
+
title: 'Erreur lors de l\'enregistrement des données',
|
|
382
|
+
status: 'error'
|
|
383
|
+
};
|
|
384
|
+
addNotification(notificationData);
|
|
385
|
+
},
|
|
386
|
+
onSuccess: async (data) => {
|
|
387
|
+
|
|
388
|
+
console.log('Données enregistrées:', data, selectedModel);
|
|
389
|
+
|
|
390
|
+
await Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
|
|
391
|
+
model: selectedModel.name,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
gtag("event", "select_content", {
|
|
395
|
+
content_type: "edit_data",
|
|
396
|
+
content_id: selectedModel.name
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const notificationData = {
|
|
400
|
+
title: data.success ? t('dataimporter.success', 'Données enregistrées') : t(data.error, data.error),
|
|
401
|
+
icon: data.success ? <FaInfo /> : undefined,
|
|
402
|
+
status: data.success ? 'completed': 'error'
|
|
403
|
+
};
|
|
404
|
+
addNotification(notificationData);
|
|
405
|
+
|
|
406
|
+
updateRelationIds(selectedModel, formData);
|
|
407
|
+
setDatasToLoad([...datasToLoad, selectedModel.name]);
|
|
408
|
+
queryClient.invalidateQueries(['api/data', selectedModel.name, 'page', page, elementsPerPage, pagedFilters[selectedModel.name], pagedSort[selectedModel.name]]);
|
|
409
|
+
|
|
410
|
+
if(data.inserted) {
|
|
411
|
+
const t = [...selectedModel.fields].reduce((acc, field, index) => {
|
|
412
|
+
if (field.type === "relation") {
|
|
413
|
+
acc[field.name] = field.multiple ? [] : null;
|
|
414
|
+
} else {
|
|
415
|
+
acc[field.name] = getDefaultForType(field);
|
|
416
|
+
}
|
|
417
|
+
return acc;
|
|
418
|
+
}, {});
|
|
419
|
+
setFormData(t)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setRelations({});
|
|
423
|
+
|
|
424
|
+
}})
|
|
425
|
+
const handleFormSubmit = async (formData, record) => { // Add record parameter
|
|
426
|
+
insertOrUpdateMutation({formData, record})
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const updateRelationIds = (model, data) => {
|
|
430
|
+
const r = {...relationIds};
|
|
431
|
+
let datas = !Array.isArray(data) ? [data] : data;
|
|
432
|
+
model.fields.forEach(field => {
|
|
433
|
+
if( field.type !== "relation")
|
|
434
|
+
return;
|
|
435
|
+
datas.forEach(d => {
|
|
436
|
+
const value = d[field.name];
|
|
437
|
+
if (field.multiple && Array.isArray(value)) {
|
|
438
|
+
for (let i = 0; i < value.length; i++) {
|
|
439
|
+
if (!r[field.relation]){
|
|
440
|
+
r[field.relation] = [];
|
|
441
|
+
}
|
|
442
|
+
if (!r[field.relation].includes(value[i])) {
|
|
443
|
+
r[field.relation].push(value[i]);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} else if (!field.multiple && value) {
|
|
447
|
+
if (!r[field.relation]){
|
|
448
|
+
r[field.relation] = [];
|
|
449
|
+
}
|
|
450
|
+
if (!r[field.relation].includes(value)) {
|
|
451
|
+
r[field.relation].push(value);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
setRelationIds(r);
|
|
457
|
+
queryClient.invalidateQueries(['api/data', model.name, r]);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const deleteMutation = useMutation((selectedModels) => {
|
|
461
|
+
return fetch('/api/data/'+checkedItems.map(m => m._id).join(',')+'?lang='+lang+'&_user='+encodeURIComponent(getUserId(me)), {
|
|
462
|
+
method: 'DELETE', headers: {
|
|
463
|
+
'Content-Type': 'application/json'
|
|
464
|
+
}
|
|
465
|
+
}).then(e => e.json());
|
|
466
|
+
}, { onSuccess: (data) => {
|
|
467
|
+
if( data.success ){
|
|
468
|
+
queryClient.invalidateQueries(['api/data', selectedModel?.name, 'page', page, elementsPerPage, pagedFilters[selectedModel?.name], pagedSort[selectedModel?.name]]);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const notificationData = {
|
|
472
|
+
id: 'dataimporter.success',
|
|
473
|
+
title: t('dataimporter.success', 'Données supprimées'),
|
|
474
|
+
icon: <FaInfo />,
|
|
475
|
+
status: 'completed'
|
|
476
|
+
};
|
|
477
|
+
addNotification(notificationData);
|
|
478
|
+
}, onError:(err)=>{
|
|
479
|
+
const notificationData = {
|
|
480
|
+
id: 'dataimporter.error',
|
|
481
|
+
title: err.message,
|
|
482
|
+
status: 'error'
|
|
483
|
+
};
|
|
484
|
+
addNotification(notificationData);
|
|
485
|
+
}});
|
|
486
|
+
const handleDeletion = () => {
|
|
487
|
+
deleteMutation.mutate();
|
|
488
|
+
setCheckedItems([]);
|
|
489
|
+
}
|
|
490
|
+
const importModelsMutation = useMutation((selectedModels) => {
|
|
491
|
+
return fetch('/api/models/import', { method: 'POST', headers: {
|
|
492
|
+
'Content-Type': 'application/json'
|
|
493
|
+
},
|
|
494
|
+
body: JSON.stringify({ models: selectedModels.map(m => m.name) })
|
|
495
|
+
})
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const handleConfigureCurrentView = () => {
|
|
499
|
+
if (!selectedModel) return;
|
|
500
|
+
|
|
501
|
+
switch (currentView) {
|
|
502
|
+
case 'kanban':
|
|
503
|
+
setKanbanModalOpen(true);
|
|
504
|
+
break;
|
|
505
|
+
case 'calendar':
|
|
506
|
+
setCalendarModalOpen(true);
|
|
507
|
+
break;
|
|
508
|
+
default:
|
|
509
|
+
// Pas de configuration pour la vue 'table'
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const handleAddData = (model, data)=>{
|
|
515
|
+
|
|
516
|
+
if (!selectedModel || model.name !== selectedModel.name) {
|
|
517
|
+
handleModelSelect(model);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if( data ){
|
|
521
|
+
setFormData(data);
|
|
522
|
+
}else {
|
|
523
|
+
const t = model ? [...model.fields].reduce((acc, field) => {
|
|
524
|
+
if (field.type === "relation") {
|
|
525
|
+
acc[field.name] = field.multiple ? [] : null;
|
|
526
|
+
} else {
|
|
527
|
+
acc[field.name] = getDefaultForType(field);
|
|
528
|
+
}
|
|
529
|
+
return acc;
|
|
530
|
+
}, {}) : [];
|
|
531
|
+
setFormData(t)
|
|
532
|
+
}
|
|
533
|
+
setEditionMode(false);
|
|
534
|
+
setDataEditorVisible(true);
|
|
535
|
+
setAPIInfoVisible(false);
|
|
536
|
+
gtag("event", "select_content", {
|
|
537
|
+
content_type: "select_model",
|
|
538
|
+
content_id: model.name
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const onImportModels = (models) => {
|
|
543
|
+
importModelsMutation.mutateAsync(models).then(e => {
|
|
544
|
+
queryClient.invalidateQueries('api/models');
|
|
545
|
+
});
|
|
546
|
+
setImportModalVisible(false);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
useEffect(() => {
|
|
550
|
+
if( showDataEditor && mainPartRef.current ){
|
|
551
|
+
|
|
552
|
+
}
|
|
553
|
+
}, [showDataEditor,mainPartRef.current]);
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
const [currentProfile, setCurrentProfile] = useLocalStorage('profile', null);
|
|
557
|
+
const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour, addLaunchedTour, assistantConfig, setAssistantConfig} = useUI();
|
|
558
|
+
|
|
559
|
+
const startTour = () => {
|
|
560
|
+
setIsTourOpen(true);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const closeTour = (completedTourName) => {
|
|
564
|
+
// On génère le nom du tour de démo pour le comparer
|
|
565
|
+
// This function is called when ANY tour is closed.
|
|
566
|
+
// It needs to persist the fact that the tour has been seen.
|
|
567
|
+
|
|
568
|
+
// 1. Add the tour's unique name to the list of launched tours.
|
|
569
|
+
// This list is managed by the UI context and stored in localStorage.
|
|
570
|
+
if (addLaunchedTour) { // Check if the function exists to be safe
|
|
571
|
+
addLaunchedTour(completedTourName);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// 2. Close the tour's UI.
|
|
575
|
+
setIsTourOpen(false);
|
|
576
|
+
|
|
577
|
+
// 3. Specific logic for the very first "demo" tour:
|
|
578
|
+
// We also set the user's profile to mark that they are no longer a brand new user.
|
|
579
|
+
// This prevents the demo tour from trying to launch on every page load.
|
|
580
|
+
const demoTourName = `tour_${getObjectHash({steps: allTourSteps.demo || []})}`;
|
|
581
|
+
|
|
582
|
+
// Si le tour qui vient de se terminer est le tour de démo
|
|
583
|
+
if (completedTourName === demoTourName) {
|
|
584
|
+
|
|
585
|
+
setIsTourOpen(false);
|
|
586
|
+
setCurrentProfile({ lastSeen: new Date().toISOString() });
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
if( !currentProfile ){
|
|
592
|
+
setIsTourOpen(true);
|
|
593
|
+
}
|
|
594
|
+
}, [currentProfile])
|
|
595
|
+
|
|
596
|
+
useEffect(() => {
|
|
597
|
+
// Si un modèle est sélectionné...
|
|
598
|
+
if (selectedModel) {
|
|
599
|
+
// Récupérer les étapes du tour "datapacks"
|
|
600
|
+
const datapacksSteps = allTourSteps.guides;
|
|
601
|
+
// S'assurer que ce tour existe et a des étapes
|
|
602
|
+
if (datapacksSteps && datapacksSteps.length > 0) {
|
|
603
|
+
// 1. Définir les étapes du tour qui doit être affiché
|
|
604
|
+
setCurrentTourSteps(datapacksSteps);
|
|
605
|
+
// 2. Calculer le nom unique du tour et le définir comme tour courant
|
|
606
|
+
// C'est l'clé qui manquait.
|
|
607
|
+
const tourName = `tour_${getObjectHash({ steps: datapacksSteps })}`;
|
|
608
|
+
setCurrentTour(tourName);
|
|
609
|
+
// 3. Activer l'affichage du composant de tour
|
|
610
|
+
setIsTourOpen(true);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}, [selectedModel, allTourSteps]); // Ajout de allTourSteps aux dépendances pour la bonne pratique
|
|
614
|
+
|
|
615
|
+
const handleShowTutorialMenu = () => {
|
|
616
|
+
setTutorialDialogVisible(!tutorialDialogVisible);
|
|
617
|
+
}
|
|
618
|
+
return (
|
|
619
|
+
<>
|
|
620
|
+
<Tooltip id="tooltipField" />
|
|
621
|
+
<>{/^demo[0-9]{1,2}$/.test(me.username) && (
|
|
622
|
+
<TourSpotlight
|
|
623
|
+
name={"tour_"+getObjectHash({steps:currentTourSteps})}
|
|
624
|
+
steps={currentTourSteps}
|
|
625
|
+
isOpen={isTourOpen}
|
|
626
|
+
onClose={closeTour}
|
|
627
|
+
/>)}</>
|
|
628
|
+
<div className="flex actions">
|
|
629
|
+
|
|
630
|
+
{<ViewSwitcher
|
|
631
|
+
currentView={currentView}
|
|
632
|
+
onViewChange={handleSwitchView}
|
|
633
|
+
configuredViews={configuredViews}
|
|
634
|
+
onConfigureView={handleConfigureCurrentView}
|
|
635
|
+
/>}
|
|
636
|
+
<Button onClick={() => {
|
|
637
|
+
setImportModalVisible(true);
|
|
638
|
+
}} className="btn tourStep-import-model"><FaFileImport/><Trans
|
|
639
|
+
i18nKey="btns.importModels">Modèles</Trans></Button>
|
|
640
|
+
<Button onClick={() => {
|
|
641
|
+
setShowPackGallery(true);
|
|
642
|
+
}} className="btn tourStep-import-pack"><FaBoxOpen/><Trans
|
|
643
|
+
i18nKey="btns.importPacks">Packs</Trans></Button>
|
|
644
|
+
<Button className={"tourStep-tutorials btn"} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaBookAtlas/><Trans
|
|
645
|
+
i18nKey="btns.showTutos">Tutoriels</Trans></Button>
|
|
646
|
+
|
|
647
|
+
<DialogProvider>
|
|
648
|
+
{tutorialDialogVisible && (
|
|
649
|
+
<Dialog isClosable={true} isModal={true} onClose={() => setTutorialDialogVisible(false)}>
|
|
650
|
+
<TutorialsMenu />
|
|
651
|
+
</Dialog>
|
|
652
|
+
)}
|
|
653
|
+
</DialogProvider>
|
|
654
|
+
<Button className="btn" onClick={() => {
|
|
655
|
+
setAPIInfoVisible(true);
|
|
656
|
+
setDataEditorVisible(false);
|
|
657
|
+
setEditionMode(false);
|
|
658
|
+
}}><FaBook/> {t('btns.api', 'API')}</Button>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<div className="datalayout flex flex-start">
|
|
662
|
+
<ModelList tourSteps={currentTourSteps}
|
|
663
|
+
editionMode={editionMode}
|
|
664
|
+
onAPIInfo={(model) => {
|
|
665
|
+
setAPIInfoVisible(true);
|
|
666
|
+
setDataEditorVisible(false);
|
|
667
|
+
setEditionMode(false);
|
|
668
|
+
setSelectedModel(model);
|
|
669
|
+
gtag("event", "select_content", {
|
|
670
|
+
content_type: "api",
|
|
671
|
+
content_id: model.name
|
|
672
|
+
});
|
|
673
|
+
}} onImportModel={() => {
|
|
674
|
+
setImportModalVisible(true);
|
|
675
|
+
}} onCreateModel={() => {
|
|
676
|
+
setSelectedModel(null);
|
|
677
|
+
setAPIInfoVisible(false);
|
|
678
|
+
setDataEditorVisible(false);
|
|
679
|
+
setEditionMode(true);
|
|
680
|
+
mainPartRef.current.scrollIntoView({behavior: 'smooth'});
|
|
681
|
+
gtag("event", "select_content", {
|
|
682
|
+
content_type: "create_model",
|
|
683
|
+
});
|
|
684
|
+
}} onEditModel={(model) => {
|
|
685
|
+
handleModelSelect(model);
|
|
686
|
+
setDataEditorVisible(false);
|
|
687
|
+
setAPIInfoVisible(false);
|
|
688
|
+
setEditionMode(true);
|
|
689
|
+
mainPartRef.current.scrollIntoView({behavior: 'smooth'});
|
|
690
|
+
gtag("event", "select_content", {
|
|
691
|
+
content_type: "edit_model",
|
|
692
|
+
content_id: model.name
|
|
693
|
+
});
|
|
694
|
+
}} onModelSelect={(model) => {
|
|
695
|
+
handleModelSelect(model);
|
|
696
|
+
setDataEditorVisible(false);
|
|
697
|
+
setEditionMode(false);
|
|
698
|
+
setAPIInfoVisible(false);
|
|
699
|
+
mainPartRef.current.scrollIntoView({behavior: 'smooth'});
|
|
700
|
+
gtag("event", "select_content", {
|
|
701
|
+
content_type: "select_model",
|
|
702
|
+
content_id: model.name
|
|
703
|
+
});
|
|
704
|
+
}} onImportPack={() => {
|
|
705
|
+
setShowPackGallery(true);
|
|
706
|
+
}} onNewData={(model) => {
|
|
707
|
+
mainPartRef.current.scrollIntoView({behavior: 'smooth'});
|
|
708
|
+
handleAddData(model);
|
|
709
|
+
}}/>
|
|
710
|
+
{(editionMode) && (
|
|
711
|
+
<ModelCreator ref={modelCreatorRef} onModelGenerated={() =>{
|
|
712
|
+
modelCreatorRef.current.scrollIntoView({behavior: 'smooth'});
|
|
713
|
+
}} initialModel={selectedModel} onModelSaved={(model) => {
|
|
714
|
+
setRefreshTime(new Date().getTime());
|
|
715
|
+
handleModelSelect(model);
|
|
716
|
+
}}/>)}
|
|
717
|
+
|
|
718
|
+
<div className="hidden-anchor" ref={mainPartRef}></div>
|
|
719
|
+
|
|
720
|
+
{showDataEditor && (<DataEditor
|
|
721
|
+
key={selectedModel?.name}
|
|
722
|
+
isLoading={isLoading}
|
|
723
|
+
model={selectedModel}
|
|
724
|
+
formData={formData}
|
|
725
|
+
setFormData={setFormData}
|
|
726
|
+
refreshTime={refreshTime}
|
|
727
|
+
onSubmit={handleFormSubmit}
|
|
728
|
+
setRecord={setRecordToEdit}
|
|
729
|
+
record={recordToEdit} // Pass record to edit to DataEditor
|
|
730
|
+
/>)}
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
{selectedModel && showAPIInfo && <APIInfo/>}
|
|
734
|
+
{selectedModel && !showAPIInfo && !generatedModels.some(g => g.name === selectedModel?.name) && (<div className="datas">
|
|
735
|
+
|
|
736
|
+
<h2 className={"field-bg p-2"}>{t(`model_${selectedModel?.name}`, selectedModel?.name)} <>({countByModel?.[selectedModel?.name]})</></h2>
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
{renderCurrentView()}
|
|
740
|
+
|
|
741
|
+
{isDataLoaded && currentView === 'table' && (<>
|
|
742
|
+
{selectedModel && (<Pagination showElementsPerPage={true} onChange={page => {
|
|
743
|
+
setPage(page);
|
|
744
|
+
setCheckedItems([]);
|
|
745
|
+
gtag("event", "select_content", {
|
|
746
|
+
content_type: "change_page",
|
|
747
|
+
content_id: page
|
|
748
|
+
});
|
|
749
|
+
queryClient.invalidateQueries(['api/data', selectedModel.name, 'page', page, pagedFilters[selectedModel.name], pagedSort[selectedModel.name]]);
|
|
750
|
+
}} page={page} setPage={setPage} totalCount={countByModel[selectedModel.name]}
|
|
751
|
+
hasPreviousNext={true} visibleItemsCount={5}
|
|
752
|
+
elementsPerPage={elementsPerPage}/>)}
|
|
753
|
+
<div className="actions flex">
|
|
754
|
+
<Button onClick={() => {
|
|
755
|
+
setCheckedItems(paginatedDataByModel[selectedModel.name]);
|
|
756
|
+
}}><Trans i18nKey={"datatable.selectAll"}>Tout sélectionner</Trans></Button>
|
|
757
|
+
<Button onClick={handleDeletion} disabled={!checkedItems?.length}><Trans
|
|
758
|
+
i18nKey={"datatable.deleteSelection"}>Supprimer la sélection</Trans></Button>
|
|
759
|
+
</div>
|
|
760
|
+
</>)}
|
|
761
|
+
</div>)}
|
|
762
|
+
</div>
|
|
763
|
+
|
|
764
|
+
<div className={"fabs"}>
|
|
765
|
+
<Button data-tooltip-id={"tooltipField"} data-tooltip-html={t("btns.addData", "Ajouter une donnée au modèle")} className="fab plus-fab" onClick={() => {
|
|
766
|
+
mainPartRef.current.scrollIntoView({behavior: "smooth"});
|
|
767
|
+
handleAddData(selectedModel);
|
|
768
|
+
}}><FaPlus/></Button>
|
|
769
|
+
{me && <AssistantChat config={assistantConfig} />}
|
|
770
|
+
<NotificationList />
|
|
771
|
+
</div>
|
|
772
|
+
|
|
773
|
+
<DialogProvider>
|
|
774
|
+
{importModalVisible && (<Dialog onClose={() => {
|
|
775
|
+
setImportModalVisible(false)
|
|
776
|
+
}} isModal={true} isClosable={true}>
|
|
777
|
+
<ModelImporter onImport={onImportModels}/>
|
|
778
|
+
</Dialog>)}
|
|
779
|
+
|
|
780
|
+
{showPackGallery && (
|
|
781
|
+
<Dialog isClosable={true} isModal={true} onClose={() => setShowPackGallery(false)}>
|
|
782
|
+
<PackGallery />
|
|
783
|
+
</Dialog>
|
|
784
|
+
)}
|
|
785
|
+
|
|
786
|
+
<CalendarConfigModal
|
|
787
|
+
isOpen={isCalendarModalOpen}
|
|
788
|
+
onClose={() => setCalendarModalOpen(false)}
|
|
789
|
+
onSave={handleSaveCalendarConfig}
|
|
790
|
+
modelFields={selectedModel?.fields ||[]}
|
|
791
|
+
initialSettings={currentModelViewSettings.calendar}
|
|
792
|
+
/>
|
|
793
|
+
|
|
794
|
+
<KanbanConfigModal
|
|
795
|
+
isOpen={isKanbanModalOpen}
|
|
796
|
+
onClose={() => setKanbanModalOpen(false)}
|
|
797
|
+
onSave={handleSaveKanbanConfig}
|
|
798
|
+
model={selectedModel}
|
|
799
|
+
modelFields={selectedModel?.fields||[]}
|
|
800
|
+
initialSettings={currentModelViewSettings.kanban}
|
|
801
|
+
/>
|
|
802
|
+
</DialogProvider>
|
|
803
|
+
</>
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
|
|
756
808
|
export default DataLayout;
|