ui-soxo-bootstrap-core 2.6.1-dev.6 → 2.6.1-dev.8
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/components/sidemenu/sidemenu.js +19 -13
- package/core/lib/utils/api/api.utils.js +27 -15
- package/core/models/menus/components/menu-lists/menu-lists.js +2 -2
- package/core/models/menus/menus.js +15 -1
- package/core/models/users/components/assign-role/assign-role.js +114 -40
- package/core/models/users/components/assign-role/assign-role.scss +182 -44
- package/core/models/users/components/assign-role/avatar-props.js +45 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +3 -5
- package/package.json +1 -1
|
@@ -39,18 +39,18 @@ function CollapsedIconMenu({ menu, collapsed, icon, caption }) {
|
|
|
39
39
|
// Import t and i18n from useTranslation
|
|
40
40
|
const { t, i18n } = useTranslation();
|
|
41
41
|
const { state } = useContext(GlobalContext);
|
|
42
|
-
|
|
42
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const handleResize = () => {
|
|
46
|
+
setIsMobile(window.innerWidth < 768); // Common breakpoint for mobile
|
|
47
|
+
};
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
handleResize(); // Set initial value
|
|
50
|
+
window.addEventListener('resize', handleResize);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
53
|
+
}, []);
|
|
54
54
|
|
|
55
55
|
const menuText = t(caption);
|
|
56
56
|
const menuContent = (
|
|
@@ -85,8 +85,14 @@ function CollapsedIconMenu({ menu, collapsed, icon, caption }) {
|
|
|
85
85
|
</>
|
|
86
86
|
);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// On mobile, or when the menu is collapsed (based on original logic), don't show the popover tooltip.
|
|
89
|
+
return isMobile || collapsed ? (
|
|
90
|
+
menuContent
|
|
91
|
+
) : (
|
|
92
|
+
<Popover content={menuText} placement="right">
|
|
93
|
+
{menuContent}
|
|
94
|
+
</Popover>
|
|
95
|
+
);
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
export default function SideMenu({ loading, modules = [], callback, appSettings, collapsed }) {
|
|
@@ -123,7 +129,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
|
|
|
123
129
|
* Logout Function
|
|
124
130
|
*/
|
|
125
131
|
async function handleLogout() {
|
|
126
|
-
let dbPtrValue = appSettings.headers.db_ptr;
|
|
132
|
+
// let dbPtrValue = appSettings.headers.db_ptr;
|
|
127
133
|
const isEnvThemeTrue = process.env.REACT_APP_THEME === 'true';
|
|
128
134
|
|
|
129
135
|
// Only clear localStorage if theme is not 'true'
|
|
@@ -137,7 +143,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
|
|
|
137
143
|
// const result = Users.logout()
|
|
138
144
|
localStorage.clear();
|
|
139
145
|
|
|
140
|
-
localStorage.setItem('db_ptr', dbPtrValue);
|
|
146
|
+
// localStorage.setItem('db_ptr', dbPtrValue);
|
|
141
147
|
|
|
142
148
|
let userInfo = {
|
|
143
149
|
dms: {},
|
|
@@ -24,15 +24,15 @@ export default class ApiUtils {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
static get = async ({ url, config = { queries: [], order: {} }, headers: customHeaders = {}, ...props }) => {
|
|
27
|
-
const
|
|
27
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
30
|
return await GetData({
|
|
31
31
|
url: createUrlParams(url, config),
|
|
32
32
|
settings,
|
|
33
33
|
headers: {
|
|
34
|
-
...
|
|
35
|
-
|
|
34
|
+
...(settings.headers || {}),
|
|
35
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
36
36
|
...customHeaders,
|
|
37
37
|
},
|
|
38
38
|
...props,
|
|
@@ -43,15 +43,15 @@ export default class ApiUtils {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
static getRecordDetail = async ({ url, config = { queries: [] }, headers: customHeaders = {}, ...props }) => {
|
|
46
|
-
const
|
|
46
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
49
|
return await GetData({
|
|
50
50
|
url: createUrlParams(url, config),
|
|
51
51
|
settings,
|
|
52
52
|
headers: {
|
|
53
|
-
...
|
|
54
|
-
|
|
53
|
+
...(settings.headers || {}),
|
|
54
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
55
55
|
...customHeaders,
|
|
56
56
|
},
|
|
57
57
|
...props,
|
|
@@ -62,15 +62,15 @@ export default class ApiUtils {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
static post = ({ url, formBody, headers: customHeaders = {}, ...props }) => {
|
|
65
|
-
const
|
|
65
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
66
66
|
|
|
67
67
|
return PostData({
|
|
68
68
|
url,
|
|
69
69
|
formBody,
|
|
70
70
|
settings,
|
|
71
71
|
headers: {
|
|
72
|
-
...
|
|
73
|
-
|
|
72
|
+
...(settings.headers || {}),
|
|
73
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
74
74
|
...customHeaders,
|
|
75
75
|
},
|
|
76
76
|
...props,
|
|
@@ -78,36 +78,48 @@ export default class ApiUtils {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
static put = ({ url, formBody, ...props }) => {
|
|
81
|
-
const
|
|
81
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
82
82
|
|
|
83
83
|
return PutData({
|
|
84
84
|
url,
|
|
85
85
|
settings,
|
|
86
86
|
formBody,
|
|
87
|
-
headers
|
|
87
|
+
headers: {
|
|
88
|
+
...(settings.headers || {}),
|
|
89
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
90
|
+
...customHeaders,
|
|
91
|
+
},
|
|
88
92
|
...props,
|
|
89
93
|
});
|
|
90
94
|
};
|
|
91
95
|
|
|
92
96
|
static patch = ({ url, formBody, ...props }) => {
|
|
93
|
-
const
|
|
97
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
94
98
|
|
|
95
99
|
return PatchData({
|
|
96
100
|
url,
|
|
97
101
|
settings,
|
|
98
102
|
formBody,
|
|
99
|
-
headers
|
|
103
|
+
headers: {
|
|
104
|
+
...(settings.headers || {}),
|
|
105
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
106
|
+
...customHeaders,
|
|
107
|
+
},
|
|
100
108
|
...props,
|
|
101
109
|
});
|
|
102
110
|
};
|
|
103
111
|
|
|
104
112
|
static delete = ({ url, formBody, ...props }) => {
|
|
105
|
-
const
|
|
113
|
+
const dbPtr = localStorage.getItem('db_ptr');
|
|
106
114
|
|
|
107
115
|
return DeleteData({
|
|
108
116
|
url,
|
|
109
117
|
settings,
|
|
110
|
-
headers
|
|
118
|
+
headers: {
|
|
119
|
+
...(settings.headers || {}),
|
|
120
|
+
...(dbPtr ? { db_ptr: dbPtr } : {}),
|
|
121
|
+
...customHeaders,
|
|
122
|
+
},
|
|
111
123
|
...props,
|
|
112
124
|
});
|
|
113
125
|
};
|
|
@@ -474,7 +474,7 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
|
|
|
474
474
|
</Button>
|
|
475
475
|
)}
|
|
476
476
|
|
|
477
|
-
|
|
477
|
+
<Button
|
|
478
478
|
size="small"
|
|
479
479
|
type="default"
|
|
480
480
|
onClick={() => {
|
|
@@ -484,7 +484,7 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
|
|
|
484
484
|
}}
|
|
485
485
|
>
|
|
486
486
|
<CopyOutlined />
|
|
487
|
-
</Button>
|
|
487
|
+
</Button>
|
|
488
488
|
|
|
489
489
|
<Popconfirm title="Are you sure?" onConfirm={() => deleteRecord(item)}>
|
|
490
490
|
<Button danger size="small" type="default">
|
|
@@ -195,15 +195,29 @@ class MenusAPI extends Base {
|
|
|
195
195
|
}).then((result) => result);
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
+
getMenubyUser = (user_id) => {
|
|
199
|
+
const url = `menus/get-menus?userId=${user_id}`;
|
|
200
|
+
return this.get({
|
|
201
|
+
url,
|
|
202
|
+
}).then((result) => result);
|
|
203
|
+
};
|
|
204
|
+
|
|
198
205
|
// get core-menu list with submenu
|
|
199
206
|
getCoreMenuLists = () => {
|
|
200
207
|
const url = 'core-menus/core-menus?step=1&header_id=null';
|
|
201
|
-
|
|
202
208
|
return this.get({
|
|
203
209
|
url,
|
|
204
210
|
}).then((result) => result);
|
|
205
211
|
};
|
|
206
212
|
|
|
213
|
+
getCoreMenuByRoleId = async (role_id) => {
|
|
214
|
+
const url = `menus/get-menus-by-role/${role_id}`;
|
|
215
|
+
const result = await this.get({
|
|
216
|
+
url,
|
|
217
|
+
});
|
|
218
|
+
return result;
|
|
219
|
+
};
|
|
220
|
+
|
|
207
221
|
getCoreMenus = () => {
|
|
208
222
|
return [
|
|
209
223
|
// {
|
|
@@ -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 })]);
|
|
@@ -100,8 +100,6 @@ export default function AssignRole() {
|
|
|
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([]);
|
|
@@ -260,60 +260,134 @@ export default function AssignRole() {
|
|
|
260
260
|
return sortedInitial.some((value, index) => value !== sortedSelected[index]);
|
|
261
261
|
}, [initialRoles, selectedRoles]);
|
|
262
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
|
+
|
|
263
280
|
return (
|
|
264
281
|
<section className="assign-role">
|
|
265
282
|
{/* LEFT PANEL */}
|
|
266
|
-
<Card
|
|
267
|
-
|
|
268
|
-
|
|
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>
|
|
269
296
|
|
|
270
297
|
<div className="user-info">
|
|
271
|
-
<div>{user?.name || '--'}</div>
|
|
298
|
+
<div className="name">{user?.name || '--'}</div>
|
|
272
299
|
<Text className="user-id">ID : {user?.id || '--'}</Text>
|
|
273
300
|
</div>
|
|
274
301
|
</div>
|
|
275
302
|
|
|
276
303
|
<div className="role-list-header">
|
|
277
|
-
<
|
|
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>
|
|
278
312
|
</div>
|
|
279
313
|
|
|
280
|
-
<Search placeholder="
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
</
|
|
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
|
+
className="role-list"
|
|
340
|
+
renderItem={(role) => {
|
|
341
|
+
const roleAvatar = getAvatarProps(role?.name);
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<List.Item
|
|
345
|
+
key={role.id}
|
|
346
|
+
onClick={() => loadRoleMenus(role)}
|
|
347
|
+
className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
|
|
348
|
+
actions={[
|
|
349
|
+
<Checkbox
|
|
350
|
+
checked={selectedRoles.includes(role.id)}
|
|
351
|
+
onChange={(e) => toggleRole(role.id, e.target.checked)}
|
|
352
|
+
onClick={(e) => e.stopPropagation()}
|
|
353
|
+
/>,
|
|
354
|
+
]}
|
|
355
|
+
>
|
|
356
|
+
<List.Item.Meta
|
|
357
|
+
avatar={<Avatar style={roleAvatar.style}>{roleAvatar?.letter}</Avatar>}
|
|
358
|
+
title={role.name}
|
|
359
|
+
description={role.description}
|
|
360
|
+
/>
|
|
361
|
+
</List.Item>
|
|
362
|
+
);
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
301
365
|
)}
|
|
302
|
-
|
|
366
|
+
</div>
|
|
303
367
|
</Card>
|
|
304
368
|
|
|
305
369
|
{/* RIGHT PANEL */}
|
|
306
|
-
<Card
|
|
370
|
+
<Card
|
|
371
|
+
className="right-panel"
|
|
372
|
+
bodyStyle={{
|
|
373
|
+
height: '100%',
|
|
374
|
+
display: 'flex',
|
|
375
|
+
flexDirection: 'column',
|
|
376
|
+
padding: 16,
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
307
379
|
<div className="menus-header">
|
|
308
|
-
<
|
|
309
|
-
<div className="sub-text">
|
|
380
|
+
<div className="title">Menus {activeRole ? `– ${activeRole.name}` : ''}</div>
|
|
381
|
+
<div className="sub-text">Not editable here. Go to role settings to modify.</div>
|
|
310
382
|
</div>
|
|
311
383
|
|
|
312
384
|
<div className="menus-content">
|
|
313
385
|
{loadingMenus ? (
|
|
314
386
|
<Skeleton active paragraph={{ rows: 6 }} />
|
|
315
387
|
) : modules.length === 0 ? (
|
|
316
|
-
<
|
|
388
|
+
<div className="empty-state">
|
|
389
|
+
<Empty description="Select a role to view menus" />
|
|
390
|
+
</div>
|
|
317
391
|
) : (
|
|
318
392
|
<MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />
|
|
319
393
|
)}
|
|
@@ -322,7 +396,7 @@ export default function AssignRole() {
|
|
|
322
396
|
<div className="footer-actions">
|
|
323
397
|
{rolesChanged && (
|
|
324
398
|
<Button type="primary" onClick={handleSaveUserRole} loading={saving}>
|
|
325
|
-
Save
|
|
399
|
+
Save Changes
|
|
326
400
|
</Button>
|
|
327
401
|
)}
|
|
328
402
|
</div>
|
|
@@ -1,25 +1,82 @@
|
|
|
1
1
|
.assign-role {
|
|
2
2
|
display: flex;
|
|
3
|
-
gap:
|
|
3
|
+
gap: 8px;
|
|
4
4
|
|
|
5
|
+
/* Checkbox Color Override */
|
|
6
|
+
.ant-checkbox-checked .ant-checkbox-inner {
|
|
7
|
+
background-color: rgb(68, 106, 169);
|
|
8
|
+
border-color: rgb(68, 106, 169);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ant-checkbox:hover .ant-checkbox-inner,
|
|
12
|
+
.ant-checkbox-wrapper:hover .ant-checkbox-inner {
|
|
13
|
+
border-color: rgb(68, 106, 169);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ant-checkbox-checked::after {
|
|
17
|
+
border-color: rgb(68, 106, 169);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* TAKE FULL SCREEN HEIGHT */
|
|
21
|
+
height: calc(100vh - 80px);
|
|
22
|
+
min-height: 0;
|
|
23
|
+
|
|
24
|
+
.ant-card {
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
27
|
+
border: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* LEFT PANEL */
|
|
5
31
|
.left-panel {
|
|
6
|
-
width:
|
|
32
|
+
width: 360px;
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
height: 100%;
|
|
36
|
+
min-height: 0;
|
|
7
37
|
|
|
38
|
+
/* User Card */
|
|
8
39
|
.user-card {
|
|
9
|
-
margin-bottom: 12px;
|
|
10
|
-
background: #fafafa;
|
|
11
|
-
border: none;
|
|
12
40
|
display: flex;
|
|
13
41
|
align-items: center;
|
|
14
42
|
gap: 12px;
|
|
15
|
-
padding:
|
|
43
|
+
padding: 14px;
|
|
44
|
+
|
|
45
|
+
background: #ffffff;
|
|
46
|
+
border-radius: 10px;
|
|
47
|
+
|
|
48
|
+
border: 1px solid #f0f0f0;
|
|
49
|
+
|
|
50
|
+
box-shadow:
|
|
51
|
+
0 2px 6px rgba(0, 0, 0, 0.06),
|
|
52
|
+
0 6px 16px rgba(0, 0, 0, 0.04);
|
|
53
|
+
|
|
54
|
+
margin-bottom: 16px;
|
|
55
|
+
|
|
56
|
+
transition: all 0.2s ease;
|
|
57
|
+
|
|
58
|
+
&:hover {
|
|
59
|
+
box-shadow:
|
|
60
|
+
0 4px 10px rgba(0, 0, 0, 0.08),
|
|
61
|
+
0 10px 22px rgba(0, 0, 0, 0.06);
|
|
62
|
+
transform: translateY(-1px);
|
|
63
|
+
}
|
|
16
64
|
|
|
17
65
|
.user-info {
|
|
18
|
-
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
line-height: 1.2;
|
|
69
|
+
|
|
70
|
+
.name {
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
font-size: 14px;
|
|
73
|
+
color: #1f1f1f;
|
|
74
|
+
}
|
|
19
75
|
|
|
20
76
|
.user-id {
|
|
21
77
|
font-size: 12px;
|
|
22
|
-
color:
|
|
78
|
+
color: #8c8c8c;
|
|
79
|
+
margin-top: 2px;
|
|
23
80
|
}
|
|
24
81
|
}
|
|
25
82
|
}
|
|
@@ -28,8 +85,11 @@
|
|
|
28
85
|
display: flex;
|
|
29
86
|
justify-content: space-between;
|
|
30
87
|
align-items: center;
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
margin-bottom: 10px;
|
|
89
|
+
|
|
90
|
+
.count {
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
color: #888;
|
|
33
93
|
}
|
|
34
94
|
}
|
|
35
95
|
|
|
@@ -40,16 +100,70 @@
|
|
|
40
100
|
}
|
|
41
101
|
|
|
42
102
|
.role-search {
|
|
43
|
-
margin:
|
|
103
|
+
margin-bottom: 10px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.selected-summary {
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
color: rgb(68, 106, 169);
|
|
109
|
+
margin-bottom: 10px;
|
|
110
|
+
font-weight: 500;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* SCROLL AREA */
|
|
114
|
+
.role-list-wrapper {
|
|
115
|
+
flex: 1;
|
|
116
|
+
overflow-y: auto;
|
|
117
|
+
min-height: 0;
|
|
118
|
+
padding-right: 4px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Scrollbar */
|
|
122
|
+
.role-list-wrapper,
|
|
123
|
+
.menus-content {
|
|
124
|
+
scrollbar-width: thin; /* Firefox */
|
|
125
|
+
scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
|
|
126
|
+
|
|
127
|
+
&::-webkit-scrollbar-thumb {
|
|
128
|
+
background: rgba(0, 0, 0, 0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&:hover::-webkit-scrollbar-thumb {
|
|
132
|
+
background: rgba(0, 0, 0, 0.25);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&::-webkit-scrollbar {
|
|
136
|
+
width: 6px;
|
|
137
|
+
height: 6px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&::-webkit-scrollbar-track {
|
|
141
|
+
background: transparent;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&::-webkit-scrollbar-thumb {
|
|
145
|
+
background: rgba(0, 0, 0, 0.25);
|
|
146
|
+
border-radius: 10px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
150
|
+
background: rgba(0, 0, 0, 0.4);
|
|
151
|
+
}
|
|
44
152
|
}
|
|
45
153
|
|
|
46
154
|
.role-item {
|
|
47
155
|
cursor: pointer;
|
|
48
156
|
border-radius: 6px;
|
|
49
|
-
padding:
|
|
157
|
+
padding: 10px 12px;
|
|
158
|
+
transition: 0.2s;
|
|
159
|
+
|
|
160
|
+
&:hover {
|
|
161
|
+
background: #f5f7fa;
|
|
162
|
+
}
|
|
50
163
|
|
|
51
164
|
&.active {
|
|
52
|
-
background: #
|
|
165
|
+
background: #e6f4ff;
|
|
166
|
+
// border-left: 3px solid rgb(68, 106, 169);
|
|
53
167
|
}
|
|
54
168
|
|
|
55
169
|
.ant-list-item-meta-title {
|
|
@@ -58,66 +172,90 @@
|
|
|
58
172
|
|
|
59
173
|
.ant-list-item-meta-description {
|
|
60
174
|
font-size: 12px;
|
|
61
|
-
color:
|
|
175
|
+
color: #777;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Skeleton Loader */
|
|
180
|
+
.role-skeleton {
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
gap: 8px;
|
|
184
|
+
padding: 2px;
|
|
185
|
+
|
|
186
|
+
.role-skeleton-item {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 12px;
|
|
190
|
+
padding: 10px 12px;
|
|
191
|
+
border-radius: 6px;
|
|
192
|
+
|
|
193
|
+
background: #fff;
|
|
194
|
+
border: 1px solid #f0f0f0;
|
|
195
|
+
|
|
196
|
+
.meta {
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
gap: 6px;
|
|
200
|
+
flex: 1;
|
|
201
|
+
}
|
|
62
202
|
}
|
|
63
203
|
}
|
|
64
204
|
}
|
|
65
205
|
|
|
206
|
+
/* RIGHT PANEL */
|
|
66
207
|
.right-panel {
|
|
67
208
|
flex: 1;
|
|
68
209
|
display: flex;
|
|
69
210
|
flex-direction: column;
|
|
70
|
-
|
|
71
|
-
|
|
211
|
+
height: 100%;
|
|
212
|
+
min-height: 0;
|
|
72
213
|
|
|
73
214
|
.menus-header {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
215
|
+
margin-bottom: 12px;
|
|
216
|
+
|
|
217
|
+
.title {
|
|
218
|
+
font-size: 16px;
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
}
|
|
77
221
|
|
|
78
222
|
.sub-text {
|
|
79
223
|
font-size: 12px;
|
|
80
224
|
color: #888;
|
|
81
|
-
margin-top: 4px;
|
|
82
225
|
}
|
|
83
226
|
}
|
|
84
227
|
|
|
85
228
|
.menus-content {
|
|
86
|
-
|
|
229
|
+
flex: 1;
|
|
230
|
+
overflow-y: auto;
|
|
231
|
+
min-height: 0;
|
|
232
|
+
padding: 8px 4px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.empty-state {
|
|
236
|
+
height: 300px;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
justify-content: center;
|
|
87
240
|
}
|
|
88
241
|
|
|
89
242
|
.footer-actions {
|
|
90
|
-
margin-top: 16px;
|
|
91
243
|
display: flex;
|
|
92
244
|
justify-content: flex-end;
|
|
93
|
-
|
|
245
|
+
padding-top: 12px;
|
|
246
|
+
border-top: 1px solid #f0f0f0;
|
|
94
247
|
}
|
|
95
248
|
}
|
|
96
249
|
|
|
97
|
-
/*
|
|
98
|
-
📱 iPad Mini (481px – 768px)
|
|
99
|
-
=============================== */
|
|
250
|
+
/* TABLET / MOBILE */
|
|
100
251
|
@media (max-width: 768px) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
gap: 12px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.assign-role .left-panel {
|
|
107
|
-
width: 100%;
|
|
108
|
-
}
|
|
252
|
+
flex-direction: column;
|
|
253
|
+
height: auto;
|
|
109
254
|
|
|
110
|
-
.
|
|
255
|
+
.left-panel,
|
|
256
|
+
.right-panel {
|
|
111
257
|
width: 100%;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/* Better spacing for tablets */
|
|
115
|
-
.assign-role .right-panel {
|
|
116
|
-
padding: 14px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.assign-role .footer-actions {
|
|
120
|
-
justify-content: flex-end;
|
|
258
|
+
height: auto;
|
|
121
259
|
}
|
|
122
260
|
}
|
|
123
261
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const avatarColors = [
|
|
2
|
+
'#5B8FF9',
|
|
3
|
+
'#61DDAA',
|
|
4
|
+
'#65789B',
|
|
5
|
+
'#a0d911',
|
|
6
|
+
'#F6BD16',
|
|
7
|
+
'#7262FD',
|
|
8
|
+
'#faad14',
|
|
9
|
+
'#78D3F8',
|
|
10
|
+
'#9661BC',
|
|
11
|
+
'#F6903D',
|
|
12
|
+
'#008685',
|
|
13
|
+
'#F08BB4',
|
|
14
|
+
'#722ed1',
|
|
15
|
+
'#eb2f96',
|
|
16
|
+
'#13c2c2',
|
|
17
|
+
'#eb2f96',
|
|
18
|
+
'#fa8c16',
|
|
19
|
+
'#52c41a',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const getAvatarProps = (name) => {
|
|
23
|
+
const safeName = (name ?? '').toString().trim();
|
|
24
|
+
|
|
25
|
+
// Find first alphabetic character
|
|
26
|
+
const match = safeName.match(/[A-Za-z]/);
|
|
27
|
+
const letter = match ? match[0].toUpperCase() : '-';
|
|
28
|
+
|
|
29
|
+
// deterministic color based on string
|
|
30
|
+
let hash = 0;
|
|
31
|
+
for (let i = 0; i < safeName.length; i++) {
|
|
32
|
+
hash = safeName.charCodeAt(i) + ((hash << 5) - hash);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const color = avatarColors[Math.abs(hash) % avatarColors.length] || '#999';
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
letter,
|
|
39
|
+
style: {
|
|
40
|
+
backgroundColor: color,
|
|
41
|
+
color: '#fff',
|
|
42
|
+
fontWeight: 600,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -269,7 +269,7 @@ export default function ReportingDashboard({
|
|
|
269
269
|
getPatientDetails();
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns,paramsString) => {
|
|
272
|
+
const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns, paramsString) => {
|
|
273
273
|
const { current, pageSize } = pagination || {};
|
|
274
274
|
// If card script id is exist load that id otherwise load id
|
|
275
275
|
const coreScriptId = scriptId.current ? scriptId.current : id;
|
|
@@ -319,7 +319,7 @@ export default function ReportingDashboard({
|
|
|
319
319
|
|
|
320
320
|
// Handle both result formats
|
|
321
321
|
let resultDetails = apiData[0] || [];
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
if (result?.result && result?.result[0]) {
|
|
324
324
|
resultDetails = result.result[0];
|
|
325
325
|
}
|
|
@@ -350,7 +350,6 @@ export default function ReportingDashboard({
|
|
|
350
350
|
}
|
|
351
351
|
} catch (error) {
|
|
352
352
|
console.error('Error fetching report data:', error);
|
|
353
|
-
message.warn('Please try again');
|
|
354
353
|
} finally {
|
|
355
354
|
// Always runs, success or error
|
|
356
355
|
setLoading(false);
|
|
@@ -449,7 +448,7 @@ export default function ReportingDashboard({
|
|
|
449
448
|
|
|
450
449
|
// Call API
|
|
451
450
|
try {
|
|
452
|
-
await fetchReportData(id, values, dbPtr, paginationData, parsedColumns,paramsString);
|
|
451
|
+
await fetchReportData(id, values, dbPtr, paginationData, parsedColumns, paramsString);
|
|
453
452
|
} finally {
|
|
454
453
|
setLoading(false);
|
|
455
454
|
setCardLoading(false);
|
|
@@ -612,7 +611,6 @@ function GuestList({
|
|
|
612
611
|
attributes,
|
|
613
612
|
fetchReportData,
|
|
614
613
|
}) {
|
|
615
|
-
|
|
616
614
|
/**
|
|
617
615
|
* @param {*} propValues
|
|
618
616
|
*/
|