ui-soxo-bootstrap-core 2.4.25-dev.30 → 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.
- package/core/lib/elements/basic/menu-tree/menu-tree.js +114 -0
- package/core/models/doctor/components/doctor-add/doctor-add.js +21 -2
- package/core/models/staff/components/staff-add/staff-add.js +29 -1
- package/core/models/users/components/assign-role/assign-role.js +298 -0
- package/core/models/users/components/assign-role/assign-role.scss +117 -0
- package/core/models/users/components/user-add/user-add.js +2 -2
- package/core/models/users/users.js +15 -0
- package/core/modules/index.js +3 -0
- package/core/modules/reporting/components/index.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
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';
|
|
27
|
+
|
|
28
|
+
const { Panel } = Collapse;
|
|
29
|
+
|
|
30
|
+
export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = null, showCheckbox = true }) => {
|
|
31
|
+
const renderTree = (menuList, parentId = null) => {
|
|
32
|
+
return menuList.map((menu) => {
|
|
33
|
+
const children = menu.sub_menus || [];
|
|
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
|
+
*/
|
|
41
|
+
const toggleMenuRecursive = (m, checked) => {
|
|
42
|
+
toggleMenu && toggleMenu(m.id, checked);
|
|
43
|
+
if (m.sub_menus && m.sub_menus.length > 0) {
|
|
44
|
+
m.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handle parent checkbox change
|
|
50
|
+
* - Updates parent menu
|
|
51
|
+
* - Cascades selection to all children
|
|
52
|
+
*
|
|
53
|
+
* @param {boolean} checked
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const onParentChange = (checked) => {
|
|
57
|
+
toggleMenu && toggleMenu(menu.id, checked);
|
|
58
|
+
children.forEach((c) => toggleMenuRecursive(c, checked));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Leaf menu (no sub-menus)
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
if (children.length === 0) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
key={menu.id}
|
|
69
|
+
style={{
|
|
70
|
+
border: '1px solid rgba(198, 195, 195, 0.85)',
|
|
71
|
+
padding: '12px 16px',
|
|
72
|
+
marginBottom: 6,
|
|
73
|
+
background: '#fff',
|
|
74
|
+
display: 'flex',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
gap: 8,
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{showCheckbox && <Checkbox checked={selectedMenus.includes(menu.id)} onChange={(e) => onParentChange(e.target.checked)} />}
|
|
80
|
+
<span>{menu.title || menu.caption}</span>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parent menu (with sub-menus)
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Collapse key={menu.id} style={{ marginBottom: 6 }}>
|
|
91
|
+
<Panel
|
|
92
|
+
key={menu.id}
|
|
93
|
+
header={
|
|
94
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
95
|
+
{showCheckbox && (
|
|
96
|
+
<Checkbox
|
|
97
|
+
checked={children.every((c) => selectedMenus.includes(c.id))}
|
|
98
|
+
indeterminate={children.some((c) => selectedMenus.includes(c.id)) && !children.every((c) => selectedMenus.includes(c.id))}
|
|
99
|
+
onChange={(e) => onParentChange(e.target.checked)}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
<span>{menu.title || menu.caption}</span>
|
|
103
|
+
</div>
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
<div style={{ paddingLeft: 20 }}>{renderTree(children, menu.id)}</div>
|
|
107
|
+
</Panel>
|
|
108
|
+
</Collapse>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return <>{renderTree(menus, parentId)}</>;
|
|
114
|
+
};
|
|
@@ -105,6 +105,11 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
105
105
|
return Promise.resolve();
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
if (!/^[a-zA-Z0-9]*$/.test(value)) {
|
|
109
|
+
setCodeStatus({ status: 'error', message: 'Code can only contain letters and numbers.' });
|
|
110
|
+
return Promise.reject(new Error('Code can only contain letters and numbers.'));
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
if (editMode && doctorData?.do_code === value) {
|
|
109
114
|
setCodeStatus({ status: 'success', message: 'Code is unchanged' });
|
|
110
115
|
return Promise.resolve();
|
|
@@ -218,7 +223,17 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
218
223
|
{/* NAME + CODE */}
|
|
219
224
|
<Row gutter={16}>
|
|
220
225
|
<Col span={12}>
|
|
221
|
-
<Form.Item
|
|
226
|
+
<Form.Item
|
|
227
|
+
label="Name"
|
|
228
|
+
name="name"
|
|
229
|
+
rules={[
|
|
230
|
+
{ required: true },
|
|
231
|
+
{
|
|
232
|
+
pattern: /^[A-Za-z]+$/,
|
|
233
|
+
message: 'Name should contain only alphabets',
|
|
234
|
+
},
|
|
235
|
+
]}
|
|
236
|
+
>
|
|
222
237
|
<Input autoComplete="off" ref={nameInputRef} placeholder="Enter Name" onKeyDown={handleEnterKey} />
|
|
223
238
|
</Form.Item>
|
|
224
239
|
</Col>
|
|
@@ -369,7 +384,11 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
369
384
|
</Col>
|
|
370
385
|
|
|
371
386
|
<Col span={8}>
|
|
372
|
-
<Form.Item
|
|
387
|
+
<Form.Item
|
|
388
|
+
label="Email ID"
|
|
389
|
+
name="email_id"
|
|
390
|
+
rules={[{ type: 'email', message: 'Please enter a valid email address' }]}
|
|
391
|
+
>
|
|
373
392
|
<Input placeholder="Enter Email ID" onKeyDown={handleEnterKey} />
|
|
374
393
|
</Form.Item>
|
|
375
394
|
</Col>
|
|
@@ -13,6 +13,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
13
13
|
* ------------------------------- */
|
|
14
14
|
const [loading, setLoading] = useState(false);
|
|
15
15
|
const [designations, setDesignations] = useState([]);
|
|
16
|
+
const [staffList, setStaffList] = useState([]);
|
|
16
17
|
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
|
|
17
18
|
const [codeStatus, setCodeStatus] = useState({ status: '', message: '' });
|
|
18
19
|
|
|
@@ -47,6 +48,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
47
48
|
useEffect(() => {
|
|
48
49
|
if (visible) {
|
|
49
50
|
getDesignations();
|
|
51
|
+
if (!editMode) getStaffList();
|
|
50
52
|
}
|
|
51
53
|
}, [visible]);
|
|
52
54
|
|
|
@@ -95,6 +97,18 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
95
97
|
.finally(() => setLoading(false));
|
|
96
98
|
};
|
|
97
99
|
|
|
100
|
+
/** -------------------------------
|
|
101
|
+
* API CALL – Staff List
|
|
102
|
+
* ------------------------------- */
|
|
103
|
+
const getStaffList = () => {
|
|
104
|
+
UsersAPI.getAllStaff()
|
|
105
|
+
.then((res) => {
|
|
106
|
+
if (Array.isArray(res)) {
|
|
107
|
+
setStaffList(res);
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => console.error(err));
|
|
111
|
+
};
|
|
98
112
|
|
|
99
113
|
/** -------------------------------
|
|
100
114
|
* VALIDATION – Input
|
|
@@ -149,6 +163,16 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
149
163
|
return Promise.resolve();
|
|
150
164
|
};
|
|
151
165
|
|
|
166
|
+
const validateSerial = (_, value) => {
|
|
167
|
+
if (!value) {
|
|
168
|
+
return Promise.resolve();
|
|
169
|
+
}
|
|
170
|
+
const exists = staffList.some((staff) => String(staff.slNo) === value);
|
|
171
|
+
if (exists) {
|
|
172
|
+
return Promise.reject('Serial Number already exists');
|
|
173
|
+
}
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
};
|
|
152
176
|
|
|
153
177
|
/** -------------------------------
|
|
154
178
|
* UTIL – Enter Key Navigation
|
|
@@ -350,7 +374,11 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
350
374
|
</Col>
|
|
351
375
|
|
|
352
376
|
<Col span={8}>
|
|
353
|
-
<Form.Item
|
|
377
|
+
<Form.Item
|
|
378
|
+
label="Serial Number"
|
|
379
|
+
name="slno"
|
|
380
|
+
rules={[{ message: 'only numbers are allowed', pattern: /^\d*$/ }, { validator: validateSerial }]}
|
|
381
|
+
>
|
|
354
382
|
<Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} disabled={editMode} />
|
|
355
383
|
</Form.Item>
|
|
356
384
|
</Col>
|
|
@@ -0,0 +1,298 @@
|
|
|
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
|
+
*/
|
|
16
|
+
import React, { useMemo, useState, useEffect } from 'react';
|
|
17
|
+
import { Checkbox, Input, Typography, Empty, Avatar, List, Card, message, Skeleton } from 'antd';
|
|
18
|
+
|
|
19
|
+
import { UsersAPI, RolesAPI, MenusAPI } from '../../..';
|
|
20
|
+
import { UserRolesAPI } from '../../..';
|
|
21
|
+
import { useParams } from 'react-router-dom';
|
|
22
|
+
import { Button } from '../../../../lib';
|
|
23
|
+
import { MenuTree } from '../../../../lib/elements/basic/menu-tree/menu-tree';
|
|
24
|
+
import './assign-role.scss';
|
|
25
|
+
|
|
26
|
+
const { Text } = Typography;
|
|
27
|
+
|
|
28
|
+
const { Search } = Input;
|
|
29
|
+
|
|
30
|
+
export default function AssignRole() {
|
|
31
|
+
/**
|
|
32
|
+
* User ID from route params
|
|
33
|
+
* @type {{ id: string }}
|
|
34
|
+
*/
|
|
35
|
+
const { id } = useParams();
|
|
36
|
+
// for user
|
|
37
|
+
const [user, setUser] = useState(null);
|
|
38
|
+
// for roles
|
|
39
|
+
const [roles, setRoles] = useState([]);
|
|
40
|
+
// for active roles
|
|
41
|
+
const [activeRole, setActiveRole] = useState(null);
|
|
42
|
+
const [modules, setModules] = useState([]);
|
|
43
|
+
// loading
|
|
44
|
+
const [selectedRoles, setSelectedRoles] = useState([]);
|
|
45
|
+
const [loadingUser, setLoadingUser] = useState(false);
|
|
46
|
+
const [loadingRoles, setLoadingRoles] = useState(false);
|
|
47
|
+
const [loadingMenus, setLoadingMenus] = useState(false);
|
|
48
|
+
|
|
49
|
+
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
50
|
+
const [search, setSearch] = useState('');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load user details and assigned roles when user ID changes
|
|
54
|
+
*/
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (id) loadUser();
|
|
57
|
+
}, [id]);
|
|
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
|
+
|
|
69
|
+
const loadUser = async () => {
|
|
70
|
+
setLoadingUser(true);
|
|
71
|
+
try {
|
|
72
|
+
const res = await UsersAPI.getUserRole({ id });
|
|
73
|
+
const list = Array.isArray(res?.result) ? res.result : [];
|
|
74
|
+
|
|
75
|
+
if (!list.length) {
|
|
76
|
+
setUser(null);
|
|
77
|
+
setSelectedRoles([]);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract user from first record
|
|
82
|
+
const userInfo = list[0].user;
|
|
83
|
+
setUser({
|
|
84
|
+
id: userInfo.id,
|
|
85
|
+
name: userInfo.name,
|
|
86
|
+
});
|
|
87
|
+
|
|
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)))];
|
|
90
|
+
|
|
91
|
+
setSelectedRoles(roleIds);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(e);
|
|
94
|
+
message.error('Unable to load user details');
|
|
95
|
+
} finally {
|
|
96
|
+
setLoadingUser(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load active roles on component mount
|
|
102
|
+
*/
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
getRoles();
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fetch all active roles
|
|
109
|
+
*
|
|
110
|
+
* @async
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
const getRoles = async () => {
|
|
115
|
+
setLoadingRoles(true);
|
|
116
|
+
try {
|
|
117
|
+
const res = await RolesAPI.getRole();
|
|
118
|
+
const activeRoles = Array.isArray(res?.result) ? res.result.filter((r) => r.active === 'Y') : [];
|
|
119
|
+
setRoles(activeRoles);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error(e);
|
|
122
|
+
setRoles([]);
|
|
123
|
+
} finally {
|
|
124
|
+
setLoadingRoles(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
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
|
+
|
|
136
|
+
const filterAndSortMenus = (menus, allowedIds) => {
|
|
137
|
+
return menus
|
|
138
|
+
.filter((m) => allowedIds.includes(m.id))
|
|
139
|
+
.map((m) => ({
|
|
140
|
+
...m,
|
|
141
|
+
sub_menus: filterAndSortMenus(m.sub_menus || [], allowedIds),
|
|
142
|
+
}));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Load menus for a selected role
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} role - Selected role object
|
|
149
|
+
* @async
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
const loadRoleMenus = async (role) => {
|
|
154
|
+
setActiveRole(role);
|
|
155
|
+
setSelectedMenus(role.menu_ids || []);
|
|
156
|
+
setLoadingMenus(true);
|
|
157
|
+
try {
|
|
158
|
+
const res = await MenusAPI.getCoreMenuLists();
|
|
159
|
+
const allMenus = res.result || [];
|
|
160
|
+
const filteredMenus = filterAndSortMenus(allMenus, role.menu_ids);
|
|
161
|
+
setModules(filteredMenus);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(e);
|
|
164
|
+
setModules([]);
|
|
165
|
+
} finally {
|
|
166
|
+
setLoadingMenus(false);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Toggle menu selection (currently read-only usage)
|
|
172
|
+
*
|
|
173
|
+
* @param {number} menuId
|
|
174
|
+
* @param {boolean} checked
|
|
175
|
+
*/
|
|
176
|
+
const toggleMenu = (menuId, checked) => {
|
|
177
|
+
setSelectedMenus((prev) => (checked ? [...prev, menuId] : prev.filter((id) => id !== menuId)));
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** Filtered roles */
|
|
181
|
+
const filteredRoles = useMemo(() => {
|
|
182
|
+
if (!search) return roles;
|
|
183
|
+
return roles.filter((r) => r.name.toLowerCase().includes(search.toLowerCase()));
|
|
184
|
+
}, [roles, search]);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Toggle role selection
|
|
188
|
+
*
|
|
189
|
+
* @param {number} roleId
|
|
190
|
+
* @param {boolean} checked
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
const toggleRole = (roleId, checked) => {
|
|
194
|
+
const next = checked ? [...selectedRoles, roleId] : selectedRoles.filter((v) => v !== roleId);
|
|
195
|
+
setSelectedRoles(next);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Save selected roles for the user
|
|
200
|
+
*
|
|
201
|
+
* @async
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
*/
|
|
204
|
+
const handleSaveUserRole = async () => {
|
|
205
|
+
if (!id || !selectedRoles.length) {
|
|
206
|
+
message.warning('User or roles missing');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
message.loading({ key: 'save', content: 'Saving user roles...' });
|
|
212
|
+
|
|
213
|
+
await Promise.all(
|
|
214
|
+
selectedRoles.map((roleId) =>
|
|
215
|
+
UserRolesAPI.addUserRole({
|
|
216
|
+
values: {
|
|
217
|
+
user_id: id,
|
|
218
|
+
role_id: roleId,
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
message.success({ key: 'save', content: 'User roles saved successfully' });
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error(err);
|
|
227
|
+
message.error({ key: 'save', content: 'Failed to save user roles' });
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<section className="assign-role">
|
|
233
|
+
{/* LEFT PANEL */}
|
|
234
|
+
<Card className="left-panel" bodyStyle={{ padding: 12 }}>
|
|
235
|
+
<div size="small" className="user-card">
|
|
236
|
+
<Avatar size={40}>{user?.name[0]}</Avatar>
|
|
237
|
+
|
|
238
|
+
<div className="user-info">
|
|
239
|
+
<div>{user?.name || '--'}</div>
|
|
240
|
+
<Text className="user-id">ID : {user?.id || '--'}</Text>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="role-list-header">
|
|
245
|
+
<Text strong>Role List ({roles.length})</Text>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={(e) => setSearch(e.target.value)} />
|
|
249
|
+
{/* <Input className="role-search" placeholder="Search Here" allowClear value={search} onChange={(e) => setSearch(e.target.value)} /> */}
|
|
250
|
+
|
|
251
|
+
<List
|
|
252
|
+
itemLayout="horizontal"
|
|
253
|
+
dataSource={filteredRoles}
|
|
254
|
+
renderItem={(role) => (
|
|
255
|
+
<List.Item
|
|
256
|
+
key={role.id}
|
|
257
|
+
onClick={() => loadRoleMenus(role)}
|
|
258
|
+
className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
|
|
259
|
+
actions={[
|
|
260
|
+
<Checkbox
|
|
261
|
+
checked={selectedRoles.includes(role.id)}
|
|
262
|
+
onChange={(e) => toggleRole(role.id, e.target.checked)}
|
|
263
|
+
onClick={(e) => e.stopPropagation()}
|
|
264
|
+
/>,
|
|
265
|
+
]}
|
|
266
|
+
>
|
|
267
|
+
<List.Item.Meta avatar={<Avatar size={28}>{role.name[0]}</Avatar>} title={role.name} description={role.description} />
|
|
268
|
+
</List.Item>
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
</Card>
|
|
272
|
+
|
|
273
|
+
{/* RIGHT PANEL */}
|
|
274
|
+
<Card className="right-panel">
|
|
275
|
+
<div className="menus-header">
|
|
276
|
+
<Text>Menus {activeRole ? `– ${activeRole.name}` : ''}</Text>
|
|
277
|
+
<div className="sub-text">It's not editable. To edit it, go to the role editing page.</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<div className="menus-content">
|
|
281
|
+
{loadingMenus ? (
|
|
282
|
+
<Skeleton active paragraph={{ rows: 6 }} />
|
|
283
|
+
) : modules.length === 0 ? (
|
|
284
|
+
<Empty description="Click a role to view menus" />
|
|
285
|
+
) : (
|
|
286
|
+
<MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div className="footer-actions">
|
|
291
|
+
<Button type="primary" onClick={handleSaveUserRole}>
|
|
292
|
+
Save
|
|
293
|
+
</Button>
|
|
294
|
+
</div>
|
|
295
|
+
</Card>
|
|
296
|
+
</section>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
.assign-role {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 6px;
|
|
4
|
+
|
|
5
|
+
.left-panel {
|
|
6
|
+
width: 340px;
|
|
7
|
+
|
|
8
|
+
.user-card {
|
|
9
|
+
margin-bottom: 12px;
|
|
10
|
+
background: #fafafa;
|
|
11
|
+
border: none;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 12px;
|
|
15
|
+
padding: 8px;
|
|
16
|
+
|
|
17
|
+
.user-info {
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
|
|
20
|
+
.user-id {
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
color: rgba(0, 0, 0, 0.45);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.role-list-header {
|
|
28
|
+
display: flex;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
margin-bottom: 12px;
|
|
31
|
+
strong {
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.role-search {
|
|
37
|
+
margin: 12px 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.role-item {
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
border-radius: 6px;
|
|
43
|
+
padding: 8px 12px;
|
|
44
|
+
|
|
45
|
+
&.active {
|
|
46
|
+
background: #e6f7ff;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ant-list-item-meta-title {
|
|
50
|
+
font-weight: 500;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ant-list-item-meta-description {
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
color: rgba(0, 0, 0, 0.45);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.right-panel {
|
|
61
|
+
flex: 1;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
padding: 16px;
|
|
66
|
+
|
|
67
|
+
.menus-header {
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
margin-bottom: 4px;
|
|
70
|
+
color: #000;
|
|
71
|
+
|
|
72
|
+
.sub-text {
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
color: #888;
|
|
75
|
+
margin-top: 4px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.menus-content {
|
|
80
|
+
margin-top: 16px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.footer-actions {
|
|
84
|
+
margin-top: 16px;
|
|
85
|
+
display: flex;
|
|
86
|
+
justify-content: flex-end;
|
|
87
|
+
gap: 8px;
|
|
88
|
+
}
|
|
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
|
+
}
|
|
117
|
+
}
|
|
@@ -728,7 +728,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
728
728
|
<Input placeholder="Enter User Group" />
|
|
729
729
|
</Form.Item>
|
|
730
730
|
</Col>
|
|
731
|
-
<Col span={8}>
|
|
731
|
+
{/* <Col span={8}>
|
|
732
732
|
<Form.Item name="role_id" label="Role" rules={[{ required: true, message: 'Please select a Role' }]}>
|
|
733
733
|
<Select placeholder="Select Role">
|
|
734
734
|
{roles.map((role) => (
|
|
@@ -738,7 +738,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
738
738
|
))}
|
|
739
739
|
</Select>
|
|
740
740
|
</Form.Item>
|
|
741
|
-
</Col>
|
|
741
|
+
</Col> */}
|
|
742
742
|
|
|
743
743
|
{/* <Form.Item name="mobile" label="Mobile" required={mobileRequired}>
|
|
744
744
|
<Input placeholder="Enter Mobile" />
|
|
@@ -273,8 +273,23 @@ class Users extends Base {
|
|
|
273
273
|
updateUser = ({ id, formBody }) => {
|
|
274
274
|
return ApiUtils.put({ url: `users/update-user/${id}`, formBody });
|
|
275
275
|
};
|
|
276
|
+
|
|
277
|
+
/* The `getUser` method is a function that takes an object with an `id` property as a parameter. It
|
|
278
|
+
then uses the `ApiUtils.get` function to make a GET request to the endpoint `users/` to fetch
|
|
279
|
+
user data based on the provided `id`. This method is used to retrieve a specific user's
|
|
280
|
+
information from the API. */
|
|
281
|
+
|
|
276
282
|
getUser = ({ id }) => {
|
|
277
283
|
return ApiUtils.get({ url: `users/${id}` });
|
|
284
|
+
};
|
|
285
|
+
/* The `getUserRole` method is a function that takes an object with an `id` property as a parameter.
|
|
286
|
+
It then uses the `ApiUtils.get` function to make a GET request to the endpoint `core-user-roles`
|
|
287
|
+
with the `user_id` parameter set to the provided `id`. This method is used to fetch the role or
|
|
288
|
+
roles associated with a specific user from the API. */
|
|
289
|
+
|
|
290
|
+
getUserRole = ({ id }) => {
|
|
291
|
+
return ApiUtils.get({ url: `core-user-roles?user_id=${id}` });
|
|
292
|
+
|
|
278
293
|
};
|
|
279
294
|
|
|
280
295
|
/**
|
package/core/modules/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import GenericDetail from './generic/components/generic-detail/generic-detail';
|
|
|
8
8
|
|
|
9
9
|
import ModuleRoutes from './module-routes/module-routes';
|
|
10
10
|
|
|
11
|
+
import { AssignRole } from './reporting/components';
|
|
12
|
+
|
|
11
13
|
// All Dashboard Components
|
|
12
14
|
|
|
13
15
|
import DashboardCard from './dashboard/components/dashboard-card/dashboard-card';
|
|
@@ -30,6 +32,7 @@ export {
|
|
|
30
32
|
GenericDetail,
|
|
31
33
|
ModuleRoutes,
|
|
32
34
|
DashboardCard,
|
|
35
|
+
AssignRole,
|
|
33
36
|
PopQueryDashboard,
|
|
34
37
|
HomePageAPI,
|
|
35
38
|
ReportingDashboard,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import UserEdit from '../../../models/users/components/user-add/user-edit';
|
|
2
2
|
|
|
3
3
|
import UserAdd from '../../../models/users/components/user-add/user-add';
|
|
4
|
+
import AssignRole from '../../../models/users/components/assign-role/assign-role';
|
|
4
5
|
|
|
5
|
-
export { UserEdit, UserAdd };
|
|
6
|
+
export { UserEdit, UserAdd, AssignRole };
|