ui-soxo-bootstrap-core 2.6.1-dev.5 → 2.6.1-dev.7

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.
@@ -88,9 +88,13 @@ class BaseAPI {
88
88
  * Get the data from the table
89
89
  */
90
90
  get(config = {}) {
91
- // Get the records from firebase
92
-
93
- return ApiUtils.get({ url: config.url || this.endpoint, config });
91
+ const { url, headers, ...rest } = config;
92
+ return ApiUtils.get({
93
+ url: url || this.endpoint,
94
+ headers,
95
+ config: rest,
96
+ ...rest,
97
+ });
94
98
  }
95
99
 
96
100
  getRelations(id) {
@@ -474,7 +474,7 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
474
474
  </Button>
475
475
  )}
476
476
 
477
- {/* <Button
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">
@@ -177,23 +177,34 @@ class MenusAPI extends Base {
177
177
  * @param {*} menu
178
178
  * @returns
179
179
  */
180
- getMenus = (config) => {
180
+ getMenus = (config, dbPtr = null) => {
181
181
  // Use 'core-menus' endpoint if REACT_APP_USE_CORE_MENUS is true (used for Matria)
182
182
  const url =
183
183
  process.env.REACT_APP_USE_CORE_MENUS === 'true'
184
184
  ? 'core-menus/get-menus' // Matria
185
185
  : 'menus/get-menus'; // NURA
186
186
 
187
+ if (!dbPtr) dbPtr = localStorage.db_ptr;
188
+
187
189
  return this.get({
188
190
  url,
189
191
  config,
192
+ headers: {
193
+ db_ptr: dbPtr,
194
+ },
190
195
  }).then((result) => result);
191
196
  };
192
197
 
193
- // get core-menu list with submenu
194
- getCoreMenuLists = () => {
195
- const url = 'core-menus/core-menus?step=1&header_id=null';
198
+ getMenubyUser = (user_id) => {
199
+ const url = `menus/get-menus?userId=${user_id}`;
200
+ return this.get({
201
+ url,
202
+ }).then((result) => result);
203
+ };
196
204
 
205
+ // get core-menu list with submenu
206
+ getCoreMenuLists = (role_id) => {
207
+ const url = `menus/get-menus-by-role/${role_id}`;
197
208
  return this.get({
198
209
  url,
199
210
  }).then((result) => result);
@@ -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.getCoreMenuLists();
162
+ const res = await MenusAPI.getCoreMenuLists(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([]);
@@ -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 className="left-panel" bodyStyle={{ padding: 12 }}>
267
- <div size="small" className="user-card">
268
- <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>
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
- <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>
278
312
  </div>
279
313
 
280
- <Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={(e) => setSearch(e.target.value)} />
281
- {/* <Input className="role-search" placeholder="Search Here" allowClear value={search} onChange={(e) => setSearch(e.target.value)} /> */}
282
-
283
- <List
284
- itemLayout="horizontal"
285
- dataSource={filteredRoles}
286
- renderItem={(role) => (
287
- <List.Item
288
- key={role.id}
289
- onClick={() => loadRoleMenus(role)}
290
- className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
291
- actions={[
292
- <Checkbox
293
- checked={selectedRoles.includes(role.id)}
294
- onChange={(e) => toggleRole(role.id, e.target.checked)}
295
- onClick={(e) => e.stopPropagation()}
296
- />,
297
- ]}
298
- >
299
- <List.Item.Meta avatar={<Avatar size={28}>{role?.name?.[0] || '-'}</Avatar>} title={role.name} description={role.description} />
300
- </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
+ 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 className="right-panel">
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
- <Text>Menus {activeRole ? `– ${activeRole.name}` : ''}</Text>
309
- <div className="sub-text">It's not editable. To edit it, go to the role editing page.</div>
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
- <Empty description="Click a role to view menus" />
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: 6px;
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: 340px;
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: 8px;
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
- font-weight: 500;
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: rgba(0, 0, 0, 0.45);
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
- strong {
32
- font-weight: 600;
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: 12px 0;
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: 8px 12px;
157
+ padding: 10px 12px;
158
+ transition: 0.2s;
159
+
160
+ &:hover {
161
+ background: #f5f7fa;
162
+ }
50
163
 
51
164
  &.active {
52
- background: #e6f7ff;
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: rgba(0, 0, 0, 0.45);
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
- justify-content: space-between;
71
- padding: 16px;
211
+ height: 100%;
212
+ min-height: 0;
72
213
 
73
214
  .menus-header {
74
- font-weight: 500;
75
- margin-bottom: 4px;
76
- color: #000;
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
- margin-top: 16px;
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
- gap: 8px;
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
- .assign-role {
102
- flex-direction: column;
103
- gap: 12px;
104
- }
105
-
106
- .assign-role .left-panel {
107
- width: 100%;
108
- }
252
+ flex-direction: column;
253
+ height: auto;
109
254
 
110
- .assign-role .right-panel {
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;
@@ -314,8 +314,12 @@ export default function ReportingDashboard({
314
314
 
315
315
  // Fetch result
316
316
  const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
317
+
318
+ const apiData = Array.isArray(result) ? result : Array.isArray(result?.result) ? result.result : [];
319
+
317
320
  // Handle both result formats
318
- let resultDetails = result[0];
321
+ let resultDetails = apiData[0] || [];
322
+
319
323
  if (result?.result && result?.result[0]) {
320
324
  resultDetails = result.result[0];
321
325
  }
@@ -346,7 +350,6 @@ export default function ReportingDashboard({
346
350
  }
347
351
  } catch (error) {
348
352
  console.error('Error fetching report data:', error);
349
- message.warn('Please try again');
350
353
  } finally {
351
354
  // Always runs, success or error
352
355
  setLoading(false);
@@ -445,7 +448,7 @@ export default function ReportingDashboard({
445
448
 
446
449
  // Call API
447
450
  try {
448
- await fetchReportData(id, values, dbPtr, paginationData, parsedColumns,paramsString);
451
+ await fetchReportData(id, values, dbPtr, paginationData, parsedColumns, paramsString);
449
452
  } finally {
450
453
  setLoading(false);
451
454
  setCardLoading(false);
@@ -608,7 +611,6 @@ function GuestList({
608
611
  attributes,
609
612
  fetchReportData,
610
613
  }) {
611
-
612
614
  /**
613
615
  * @param {*} propValues
614
616
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.1-dev.5",
3
+ "version": "2.6.1-dev.7",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"