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
|
@@ -30,9 +30,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
30
30
|
const [query, setQuery] = useState('');
|
|
31
31
|
const [dragMode, setDragMode] = useState(false);
|
|
32
32
|
const [orderChanged, setOrderChanged] = useState(false);
|
|
33
|
-
// keys of currently-expanded top-level panels. Seeded from the search results
|
|
34
|
-
// when the query changes, but the user can freely toggle panels afterwards.
|
|
35
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
36
33
|
|
|
37
34
|
const [nextId, setNextId] = useState(10000);
|
|
38
35
|
|
|
@@ -184,16 +181,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
184
181
|
return;
|
|
185
182
|
}
|
|
186
183
|
|
|
187
|
-
// Prevent moving a menu into one of its own descendants
|
|
188
|
-
// (e.g. A > B, C — A cannot be dropped under B or C).
|
|
189
|
-
if (targetParentId) {
|
|
190
|
-
const draggedNode = findMenuById(records, draggedItem.id);
|
|
191
|
-
if (draggedNode && findMenuById(draggedNode.sub_menus || [], targetParentId)) {
|
|
192
|
-
message.warning('Cannot move a menu into its own sub menu');
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
184
|
// Remove item from original location
|
|
198
185
|
const { newItems: itemsAfterRemove, foundItem } = findAndRemoveItem(records, draggedItem.id);
|
|
199
186
|
|
|
@@ -207,7 +194,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
207
194
|
|
|
208
195
|
setRecords(finalItems);
|
|
209
196
|
setOrderChanged(true);
|
|
210
|
-
message.success(`Moved "${foundItem.
|
|
197
|
+
message.success(`Moved "${foundItem.name}" to level ${targetLevel}`);
|
|
211
198
|
},
|
|
212
199
|
[records]
|
|
213
200
|
);
|
|
@@ -216,42 +203,9 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
216
203
|
model.delete(rec).then(loadMenus);
|
|
217
204
|
};
|
|
218
205
|
|
|
219
|
-
|
|
220
|
-
// caption matches OR any descendant matches. When the menu itself matches we
|
|
221
|
-
// keep its full subtree; when only a descendant matches we keep just the
|
|
222
|
-
// pruned path so the matching item stays reachable.
|
|
223
|
-
const filterMenus = (menus, q) => {
|
|
224
|
-
if (!q) return menus || [];
|
|
225
|
-
const lower = q.toLowerCase();
|
|
226
|
-
|
|
227
|
-
return (menus || []).reduce((acc, menu) => {
|
|
228
|
-
const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
|
|
229
|
-
if (selfMatch) {
|
|
230
|
-
acc.push({ ...menu });
|
|
231
|
-
return acc;
|
|
232
|
-
}
|
|
233
|
-
const subFiltered = filterMenus(menu.sub_menus || [], q);
|
|
234
|
-
if (subFiltered.length) {
|
|
235
|
-
acc.push({ ...menu, sub_menus: subFiltered });
|
|
236
|
-
}
|
|
237
|
-
return acc;
|
|
238
|
-
}, []);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Ids of items (at one level) that have sub_menus — used to auto-expand
|
|
242
|
-
// every branch leading to a search match.
|
|
243
|
-
const expandableKeys = (items) => (items || []).filter((m) => m.sub_menus && m.sub_menus.length > 0).map((m) => String(m.id));
|
|
206
|
+
const filtered = records.filter((r) => r.caption?.toUpperCase().includes(query.toUpperCase()));
|
|
244
207
|
|
|
245
|
-
const
|
|
246
|
-
const visibleItems = filterMenus(records, query);
|
|
247
|
-
|
|
248
|
-
// When the query changes, seed the open panels from the search results (every
|
|
249
|
-
// branch on the path to a match) so the matched sub-menu is revealed. After
|
|
250
|
-
// that the user can collapse/expand freely via the Collapse onChange below.
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
setOpenKeys(query ? expandableKeys(filterMenus(records, query)) : []);
|
|
253
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
|
-
}, [query]);
|
|
208
|
+
const visibleItems = filtered;
|
|
255
209
|
|
|
256
210
|
const onSearch = (event) => {
|
|
257
211
|
setQuery(event.target.value);
|
|
@@ -351,12 +305,12 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
351
305
|
<Card className="generic-list">
|
|
352
306
|
<div style={{ marginBottom: 16 }}>
|
|
353
307
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
354
|
-
<Search placeholder="Search
|
|
308
|
+
<Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={onSearch} />
|
|
355
309
|
|
|
356
310
|
<Space size="small">
|
|
357
|
-
|
|
311
|
+
<Button onClick={getRecords} size={'small'} type="default">
|
|
358
312
|
<ReloadOutlined />
|
|
359
|
-
</Button>
|
|
313
|
+
</Button>
|
|
360
314
|
|
|
361
315
|
<Switch checked={dragMode} onChange={toggleDragMode} checkedChildren="Order On" unCheckedChildren="Order Off" />
|
|
362
316
|
|
|
@@ -407,11 +361,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
407
361
|
{/* {!view ? ( */}
|
|
408
362
|
<Card>
|
|
409
363
|
<DndProvider backend={HTML5Backend}>
|
|
410
|
-
<Collapse
|
|
411
|
-
accordion={!searchActive}
|
|
412
|
-
activeKey={openKeys}
|
|
413
|
-
onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}
|
|
414
|
-
>
|
|
364
|
+
<Collapse accordion>
|
|
415
365
|
{visibleItems && visibleItems.length > 0 ? (
|
|
416
366
|
visibleItems.map((item, index) => (
|
|
417
367
|
<Panel
|
|
@@ -439,8 +389,6 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
439
389
|
items={item.sub_menus || []}
|
|
440
390
|
model={model}
|
|
441
391
|
dragMode={dragMode}
|
|
442
|
-
searchActive={searchActive}
|
|
443
|
-
query={query}
|
|
444
392
|
setSelectedRecord={setSelectedRecord}
|
|
445
393
|
setDrawerTitle={setDrawerTitle}
|
|
446
394
|
setDrawerVisible={setDrawerVisible}
|
|
@@ -489,7 +437,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
489
437
|
);
|
|
490
438
|
};
|
|
491
439
|
/* -----------------------------------------------------------------------
|
|
492
|
-
PANEL ACTIONS
|
|
440
|
+
PANEL ACTIONS
|
|
493
441
|
------------------------------------------------------------------------ */
|
|
494
442
|
function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord) {
|
|
495
443
|
return (
|
|
@@ -552,8 +500,6 @@ function NestedMenu({
|
|
|
552
500
|
items,
|
|
553
501
|
model,
|
|
554
502
|
dragMode,
|
|
555
|
-
searchActive,
|
|
556
|
-
query,
|
|
557
503
|
setSelectedRecord,
|
|
558
504
|
setDrawerTitle,
|
|
559
505
|
setDrawerVisible,
|
|
@@ -562,22 +508,15 @@ function NestedMenu({
|
|
|
562
508
|
onCrossLevelMove,
|
|
563
509
|
onChange,
|
|
564
510
|
}) {
|
|
511
|
+
// do not render Collapse
|
|
512
|
+
if (!items || items.length === 0) return null;
|
|
513
|
+
|
|
565
514
|
const [localItems, setLocalItems] = useState(items);
|
|
566
|
-
// keys of currently-expanded sub-panels. Seeded from the search results when
|
|
567
|
-
// the query changes, but the user can freely toggle panels afterwards.
|
|
568
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
569
515
|
|
|
570
516
|
useEffect(() => {
|
|
571
517
|
setLocalItems(items);
|
|
572
518
|
}, [items]);
|
|
573
519
|
|
|
574
|
-
// Seed the open sub-panels from the search path when the query changes; keep
|
|
575
|
-
// the user's toggles otherwise so a search-opened panel can be closed.
|
|
576
|
-
useEffect(() => {
|
|
577
|
-
setOpenKeys(searchActive ? (items || []).filter((c) => c.sub_menus && c.sub_menus.length > 0).map((c) => String(c.id)) : []);
|
|
578
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
579
|
-
}, [query, searchActive]);
|
|
580
|
-
|
|
581
520
|
const moveSubMenu = useCallback(
|
|
582
521
|
(from, to) => {
|
|
583
522
|
if (!dragMode || from === to) return;
|
|
@@ -597,7 +536,7 @@ function NestedMenu({
|
|
|
597
536
|
}
|
|
598
537
|
|
|
599
538
|
return (
|
|
600
|
-
<Collapse accordion
|
|
539
|
+
<Collapse accordion>
|
|
601
540
|
{localItems.map((child, index) => (
|
|
602
541
|
<Panel
|
|
603
542
|
key={child.id}
|
|
@@ -624,8 +563,6 @@ function NestedMenu({
|
|
|
624
563
|
step={step + 1}
|
|
625
564
|
items={child.sub_menus || []}
|
|
626
565
|
dragMode={dragMode}
|
|
627
|
-
searchActive={searchActive}
|
|
628
|
-
query={query}
|
|
629
566
|
setSelectedRecord={setSelectedRecord}
|
|
630
567
|
setDrawerTitle={setDrawerTitle}
|
|
631
568
|
setDrawerVisible={setDrawerVisible}
|
|
@@ -160,29 +160,6 @@ class MenusAPI extends Base {
|
|
|
160
160
|
});
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
-
getBranches = () => {
|
|
164
|
-
return ApiUtils.get({
|
|
165
|
-
url: 'branches',
|
|
166
|
-
});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
switchBranch = (formBody, dbPtr) => {
|
|
170
|
-
return ApiUtils.post({
|
|
171
|
-
url: `auth/switch-branch`,
|
|
172
|
-
headers: { db_ptr: dbPtr },
|
|
173
|
-
formBody,
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
getProfile = (token) => {
|
|
178
|
-
return ApiUtils.get({
|
|
179
|
-
url: 'auth/profile',
|
|
180
|
-
headers: {
|
|
181
|
-
Authorization: `Bearer ${token}`,
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
};
|
|
185
|
-
|
|
186
163
|
/**
|
|
187
164
|
* create menu
|
|
188
165
|
*/
|
|
@@ -208,6 +185,7 @@ class MenusAPI extends Base {
|
|
|
208
185
|
: 'menus/get-menus'; // NURA
|
|
209
186
|
|
|
210
187
|
if (!dbPtr) dbPtr = localStorage.db_ptr;
|
|
188
|
+
|
|
211
189
|
return this.get({
|
|
212
190
|
url,
|
|
213
191
|
config,
|
|
@@ -327,12 +305,6 @@ class MenusAPI extends Base {
|
|
|
327
305
|
// }
|
|
328
306
|
];
|
|
329
307
|
};
|
|
330
|
-
// license summary api call
|
|
331
|
-
getSummary = () => {
|
|
332
|
-
return ApiUtils.get({
|
|
333
|
-
url: 'license/summary',
|
|
334
|
-
});
|
|
335
|
-
};
|
|
336
308
|
}
|
|
337
309
|
|
|
338
310
|
export default MenusAPI;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import React, { useState, useEffect, useContext } from 'react';
|
|
2
2
|
import { useLocation, useParams } from 'react-router-dom';
|
|
3
|
-
import { Skeleton, Typography, message, Form, Input } from 'antd';
|
|
3
|
+
import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox, Tag } from 'antd';
|
|
4
4
|
import { GlobalContext } from './../../../../lib';
|
|
5
|
+
import { Button } from './../../../../lib';
|
|
5
6
|
import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
|
|
6
|
-
import MenuTree from './menu-tree';
|
|
7
7
|
import './role-add.scss';
|
|
8
8
|
|
|
9
9
|
const { Title } = Typography;
|
|
10
|
+
const { Panel } = Collapse;
|
|
10
11
|
|
|
11
|
-
const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = []
|
|
12
|
+
const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [] }) => {
|
|
12
13
|
let mode = 'Add';
|
|
13
14
|
if (formContent.id) mode = 'Edit';
|
|
14
15
|
else if (formContent.copy) mode = 'copy';
|
|
@@ -30,10 +31,8 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
30
31
|
|
|
31
32
|
const isEdit = !!(formContent.id || formContent.copy);
|
|
32
33
|
const [initialLoading, setInitialLoading] = useState(isEdit);
|
|
34
|
+
const [loading, setLoading] = useState(false);
|
|
33
35
|
const [form] = Form.useForm();
|
|
34
|
-
|
|
35
|
-
// expose the form instance so the Drawer footer Save button can submit it
|
|
36
|
-
if (formRef) formRef.current = form;
|
|
37
36
|
const [pages, setPages] = useState([]);
|
|
38
37
|
const [models, setModels] = useState([]);
|
|
39
38
|
const [menuList, setMenuList] = useState([]);
|
|
@@ -44,9 +43,6 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
44
43
|
// Selected menus (array of IDs)
|
|
45
44
|
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
46
45
|
|
|
47
|
-
// Search term for filtering the menu list
|
|
48
|
-
const [menuSearch, setMenuSearch] = useState('');
|
|
49
|
-
|
|
50
46
|
const location = useLocation();
|
|
51
47
|
const query = new URLSearchParams(location.search);
|
|
52
48
|
let step = parseInt(query.get('step')) || 1;
|
|
@@ -101,28 +97,40 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
101
97
|
setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
|
|
102
98
|
};
|
|
103
99
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
// Submit handler
|
|
101
|
+
// const onSubmit = (values) => {
|
|
102
|
+
// setLoading(true);
|
|
103
|
+
|
|
104
|
+
// const payload = {
|
|
105
|
+
// ...values,
|
|
106
|
+
// menu_ids
|
|
107
|
+
// : selectedMenus,
|
|
108
|
+
// attributes: JSON.stringify(values.attributes || {}),
|
|
109
|
+
// };
|
|
110
|
+
|
|
111
|
+
// if (formContent.id) {
|
|
112
|
+
// RolesAPI.updateRole({ id: formContent.id, formBody: payload })
|
|
113
|
+
// .then(() => {
|
|
114
|
+
// message.success('Role Updated');
|
|
115
|
+
// setLoading(false);
|
|
116
|
+
// callback();
|
|
117
|
+
// })
|
|
118
|
+
// .catch(() => setLoading(false));
|
|
119
|
+
// } else {
|
|
120
|
+
// additional_queries.forEach(({ field, value }) => {
|
|
121
|
+
// payload[field] = value;
|
|
122
|
+
// });
|
|
123
|
+
// RolesAPI.createRole(payload)
|
|
124
|
+
// .then(() => {
|
|
125
|
+
// message.success('Role Added');
|
|
126
|
+
// setLoading(false);
|
|
127
|
+
// callback();
|
|
128
|
+
// })
|
|
129
|
+
// .catch(() => setLoading(false));
|
|
130
|
+
// }
|
|
131
|
+
// };
|
|
124
132
|
const onSubmit = async (values) => {
|
|
125
|
-
|
|
133
|
+
setLoading(true);
|
|
126
134
|
|
|
127
135
|
// Find menus that were originally selected but now deselected
|
|
128
136
|
const deselectedMenus = originalMenus.filter((id) => !selectedMenus.includes(id));
|
|
@@ -145,21 +153,13 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
145
153
|
});
|
|
146
154
|
|
|
147
155
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// it via success: false — show that message as a warning.
|
|
151
|
-
if (res?.success === false) {
|
|
152
|
-
message.warning(res.message || 'Failed to save role');
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
+
await RolesAPI.createRole(payload); // single API
|
|
156
157
|
message.success(formContent?.id ? 'Role Updated' : 'Role Added');
|
|
157
158
|
callback();
|
|
158
159
|
} catch (err) {
|
|
159
|
-
//
|
|
160
|
-
message.warning(err?.message || err?.result?.message || 'Failed to save role');
|
|
160
|
+
// message.error('Something went wrong');
|
|
161
161
|
} finally {
|
|
162
|
-
|
|
162
|
+
setLoading(false);
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
|
|
@@ -194,7 +194,6 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
194
194
|
display: 'flex',
|
|
195
195
|
justifyContent: 'space-between',
|
|
196
196
|
alignItems: 'center',
|
|
197
|
-
gap: 16,
|
|
198
197
|
marginBottom: 16,
|
|
199
198
|
}}
|
|
200
199
|
>
|
|
@@ -205,20 +204,14 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
205
204
|
<p style={{ color: '#999', marginBottom: 0 }}>Choose menus and set permissions</p>
|
|
206
205
|
</div>
|
|
207
206
|
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
style={{ maxWidth: 260 }}
|
|
214
|
-
/>
|
|
207
|
+
<Form.Item style={{ marginBottom: 0 }}>
|
|
208
|
+
<Button loading={loading} htmlType="submit" type="primary">
|
|
209
|
+
Save
|
|
210
|
+
</Button>
|
|
211
|
+
</Form.Item>
|
|
215
212
|
</div>
|
|
216
213
|
|
|
217
|
-
{
|
|
218
|
-
<MenuTree menus={filteredMenuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} searchActive={!!menuSearch} />
|
|
219
|
-
) : (
|
|
220
|
-
<p style={{ color: '#999', textAlign: 'center', padding: '16px 0' }}>No menus found</p>
|
|
221
|
-
)}
|
|
214
|
+
<MenuTree menus={menuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
222
215
|
</div>
|
|
223
216
|
)}
|
|
224
217
|
</Form>
|
|
@@ -228,3 +221,125 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
228
221
|
};
|
|
229
222
|
|
|
230
223
|
export default RoleAdd;
|
|
224
|
+
|
|
225
|
+
// ------------------------
|
|
226
|
+
// Recursive Nested Menu Component
|
|
227
|
+
// ------------------------
|
|
228
|
+
// ------------------------
|
|
229
|
+
// Recursive Nested Menu Component
|
|
230
|
+
// ------------------------
|
|
231
|
+
const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
232
|
+
// Helper: check if parent should be checked
|
|
233
|
+
const isParentChecked = (menu) => {
|
|
234
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) {
|
|
235
|
+
return selectedMenus.includes(menu.id);
|
|
236
|
+
}
|
|
237
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
238
|
+
return allChildIds.every((id) => selectedMenus.includes(id));
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Helper: check if parent is indeterminate
|
|
242
|
+
const isParentIndeterminate = (menu) => {
|
|
243
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
|
|
244
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
245
|
+
const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
|
|
246
|
+
return checkedCount > 0 && checkedCount < allChildIds.length;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
{menus.map((menu) => {
|
|
252
|
+
const children = menu.sub_menus || [];
|
|
253
|
+
const parentChecked = isParentChecked(menu);
|
|
254
|
+
const parentIndeterminate = isParentIndeterminate(menu);
|
|
255
|
+
|
|
256
|
+
const onParentChange = (checked) => {
|
|
257
|
+
toggleMenu(menu.id, checked);
|
|
258
|
+
// toggle children recursively
|
|
259
|
+
children.forEach((c) => toggleMenuRecursive(c, checked));
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const toggleMenuRecursive = (menu, checked) => {
|
|
263
|
+
toggleMenu(menu.id, checked);
|
|
264
|
+
if (menu.sub_menus && menu.sub_menus.length > 0) {
|
|
265
|
+
menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (children.length === 0) {
|
|
270
|
+
return (
|
|
271
|
+
<div
|
|
272
|
+
style={{
|
|
273
|
+
justifyContent: 'space-between',
|
|
274
|
+
display: 'flex',
|
|
275
|
+
alignItems: 'center',
|
|
276
|
+
border: '1px solid rgba(198, 195, 195, 0.85)',
|
|
277
|
+
// borderRadius: 6,
|
|
278
|
+
padding: '12px 16px',
|
|
279
|
+
marginBottom: 6,
|
|
280
|
+
background: '#fff',
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<div
|
|
284
|
+
key={menu.id}
|
|
285
|
+
style={{
|
|
286
|
+
display: 'flex',
|
|
287
|
+
alignItems: 'center',
|
|
288
|
+
gap: 8,
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<Checkbox
|
|
292
|
+
checked={selectedMenus.includes(menu.id)}
|
|
293
|
+
onChange={(e) => {
|
|
294
|
+
const checked = e.target.checked;
|
|
295
|
+
|
|
296
|
+
toggleMenu(menu.id, checked);
|
|
297
|
+
|
|
298
|
+
// FORCE parent selection
|
|
299
|
+
if (checked && parentId) {
|
|
300
|
+
toggleMenu(parentId, true);
|
|
301
|
+
}
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
<span>{menu.caption}</span>
|
|
305
|
+
</div>
|
|
306
|
+
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<Collapse
|
|
313
|
+
key={menu.id}
|
|
314
|
+
style={{ marginBottom: 6 }}
|
|
315
|
+
// defaultActiveKey={[menu.id]}
|
|
316
|
+
>
|
|
317
|
+
<Panel
|
|
318
|
+
key={menu.id}
|
|
319
|
+
header={
|
|
320
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
321
|
+
<div
|
|
322
|
+
style={{
|
|
323
|
+
display: 'flex',
|
|
324
|
+
alignItems: 'center',
|
|
325
|
+
gap: 8,
|
|
326
|
+
}}
|
|
327
|
+
onClick={(e) => e.stopPropagation()}
|
|
328
|
+
>
|
|
329
|
+
<Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
|
|
330
|
+
<span>{menu.caption}</span>
|
|
331
|
+
</div>
|
|
332
|
+
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
333
|
+
</div>
|
|
334
|
+
}
|
|
335
|
+
>
|
|
336
|
+
<div style={{ paddingLeft: 20 }}>
|
|
337
|
+
<MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} />
|
|
338
|
+
</div>
|
|
339
|
+
</Panel>
|
|
340
|
+
</Collapse>
|
|
341
|
+
);
|
|
342
|
+
})}
|
|
343
|
+
</>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
@@ -40,12 +40,6 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
40
40
|
|
|
41
41
|
const [single, setSingle] = useState({});
|
|
42
42
|
|
|
43
|
-
// tracks RoleAdd form submission so the Drawer footer button shows loading
|
|
44
|
-
const [submitting, setSubmitting] = useState(false);
|
|
45
|
-
|
|
46
|
-
// holds the RoleAdd form instance so the Drawer footer Save button can submit it
|
|
47
|
-
const formRef = useRef(null);
|
|
48
|
-
|
|
49
43
|
var [queryValue, setQueryValue] = useState('');
|
|
50
44
|
|
|
51
45
|
const { Search } = Input;
|
|
@@ -366,26 +360,12 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
|
|
|
366
360
|
destroyOnClose
|
|
367
361
|
onClose={closeModal}
|
|
368
362
|
bodyStyle={{ paddingBottom: 80 }}
|
|
369
|
-
footer={
|
|
370
|
-
<div style={{ textAlign: 'right' }}>
|
|
371
|
-
<Space size="small">
|
|
372
|
-
<Button type="default" onClick={closeModal}>
|
|
373
|
-
Cancel
|
|
374
|
-
</Button>
|
|
375
|
-
<Button type="primary" loading={submitting} onClick={() => formRef.current?.submit()}>
|
|
376
|
-
Save
|
|
377
|
-
</Button>
|
|
378
|
-
</Space>
|
|
379
|
-
</div>
|
|
380
|
-
}
|
|
381
363
|
>
|
|
382
364
|
<model.ModalAddComponent
|
|
383
365
|
match={match}
|
|
384
366
|
model={model}
|
|
385
367
|
additional_queries={additional_queries}
|
|
386
368
|
formContent={single}
|
|
387
|
-
formRef={formRef}
|
|
388
|
-
onSubmittingChange={setSubmitting}
|
|
389
369
|
callback={(event) => {
|
|
390
370
|
closeModal();
|
|
391
371
|
getRecords();
|
|
@@ -163,9 +163,6 @@ class RolesAPI extends BaseAPI {
|
|
|
163
163
|
return ApiUtils.post({
|
|
164
164
|
url: `core-roles/save-core-role`,
|
|
165
165
|
formBody,
|
|
166
|
-
// Let the caller surface the backend message (as a warning) instead of
|
|
167
|
-
// the default red error toast from the http layer.
|
|
168
|
-
hideError: true,
|
|
169
166
|
});
|
|
170
167
|
};
|
|
171
168
|
|
|
@@ -160,7 +160,7 @@ export default function AssignRole() {
|
|
|
160
160
|
const role_id = role.id;
|
|
161
161
|
try {
|
|
162
162
|
const res = await MenusAPI.getCoreMenuByRoleId(role_id);
|
|
163
|
-
const allMenus =
|
|
163
|
+
const allMenus = res.result || [];
|
|
164
164
|
|
|
165
165
|
setModules(allMenus);
|
|
166
166
|
} catch (e) {
|
|
@@ -264,12 +264,11 @@ export default function AssignRole() {
|
|
|
264
264
|
const handleViewAll = async () => {
|
|
265
265
|
setLoadingMenus(true);
|
|
266
266
|
setModules([]);
|
|
267
|
-
setActiveRole({ name: 'All Roles'
|
|
267
|
+
setActiveRole({ name: 'All Roles' });
|
|
268
268
|
|
|
269
269
|
try {
|
|
270
270
|
const res = await MenusAPI.getMenubyUser(id);
|
|
271
|
-
|
|
272
|
-
setModules(userMenus);
|
|
271
|
+
setModules(res?.result?.menus ?? []);
|
|
273
272
|
setLoadingMenus(false);
|
|
274
273
|
} catch (err) {
|
|
275
274
|
setLoadingMenus(false);
|
|
@@ -278,18 +277,6 @@ export default function AssignRole() {
|
|
|
278
277
|
}
|
|
279
278
|
};
|
|
280
279
|
|
|
281
|
-
/**
|
|
282
|
-
* Context-specific empty-state message for the right (menus) panel:
|
|
283
|
-
* - View Access for a user with no roles → no role assigned
|
|
284
|
-
* - A selected role with no menus → no menus assigned
|
|
285
|
-
* - Nothing selected yet → prompt to pick a role
|
|
286
|
-
*/
|
|
287
|
-
const emptyMenusMessage = !activeRole
|
|
288
|
-
? 'Select a role to view menus'
|
|
289
|
-
: activeRole.isViewAll
|
|
290
|
-
? 'No role has been assigned to this user.'
|
|
291
|
-
: 'No menus have been assigned to this role.';
|
|
292
|
-
|
|
293
280
|
return (
|
|
294
281
|
<section className="assign-role">
|
|
295
282
|
{/* LEFT PANEL */}
|
|
@@ -396,19 +383,17 @@ export default function AssignRole() {
|
|
|
396
383
|
padding: 16,
|
|
397
384
|
}}
|
|
398
385
|
>
|
|
399
|
-
|
|
400
|
-
<div className="
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
</div>
|
|
404
|
-
)}
|
|
386
|
+
<div className="menus-header">
|
|
387
|
+
<div className="title">Menus {activeRole ? `– ${activeRole.name}` : ''}</div>
|
|
388
|
+
<div className="sub-text">You don’t have permission to edit this here. Update it in Role Settings.</div>
|
|
389
|
+
</div>
|
|
405
390
|
|
|
406
391
|
<div className="menus-content">
|
|
407
392
|
{loadingMenus ? (
|
|
408
393
|
<Skeleton active paragraph={{ rows: 6 }} />
|
|
409
394
|
) : modules.length === 0 ? (
|
|
410
395
|
<div className="empty-state">
|
|
411
|
-
<Empty description=
|
|
396
|
+
<Empty description="Select a role to view menus" />
|
|
412
397
|
</div>
|
|
413
398
|
) : (
|
|
414
399
|
<MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />
|