ui-soxo-bootstrap-core 2.6.40-dev.16 → 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.
Files changed (27) hide show
  1. package/DEVELOPER_GUIDE.md +9 -38
  2. package/core/components/index.js +11 -2
  3. package/core/components/landing-api/landing-api.js +5 -165
  4. package/core/lib/components/global-header/global-header.js +77 -20
  5. package/core/lib/components/index.js +2 -2
  6. package/core/lib/components/sidemenu/sidemenu.js +147 -66
  7. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +24 -91
  8. package/core/lib/models/menus/components/menu-add/menu-add.js +2 -2
  9. package/core/lib/modules/generic/generic-list/ExportReactCSV.js +8 -334
  10. package/core/lib/modules/generic/generic-list/generic-list.scss +0 -34
  11. package/core/models/core-scripts/core-scripts.js +1 -22
  12. package/core/models/menus/components/menu-add/menu-add.js +4 -31
  13. package/core/models/menus/components/menu-lists/menu-lists.js +9 -76
  14. package/core/models/menus/menus.js +1 -29
  15. package/core/models/roles/components/role-add/role-add.js +169 -54
  16. package/core/models/roles/components/role-list/role-list.js +0 -20
  17. package/core/models/roles/roles.js +0 -3
  18. package/core/models/users/components/assign-role/assign-role.js +8 -23
  19. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +5 -202
  20. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +0 -73
  21. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +35 -56
  22. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +0 -1
  23. package/package.json +1 -1
  24. package/core/components/license-management/license-alert.js +0 -97
  25. package/core/models/roles/components/role-add/menu-label.js +0 -14
  26. package/core/models/roles/components/role-add/menu-tree.js +0 -127
  27. 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
- {renderMenuTree(modules, { isRoot: true })}
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, useEffect } from 'react';
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
- // Root DOM node, used to locate the scrollable ancestor at drag time
19
- const rootRef = useRef(null);
20
- // Latest pointer Y, fed by a document-level `dragover` listener (see effect below)
21
- const pointerY = useRef(0);
22
- const rafRef = useRef(null);
23
- // The scrollable container to auto-scroll (resolved when a drag starts)
24
- const scrollContainerRef = useRef(null);
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 ref={rootRef} style={{ width: '100%', display: 'flex' }}>
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 allowClear style={{ width: '100%' }}>
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 allowClear style={{ width: '100%' }}>
171
+ <Select style={{ width: '100%' }}>
172
172
  {pages.map((page, key) => (
173
173
  <Option key={key} value={page.id}>{page.name}</Option>
174
174
  ))}