ui-soxo-bootstrap-core 2.6.1-dev.2 → 2.6.1-dev.20
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/components/extra-info/extra-info-details.js +2 -2
- package/core/components/index.js +2 -11
- package/core/components/landing-api/landing-api.js +91 -15
- package/core/components/landing-api/landing-api.scss +22 -0
- package/core/components/license-management/license-alert.js +97 -0
- package/core/lib/Store.js +3 -3
- package/core/lib/components/global-header/animations.js +78 -4
- package/core/lib/components/global-header/global-header.js +224 -255
- package/core/lib/components/global-header/global-header.scss +162 -24
- package/core/lib/components/sidemenu/animations.js +84 -2
- package/core/lib/components/sidemenu/sidemenu.js +191 -65
- package/core/lib/components/sidemenu/sidemenu.scss +221 -14
- package/core/lib/elements/basic/country-phone-input/country-phone-input.js +14 -8
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +1 -1
- package/core/lib/elements/basic/menu-tree/menu-tree.js +26 -13
- package/core/lib/models/forms/components/form-creator/form-creator.scss +4 -3
- package/core/lib/models/menus/components/menu-list/menu-list.js +424 -467
- package/core/lib/models/process/components/process-dashboard/process-dashboard.js +469 -3
- package/core/lib/models/process/components/process-dashboard/process-dashboard.scss +4 -0
- package/core/lib/pages/change-password/change-password.js +17 -24
- package/core/lib/pages/change-password/change-password.scss +45 -48
- package/core/lib/pages/login/commnication-mode-selection.js +2 -2
- package/core/lib/pages/login/login.js +47 -62
- package/core/lib/pages/login/login.scss +9 -0
- package/core/lib/pages/login/reset-password.js +17 -17
- package/core/lib/pages/login/reset-password.scss +10 -1
- package/core/lib/pages/profile/themes.json +4 -4
- package/core/lib/utils/api/api.utils.js +30 -18
- package/core/lib/utils/common/common.utils.js +49 -35
- package/core/lib/utils/http/http.utils.js +2 -1
- package/core/lib/utils/index.js +4 -1
- package/core/models/base/base.js +7 -3
- package/core/models/core-scripts/core-scripts.js +134 -126
- package/core/models/doctor/components/doctor-add/doctor-add.js +9 -4
- package/core/models/menus/components/menu-add/menu-add.js +1 -1
- package/core/models/menus/components/menu-lists/menu-lists.js +53 -54
- package/core/models/menus/menus.js +27 -2
- package/core/models/roles/components/role-add/role-add.js +92 -59
- package/core/models/roles/components/role-list/role-list.js +1 -1
- package/core/models/staff/components/staff-add/staff-add.js +20 -32
- package/core/models/users/components/assign-role/assign-role.js +145 -50
- package/core/models/users/components/assign-role/assign-role.scss +209 -45
- package/core/models/users/components/assign-role/avatar-props.js +45 -0
- package/core/models/users/components/user-add/user-add.js +46 -55
- package/core/models/users/components/user-add/user-edit.js +25 -4
- package/core/models/users/users.js +9 -1
- package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +1 -1
- package/core/modules/reporting/components/reporting-dashboard/README.md +316 -0
- package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +147 -0
- package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.scss +76 -0
- package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +90 -0
- package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -0
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +252 -0
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +126 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +326 -436
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +7 -0
- package/core/modules/steps/action-buttons.js +33 -15
- package/core/modules/steps/action-buttons.scss +55 -9
- package/core/modules/steps/chat-assistant.js +141 -0
- package/core/modules/steps/openai-realtime.js +275 -0
- package/core/modules/steps/readme.md +167 -0
- package/core/modules/steps/steps.js +1078 -57
- package/core/modules/steps/steps.scss +539 -90
- package/core/modules/steps/timeline.js +21 -19
- package/core/modules/steps/voice-navigation.js +709 -0
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
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, Collapse, Checkbox } from 'antd';
|
|
3
|
+
import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox, Tag } from 'antd';
|
|
4
4
|
import { GlobalContext } from './../../../../lib';
|
|
5
5
|
import { Button } from './../../../../lib';
|
|
6
6
|
import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
|
|
@@ -29,7 +29,9 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
29
29
|
formContent.attributes = {};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const isEdit = !!(formContent.id || formContent.copy);
|
|
33
|
+
const [initialLoading, setInitialLoading] = useState(isEdit);
|
|
34
|
+
const [loading, setLoading] = useState(false);
|
|
33
35
|
const [form] = Form.useForm();
|
|
34
36
|
const [pages, setPages] = useState([]);
|
|
35
37
|
const [models, setModels] = useState([]);
|
|
@@ -49,36 +51,43 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
49
51
|
|
|
50
52
|
// On component mount
|
|
51
53
|
useEffect(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
const fetchData = async () => {
|
|
55
|
+
if (isEdit) setInitialLoading(true);
|
|
56
|
+
try {
|
|
57
|
+
await Promise.all([getPages(), getModels(), loadMenus()]);
|
|
58
|
+
setShowMenus(true);
|
|
59
|
+
|
|
60
|
+
// Preselect menus when editing
|
|
61
|
+
if (formContent && formContent.menu_ids) {
|
|
62
|
+
let menus = formContent.menu_ids;
|
|
63
|
+
|
|
64
|
+
setSelectedMenus(menus);
|
|
65
|
+
// keep original copy for deselect comparison
|
|
66
|
+
setOriginalMenus(menus);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error fetching data:', error);
|
|
70
|
+
} finally {
|
|
71
|
+
setInitialLoading(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
}, [formContent]);
|
|
75
|
+
fetchData();
|
|
76
|
+
}, [formContent, isEdit]);
|
|
68
77
|
|
|
69
78
|
// Load pages
|
|
70
79
|
const getPages = () => {
|
|
71
|
-
PagesAPI.getPages().then((res) => setPages(res.result || []));
|
|
80
|
+
return PagesAPI.getPages().then((res) => setPages(res.result || []));
|
|
72
81
|
};
|
|
73
82
|
|
|
74
83
|
// Load models
|
|
75
84
|
const getModels = () => {
|
|
76
|
-
ModelsAPI.get().then((res) => setModels(res.result || []));
|
|
85
|
+
return ModelsAPI.get().then((res) => setModels(res.result || []));
|
|
77
86
|
};
|
|
78
87
|
|
|
79
88
|
// Load top-level menus
|
|
80
89
|
const loadMenus = () => {
|
|
81
|
-
MenusAPI.getCoreMenuLists()
|
|
90
|
+
return MenusAPI.getCoreMenuLists()
|
|
82
91
|
.then((res) => setMenuList(res.result || []))
|
|
83
92
|
.catch(console.error);
|
|
84
93
|
};
|
|
@@ -156,13 +165,13 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
156
165
|
|
|
157
166
|
return (
|
|
158
167
|
<section className="collection-add">
|
|
159
|
-
{
|
|
160
|
-
<Skeleton />
|
|
168
|
+
{initialLoading ? (
|
|
169
|
+
<Skeleton active paragraph={{ rows: 12 }} />
|
|
161
170
|
) : (
|
|
162
171
|
<Form initialValues={{ ...formContent }} form={form} layout="vertical" onFinish={onSubmit}>
|
|
163
172
|
{/* Role Name */}
|
|
164
173
|
<Form.Item name="name" label="Enter Role Name" rules={[{ required: true, message: 'Role name is required' }]}>
|
|
165
|
-
<Input placeholder="Enter name" />
|
|
174
|
+
<Input placeholder="Enter name" autoocus />
|
|
166
175
|
</Form.Item>
|
|
167
176
|
|
|
168
177
|
{/* Description */}
|
|
@@ -180,19 +189,31 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
180
189
|
{/* MENU TREE */}
|
|
181
190
|
{showMenus && menuList.length > 0 && (
|
|
182
191
|
<div style={{ marginTop: 30 }}>
|
|
183
|
-
<
|
|
184
|
-
|
|
192
|
+
<div
|
|
193
|
+
style={{
|
|
194
|
+
display: 'flex',
|
|
195
|
+
justifyContent: 'space-between',
|
|
196
|
+
alignItems: 'center',
|
|
197
|
+
marginBottom: 16,
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<div>
|
|
201
|
+
<Title level={5} style={{ marginBottom: 0 }}>
|
|
202
|
+
Menu List
|
|
203
|
+
</Title>
|
|
204
|
+
<p style={{ color: '#999', marginBottom: 0 }}>Choose menus and set permissions</p>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<Form.Item style={{ marginBottom: 0 }}>
|
|
208
|
+
<Button loading={loading} htmlType="submit" type="primary">
|
|
209
|
+
Save
|
|
210
|
+
</Button>
|
|
211
|
+
</Form.Item>
|
|
212
|
+
</div>
|
|
185
213
|
|
|
186
214
|
<MenuTree menus={menuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
187
215
|
</div>
|
|
188
216
|
)}
|
|
189
|
-
|
|
190
|
-
{/* Submit Button */}
|
|
191
|
-
<Form.Item style={{ marginTop: 20 }}>
|
|
192
|
-
<Button loading={loading} htmlType="submit" type="primary">
|
|
193
|
-
Save
|
|
194
|
-
</Button>
|
|
195
|
-
</Form.Item>
|
|
196
217
|
</Form>
|
|
197
218
|
)}
|
|
198
219
|
</section>
|
|
@@ -248,32 +269,41 @@ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
|
248
269
|
if (children.length === 0) {
|
|
249
270
|
return (
|
|
250
271
|
<div
|
|
251
|
-
key={menu.id}
|
|
252
272
|
style={{
|
|
273
|
+
justifyContent: 'space-between',
|
|
274
|
+
display: 'flex',
|
|
275
|
+
alignItems: 'center',
|
|
253
276
|
border: '1px solid rgba(198, 195, 195, 0.85)',
|
|
254
277
|
// borderRadius: 6,
|
|
255
278
|
padding: '12px 16px',
|
|
256
279
|
marginBottom: 6,
|
|
257
280
|
background: '#fff',
|
|
258
|
-
display: 'flex',
|
|
259
|
-
alignItems: 'center',
|
|
260
|
-
gap: 8,
|
|
261
281
|
}}
|
|
262
282
|
>
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// ✅ FORCE parent selection
|
|
271
|
-
if (checked && parentId) {
|
|
272
|
-
toggleMenu(parentId, true);
|
|
273
|
-
}
|
|
283
|
+
<div
|
|
284
|
+
key={menu.id}
|
|
285
|
+
style={{
|
|
286
|
+
display: 'flex',
|
|
287
|
+
alignItems: 'center',
|
|
288
|
+
gap: 8,
|
|
274
289
|
}}
|
|
275
|
-
|
|
276
|
-
|
|
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>{' '}
|
|
277
307
|
</div>
|
|
278
308
|
);
|
|
279
309
|
}
|
|
@@ -287,16 +317,19 @@ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
|
287
317
|
<Panel
|
|
288
318
|
key={menu.id}
|
|
289
319
|
header={
|
|
290
|
-
<div
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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>{' '}
|
|
300
333
|
</div>
|
|
301
334
|
}
|
|
302
335
|
>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Modal, Tabs, Input, Form, Row, Col, message, Checkbox, Select } from 'antd';
|
|
4
|
-
import { useTranslation, Button } from './../../../../lib/';
|
|
4
|
+
import { useTranslation, Button, CountryPhoneInput, phoneValidator as libPhoneValidator, formatPhoneForForm } from './../../../../lib/';
|
|
5
5
|
import { UsersAPI } from '../../..';
|
|
6
|
+
import { formatPhoneWithCountryCode } from '../../../../lib/utils/common/common.utils';
|
|
6
7
|
|
|
7
8
|
const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
8
9
|
const [form] = Form.useForm();
|
|
@@ -61,18 +62,6 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
61
62
|
}
|
|
62
63
|
}, [visible]);
|
|
63
64
|
|
|
64
|
-
const phoneValidator = (_, value) => {
|
|
65
|
-
if (!value) {
|
|
66
|
-
return Promise.resolve(); // not required
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!/^\d{10}$/.test(value)) {
|
|
70
|
-
return Promise.reject('Phone number must be 10 digits');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return Promise.resolve();
|
|
74
|
-
};
|
|
75
|
-
|
|
76
65
|
/** -------------------------------
|
|
77
66
|
* API CALL – Designations
|
|
78
67
|
* ------------------------------- */
|
|
@@ -201,8 +190,8 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
201
190
|
shortName: data.shortName,
|
|
202
191
|
description: data.description,
|
|
203
192
|
designation: data.designationPtr,
|
|
204
|
-
phone1: data.phone || data.mobile,
|
|
205
|
-
phone2: data.alternateMobile,
|
|
193
|
+
phone1: formatPhoneForForm(data.phone || data.mobile),
|
|
194
|
+
phone2: formatPhoneForForm(data.alternateMobile),
|
|
206
195
|
email1: data.email,
|
|
207
196
|
email2: data.alternateEmail,
|
|
208
197
|
slno: data.slNo,
|
|
@@ -223,14 +212,17 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
223
212
|
const handleFinish = async (values) => {
|
|
224
213
|
setLoading(true);
|
|
225
214
|
|
|
215
|
+
let phone1Value = formatPhoneWithCountryCode(values.phone1);
|
|
216
|
+
let phone2Value = formatPhoneWithCountryCode(values.phone2);
|
|
217
|
+
|
|
226
218
|
const payload = {
|
|
227
219
|
id: values.id,
|
|
228
220
|
shortName: values.shortName,
|
|
229
221
|
description: values.description,
|
|
230
222
|
designationPtr: values.designation ?? existing.designationPtr ?? null,
|
|
231
|
-
phone:
|
|
232
|
-
mobile:
|
|
233
|
-
alternateMobile:
|
|
223
|
+
phone: phone1Value ?? existing.phone ?? null,
|
|
224
|
+
mobile: phone1Value ?? existing.mobile ?? null,
|
|
225
|
+
alternateMobile: phone2Value ?? existing.alternateMobile ?? null,
|
|
234
226
|
email: values.email1 ?? existing.email ?? null,
|
|
235
227
|
alternateEmail: values.email2 ?? existing.alternateEmail ?? null,
|
|
236
228
|
remarks: values.remarks ?? existing.remarks ?? null,
|
|
@@ -331,26 +323,22 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
331
323
|
</Form.Item>
|
|
332
324
|
</Col> */}
|
|
333
325
|
<Col span={6}>
|
|
334
|
-
<Form.Item label="Phone Number" name="phone1" rules={[{ validator:
|
|
335
|
-
<
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
inputMode="numeric"
|
|
340
|
-
onChange={(e) => (e.target.value = e.target.value.replace(/\D/g, ''))}
|
|
326
|
+
<Form.Item label="Phone Number" name="phone1" validateTrigger="onBlur" rules={[{ validator: libPhoneValidator }]}>
|
|
327
|
+
<CountryPhoneInput
|
|
328
|
+
defaultCountryCode={process.env.REACT_APP_COUNTRYCODE}
|
|
329
|
+
enableSearch
|
|
330
|
+
inputStyle={{ width: '100%' }}
|
|
341
331
|
onKeyDown={handleEnterKey}
|
|
342
332
|
/>
|
|
343
333
|
</Form.Item>
|
|
344
334
|
</Col>
|
|
345
335
|
|
|
346
336
|
<Col span={6}>
|
|
347
|
-
<Form.Item label="Alternate Phone Number" name="phone2" rules={[{ validator:
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
inputMode="numeric"
|
|
353
|
-
onChange={(e) => (e.target.value = e.target.value.replace(/\D/g, ''))}
|
|
337
|
+
<Form.Item label="Alternate Phone Number" name="phone2" validateTrigger="onBlur" rules={[{ validator: libPhoneValidator }]}>
|
|
338
|
+
<CountryPhoneInput
|
|
339
|
+
defaultCountryCode={process.env.REACT_APP_COUNTRYCODE}
|
|
340
|
+
enableSearch
|
|
341
|
+
inputStyle={{ width: '100%' }}
|
|
354
342
|
onKeyDown={handleEnterKey}
|
|
355
343
|
/>
|
|
356
344
|
</Form.Item>
|
|
@@ -21,6 +21,7 @@ import { UserRolesAPI } from '../../..';
|
|
|
21
21
|
import { useParams } from 'react-router-dom';
|
|
22
22
|
import { Button } from '../../../../lib';
|
|
23
23
|
import { MenuTree } from '../../../../lib/elements/basic/menu-tree/menu-tree';
|
|
24
|
+
import { getAvatarProps } from './avatar-props';
|
|
24
25
|
import './assign-role.scss';
|
|
25
26
|
|
|
26
27
|
const { Text } = Typography;
|
|
@@ -42,7 +43,6 @@ export default function AssignRole() {
|
|
|
42
43
|
const [modules, setModules] = useState([]);
|
|
43
44
|
// loading
|
|
44
45
|
const [selectedRoles, setSelectedRoles] = useState([]);
|
|
45
|
-
const [loadingUser, setLoadingUser] = useState(false);
|
|
46
46
|
const [loadingRoles, setLoadingRoles] = useState(false);
|
|
47
47
|
const [loadingMenus, setLoadingMenus] = useState(false);
|
|
48
48
|
// for save
|
|
@@ -54,6 +54,8 @@ export default function AssignRole() {
|
|
|
54
54
|
// for initial roles
|
|
55
55
|
const [initialRoles, setInitialRoles] = useState([]);
|
|
56
56
|
|
|
57
|
+
const userAvatar = getAvatarProps(user?.name);
|
|
58
|
+
|
|
57
59
|
/**
|
|
58
60
|
* Load user details and assigned roles when user ID changes
|
|
59
61
|
*/
|
|
@@ -72,8 +74,6 @@ export default function AssignRole() {
|
|
|
72
74
|
*/
|
|
73
75
|
|
|
74
76
|
const loadUser = async () => {
|
|
75
|
-
setLoadingUser(true);
|
|
76
|
-
|
|
77
77
|
try {
|
|
78
78
|
// Call both APIs in parallel
|
|
79
79
|
const [userRes, roleRes] = await Promise.all([UsersAPI.getUser({ id }), UsersAPI.getUserRole({ id })]);
|
|
@@ -93,15 +93,13 @@ export default function AssignRole() {
|
|
|
93
93
|
const roleList = Array.isArray(roleRes?.result) ? roleRes.result : [];
|
|
94
94
|
|
|
95
95
|
// Extract VALID role IDs (ignore nulls & duplicates)
|
|
96
|
-
const roleIds = [...new Set(
|
|
96
|
+
const roleIds = [...new Set(roleList.filter((item) => item.role_id && item.active === 'Y').map((item) => Number(item.role_id)))];
|
|
97
97
|
|
|
98
98
|
setSelectedRoles(roleIds);
|
|
99
|
-
setInitialRoles(roleIds);
|
|
99
|
+
setInitialRoles(roleIds);
|
|
100
100
|
} catch (e) {
|
|
101
101
|
console.error(e);
|
|
102
102
|
message.error('Unable to load user details');
|
|
103
|
-
} finally {
|
|
104
|
-
setLoadingUser(false);
|
|
105
103
|
}
|
|
106
104
|
};
|
|
107
105
|
|
|
@@ -158,11 +156,13 @@ export default function AssignRole() {
|
|
|
158
156
|
setActiveRole(role);
|
|
159
157
|
setSelectedMenus(role.menu_ids || []);
|
|
160
158
|
setLoadingMenus(true);
|
|
159
|
+
|
|
160
|
+
const role_id = role.id;
|
|
161
161
|
try {
|
|
162
|
-
const res = await MenusAPI.
|
|
162
|
+
const res = await MenusAPI.getCoreMenuByRoleId(role_id);
|
|
163
163
|
const allMenus = res.result || [];
|
|
164
|
-
|
|
165
|
-
setModules(
|
|
164
|
+
|
|
165
|
+
setModules(allMenus);
|
|
166
166
|
} catch (e) {
|
|
167
167
|
console.error(e);
|
|
168
168
|
setModules([]);
|
|
@@ -206,10 +206,10 @@ export default function AssignRole() {
|
|
|
206
206
|
* @returns {Promise<void>}
|
|
207
207
|
*/
|
|
208
208
|
const handleSaveUserRole = async () => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
if (!id) {
|
|
210
|
+
message.error('Invalid user');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
213
|
// start button spinner
|
|
214
214
|
setSaving(true);
|
|
215
215
|
|
|
@@ -236,9 +236,8 @@ export default function AssignRole() {
|
|
|
236
236
|
// Only show success AFTER all saves
|
|
237
237
|
|
|
238
238
|
message.success('User roles updated successfully');
|
|
239
|
-
|
|
240
|
-
await loadUser();
|
|
241
239
|
|
|
240
|
+
await loadUser();
|
|
242
241
|
} catch (err) {
|
|
243
242
|
console.error(err);
|
|
244
243
|
message.error('Failed to save user roles');
|
|
@@ -248,69 +247,165 @@ export default function AssignRole() {
|
|
|
248
247
|
}
|
|
249
248
|
};
|
|
250
249
|
|
|
250
|
+
// Role Change
|
|
251
|
+
const rolesChanged = useMemo(() => {
|
|
252
|
+
// Length mismatch means roles were added or removed
|
|
253
|
+
if (initialRoles.length !== selectedRoles.length) return true;
|
|
254
|
+
|
|
255
|
+
// Sort both arrays before comparison (order should not matter)
|
|
256
|
+
const sortedInitial = [...initialRoles].sort();
|
|
257
|
+
const sortedSelected = [...selectedRoles].sort();
|
|
258
|
+
|
|
259
|
+
// Check for any value difference
|
|
260
|
+
return sortedInitial.some((value, index) => value !== sortedSelected[index]);
|
|
261
|
+
}, [initialRoles, selectedRoles]);
|
|
262
|
+
|
|
263
|
+
// View All Role
|
|
264
|
+
const handleViewAll = async () => {
|
|
265
|
+
setLoadingMenus(true);
|
|
266
|
+
setModules([]);
|
|
267
|
+
setActiveRole({ name: 'All Roles' });
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const res = await MenusAPI.getMenubyUser(id);
|
|
271
|
+
setModules(res?.result?.menus ?? []);
|
|
272
|
+
setLoadingMenus(false);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
setLoadingMenus(false);
|
|
275
|
+
console.error(err);
|
|
276
|
+
setModules([]);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
251
280
|
return (
|
|
252
281
|
<section className="assign-role">
|
|
253
282
|
{/* LEFT PANEL */}
|
|
254
|
-
<Card
|
|
255
|
-
|
|
256
|
-
|
|
283
|
+
<Card
|
|
284
|
+
className="left-panel"
|
|
285
|
+
bodyStyle={{
|
|
286
|
+
padding: 16,
|
|
287
|
+
height: '100%',
|
|
288
|
+
display: 'flex',
|
|
289
|
+
flexDirection: 'column',
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<div className="user-card">
|
|
293
|
+
<Avatar size={44} style={userAvatar.style}>
|
|
294
|
+
{userAvatar.letter}
|
|
295
|
+
</Avatar>
|
|
257
296
|
|
|
258
297
|
<div className="user-info">
|
|
259
|
-
<div>{user?.name || '--'}</div>
|
|
298
|
+
<div className="name">{user?.name || '--'}</div>
|
|
260
299
|
<Text className="user-id">ID : {user?.id || '--'}</Text>
|
|
261
300
|
</div>
|
|
262
301
|
</div>
|
|
263
302
|
|
|
264
303
|
<div className="role-list-header">
|
|
265
|
-
<
|
|
304
|
+
<div>
|
|
305
|
+
<Text strong>Role List</Text>
|
|
306
|
+
<div className="count">{roles.length} roles</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<Button size="small" onClick={handleViewAll}>
|
|
310
|
+
View Access
|
|
311
|
+
</Button>
|
|
266
312
|
</div>
|
|
267
313
|
|
|
268
|
-
<Search placeholder="
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
</
|
|
314
|
+
<Search placeholder="Search roles..." allowClear onChange={(e) => setSearch(e.target.value)} className="role-search" />
|
|
315
|
+
|
|
316
|
+
<div className="selected-summary">{selectedRoles.length} selected</div>
|
|
317
|
+
|
|
318
|
+
{/* SCROLL AREA */}
|
|
319
|
+
<div className="role-list-wrapper">
|
|
320
|
+
{loadingRoles ? (
|
|
321
|
+
<div className="role-skeleton">
|
|
322
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
323
|
+
<div key={i} className="role-skeleton-item">
|
|
324
|
+
<Skeleton.Avatar active size={32} shape="circle" />
|
|
325
|
+
|
|
326
|
+
<div className="meta">
|
|
327
|
+
<Skeleton.Input active size="small" style={{ width: 140 }} />
|
|
328
|
+
<Skeleton.Input active size="small" style={{ width: 200 }} />
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<Skeleton.Button active size="small" shape="square" />
|
|
332
|
+
</div>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
) : (
|
|
336
|
+
<List
|
|
337
|
+
itemLayout="horizontal"
|
|
338
|
+
// dataSource={filteredRoles}
|
|
339
|
+
dataSource={[...filteredRoles].sort((a, b) => {
|
|
340
|
+
const aSelected = selectedRoles.includes(a.id);
|
|
341
|
+
const bSelected = selectedRoles.includes(b.id);
|
|
342
|
+
|
|
343
|
+
if (aSelected === bSelected) return 0;
|
|
344
|
+
return aSelected ? -1 : 1;
|
|
345
|
+
})}
|
|
346
|
+
className="role-list"
|
|
347
|
+
renderItem={(role) => {
|
|
348
|
+
const roleAvatar = getAvatarProps(role?.name);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<List.Item
|
|
352
|
+
key={role.id}
|
|
353
|
+
onClick={() => loadRoleMenus(role)}
|
|
354
|
+
className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
|
|
355
|
+
actions={[
|
|
356
|
+
<Checkbox
|
|
357
|
+
checked={selectedRoles.includes(role.id)}
|
|
358
|
+
onChange={(e) => toggleRole(role.id, e.target.checked)}
|
|
359
|
+
onClick={(e) => e.stopPropagation()}
|
|
360
|
+
/>,
|
|
361
|
+
]}
|
|
362
|
+
>
|
|
363
|
+
<List.Item.Meta
|
|
364
|
+
avatar={<Avatar style={roleAvatar.style}>{roleAvatar?.letter}</Avatar>}
|
|
365
|
+
title={role.name}
|
|
366
|
+
description={role.description}
|
|
367
|
+
/>
|
|
368
|
+
</List.Item>
|
|
369
|
+
);
|
|
370
|
+
}}
|
|
371
|
+
/>
|
|
289
372
|
)}
|
|
290
|
-
|
|
373
|
+
</div>
|
|
291
374
|
</Card>
|
|
292
375
|
|
|
293
376
|
{/* RIGHT PANEL */}
|
|
294
|
-
<Card
|
|
377
|
+
<Card
|
|
378
|
+
className="right-panel"
|
|
379
|
+
bodyStyle={{
|
|
380
|
+
height: '100%',
|
|
381
|
+
display: 'flex',
|
|
382
|
+
flexDirection: 'column',
|
|
383
|
+
padding: 16,
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
295
386
|
<div className="menus-header">
|
|
296
|
-
<
|
|
297
|
-
<div className="sub-text">
|
|
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>
|
|
298
389
|
</div>
|
|
299
390
|
|
|
300
391
|
<div className="menus-content">
|
|
301
392
|
{loadingMenus ? (
|
|
302
393
|
<Skeleton active paragraph={{ rows: 6 }} />
|
|
303
394
|
) : modules.length === 0 ? (
|
|
304
|
-
<
|
|
395
|
+
<div className="empty-state">
|
|
396
|
+
<Empty description="Select a role to view menus" />
|
|
397
|
+
</div>
|
|
305
398
|
) : (
|
|
306
399
|
<MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />
|
|
307
400
|
)}
|
|
308
401
|
</div>
|
|
309
402
|
|
|
310
403
|
<div className="footer-actions">
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
404
|
+
{rolesChanged && (
|
|
405
|
+
<Button type="primary" onClick={handleSaveUserRole} loading={saving}>
|
|
406
|
+
Save Changes
|
|
407
|
+
</Button>
|
|
408
|
+
)}
|
|
314
409
|
</div>
|
|
315
410
|
</Card>
|
|
316
411
|
</section>
|