ui-soxo-bootstrap-core 2.4.25-dev.31 → 2.4.25-dev.32

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.
@@ -1,5 +1,29 @@
1
- import React from "react";
2
- import { Checkbox, Collapse } from "antd";
1
+ /**
2
+ * MenuTree Component
3
+ *
4
+ * @description
5
+ * Renders a hierarchical menu structure with optional checkboxes.
6
+ * Supports:
7
+ * - Recursive menu rendering
8
+ * - Parent–child checkbox synchronization
9
+ * - Indeterminate state for partially selected parents
10
+ *
11
+ * Common use cases:
12
+ * - Role-based menu preview
13
+ * - Permission assignment trees
14
+ *
15
+ * @param {Object} props
16
+ * @param {Array<Object>} props.menus - Menu tree data
17
+ * @param {Array<number>} props.selectedMenus - Selected menu IDs
18
+ * @param {(menuId:number, checked:boolean) => void} props.toggleMenu - Callback for menu selection
19
+ * @param {number|null} props.parentId - Parent menu ID (used internally for recursion)
20
+ * @param {boolean} props.showCheckbox - Whether to display checkboxes
21
+ *
22
+ * @returns {JSX.Element}
23
+ */
24
+
25
+ import React from 'react';
26
+ import { Checkbox, Collapse } from 'antd';
3
27
 
4
28
  const { Panel } = Collapse;
5
29
 
@@ -8,6 +32,12 @@ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = nul
8
32
  return menuList.map((menu) => {
9
33
  const children = menu.sub_menus || [];
10
34
 
35
+ /**
36
+ * Toggle menu selection recursively for all child menus
37
+ *
38
+ * @param {Object} m - Menu object
39
+ * @param {boolean} checked - Checkbox state
40
+ */
11
41
  const toggleMenuRecursive = (m, checked) => {
12
42
  toggleMenu && toggleMenu(m.id, checked);
13
43
  if (m.sub_menus && m.sub_menus.length > 0) {
@@ -15,49 +45,57 @@ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = nul
15
45
  }
16
46
  };
17
47
 
48
+ /**
49
+ * Handle parent checkbox change
50
+ * - Updates parent menu
51
+ * - Cascades selection to all children
52
+ *
53
+ * @param {boolean} checked
54
+ */
55
+
18
56
  const onParentChange = (checked) => {
19
57
  toggleMenu && toggleMenu(menu.id, checked);
20
58
  children.forEach((c) => toggleMenuRecursive(c, checked));
21
59
  };
22
60
 
61
+ /**
62
+ * Leaf menu (no sub-menus)
63
+ */
64
+
23
65
  if (children.length === 0) {
24
66
  return (
25
67
  <div
26
68
  key={menu.id}
27
69
  style={{
28
- border: "1px solid rgba(198, 195, 195, 0.85)",
29
- padding: "12px 16px",
70
+ border: '1px solid rgba(198, 195, 195, 0.85)',
71
+ padding: '12px 16px',
30
72
  marginBottom: 6,
31
- background: "#fff",
32
- display: "flex",
33
- alignItems: "center",
73
+ background: '#fff',
74
+ display: 'flex',
75
+ alignItems: 'center',
34
76
  gap: 8,
35
77
  }}
36
78
  >
37
- {showCheckbox && (
38
- <Checkbox
39
- checked={selectedMenus.includes(menu.id)}
40
- onChange={(e) => onParentChange(e.target.checked)}
41
- />
42
- )}
79
+ {showCheckbox && <Checkbox checked={selectedMenus.includes(menu.id)} onChange={(e) => onParentChange(e.target.checked)} />}
43
80
  <span>{menu.title || menu.caption}</span>
44
81
  </div>
45
82
  );
46
83
  }
47
84
 
85
+ /**
86
+ * Parent menu (with sub-menus)
87
+ */
88
+
48
89
  return (
49
90
  <Collapse key={menu.id} style={{ marginBottom: 6 }}>
50
91
  <Panel
51
92
  key={menu.id}
52
93
  header={
53
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
94
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
54
95
  {showCheckbox && (
55
96
  <Checkbox
56
97
  checked={children.every((c) => selectedMenus.includes(c.id))}
57
- indeterminate={
58
- children.some((c) => selectedMenus.includes(c.id)) &&
59
- !children.every((c) => selectedMenus.includes(c.id))
60
- }
98
+ indeterminate={children.some((c) => selectedMenus.includes(c.id)) && !children.every((c) => selectedMenus.includes(c.id))}
61
99
  onChange={(e) => onParentChange(e.target.checked)}
62
100
  />
63
101
  )}
@@ -65,9 +103,7 @@ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = nul
65
103
  </div>
66
104
  }
67
105
  >
68
- <div style={{ paddingLeft: 20 }}>
69
- {renderTree(children, menu.id)}
70
- </div>
106
+ <div style={{ paddingLeft: 20 }}>{renderTree(children, menu.id)}</div>
71
107
  </Panel>
72
108
  </Collapse>
73
109
  );
@@ -1,3 +1,18 @@
1
+ /**
2
+ * AssignRole Component
3
+ *
4
+ * @description
5
+ * Allows administrators to:
6
+ * - View user details
7
+ * - Assign one or more roles to a user
8
+ * - View menus associated with a selected role (read-only)
9
+ *
10
+ * UI Layout:
11
+ * - Left Panel: User info + role list with search and checkboxes
12
+ * - Right Panel: Menu tree preview for the selected role
13
+ *
14
+ * @returns {JSX.Element}
15
+ */
1
16
  import React, { useMemo, useState, useEffect } from 'react';
2
17
  import { Checkbox, Input, Typography, Empty, Avatar, List, Card, message, Skeleton } from 'antd';
3
18
 
@@ -13,24 +28,44 @@ const { Text } = Typography;
13
28
  const { Search } = Input;
14
29
 
15
30
  export default function AssignRole() {
31
+ /**
32
+ * User ID from route params
33
+ * @type {{ id: string }}
34
+ */
16
35
  const { id } = useParams();
17
-
36
+ // for user
18
37
  const [user, setUser] = useState(null);
38
+ // for roles
19
39
  const [roles, setRoles] = useState([]);
40
+ // for active roles
20
41
  const [activeRole, setActiveRole] = useState(null);
21
42
  const [modules, setModules] = useState([]);
43
+ // loading
22
44
  const [selectedRoles, setSelectedRoles] = useState([]);
23
- // const [loadingUser, setLoadingUser] = useState(false);
24
- // const [loadingRoles, setLoadingRoles] = useState(false);
45
+ const [loadingUser, setLoadingUser] = useState(false);
46
+ const [loadingRoles, setLoadingRoles] = useState(false);
25
47
  const [loadingMenus, setLoadingMenus] = useState(false);
48
+
26
49
  const [selectedMenus, setSelectedMenus] = useState([]);
27
50
  const [search, setSearch] = useState('');
28
51
 
29
- /** Load user by id */
52
+ /**
53
+ * Load user details and assigned roles when user ID changes
54
+ */
30
55
  useEffect(() => {
31
56
  if (id) loadUser();
32
57
  }, [id]);
33
58
 
59
+ /**
60
+ * Fetch user details and assigned roles
61
+ *
62
+ * - Extracts user info from first record
63
+ * - Deduplicates role IDs
64
+ *
65
+ * @async
66
+ * @returns {Promise<void>}
67
+ */
68
+
34
69
  const loadUser = async () => {
35
70
  setLoadingUser(true);
36
71
  try {
@@ -43,21 +78,15 @@ export default function AssignRole() {
43
78
  return;
44
79
  }
45
80
 
46
- // 1️⃣ Extract user from first record
81
+ // Extract user from first record
47
82
  const userInfo = list[0].user;
48
83
  setUser({
49
84
  id: userInfo.id,
50
85
  name: userInfo.name,
51
86
  });
52
87
 
53
- // 2️⃣ Extract VALID role IDs (ignore nulls & duplicates)
54
- const roleIds = [
55
- ...new Set(
56
- list
57
- .filter((item) => item.role_id) // ignore null role_id
58
- .map((item) => Number(item.role_id))
59
- ),
60
- ];
88
+ // Extract VALID role IDs (ignore nulls & duplicates)
89
+ const roleIds = [...new Set(list.filter((item) => item.role_id).map((item) => Number(item.role_id)))];
61
90
 
62
91
  setSelectedRoles(roleIds);
63
92
  } catch (e) {
@@ -68,11 +97,20 @@ export default function AssignRole() {
68
97
  }
69
98
  };
70
99
 
71
- /** Load roles */
100
+ /**
101
+ * Load active roles on component mount
102
+ */
72
103
  useEffect(() => {
73
104
  getRoles();
74
105
  }, []);
75
106
 
107
+ /**
108
+ * Fetch all active roles
109
+ *
110
+ * @async
111
+ * @returns {Promise<void>}
112
+ */
113
+
76
114
  const getRoles = async () => {
77
115
  setLoadingRoles(true);
78
116
  try {
@@ -87,6 +125,14 @@ export default function AssignRole() {
87
125
  }
88
126
  };
89
127
 
128
+ /**
129
+ * Recursively filter menus by allowed menu IDs
130
+ *
131
+ * @param {Array<Object>} menus - Full menu tree
132
+ * @param {Array<number>} allowedIds - Menu IDs assigned to role
133
+ * @returns {Array<Object>}
134
+ */
135
+
90
136
  const filterAndSortMenus = (menus, allowedIds) => {
91
137
  return menus
92
138
  .filter((m) => allowedIds.includes(m.id))
@@ -96,7 +142,14 @@ export default function AssignRole() {
96
142
  }));
97
143
  };
98
144
 
99
- /** Load menus for a role */
145
+ /**
146
+ * Load menus for a selected role
147
+ *
148
+ * @param {Object} role - Selected role object
149
+ * @async
150
+ * @returns {Promise<void>}
151
+ */
152
+
100
153
  const loadRoleMenus = async (role) => {
101
154
  setActiveRole(role);
102
155
  setSelectedMenus(role.menu_ids || []);
@@ -114,7 +167,12 @@ export default function AssignRole() {
114
167
  }
115
168
  };
116
169
 
117
- /** Toggle menu selection */
170
+ /**
171
+ * Toggle menu selection (currently read-only usage)
172
+ *
173
+ * @param {number} menuId
174
+ * @param {boolean} checked
175
+ */
118
176
  const toggleMenu = (menuId, checked) => {
119
177
  setSelectedMenus((prev) => (checked ? [...prev, menuId] : prev.filter((id) => id !== menuId)));
120
178
  };
@@ -125,12 +183,24 @@ export default function AssignRole() {
125
183
  return roles.filter((r) => r.name.toLowerCase().includes(search.toLowerCase()));
126
184
  }, [roles, search]);
127
185
 
128
- /** Toggle role selection */
186
+ /**
187
+ * Toggle role selection
188
+ *
189
+ * @param {number} roleId
190
+ * @param {boolean} checked
191
+ */
192
+
129
193
  const toggleRole = (roleId, checked) => {
130
194
  const next = checked ? [...selectedRoles, roleId] : selectedRoles.filter((v) => v !== roleId);
131
195
  setSelectedRoles(next);
132
196
  };
133
197
 
198
+ /**
199
+ * Save selected roles for the user
200
+ *
201
+ * @async
202
+ * @returns {Promise<void>}
203
+ */
134
204
  const handleSaveUserRole = async () => {
135
205
  if (!id || !selectedRoles.length) {
136
206
  message.warning('User or roles missing');
@@ -87,4 +87,31 @@
87
87
  gap: 8px;
88
88
  }
89
89
  }
90
+
91
+ /* ===============================
92
+ 📱 iPad Mini (481px – 768px)
93
+ =============================== */
94
+ @media (max-width: 768px) {
95
+ .assign-role {
96
+ flex-direction: column;
97
+ gap: 12px;
98
+ }
99
+
100
+ .assign-role .left-panel {
101
+ width: 100%;
102
+ }
103
+
104
+ .assign-role .right-panel {
105
+ width: 100%;
106
+ }
107
+
108
+ /* Better spacing for tablets */
109
+ .assign-role .right-panel {
110
+ padding: 14px;
111
+ }
112
+
113
+ .assign-role .footer-actions {
114
+ justify-content: flex-end;
115
+ }
116
+ }
90
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.4.25-dev.31",
3
+ "version": "2.4.25-dev.32",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"