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.
Files changed (66) hide show
  1. package/core/components/extra-info/extra-info-details.js +2 -2
  2. package/core/components/index.js +2 -11
  3. package/core/components/landing-api/landing-api.js +91 -15
  4. package/core/components/landing-api/landing-api.scss +22 -0
  5. package/core/components/license-management/license-alert.js +97 -0
  6. package/core/lib/Store.js +3 -3
  7. package/core/lib/components/global-header/animations.js +78 -4
  8. package/core/lib/components/global-header/global-header.js +224 -255
  9. package/core/lib/components/global-header/global-header.scss +162 -24
  10. package/core/lib/components/sidemenu/animations.js +84 -2
  11. package/core/lib/components/sidemenu/sidemenu.js +191 -65
  12. package/core/lib/components/sidemenu/sidemenu.scss +221 -14
  13. package/core/lib/elements/basic/country-phone-input/country-phone-input.js +14 -8
  14. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +1 -1
  15. package/core/lib/elements/basic/menu-tree/menu-tree.js +26 -13
  16. package/core/lib/models/forms/components/form-creator/form-creator.scss +4 -3
  17. package/core/lib/models/menus/components/menu-list/menu-list.js +424 -467
  18. package/core/lib/models/process/components/process-dashboard/process-dashboard.js +469 -3
  19. package/core/lib/models/process/components/process-dashboard/process-dashboard.scss +4 -0
  20. package/core/lib/pages/change-password/change-password.js +17 -24
  21. package/core/lib/pages/change-password/change-password.scss +45 -48
  22. package/core/lib/pages/login/commnication-mode-selection.js +2 -2
  23. package/core/lib/pages/login/login.js +47 -62
  24. package/core/lib/pages/login/login.scss +9 -0
  25. package/core/lib/pages/login/reset-password.js +17 -17
  26. package/core/lib/pages/login/reset-password.scss +10 -1
  27. package/core/lib/pages/profile/themes.json +4 -4
  28. package/core/lib/utils/api/api.utils.js +30 -18
  29. package/core/lib/utils/common/common.utils.js +49 -35
  30. package/core/lib/utils/http/http.utils.js +2 -1
  31. package/core/lib/utils/index.js +4 -1
  32. package/core/models/base/base.js +7 -3
  33. package/core/models/core-scripts/core-scripts.js +134 -126
  34. package/core/models/doctor/components/doctor-add/doctor-add.js +9 -4
  35. package/core/models/menus/components/menu-add/menu-add.js +1 -1
  36. package/core/models/menus/components/menu-lists/menu-lists.js +53 -54
  37. package/core/models/menus/menus.js +27 -2
  38. package/core/models/roles/components/role-add/role-add.js +92 -59
  39. package/core/models/roles/components/role-list/role-list.js +1 -1
  40. package/core/models/staff/components/staff-add/staff-add.js +20 -32
  41. package/core/models/users/components/assign-role/assign-role.js +145 -50
  42. package/core/models/users/components/assign-role/assign-role.scss +209 -45
  43. package/core/models/users/components/assign-role/avatar-props.js +45 -0
  44. package/core/models/users/components/user-add/user-add.js +46 -55
  45. package/core/models/users/components/user-add/user-edit.js +25 -4
  46. package/core/models/users/users.js +9 -1
  47. package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +1 -1
  48. package/core/modules/reporting/components/reporting-dashboard/README.md +316 -0
  49. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +147 -0
  50. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.scss +76 -0
  51. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +90 -0
  52. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -0
  53. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +252 -0
  54. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +126 -0
  55. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +326 -436
  56. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +7 -0
  57. package/core/modules/steps/action-buttons.js +33 -15
  58. package/core/modules/steps/action-buttons.scss +55 -9
  59. package/core/modules/steps/chat-assistant.js +141 -0
  60. package/core/modules/steps/openai-realtime.js +275 -0
  61. package/core/modules/steps/readme.md +167 -0
  62. package/core/modules/steps/steps.js +1078 -57
  63. package/core/modules/steps/steps.scss +539 -90
  64. package/core/modules/steps/timeline.js +21 -19
  65. package/core/modules/steps/voice-navigation.js +709 -0
  66. 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 [loading, setLoading] = useState(true);
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
- getPages();
53
- getModels();
54
- loadMenus();
55
- setShowMenus(true);
56
-
57
- // Preselect menus when editing
58
- if (formContent && formContent.menu_ids) {
59
- let menus = formContent.menu_ids;
60
-
61
- setSelectedMenus(menus);
62
- // keep original copy for deselect comparison
63
- setOriginalMenus(menus);
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
- setLoading(false);
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
- {loading ? (
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
- <Title level={5}>Menu List</Title>
184
- <p style={{ color: '#999' }}>Choose menus and set permissions</p>
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
- <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
- }
283
+ <div
284
+ key={menu.id}
285
+ style={{
286
+ display: 'flex',
287
+ alignItems: 'center',
288
+ gap: 8,
274
289
  }}
275
- />
276
- <span>{menu.title || menu.caption}</span>
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
- 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)} />
299
- <span>{menu.title || menu.caption}</span>
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
  >
@@ -331,7 +331,7 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
331
331
  {/* Table Header Ends */}
332
332
 
333
333
  {loading ? (
334
- <Skeleton />
334
+ <Skeleton active paragraph={{ rows: 10 }} />
335
335
  ) : (
336
336
  <>
337
337
  <>
@@ -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: values.phone1 ?? existing.phone ?? null,
232
- mobile: values.phone1 ?? existing.mobile ?? null,
233
- alternateMobile: values.phone2 ?? existing.alternateMobile ?? null,
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: phoneValidator }]}>
335
- <Input
336
- maxLength={10}
337
- autoComplete="off"
338
- placeholder="Enter Phone Number"
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: phoneValidator }]}>
348
- <Input
349
- maxLength={10}
350
- placeholder="Enter Phone Number"
351
- autoComplete="off"
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(list.filter((item) => item.role_id && item.active === 'Y').map((item) => Number(item.role_id)))];
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.getCoreMenuLists();
162
+ const res = await MenusAPI.getCoreMenuByRoleId(role_id);
163
163
  const allMenus = res.result || [];
164
- const filteredMenus = filterAndSortMenus(allMenus, role.menu_ids);
165
- setModules(filteredMenus);
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
- if (!id) {
210
- message.error('Invalid user');
211
- return;
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 className="left-panel" bodyStyle={{ padding: 12 }}>
255
- <div size="small" className="user-card">
256
- <Avatar size={40}>{user?.name?.[0] || ''}</Avatar>
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
- <Text strong>Role List ({roles.length})</Text>
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="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={(e) => setSearch(e.target.value)} />
269
- {/* <Input className="role-search" placeholder="Search Here" allowClear value={search} onChange={(e) => setSearch(e.target.value)} /> */}
270
-
271
- <List
272
- itemLayout="horizontal"
273
- dataSource={filteredRoles}
274
- renderItem={(role) => (
275
- <List.Item
276
- key={role.id}
277
- onClick={() => loadRoleMenus(role)}
278
- className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
279
- actions={[
280
- <Checkbox
281
- checked={selectedRoles.includes(role.id)}
282
- onChange={(e) => toggleRole(role.id, e.target.checked)}
283
- onClick={(e) => e.stopPropagation()}
284
- />,
285
- ]}
286
- >
287
- <List.Item.Meta avatar={<Avatar size={28}>{role?.name?.[0] || '-'}</Avatar>} title={role.name} description={role.description} />
288
- </List.Item>
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 className="right-panel">
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
- <Text>Menus {activeRole ? `– ${activeRole.name}` : ''}</Text>
297
- <div className="sub-text">It's not editable. To edit it, go to the role editing page.</div>
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
- <Empty description="Click a role to view menus" />
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
- <Button type="primary" onClick={handleSaveUserRole} loading={saving} disabled={!selectedRoles.length}>
312
- Save
313
- </Button>
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>