ui-soxo-bootstrap-core 2.6.40-dev.17 → 2.6.40
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/DEVELOPER_GUIDE.md +9 -38
- package/core/components/index.js +11 -2
- package/core/components/landing-api/landing-api.js +5 -165
- package/core/lib/components/global-header/global-header.js +77 -20
- package/core/lib/components/index.js +2 -2
- package/core/lib/components/sidemenu/sidemenu.js +147 -66
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +24 -91
- package/core/lib/models/menus/components/menu-add/menu-add.js +2 -2
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +8 -334
- package/core/lib/modules/generic/generic-list/generic-list.scss +0 -34
- package/core/models/core-scripts/core-scripts.js +1 -22
- package/core/models/menus/components/menu-add/menu-add.js +4 -31
- package/core/models/menus/components/menu-lists/menu-lists.js +12 -75
- package/core/models/menus/menus.js +1 -29
- package/core/models/roles/components/role-add/role-add.js +169 -54
- package/core/models/roles/components/role-list/role-list.js +0 -20
- package/core/models/roles/roles.js +0 -3
- package/core/models/users/components/assign-role/assign-role.js +8 -23
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +5 -202
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +0 -73
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +35 -56
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +0 -1
- package/package.json +1 -1
- package/core/components/license-management/license-alert.js +0 -97
- package/core/models/roles/components/role-add/menu-label.js +0 -14
- package/core/models/roles/components/role-add/menu-tree.js +0 -127
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +0 -519
|
@@ -385,71 +385,6 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
|
|
|
385
385
|
|
|
386
386
|
const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.id || m.path || m.caption);
|
|
387
387
|
|
|
388
|
-
/**
|
|
389
|
-
* Recursively renders the menu tree to any depth. Any menu that has visible
|
|
390
|
-
* children is rendered as a <SubMenu>; everything else becomes a clickable
|
|
391
|
-
* leaf <Menu.Item>. This replaces the old hard-coded 3-level rendering so
|
|
392
|
-
* menus nested 4, 5, 6+ levels deep all show up.
|
|
393
|
-
*
|
|
394
|
-
* Keys are kept identical to the previous implementation
|
|
395
|
-
* (SubMenu => id || path || caption, leaf => path || caption) so the
|
|
396
|
-
* openKeys / selectedKeys / onOpenChange logic continues to work.
|
|
397
|
-
*/
|
|
398
|
-
const renderMenuTree = (items, { isRoot = false, parentKey } = {}) => {
|
|
399
|
-
const visibleItems = Menus.screenMenus(items, 'order').filter((record) => {
|
|
400
|
-
// Drop entries without a caption — they have no label to render.
|
|
401
|
-
const hasCaption = typeof record.caption === 'string' && record.caption.trim().length > 0;
|
|
402
|
-
if (!hasCaption) return false;
|
|
403
|
-
|
|
404
|
-
// The visibility / icon rule only applies to top-level menus, matching the
|
|
405
|
-
// previous behaviour; nested items just need a caption.
|
|
406
|
-
if (!isRoot) return true;
|
|
407
|
-
|
|
408
|
-
if (record.id) {
|
|
409
|
-
if (record.is_visible) return true;
|
|
410
|
-
return !!record.icon_name;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return true;
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
return visibleItems.map((menu, index) => {
|
|
417
|
-
const icon = menu.icon_name || 'fa-solid fas fa-user';
|
|
418
|
-
|
|
419
|
-
// Children that actually have something to show (a caption).
|
|
420
|
-
const children =
|
|
421
|
-
menu && menu.sub_menus
|
|
422
|
-
? Menus.screenMenus(menu.sub_menus, 'order').filter((s) => typeof s.caption === 'string' && s.caption.trim().length > 0)
|
|
423
|
-
: [];
|
|
424
|
-
|
|
425
|
-
if (children.length) {
|
|
426
|
-
return (
|
|
427
|
-
<SubMenu
|
|
428
|
-
className="popup"
|
|
429
|
-
style={{ color: state.theme.colors.leftSectionColor }}
|
|
430
|
-
key={menu.id || menu.path || menu.caption}
|
|
431
|
-
title={
|
|
432
|
-
<span>
|
|
433
|
-
<CollapsedIconMenu menu={menu} caption={menu.caption} icon={icon} collapsed={collapsed} />
|
|
434
|
-
</span>
|
|
435
|
-
}
|
|
436
|
-
>
|
|
437
|
-
{renderMenuTree(menu.sub_menus, { isRoot: false, parentKey: menu.path || menu.caption })}
|
|
438
|
-
</SubMenu>
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return (
|
|
443
|
-
<Menu.Item
|
|
444
|
-
key={menu.path || menu.caption}
|
|
445
|
-
onClick={() => onMenuClick({ ...menu, parentKey: parentKey || menu.path || menu.caption, isRoot }, index)}
|
|
446
|
-
>
|
|
447
|
-
<CollapsedIconMenu menu={menu} caption={menu.caption} icon={icon} collapsed={collapsed} />
|
|
448
|
-
</Menu.Item>
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
};
|
|
452
|
-
|
|
453
388
|
return (
|
|
454
389
|
<div className="sidemenu">
|
|
455
390
|
{loading ? (
|
|
@@ -555,7 +490,153 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
|
|
|
555
490
|
})
|
|
556
491
|
} */}
|
|
557
492
|
|
|
558
|
-
{
|
|
493
|
+
{Menus.screenMenus(modules, 'order')
|
|
494
|
+
|
|
495
|
+
.filter((record) => {
|
|
496
|
+
icon = record;
|
|
497
|
+
|
|
498
|
+
// Drop entries without a caption — they have no permission/label
|
|
499
|
+
// to render, so showing just an icon is misleading.
|
|
500
|
+
const hasCaption = typeof record.caption === 'string' && record.caption.trim().length > 0;
|
|
501
|
+
if (!hasCaption) return false;
|
|
502
|
+
|
|
503
|
+
if (record.id) {
|
|
504
|
+
if (record.is_visible) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (record.icon_name) {
|
|
509
|
+
return true;
|
|
510
|
+
} else {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
.map((menu, index) => {
|
|
518
|
+
// return <MenuItem menu={menu} index={index} />
|
|
519
|
+
|
|
520
|
+
let sub_menus = menu && menu.sub_menus
|
|
521
|
+
? Menus.screenMenus(menu.sub_menus).filter((s) => typeof s.caption === 'string' && s.caption.trim().length > 0)
|
|
522
|
+
: [];
|
|
523
|
+
|
|
524
|
+
if (menu && sub_menus && sub_menus.length) {
|
|
525
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
526
|
+
|
|
527
|
+
return (
|
|
528
|
+
<SubMenu
|
|
529
|
+
className="popup"
|
|
530
|
+
style={{ color: state.theme.colors.leftSectionColor }}
|
|
531
|
+
// key={`first-level-${randomIndex}-${menu.caption}`}
|
|
532
|
+
|
|
533
|
+
key={menu.id || menu.path || menu.caption}
|
|
534
|
+
title={
|
|
535
|
+
<>
|
|
536
|
+
<CollapsedIconMenu
|
|
537
|
+
menu={menu}
|
|
538
|
+
caption={menu.caption}
|
|
539
|
+
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
540
|
+
collapsed={collapsed}
|
|
541
|
+
/>
|
|
542
|
+
</>
|
|
543
|
+
}
|
|
544
|
+
>
|
|
545
|
+
{sub_menus.map((submenu, innerIndex) => {
|
|
546
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
547
|
+
|
|
548
|
+
let third_menus = submenu && submenu.sub_menus ? Menus.screenMenus(submenu.sub_menus) : [];
|
|
549
|
+
|
|
550
|
+
if (third_menus && third_menus.length) {
|
|
551
|
+
return (
|
|
552
|
+
<SubMenu
|
|
553
|
+
className="popup"
|
|
554
|
+
// key={`second-level-${randomIndex}-${submenu.id}`}
|
|
555
|
+
|
|
556
|
+
key={submenu.id || submenu.path || submenu.caption}
|
|
557
|
+
title={
|
|
558
|
+
<span>
|
|
559
|
+
<CollapsedIconMenu
|
|
560
|
+
menu={menu}
|
|
561
|
+
caption={submenu.caption}
|
|
562
|
+
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
563
|
+
collapsed={collapsed}
|
|
564
|
+
/>
|
|
565
|
+
</span>
|
|
566
|
+
}
|
|
567
|
+
>
|
|
568
|
+
{third_menus.map((menu) => {
|
|
569
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<Menu.Item
|
|
573
|
+
// onClick={() => {
|
|
574
|
+
// onMenuClick(menu, index);
|
|
575
|
+
// }}
|
|
576
|
+
onClick={() => {
|
|
577
|
+
onMenuClick({ ...menu, parentKey: submenu.path || submenu.caption }, index);
|
|
578
|
+
}}
|
|
579
|
+
// key={`second-level-${randomIndex}-${index}`}
|
|
580
|
+
|
|
581
|
+
key={menu.path || menu.caption}
|
|
582
|
+
>
|
|
583
|
+
<CollapsedIconMenu
|
|
584
|
+
menu={menu}
|
|
585
|
+
caption={menu.caption}
|
|
586
|
+
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
587
|
+
collapsed={collapsed}
|
|
588
|
+
/>
|
|
589
|
+
</Menu.Item>
|
|
590
|
+
);
|
|
591
|
+
})}
|
|
592
|
+
</SubMenu>
|
|
593
|
+
);
|
|
594
|
+
} else {
|
|
595
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
596
|
+
|
|
597
|
+
return (
|
|
598
|
+
<Menu.Item
|
|
599
|
+
// onClick={() => {
|
|
600
|
+
// onMenuClick(submenu, index);
|
|
601
|
+
// }}
|
|
602
|
+
onClick={() => {
|
|
603
|
+
onMenuClick({ ...submenu, parentKey: menu.path || menu.caption }, index);
|
|
604
|
+
}}
|
|
605
|
+
// key={`first-level-${randomIndex}-${innerIndex}`}
|
|
606
|
+
key={submenu.path || submenu.caption}
|
|
607
|
+
>
|
|
608
|
+
<CollapsedIconMenu
|
|
609
|
+
menu={menu}
|
|
610
|
+
caption={submenu.caption}
|
|
611
|
+
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
612
|
+
collapsed={collapsed}
|
|
613
|
+
/>
|
|
614
|
+
</Menu.Item>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
})}
|
|
618
|
+
</SubMenu>
|
|
619
|
+
);
|
|
620
|
+
} else {
|
|
621
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<Menu.Item
|
|
625
|
+
// onClick={() => {
|
|
626
|
+
// onMenuClick(menu, index);
|
|
627
|
+
// }}
|
|
628
|
+
|
|
629
|
+
onClick={() => {
|
|
630
|
+
onMenuClick({ ...menu, parentKey: menu.path || menu.caption, isRoot: true }, index);
|
|
631
|
+
}}
|
|
632
|
+
// key={`${menu.id}-${randomIndex}`}
|
|
633
|
+
key={menu.path || menu.caption}
|
|
634
|
+
>
|
|
635
|
+
<CollapsedIconMenu menu={menu} caption={menu.caption} icon={menu.icon_name || 'fa-solid fas fa-user'} collapsed={collapsed} />
|
|
636
|
+
</Menu.Item>
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
})}
|
|
559
640
|
|
|
560
641
|
{loading ? (
|
|
561
642
|
<div class="skeleton-wrapper"></div>
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import React, { useRef
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
2
|
import { useDrag, useDrop } from 'react-dnd';
|
|
3
3
|
|
|
4
|
-
// Walk up the DOM to find the nearest vertically-scrollable ancestor.
|
|
5
|
-
// Returns null when the page (window) is the scroller.
|
|
6
|
-
function getScrollableAncestor(node) {
|
|
7
|
-
let el = node?.parentElement;
|
|
8
|
-
while (el) {
|
|
9
|
-
const { overflowY } = window.getComputedStyle(el);
|
|
10
|
-
const scrollable = overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay';
|
|
11
|
-
if (scrollable && el.scrollHeight > el.clientHeight) return el;
|
|
12
|
-
el = el.parentElement;
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
4
|
export default function DraggableWrapper({ id, index, movePanel, item, dragEnabled, level, parentId, onCrossLevelMove, canAcceptChildren }) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
const autoScrollWindow = (monitor) => {
|
|
6
|
+
const offset = monitor.getClientOffset();
|
|
7
|
+
if (!offset) return;
|
|
8
|
+
|
|
9
|
+
const EDGE = 80;
|
|
10
|
+
const SPEED = 20;
|
|
11
|
+
|
|
12
|
+
const viewportHeight = window.innerHeight;
|
|
13
|
+
|
|
14
|
+
// 🔼 scroll UP
|
|
15
|
+
if (offset.y < EDGE) {
|
|
16
|
+
window.scrollBy(0, -SPEED);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 🔽 scroll DOWN
|
|
20
|
+
if (offset.y > viewportHeight - EDGE) {
|
|
21
|
+
window.scrollBy(0, SPEED);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
25
24
|
|
|
26
25
|
const [{ isDragging }, drag] = useDrag({
|
|
27
26
|
type: 'PANEL',
|
|
@@ -32,78 +31,12 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
|
|
|
32
31
|
}),
|
|
33
32
|
});
|
|
34
33
|
|
|
35
|
-
/**
|
|
36
|
-
* Continuous edge auto-scroll while THIS item is being dragged.
|
|
37
|
-
*
|
|
38
|
-
* Driven by a document-level `dragover` listener + requestAnimationFrame loop
|
|
39
|
-
* rather than the drop target's `hover` handler. `hover` only fires while the
|
|
40
|
-
* pointer is over a droppable panel and only on movement, so scrolling UP
|
|
41
|
-
* failed: the top of the viewport is the (non-droppable) header, and holding
|
|
42
|
-
* the pointer still at an edge produced no events. The rAF loop keeps
|
|
43
|
-
* scrolling from the last known pointer position regardless of what's beneath.
|
|
44
|
-
*
|
|
45
|
-
* The list may scroll inside an overflow container rather than the window, so
|
|
46
|
-
* we resolve the nearest scrollable ancestor and scroll that (falling back to
|
|
47
|
-
* the window), computing the edges from the container's own bounds.
|
|
48
|
-
*/
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (!isDragging) return;
|
|
51
|
-
|
|
52
|
-
const EDGE = 80;
|
|
53
|
-
const SPEED = 20;
|
|
54
|
-
|
|
55
|
-
scrollContainerRef.current = getScrollableAncestor(rootRef.current);
|
|
56
|
-
|
|
57
|
-
const onDragOver = (e) => {
|
|
58
|
-
pointerY.current = e.clientY;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const tick = () => {
|
|
62
|
-
const y = pointerY.current;
|
|
63
|
-
|
|
64
|
-
if (y > 0) {
|
|
65
|
-
const container = scrollContainerRef.current;
|
|
66
|
-
|
|
67
|
-
if (container) {
|
|
68
|
-
const rect = container.getBoundingClientRect();
|
|
69
|
-
// near container top
|
|
70
|
-
if (y < rect.top + EDGE) {
|
|
71
|
-
container.scrollTop -= SPEED;
|
|
72
|
-
}
|
|
73
|
-
// near container bottom
|
|
74
|
-
else if (y > rect.bottom - EDGE) {
|
|
75
|
-
container.scrollTop += SPEED;
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
const viewportHeight = window.innerHeight;
|
|
79
|
-
// near viewport top
|
|
80
|
-
if (y < EDGE) {
|
|
81
|
-
window.scrollBy(0, -SPEED);
|
|
82
|
-
}
|
|
83
|
-
// near viewport bottom
|
|
84
|
-
else if (y > viewportHeight - EDGE) {
|
|
85
|
-
window.scrollBy(0, SPEED);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
window.addEventListener('dragover', onDragOver);
|
|
94
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
95
|
-
|
|
96
|
-
return () => {
|
|
97
|
-
window.removeEventListener('dragover', onDragOver);
|
|
98
|
-
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
99
|
-
pointerY.current = 0;
|
|
100
|
-
scrollContainerRef.current = null;
|
|
101
|
-
};
|
|
102
|
-
}, [isDragging]);
|
|
103
|
-
|
|
104
34
|
const [{ isOver, canDrop }, drop] = useDrop({
|
|
105
35
|
accept: 'PANEL',
|
|
106
36
|
hover: (dragItem, monitor) => {
|
|
37
|
+
// THIS FIXES BOTTOM → TOP
|
|
38
|
+
autoScrollWindow(monitor);
|
|
39
|
+
|
|
107
40
|
if (dragItem.index === index) return;
|
|
108
41
|
|
|
109
42
|
if (dragItem.level === level && dragItem.parentId === parentId) {
|
|
@@ -152,7 +85,7 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
|
|
|
152
85
|
const childZoneBackgroundColor = isOverChild && canDropChild ? '#d4f4dd' : 'transparent';
|
|
153
86
|
|
|
154
87
|
return (
|
|
155
|
-
<div
|
|
88
|
+
<div style={{ width: '100%', display: 'flex' }}>
|
|
156
89
|
{/* HEADER DROP — reorder only */}
|
|
157
90
|
<div ref={drop}>
|
|
158
91
|
<div
|
|
@@ -158,7 +158,7 @@ export default function MenuAdd({ model, edit, history, match, formContent, call
|
|
|
158
158
|
|
|
159
159
|
{/* Model */}
|
|
160
160
|
<Form.Item label="Model" name="model_id">
|
|
161
|
-
<Select
|
|
161
|
+
<Select style={{ width: '100%' }}>
|
|
162
162
|
{models.map((model, key) => (
|
|
163
163
|
<Option key={key} value={model.id}>{model.name}</Option>
|
|
164
164
|
))}
|
|
@@ -168,7 +168,7 @@ export default function MenuAdd({ model, edit, history, match, formContent, call
|
|
|
168
168
|
|
|
169
169
|
{/* Page */}
|
|
170
170
|
<Form.Item label="Page" name="page_id">
|
|
171
|
-
<Select
|
|
171
|
+
<Select style={{ width: '100%' }}>
|
|
172
172
|
{pages.map((page, key) => (
|
|
173
173
|
<Option key={key} value={page.id}>{page.name}</Option>
|
|
174
174
|
))}
|