ui-soxo-bootstrap-core 2.4.25-dev.26 → 2.4.25-dev.28

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.
@@ -214,7 +214,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
214
214
  // document.documentElement.style.setProperty('--custom-text-color', state.theme.colors.colorText);
215
215
  }, [state.theme]);
216
216
 
217
- const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.path || m.caption);
217
+ const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.id || m.path || m.caption);
218
218
 
219
219
  return (
220
220
  <div className="sidemenu">
@@ -354,7 +354,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
354
354
  style={{ color: state.theme.colors.leftSectionColor }}
355
355
  // key={`first-level-${randomIndex}-${menu.caption}`}
356
356
 
357
- key={menu.path || menu.caption}
357
+ key={menu.id || menu.path || menu.caption}
358
358
  title={
359
359
  <>
360
360
  <CollapsedIconMenu
@@ -377,7 +377,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
377
377
  className="popup"
378
378
  // key={`second-level-${randomIndex}-${submenu.id}`}
379
379
 
380
- key={submenu.path || submenu.caption}
380
+ key={submenu.id || submenu.path || submenu.caption}
381
381
  title={
382
382
  <span>
383
383
  <CollapsedIconMenu
@@ -2,26 +2,25 @@ import React, { useRef } from 'react';
2
2
  import { useDrag, useDrop } from 'react-dnd';
3
3
 
4
4
  export default function DraggableWrapper({ id, index, movePanel, item, dragEnabled, level, parentId, onCrossLevelMove, canAcceptChildren }) {
5
-
6
5
  const autoScrollWindow = (monitor) => {
7
- const offset = monitor.getClientOffset();
8
- if (!offset) return;
6
+ const offset = monitor.getClientOffset();
7
+ if (!offset) return;
9
8
 
10
- const EDGE = 80;
11
- const SPEED = 20;
9
+ const EDGE = 80;
10
+ const SPEED = 20;
12
11
 
13
- const viewportHeight = window.innerHeight;
12
+ const viewportHeight = window.innerHeight;
14
13
 
15
- // 🔼 scroll UP
16
- if (offset.y < EDGE) {
17
- window.scrollBy(0, -SPEED);
18
- }
14
+ // 🔼 scroll UP
15
+ if (offset.y < EDGE) {
16
+ window.scrollBy(0, -SPEED);
17
+ }
19
18
 
20
- // 🔽 scroll DOWN
21
- if (offset.y > viewportHeight - EDGE) {
22
- window.scrollBy(0, SPEED);
23
- }
24
- };
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',
@@ -34,36 +33,30 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
34
33
 
35
34
  const [{ isOver, canDrop }, drop] = useDrop({
36
35
  accept: 'PANEL',
37
- hover: (dragItem, monitor) => {
38
- // THIS FIXES BOTTOM → TOP
39
- autoScrollWindow(monitor);
40
-
41
- if (dragItem.index === index) return;
42
-
43
- if (
44
- dragItem.level === level &&
45
- dragItem.parentId === parentId
46
- ) {
47
- movePanel(dragItem.index, index);
48
- dragItem.index = index; // keep in sync
49
- }
50
- },
36
+ hover: (dragItem, monitor) => {
37
+ // THIS FIXES BOTTOM → TOP
38
+ autoScrollWindow(monitor);
39
+
40
+ if (dragItem.index === index) return;
41
+
42
+ if (dragItem.level === level && dragItem.parentId === parentId) {
43
+ movePanel(dragItem.index, index);
44
+ dragItem.index = index; // keep in sync
45
+ }
46
+ },
51
47
 
52
48
  canDrop: (item) => dragEnabled,
53
- drop: (dragItem, monitor) => {
54
- if (monitor.didDrop()) return;
55
-
56
- if (
57
- dragItem.level !== level ||
58
- dragItem.parentId !== parentId
59
- ) {
60
- onCrossLevelMove?.(dragItem, {
61
- targetLevel: level,
62
- targetParentId: parentId,
63
- targetIndex: index,
64
- });
65
- }
66
- },
49
+ drop: (dragItem, monitor) => {
50
+ if (monitor.didDrop()) return;
51
+
52
+ if (dragItem.level !== level || dragItem.parentId !== parentId) {
53
+ onCrossLevelMove?.(dragItem, {
54
+ targetLevel: level,
55
+ targetParentId: parentId,
56
+ targetIndex: index,
57
+ });
58
+ }
59
+ },
67
60
  collect: (monitor) => ({
68
61
  isOver: monitor.isOver({ shallow: true }),
69
62
  canDrop: monitor.canDrop(),
@@ -75,7 +68,7 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
75
68
  accept: 'PANEL',
76
69
  canDrop: (item) => dragEnabled && item.id !== id && canAcceptChildren,
77
70
  drop: (item, monitor) => {
78
- if (monitor.didDrop()) return;
71
+ // if (monitor.didDrop()) return;
79
72
 
80
73
  if (onCrossLevelMove && item.id !== id) {
81
74
  // Drop as child of this item
@@ -88,51 +81,56 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
88
81
  }),
89
82
  });
90
83
 
91
- const backgroundColor = isOver && canDrop ? '#bae7ff' : isDragging ? '#e6f7ff' : 'transparent';
84
+ const backgroundColor = isOver && canDrop ? '#bae7ff' : isDragging ? '#ffff' : 'transparent';
92
85
  const childZoneBackgroundColor = isOverChild && canDropChild ? '#d4f4dd' : 'transparent';
93
86
 
94
87
  return (
95
- <div ref={drop} style={{ width: '100%' }}>
96
- <div
97
- ref={drag}
98
- style={{
99
- opacity: isDragging ? 0.5 : 1,
100
- cursor: dragEnabled ? 'move' : 'default',
101
- // padding: '8px',
102
- backgroundColor,
103
- transition: 'all 0.2s ease',
104
- border: isOver && canDrop ? '2px dashed #1890ff' : '2px solid transparent',
105
- display: 'flex',
106
- justifyContent: 'space-between',
107
- alignItems: 'center',
108
- }}
109
- >
110
- <div style={{ flex: 1 }}>
111
- {dragEnabled && <span style={{ marginRight: 8, color: '#999' }}>⋮⋮</span>}
112
- <strong>{item.name}</strong>
113
- {dragEnabled ?( <span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>(Level {level})</span>):(<span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>{item.path}</span>)}
114
- </div>
115
-
116
- {canAcceptChildren && dragEnabled && (
117
- <div
118
- ref={dropChild}
119
- onClick={(e) => e.stopPropagation()}
120
- style={{
121
- padding: '4px 12px',
122
- marginLeft: 8,
123
- backgroundColor: childZoneBackgroundColor,
124
- border: isOverChild && canDropChild ? '2px solid #52c41a' : '1px dashed #d9d9d9',
125
- borderRadius: 4,
126
- fontSize: 11,
127
- color: isOverChild && canDropChild ? '#52c41a' : '#999',
128
- cursor: 'default',
129
- transition: 'all 0.2s ease',
130
- }}
131
- >
132
- {isOverChild && canDropChild ? '✓ Drop as child' : '↳ Drop here'}
88
+ <div style={{ width: '100%', display: 'flex' }}>
89
+ {/* HEADER DROP — reorder only */}
90
+ <div ref={drop}>
91
+ <div
92
+ ref={drag}
93
+ style={{
94
+ opacity: isDragging ? 0.5 : 1,
95
+ cursor: dragEnabled ? 'move' : 'default',
96
+ backgroundColor,
97
+ border: isOver && canDrop ? '2px dashed #1890ff' : '2px solid transparent',
98
+ display: 'flex',
99
+ justifyContent: 'space-between',
100
+ alignItems: 'center',
101
+ }}
102
+ >
103
+ <div style={{ flex: 1 }}>
104
+ {dragEnabled && <span style={{ marginRight: 8 }}>⋮⋮</span>}
105
+ <span>{item.name}</span>
106
+ {dragEnabled ? (
107
+ <span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>(Level {level})</span>
108
+ ) : (
109
+ <span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>{item.path}</span>
110
+ )}
133
111
  </div>
134
- )}
112
+ </div>
135
113
  </div>
114
+
115
+ {/* CHILD DROP — nesting */}
116
+ {canAcceptChildren && dragEnabled && (
117
+ <div
118
+ ref={dropChild}
119
+ style={{
120
+ marginLeft: 8,
121
+ // flex:1,
122
+ // marginTop: 4,
123
+ padding: '4px 12px',
124
+ backgroundColor: isOverChild && canDropChild ? '#d4f4dd' : 'transparent',
125
+ border: isOverChild && canDropChild ? '2px solid #52c41a' : '1px dashed #d9d9d9',
126
+ borderRadius: 4,
127
+ fontSize: 11,
128
+ color: isOverChild && canDropChild ? '#52c41a' : '#999',
129
+ }}
130
+ >
131
+ {isOverChild && canDropChild ? '✓ Drop as child' : '↳ Drop here'}
132
+ </div>
133
+ )}
136
134
  </div>
137
135
  );
138
136
  }
@@ -4,7 +4,7 @@
4
4
  }
5
5
 
6
6
  .ant-collapse {
7
- background-color: #fafafa !important;
7
+ background-color: #ffff !important;
8
8
  border: none !important;
9
9
  }
10
10
  .ant-collapse > .ant-collapse-item {
@@ -39,7 +39,8 @@
39
39
  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
40
40
  }
41
41
  .ant-collapse > .ant-collapse-item > .ant-collapse-heade{
42
- align-items: center;
42
+ align-items: center !important;
43
43
  }
44
44
 
45
45
 
46
+
@@ -4,6 +4,7 @@ import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox } from '
4
4
  import { GlobalContext } from './../../../../lib';
5
5
  import { Button } from './../../../../lib';
6
6
  import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
7
+ import './role-add.scss';
7
8
 
8
9
  const { Title } = Typography;
9
10
  const { Panel } = Collapse;
@@ -59,7 +60,7 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
59
60
 
60
61
  setSelectedMenus(menus);
61
62
  // keep original copy for deselect comparison
62
- setOriginalMenus(menus);
63
+ setOriginalMenus(menus);
63
64
  }
64
65
 
65
66
  setLoading(false);
@@ -203,48 +204,105 @@ export default RoleAdd;
203
204
  // ------------------------
204
205
  // Recursive Nested Menu Component
205
206
  // ------------------------
206
- const MenuTree = ({ menus, selectedMenus, toggleMenu }) => {
207
+ // ------------------------
208
+ // Recursive Nested Menu Component
209
+ // ------------------------
210
+ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
211
+ // Helper: check if parent should be checked
212
+ const isParentChecked = (menu) => {
213
+ if (!menu.sub_menus || menu.sub_menus.length === 0) {
214
+ return selectedMenus.includes(menu.id);
215
+ }
216
+ const allChildIds = menu.sub_menus.map((c) => c.id);
217
+ return allChildIds.every((id) => selectedMenus.includes(id));
218
+ };
219
+
220
+ // Helper: check if parent is indeterminate
221
+ const isParentIndeterminate = (menu) => {
222
+ if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
223
+ const allChildIds = menu.sub_menus.map((c) => c.id);
224
+ const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
225
+ return checkedCount > 0 && checkedCount < allChildIds.length;
226
+ };
227
+
207
228
  return (
208
229
  <>
209
230
  {menus.map((menu) => {
210
- const hasChildren = Array.isArray(menu.sub_menus) && menu.sub_menus.length > 0;
211
-
212
- // NO CHILD SIMPLE ROW (NO COLLAPSE)
213
- if (!hasChildren) {
231
+ const children = menu.sub_menus || [];
232
+ const parentChecked = isParentChecked(menu);
233
+ const parentIndeterminate = isParentIndeterminate(menu);
234
+
235
+ const onParentChange = (checked) => {
236
+ toggleMenu(menu.id, checked);
237
+ // toggle children recursively
238
+ children.forEach((c) => toggleMenuRecursive(c, checked));
239
+ };
240
+
241
+ const toggleMenuRecursive = (menu, checked) => {
242
+ toggleMenu(menu.id, checked);
243
+ if (menu.sub_menus && menu.sub_menus.length > 0) {
244
+ menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
245
+ }
246
+ };
247
+
248
+ if (children.length === 0) {
214
249
  return (
215
250
  <div
216
251
  key={menu.id}
217
252
  style={{
218
- padding: '10px 16px',
253
+ border: '1px solid rgba(198, 195, 195, 0.85)',
254
+ // borderRadius: 6,
255
+ padding: '12px 16px',
256
+ marginBottom: 6,
257
+ background: '#fff',
219
258
  display: 'flex',
220
259
  alignItems: 'center',
221
260
  gap: 8,
222
- borderBottom: '1px solid #f0f0f0',
223
261
  }}
224
262
  >
225
- <Checkbox checked={selectedMenus.includes(menu.id)} onChange={(e) => toggleMenu(menu.id, e.target.checked)} />
263
+ <Checkbox
264
+ checked={selectedMenus.includes(menu.id)}
265
+ onChange={(e) => {
266
+ const checked = e.target.checked;
267
+
268
+ toggleMenu(menu.id, checked);
269
+
270
+ // ✅ FORCE parent selection
271
+ if (checked && parentId) {
272
+ toggleMenu(parentId, true);
273
+ }
274
+ }}
275
+ />
226
276
  <span>{menu.title || menu.caption}</span>
227
277
  </div>
228
278
  );
229
279
  }
230
280
 
231
- // HAS CHILD → COLLAPSE WITH ICON
232
281
  return (
233
- <Collapse key={menu.id} collapsible="icon" style={{ marginBottom: '4px' }}>
282
+ <Collapse
283
+ key={menu.id}
284
+ style={{ marginBottom: 6 }}
285
+ // defaultActiveKey={[menu.id]}
286
+ >
234
287
  <Panel
235
288
  key={menu.id}
236
289
  header={
237
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
238
- <Checkbox
239
- checked={selectedMenus.includes(menu.id)}
240
- onClick={(e) => e.stopPropagation()}
241
- onChange={(e) => toggleMenu(menu.id, e.target.checked)}
242
- />
290
+ <div
291
+ style={{
292
+ display: 'flex',
293
+ alignItems: 'center',
294
+ gap: 8,
295
+ }}
296
+ onClick={(e) => e.stopPropagation()}
297
+ >
298
+ <Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
243
299
  <span>{menu.title || menu.caption}</span>
244
300
  </div>
245
301
  }
246
302
  >
247
- <MenuTree menus={menu.sub_menus} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
303
+ <div style={{ paddingLeft: 20 }}>
304
+ <MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} />
305
+ </div>
248
306
  </Panel>
249
307
  </Collapse>
250
308
  );
@@ -0,0 +1,4 @@
1
+ .ant-checkbox-indeterminate .ant-checkbox-inner::after {
2
+ height: 2.5px;
3
+ opacity: 0.85;
4
+ }
@@ -16,8 +16,6 @@ import PopQueryDashboard from './dashboard/components/pop-query-dashboard/pop-qu
16
16
 
17
17
  import HomePageAPI from './../pages/homepage-api/homepage-api';
18
18
 
19
- import { Profile, ChangePassword } from './../lib';
20
-
21
19
  import ReportingDashboard from '../modules/reporting/components/reporting-dashboard/reporting-dashboard';
22
20
 
23
21
  import ChangeInfo from './Informations/change-info/change-info';
@@ -34,9 +32,7 @@ export {
34
32
  DashboardCard,
35
33
  PopQueryDashboard,
36
34
  HomePageAPI,
37
- Profile,
38
35
  ReportingDashboard,
39
- ChangePassword,
40
36
  ChangeInfo,
41
37
  };
42
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.4.25-dev.26",
3
+ "version": "2.4.25-dev.28",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"