ui-soxo-bootstrap-core 2.6.40-dev.16 → 2.6.40

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 (27) hide show
  1. package/DEVELOPER_GUIDE.md +9 -38
  2. package/core/components/index.js +11 -2
  3. package/core/components/landing-api/landing-api.js +5 -165
  4. package/core/lib/components/global-header/global-header.js +77 -20
  5. package/core/lib/components/index.js +2 -2
  6. package/core/lib/components/sidemenu/sidemenu.js +147 -66
  7. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +24 -91
  8. package/core/lib/models/menus/components/menu-add/menu-add.js +2 -2
  9. package/core/lib/modules/generic/generic-list/ExportReactCSV.js +8 -334
  10. package/core/lib/modules/generic/generic-list/generic-list.scss +0 -34
  11. package/core/models/core-scripts/core-scripts.js +1 -22
  12. package/core/models/menus/components/menu-add/menu-add.js +4 -31
  13. package/core/models/menus/components/menu-lists/menu-lists.js +9 -76
  14. package/core/models/menus/menus.js +1 -29
  15. package/core/models/roles/components/role-add/role-add.js +169 -54
  16. package/core/models/roles/components/role-list/role-list.js +0 -20
  17. package/core/models/roles/roles.js +0 -3
  18. package/core/models/users/components/assign-role/assign-role.js +8 -23
  19. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +5 -202
  20. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +0 -73
  21. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +35 -56
  22. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +0 -1
  23. package/package.json +1 -1
  24. package/core/components/license-management/license-alert.js +0 -97
  25. package/core/models/roles/components/role-add/menu-label.js +0 -14
  26. package/core/models/roles/components/role-add/menu-tree.js +0 -127
  27. package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +0 -519
@@ -160,29 +160,6 @@ class MenusAPI extends Base {
160
160
  });
161
161
  };
162
162
 
163
- getBranches = () => {
164
- return ApiUtils.get({
165
- url: 'branches',
166
- });
167
- };
168
-
169
- switchBranch = (formBody, dbPtr) => {
170
- return ApiUtils.post({
171
- url: `auth/switch-branch`,
172
- headers: { db_ptr: dbPtr },
173
- formBody,
174
- });
175
- };
176
-
177
- getProfile = (token) => {
178
- return ApiUtils.get({
179
- url: 'auth/profile',
180
- headers: {
181
- Authorization: `Bearer ${token}`,
182
- },
183
- });
184
- };
185
-
186
163
  /**
187
164
  * create menu
188
165
  */
@@ -208,6 +185,7 @@ class MenusAPI extends Base {
208
185
  : 'menus/get-menus'; // NURA
209
186
 
210
187
  if (!dbPtr) dbPtr = localStorage.db_ptr;
188
+
211
189
  return this.get({
212
190
  url,
213
191
  config,
@@ -327,12 +305,6 @@ class MenusAPI extends Base {
327
305
  // }
328
306
  ];
329
307
  };
330
- // license summary api call
331
- getSummary = () => {
332
- return ApiUtils.get({
333
- url: 'license/summary',
334
- });
335
- };
336
308
  }
337
309
 
338
310
  export default MenusAPI;
@@ -1,14 +1,15 @@
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 } from 'antd';
3
+ import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox, Tag } from 'antd';
4
4
  import { GlobalContext } from './../../../../lib';
5
+ import { Button } from './../../../../lib';
5
6
  import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
6
- import MenuTree from './menu-tree';
7
7
  import './role-add.scss';
8
8
 
9
9
  const { Title } = Typography;
10
+ const { Panel } = Collapse;
10
11
 
11
- const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [], onSubmittingChange, formRef }) => {
12
+ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [] }) => {
12
13
  let mode = 'Add';
13
14
  if (formContent.id) mode = 'Edit';
14
15
  else if (formContent.copy) mode = 'copy';
@@ -30,10 +31,8 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
30
31
 
31
32
  const isEdit = !!(formContent.id || formContent.copy);
32
33
  const [initialLoading, setInitialLoading] = useState(isEdit);
34
+ const [loading, setLoading] = useState(false);
33
35
  const [form] = Form.useForm();
34
-
35
- // expose the form instance so the Drawer footer Save button can submit it
36
- if (formRef) formRef.current = form;
37
36
  const [pages, setPages] = useState([]);
38
37
  const [models, setModels] = useState([]);
39
38
  const [menuList, setMenuList] = useState([]);
@@ -44,9 +43,6 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
44
43
  // Selected menus (array of IDs)
45
44
  const [selectedMenus, setSelectedMenus] = useState([]);
46
45
 
47
- // Search term for filtering the menu list
48
- const [menuSearch, setMenuSearch] = useState('');
49
-
50
46
  const location = useLocation();
51
47
  const query = new URLSearchParams(location.search);
52
48
  let step = parseInt(query.get('step')) || 1;
@@ -101,28 +97,40 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
101
97
  setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
102
98
  };
103
99
 
104
- // Recursively filter menus by caption. A menu is kept if its caption matches
105
- // the search term, or if any of its descendants match (ancestors are kept so
106
- // the matching child stays reachable).
107
- const filterMenus = (menus, term) => {
108
- if (!term) return menus;
109
- const lower = term.toLowerCase();
110
-
111
- return menus.reduce((acc, menu) => {
112
- const children = filterMenus(menu.sub_menus || [], term);
113
- const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
114
-
115
- if (selfMatch || children.length > 0) {
116
- acc.push({ ...menu, sub_menus: children });
117
- }
118
- return acc;
119
- }, []);
120
- };
121
-
122
- const filteredMenuList = filterMenus(menuList, menuSearch);
123
-
100
+ // Submit handler
101
+ // const onSubmit = (values) => {
102
+ // setLoading(true);
103
+
104
+ // const payload = {
105
+ // ...values,
106
+ // menu_ids
107
+ // : selectedMenus,
108
+ // attributes: JSON.stringify(values.attributes || {}),
109
+ // };
110
+
111
+ // if (formContent.id) {
112
+ // RolesAPI.updateRole({ id: formContent.id, formBody: payload })
113
+ // .then(() => {
114
+ // message.success('Role Updated');
115
+ // setLoading(false);
116
+ // callback();
117
+ // })
118
+ // .catch(() => setLoading(false));
119
+ // } else {
120
+ // additional_queries.forEach(({ field, value }) => {
121
+ // payload[field] = value;
122
+ // });
123
+ // RolesAPI.createRole(payload)
124
+ // .then(() => {
125
+ // message.success('Role Added');
126
+ // setLoading(false);
127
+ // callback();
128
+ // })
129
+ // .catch(() => setLoading(false));
130
+ // }
131
+ // };
124
132
  const onSubmit = async (values) => {
125
- onSubmittingChange?.(true);
133
+ setLoading(true);
126
134
 
127
135
  // Find menus that were originally selected but now deselected
128
136
  const deselectedMenus = originalMenus.filter((id) => !selectedMenus.includes(id));
@@ -145,21 +153,13 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
145
153
  });
146
154
 
147
155
  try {
148
- const res = await RolesAPI.createRole(payload); // single API
149
-
150
- // it via success: false — show that message as a warning.
151
- if (res?.success === false) {
152
- message.warning(res.message || 'Failed to save role');
153
- return;
154
- }
155
-
156
+ await RolesAPI.createRole(payload); // single API
156
157
  message.success(formContent?.id ? 'Role Updated' : 'Role Added');
157
158
  callback();
158
159
  } catch (err) {
159
- // Surface any unexpected backend error message as a warning.
160
- message.warning(err?.message || err?.result?.message || 'Failed to save role');
160
+ // message.error('Something went wrong');
161
161
  } finally {
162
- onSubmittingChange?.(false);
162
+ setLoading(false);
163
163
  }
164
164
  };
165
165
 
@@ -194,7 +194,6 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
194
194
  display: 'flex',
195
195
  justifyContent: 'space-between',
196
196
  alignItems: 'center',
197
- gap: 16,
198
197
  marginBottom: 16,
199
198
  }}
200
199
  >
@@ -205,20 +204,14 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
205
204
  <p style={{ color: '#999', marginBottom: 0 }}>Choose menus and set permissions</p>
206
205
  </div>
207
206
 
208
- <Input.Search
209
- allowClear
210
- placeholder="Search menus"
211
- value={menuSearch}
212
- onChange={(e) => setMenuSearch(e.target.value)}
213
- style={{ maxWidth: 260 }}
214
- />
207
+ <Form.Item style={{ marginBottom: 0 }}>
208
+ <Button loading={loading} htmlType="submit" type="primary">
209
+ Save
210
+ </Button>
211
+ </Form.Item>
215
212
  </div>
216
213
 
217
- {filteredMenuList.length > 0 ? (
218
- <MenuTree menus={filteredMenuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} searchActive={!!menuSearch} />
219
- ) : (
220
- <p style={{ color: '#999', textAlign: 'center', padding: '16px 0' }}>No menus found</p>
221
- )}
214
+ <MenuTree menus={menuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
222
215
  </div>
223
216
  )}
224
217
  </Form>
@@ -228,3 +221,125 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
228
221
  };
229
222
 
230
223
  export default RoleAdd;
224
+
225
+ // ------------------------
226
+ // Recursive Nested Menu Component
227
+ // ------------------------
228
+ // ------------------------
229
+ // Recursive Nested Menu Component
230
+ // ------------------------
231
+ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
232
+ // Helper: check if parent should be checked
233
+ const isParentChecked = (menu) => {
234
+ if (!menu.sub_menus || menu.sub_menus.length === 0) {
235
+ return selectedMenus.includes(menu.id);
236
+ }
237
+ const allChildIds = menu.sub_menus.map((c) => c.id);
238
+ return allChildIds.every((id) => selectedMenus.includes(id));
239
+ };
240
+
241
+ // Helper: check if parent is indeterminate
242
+ const isParentIndeterminate = (menu) => {
243
+ if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
244
+ const allChildIds = menu.sub_menus.map((c) => c.id);
245
+ const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
246
+ return checkedCount > 0 && checkedCount < allChildIds.length;
247
+ };
248
+
249
+ return (
250
+ <>
251
+ {menus.map((menu) => {
252
+ const children = menu.sub_menus || [];
253
+ const parentChecked = isParentChecked(menu);
254
+ const parentIndeterminate = isParentIndeterminate(menu);
255
+
256
+ const onParentChange = (checked) => {
257
+ toggleMenu(menu.id, checked);
258
+ // toggle children recursively
259
+ children.forEach((c) => toggleMenuRecursive(c, checked));
260
+ };
261
+
262
+ const toggleMenuRecursive = (menu, checked) => {
263
+ toggleMenu(menu.id, checked);
264
+ if (menu.sub_menus && menu.sub_menus.length > 0) {
265
+ menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
266
+ }
267
+ };
268
+
269
+ if (children.length === 0) {
270
+ return (
271
+ <div
272
+ style={{
273
+ justifyContent: 'space-between',
274
+ display: 'flex',
275
+ alignItems: 'center',
276
+ border: '1px solid rgba(198, 195, 195, 0.85)',
277
+ // borderRadius: 6,
278
+ padding: '12px 16px',
279
+ marginBottom: 6,
280
+ background: '#fff',
281
+ }}
282
+ >
283
+ <div
284
+ key={menu.id}
285
+ style={{
286
+ display: 'flex',
287
+ alignItems: 'center',
288
+ gap: 8,
289
+ }}
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>{' '}
307
+ </div>
308
+ );
309
+ }
310
+
311
+ return (
312
+ <Collapse
313
+ key={menu.id}
314
+ style={{ marginBottom: 6 }}
315
+ // defaultActiveKey={[menu.id]}
316
+ >
317
+ <Panel
318
+ key={menu.id}
319
+ header={
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>{' '}
333
+ </div>
334
+ }
335
+ >
336
+ <div style={{ paddingLeft: 20 }}>
337
+ <MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} />
338
+ </div>
339
+ </Panel>
340
+ </Collapse>
341
+ );
342
+ })}
343
+ </>
344
+ );
345
+ };
@@ -40,12 +40,6 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
40
40
 
41
41
  const [single, setSingle] = useState({});
42
42
 
43
- // tracks RoleAdd form submission so the Drawer footer button shows loading
44
- const [submitting, setSubmitting] = useState(false);
45
-
46
- // holds the RoleAdd form instance so the Drawer footer Save button can submit it
47
- const formRef = useRef(null);
48
-
49
43
  var [queryValue, setQueryValue] = useState('');
50
44
 
51
45
  const { Search } = Input;
@@ -366,26 +360,12 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
366
360
  destroyOnClose
367
361
  onClose={closeModal}
368
362
  bodyStyle={{ paddingBottom: 80 }}
369
- footer={
370
- <div style={{ textAlign: 'right' }}>
371
- <Space size="small">
372
- <Button type="default" onClick={closeModal}>
373
- Cancel
374
- </Button>
375
- <Button type="primary" loading={submitting} onClick={() => formRef.current?.submit()}>
376
- Save
377
- </Button>
378
- </Space>
379
- </div>
380
- }
381
363
  >
382
364
  <model.ModalAddComponent
383
365
  match={match}
384
366
  model={model}
385
367
  additional_queries={additional_queries}
386
368
  formContent={single}
387
- formRef={formRef}
388
- onSubmittingChange={setSubmitting}
389
369
  callback={(event) => {
390
370
  closeModal();
391
371
  getRecords();
@@ -163,9 +163,6 @@ class RolesAPI extends BaseAPI {
163
163
  return ApiUtils.post({
164
164
  url: `core-roles/save-core-role`,
165
165
  formBody,
166
- // Let the caller surface the backend message (as a warning) instead of
167
- // the default red error toast from the http layer.
168
- hideError: true,
169
166
  });
170
167
  };
171
168
 
@@ -160,7 +160,7 @@ export default function AssignRole() {
160
160
  const role_id = role.id;
161
161
  try {
162
162
  const res = await MenusAPI.getCoreMenuByRoleId(role_id);
163
- const allMenus = Array.isArray(res?.result) ? res.result : [];
163
+ const allMenus = res.result || [];
164
164
 
165
165
  setModules(allMenus);
166
166
  } catch (e) {
@@ -264,12 +264,11 @@ export default function AssignRole() {
264
264
  const handleViewAll = async () => {
265
265
  setLoadingMenus(true);
266
266
  setModules([]);
267
- setActiveRole({ name: 'All Roles', isViewAll: true });
267
+ setActiveRole({ name: 'All Roles' });
268
268
 
269
269
  try {
270
270
  const res = await MenusAPI.getMenubyUser(id);
271
- const userMenus = Array.isArray(res?.result?.menus) ? res.result.menus : [];
272
- setModules(userMenus);
271
+ setModules(res?.result?.menus ?? []);
273
272
  setLoadingMenus(false);
274
273
  } catch (err) {
275
274
  setLoadingMenus(false);
@@ -278,18 +277,6 @@ export default function AssignRole() {
278
277
  }
279
278
  };
280
279
 
281
- /**
282
- * Context-specific empty-state message for the right (menus) panel:
283
- * - View Access for a user with no roles → no role assigned
284
- * - A selected role with no menus → no menus assigned
285
- * - Nothing selected yet → prompt to pick a role
286
- */
287
- const emptyMenusMessage = !activeRole
288
- ? 'Select a role to view menus'
289
- : activeRole.isViewAll
290
- ? 'No role has been assigned to this user.'
291
- : 'No menus have been assigned to this role.';
292
-
293
280
  return (
294
281
  <section className="assign-role">
295
282
  {/* LEFT PANEL */}
@@ -396,19 +383,17 @@ export default function AssignRole() {
396
383
  padding: 16,
397
384
  }}
398
385
  >
399
- {modules.length > 0 && (
400
- <div className="menus-header">
401
- <div className="title">Menus {activeRole ? `– ${activeRole.name}` : ''}</div>
402
- <div className="sub-text">You don’t have permission to edit this here. Update it in Role Settings.</div>
403
- </div>
404
- )}
386
+ <div className="menus-header">
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>
389
+ </div>
405
390
 
406
391
  <div className="menus-content">
407
392
  {loadingMenus ? (
408
393
  <Skeleton active paragraph={{ rows: 6 }} />
409
394
  ) : modules.length === 0 ? (
410
395
  <div className="empty-state">
411
- <Empty description={emptyMenusMessage} />
396
+ <Empty description="Select a role to view menus" />
412
397
  </div>
413
398
  ) : (
414
399
  <MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />