ui-soxo-bootstrap-core 2.5.0 → 2.5.2

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.
@@ -1,137 +1,103 @@
1
1
  /**
2
2
  *
3
3
  * Sidemenu component
4
- *
4
+ *
5
5
  * @author Ashique Mohammed
6
- * @co-author Sneha
7
- *
8
- *
6
+ * @co-author Sneha
7
+ *
8
+ *
9
9
  */
10
10
 
11
- import React, { useState, useEffect, useContext } from "react";
11
+ import React, { useState, useEffect, useContext } from 'react';
12
12
 
13
- import { animationControls, motion, useAnimation } from "framer-motion";
13
+ import { animationControls, motion, useAnimation } from 'framer-motion';
14
14
 
15
- import { GlobalContext } from "./../../Store";
15
+ import { GlobalContext } from './../../Store';
16
16
 
17
- import { Menu, message, Skeleton, Space } from "antd";
17
+ import { Menu, message, Skeleton, Space, Popover } from 'antd';
18
18
 
19
- import FirebaseUtils from "./../../utils/firebase.utils";
19
+ import FirebaseUtils from './../../utils/firebase.utils';
20
20
 
21
- import { useHistory } from "react-router-dom";
21
+ import { useHistory } from 'react-router-dom';
22
22
 
23
23
  import { useTranslation, Trans } from 'react-i18next';
24
24
 
25
- import { Menus } from "../../models"
26
-
27
-
25
+ import { Menus } from '../../models';
28
26
 
29
-
30
- import "./sidemenu.scss";
27
+ import './sidemenu.scss';
31
28
 
32
29
  const { SubMenu } = Menu;
33
30
 
34
-
35
31
  /**
36
- *
37
- * @param {*} collapsed
38
- * @param {*} icon
39
- * @param {*} caption
40
- * @returns
32
+ *
33
+ * @param {*} collapsed
34
+ * @param {*} icon
35
+ * @param {*} caption
36
+ * @returns
41
37
  */
42
38
  function CollapsedIconMenu({ menu, collapsed, icon, caption }) {
43
-
44
-
45
-
46
39
  // Import t and i18n from useTranslation
47
40
  const { t, i18n } = useTranslation();
48
41
  const { state } = useContext(GlobalContext);
49
-
42
+ const [isMobile, setIsMobile] = useState(false);
50
43
 
44
+ useEffect(() => {
45
+ const handleResize = () => {
46
+ setIsMobile(window.innerWidth < 768); // Common breakpoint for mobile
47
+ };
51
48
 
52
- return (
49
+ handleResize(); // Set initial value
50
+ window.addEventListener('resize', handleResize);
53
51
 
54
- <>
52
+ return () => window.removeEventListener('resize', handleResize);
53
+ }, []);
55
54
 
55
+ const menuText = t(caption);
56
+ const menuContent = (
57
+ <>
56
58
  {/* If value of collapsed is false show caption & icon else hiding caption and showing only icon*/}
57
59
  {!collapsed ? (
58
-
59
- <div
60
-
61
- className="menu-collapsed" >
62
-
60
+ <div className="menu-collapsed">
63
61
  <div>
64
-
65
- {
66
- menu && menu.image_path ?
67
- <img style={{ width: '25px' }} src={menu.image_path}></img>
68
- :
69
- <i className={`fa-solid fas ${icon}`} />
70
- }
71
-
62
+ {menu && menu.image_path ? <img style={{ width: '25px' }} src={menu.image_path}></img> : <i className={`fa-solid fas ${icon}`} />}
72
63
  </div>
73
64
 
74
65
  <div style={{ color: state.theme.colors.leftSectionColor }}>
75
-
76
66
  <span className="caption">
77
-
78
67
  {/* {caption} */}
79
68
  {/* <Trans i18nKey="Appointments"></Trans> */}
80
69
  {t(`${caption}`)}
81
-
82
70
  </span>
83
-
84
71
  </div>
85
-
86
72
  </div>
87
-
88
73
  ) : (
89
-
90
- <div
91
-
92
- className="menu-collapsed">
93
-
74
+ <div className="menu-collapsed">
94
75
  <span className="anticon">
95
-
96
- {
97
- menu && menu.image_path ?
98
- <img style={{ width: '25px' }} src={menu.image_path}></img>
99
- :
100
- <i className={`fa-solid fas ${icon}`} />
101
- }
102
-
76
+ {menu && menu.image_path ? <img style={{ width: '25px' }} src={menu.image_path}></img> : <i className={`fa-solid fas ${icon}`} />}
103
77
  </span>
104
78
 
105
- <span style={{ color: state.theme.colors.colorPrimaryText,paddingLeft: '6px' }}>
106
-
79
+ <span style={{ color: state.theme.colors.colorPrimaryText, paddingLeft: '6px' }}>
107
80
  {/* <>{caption}</> */}
108
81
  {t(`${caption}`)}
109
-
110
82
  </span>
111
-
112
83
  </div>
113
-
114
84
  )}
115
-
116
85
  </>
86
+ );
117
87
 
118
- )
119
-
88
+ // On mobile, or when the menu is collapsed (based on original logic), don't show the popover tooltip.
89
+ return isMobile || collapsed ? menuContent : <Popover content={menuText} placement="right">{menuContent}</Popover>;
120
90
  }
121
91
 
122
- export default function SideMenu({
123
- loading,
124
- modules = [],
125
- callback,
126
- appSettings,
127
- collapsed,
128
- }) {
129
- const { brandLogo, footerLogo, renderCustomHeader = () => { } } = appSettings;
92
+ export default function SideMenu({ loading, modules = [], callback, appSettings, collapsed }) {
93
+ const { brandLogo, footerLogo, renderCustomHeader = () => {} } = appSettings;
130
94
 
131
- let history = useHistory();
95
+ let history = useHistory();
132
96
 
133
- const [selected, setSelected] = useState([1]);
97
+ // const [selected, setSelected] = useState([1]);
134
98
 
99
+ const [selectedKeys, setSelectedKeys] = useState([]);
100
+ const [openKeys, setOpenKeys] = useState([]);
135
101
  const [menu, setMenu] = useState({});
136
102
 
137
103
  const { user = { locations: [] }, dispatch, state } = useContext(GlobalContext);
@@ -147,11 +113,16 @@ export default function SideMenu({
147
113
  // }
148
114
  }, [user]);
149
115
 
116
+ // Keep current menu item selected after reload or navigation
117
+ useEffect(() => {
118
+ const currentPath = history.location.pathname;
119
+ setSelectedKeys([currentPath]);
120
+ }, [history.location.pathname]);
121
+
150
122
  /**
151
123
  * Logout Function
152
124
  */
153
125
  async function handleLogout() {
154
-
155
126
  let dbPtrValue = appSettings.headers.db_ptr;
156
127
  const isEnvThemeTrue = process.env.REACT_APP_THEME === 'true';
157
128
 
@@ -162,11 +133,9 @@ export default function SideMenu({
162
133
 
163
134
  // localStorage.clear();
164
135
 
165
- if (process.env.REACT_APP_PRIMARY_BACKEND === "SQL") {
166
-
136
+ if (process.env.REACT_APP_PRIMARY_BACKEND === 'SQL') {
167
137
  // const result = Users.logout()
168
138
  localStorage.clear();
169
-
170
139
 
171
140
  localStorage.setItem('db_ptr', dbPtrValue);
172
141
 
@@ -176,16 +145,16 @@ export default function SideMenu({
176
145
  ...{ loggedCheckDone: true },
177
146
  };
178
147
 
179
- dispatch({ type: "user", payload: userInfo });
148
+ dispatch({ type: 'user', payload: userInfo });
180
149
 
181
- history.push("/login");
150
+ history.push('/login');
182
151
 
183
- message.success("You have logged out successfully.");
152
+ message.success('You have logged out successfully.');
184
153
  } else {
185
154
  FirebaseUtils.logout().then(() => {
186
- history.push("/login");
155
+ history.push('/login');
187
156
 
188
- message.success("You have logged out successfully.");
157
+ message.success('You have logged out successfully.');
189
158
  });
190
159
  }
191
160
  }
@@ -194,15 +163,80 @@ export default function SideMenu({
194
163
  * Menu Click Function
195
164
  */
196
165
 
166
+ // const onMenuClick = (menu, index) => {
167
+ // setSelected([index]);
168
+
169
+ // history.push(menu.path);
170
+
171
+ // setMenu(menu);
172
+
173
+ // callback();
174
+ // };
175
+
197
176
  const onMenuClick = (menu, index) => {
198
- setSelected([index]);
177
+ const key = menu.path || `menu-${menu.id || index}`;
178
+
179
+ if (menu.isRoot) {
180
+ setOpenKeys([]);
181
+ }
182
+
183
+ setSelectedKeys([key]);
199
184
 
200
- history.push(menu.path);
185
+ if (menu.path) {
186
+ history.push(menu.path);
187
+ }
201
188
 
202
189
  setMenu(menu);
190
+ if (callback) callback();
191
+ };
192
+
193
+ /**
194
+ * Controls submenu open/close behavior.
195
+ *
196
+ * - Detects the most recently opened menu key.
197
+ * - When opening a submenu, keeps only its full parent path expanded
198
+ * (accordion behavior).
199
+ * - When closing a submenu, updates state without recalculating paths.
200
+ *
201
+ * @param {string[]} keys - Currently open menu keys from the Menu component.
202
+ *
203
+ * Assumptions:
204
+ * - Menu keys are stable and unique.
205
+ * - Only one menu branch should be open at a time.
206
+ */
207
+ const onOpenChange = (keys) => {
208
+ const latestOpenKey = keys.find((k) => !openKeys.includes(k));
209
+
210
+ if (!latestOpenKey) {
211
+ setOpenKeys(keys);
212
+ return;
213
+ }
203
214
 
204
- callback();
215
+ const findPath = (items) => {
216
+ const sortedItems = Menus.screenMenus(items, 'order');
217
+ for (const item of sortedItems) {
218
+ const key = String(item.id || item.path || item.caption);
219
+ if (key === latestOpenKey) {
220
+ return [key];
221
+ }
222
+ if (item.sub_menus) {
223
+ const childPath = findPath(item.sub_menus);
224
+ if (childPath) {
225
+ return [key, ...childPath];
226
+ }
227
+ }
228
+ }
229
+ return null;
230
+ };
231
+
232
+ const path = findPath(modules);
233
+ if (path) {
234
+ setOpenKeys(path);
235
+ } else {
236
+ setOpenKeys(keys);
237
+ }
205
238
  };
239
+
206
240
  /**
207
241
  * Function renders the footer logo
208
242
  *
@@ -210,15 +244,14 @@ export default function SideMenu({
210
244
  */
211
245
  function renderFooter(footerLogo) {
212
246
  return (
213
- <div className={`sidebar-footer ${!collapsed ? "open" : "close"}`}>
214
- <img className="footer-logo" src={footerLogo} alt={"footer-logo"} />
247
+ <div className={`sidebar-footer ${!collapsed ? 'open' : 'close'}`}>
248
+ <img className="footer-logo" src={footerLogo} alt={'footer-logo'} />
215
249
  </div>
216
250
  );
217
251
  }
218
252
 
219
253
  let icon;
220
254
 
221
-
222
255
  let index = 0;
223
256
 
224
257
  useEffect(() => {
@@ -227,6 +260,7 @@ export default function SideMenu({
227
260
  // document.documentElement.style.setProperty('--custom-text-color', state.theme.colors.colorText);
228
261
  }, [state.theme]);
229
262
 
263
+ const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.id || m.path || m.caption);
230
264
 
231
265
  return (
232
266
  <div className="sidemenu">
@@ -235,16 +269,19 @@ export default function SideMenu({
235
269
  <Skeleton.Avatar />
236
270
  <Skeleton.Button />
237
271
  {/* <Skeleton.Input /> */}
238
- </Space>) :
239
-
240
- <div className="intro" style={{ backgroundColor: state.theme.colors.leftSectionBackground ,borderBottom: `2px solid ${state.theme.colors.borderColor}`}}>
272
+ </Space>
273
+ ) : (
274
+ <div
275
+ className="intro"
276
+ style={{ backgroundColor: state.theme.colors.leftSectionBackground, borderBottom: `2px solid ${state.theme.colors.borderColor}` }}
277
+ >
241
278
  {/* Logo Bar */}
242
279
  <div className="logo-wrapper">
243
280
  {/* Changing the size of logo bar according to the value of collapsed */}
244
281
  <img
245
- className={`sidemenu-brand-logo ${!collapsed ? "open" : "close"}`}
282
+ className={`sidemenu-brand-logo ${!collapsed ? 'open' : 'close'}`}
246
283
  onClick={() => {
247
- history.push("/");
284
+ history.push('/');
248
285
  }}
249
286
  src={brandLogo}
250
287
  alt="Logo"
@@ -255,25 +292,26 @@ export default function SideMenu({
255
292
  {/* If value of collapsed is true show version else not showing */}
256
293
 
257
294
  {!collapsed && process.env.REACT_APP_package_version && (
258
- <small style={{
259
-
260
- color: state.theme.colors.leftSectionColor,
261
- }}>{process.env.REACT_APP_package_version}</small>
262
- )}
295
+ <small
296
+ style={{
297
+ color: state.theme.colors.leftSectionColor,
298
+ }}
299
+ >
300
+ {process.env.REACT_APP_package_version}
301
+ </small>
302
+ )}
263
303
  </div>
264
304
  {/* Logo Bar Ends */}
265
305
 
266
306
  {/* If value of collapsed is true render header else not rendering */}
267
307
 
268
- {!collapsed ? renderCustomHeader() : ""}
269
-
308
+ {!collapsed ? renderCustomHeader() : ''}
270
309
  </div>
271
- }
310
+ )}
272
311
 
273
312
  {/* Intro Component */}
274
313
  {/* Intro Component Ends */}
275
314
 
276
-
277
315
  {/* Search for Queries */}
278
316
 
279
317
  {/* <div className="menu-search">
@@ -285,15 +323,11 @@ export default function SideMenu({
285
323
 
286
324
  {/* Search for Queries Ends */}
287
325
 
288
- <div className={`menu-item ${!collapsed ? "open" : "close"}`} style={{ backgroundColor: state.theme.colors.leftSectionBackground}}>
289
-
326
+ <div className={`menu-item ${!collapsed ? 'open' : 'close'}`} style={{ backgroundColor: state.theme.colors.leftSectionBackground }}>
290
327
  {loading ? (
291
328
  <></>
292
329
  ) : (
293
-
294
-
295
330
  <Menu
296
-
297
331
  // selectedKeys={[selected]}
298
332
  // style={{ width: 256 }}
299
333
  // defaultSelectedKeys={selected}
@@ -301,10 +335,12 @@ export default function SideMenu({
301
335
  inlineCollapsed={collapsed}
302
336
  mode="inline"
303
337
  theme={process.env.REACT_APP_THEME}
304
- style={{ backgroundColor: state.theme.colors.leftSectionBackground ,color:state.theme.colors.leftSectionColor}}
305
- // theme={''}
338
+ selectedKeys={selectedKeys}
339
+ openKeys={openKeys}
340
+ onOpenChange={onOpenChange}
341
+ style={{ backgroundColor: state.theme.colors.leftSectionBackground, color: state.theme.colors.leftSectionColor }}
342
+ // theme={''}
306
343
  >
307
-
308
344
  {/* <Menu.Item
309
345
  onClick={() => {
310
346
  setSelected([1]);
@@ -324,7 +360,6 @@ export default function SideMenu({
324
360
  />
325
361
  </Menu.Item> */}
326
362
 
327
-
328
363
  {/* {
329
364
  Menus.screenMenus(modules).map((menu, index) => {
330
365
 
@@ -332,7 +367,7 @@ export default function SideMenu({
332
367
  })
333
368
  } */}
334
369
 
335
- {Menus.screenMenus(modules, "order")
370
+ {Menus.screenMenus(modules, 'order')
336
371
 
337
372
  .filter((record) => {
338
373
  icon = record;
@@ -354,207 +389,133 @@ export default function SideMenu({
354
389
  .map((menu, index) => {
355
390
  // return <MenuItem menu={menu} index={index} />
356
391
 
357
- let sub_menus =
358
- menu && menu.sub_menus
359
- ? Menus.screenMenus(menu.sub_menus)
360
- : [];
392
+ let sub_menus = menu && menu.sub_menus ? Menus.screenMenus(menu.sub_menus) : [];
361
393
 
362
394
  if (menu && sub_menus && sub_menus.length) {
363
-
364
- let randomIndex = parseInt(Math.random() * 10000000000);
395
+ // let randomIndex = parseInt(Math.random() * 10000000000);
365
396
 
366
397
  return (
367
-
368
398
  <SubMenu
369
-
370
399
  className="popup"
371
- style={{ color:state.theme.colors.leftSectionColor}}
372
-
373
- key={`first-level-${randomIndex}-${menu.caption}`}
400
+ style={{ color: state.theme.colors.leftSectionColor }}
401
+ // key={`first-level-${randomIndex}-${menu.caption}`}
374
402
 
403
+ key={menu.id || menu.path || menu.caption}
375
404
  title={
376
-
377
405
  <>
378
-
379
406
  <CollapsedIconMenu
380
-
381
407
  menu={menu}
382
-
383
408
  caption={menu.caption}
384
-
385
409
  icon={menu.icon_name || 'fa-solid fas fa-user'}
386
-
387
-
388
410
  collapsed={collapsed}
389
-
390
411
  />
391
-
392
-
393
-
394
412
  </>
395
-
396
413
  }
397
-
398
414
  >
399
415
  {sub_menus.map((submenu, innerIndex) => {
400
- let randomIndex = parseInt(Math.random() * 10000000000);
416
+ // let randomIndex = parseInt(Math.random() * 10000000000);
401
417
 
402
- let third_menus =
403
- submenu && submenu.sub_menus
404
- ? Menus.screenMenus(submenu.sub_menus)
405
- : [];
418
+ let third_menus = submenu && submenu.sub_menus ? Menus.screenMenus(submenu.sub_menus) : [];
406
419
 
407
420
  if (third_menus && third_menus.length) {
408
421
  return (
409
422
  <SubMenu
410
-
411
423
  className="popup"
424
+ // key={`second-level-${randomIndex}-${submenu.id}`}
412
425
 
413
- key={`second-level-${randomIndex}-${submenu.id}`}
414
-
426
+ key={submenu.id || submenu.path || submenu.caption}
415
427
  title={
416
-
417
428
  <span>
418
-
419
429
  <CollapsedIconMenu
420
-
421
430
  menu={menu}
422
-
423
431
  caption={submenu.caption}
424
-
425
432
  icon={submenu.icon_name || 'fa-solid fas fa-user'}
426
-
427
433
  collapsed={collapsed}
428
-
429
434
  />
430
-
431
-
432
435
  </span>
433
436
  }
434
437
  >
435
438
  {third_menus.map((menu) => {
436
- let randomIndex = parseInt(
437
- Math.random() * 10000000000
438
- );
439
+ // let randomIndex = parseInt(Math.random() * 10000000000);
439
440
 
440
441
  return (
441
442
  <Menu.Item
443
+ // onClick={() => {
444
+ // onMenuClick(menu, index);
445
+ // }}
442
446
  onClick={() => {
443
- onMenuClick(menu, index);
447
+ onMenuClick({ ...menu, parentKey: submenu.path || submenu.caption }, index);
444
448
  }}
445
- key={`second-level-${randomIndex}-${index}`}
446
- >
449
+ // key={`second-level-${randomIndex}-${index}`}
447
450
 
451
+ key={menu.path || menu.caption}
452
+ >
448
453
  <CollapsedIconMenu
449
-
450
454
  menu={menu}
451
-
452
455
  caption={menu.caption}
453
-
454
456
  icon={menu.icon_name || 'fa-solid fas fa-user'}
455
-
456
-
457
457
  collapsed={collapsed}
458
-
459
458
  />
460
-
461
459
  </Menu.Item>
462
460
  );
463
461
  })}
464
462
  </SubMenu>
465
463
  );
466
464
  } else {
467
- let randomIndex = parseInt(
468
- Math.random() * 10000000000
469
- );
465
+ // let randomIndex = parseInt(Math.random() * 10000000000);
470
466
 
471
467
  return (
472
468
  <Menu.Item
469
+ // onClick={() => {
470
+ // onMenuClick(submenu, index);
471
+ // }}
473
472
  onClick={() => {
474
- onMenuClick(submenu, index);
473
+ onMenuClick({ ...submenu, parentKey: menu.path || menu.caption }, index);
475
474
  }}
476
- key={`first-level-${randomIndex}-${innerIndex}`}
475
+ // key={`first-level-${randomIndex}-${innerIndex}`}
476
+ key={submenu.path || submenu.caption}
477
477
  >
478
-
479
478
  <CollapsedIconMenu
480
-
481
479
  menu={menu}
482
-
483
480
  caption={submenu.caption}
484
-
485
481
  icon={submenu.icon_name || 'fa-solid fas fa-user'}
486
-
487
-
488
482
  collapsed={collapsed}
489
-
490
483
  />
491
-
492
484
  </Menu.Item>
493
-
494
485
  );
495
-
496
486
  }
497
-
498
487
  })}
499
-
500
488
  </SubMenu>
501
-
502
489
  );
503
490
  } else {
504
- let randomIndex = parseInt(Math.random() * 10000000000);
491
+ // let randomIndex = parseInt(Math.random() * 10000000000);
505
492
 
506
493
  return (
507
-
508
494
  <Menu.Item
495
+ // onClick={() => {
496
+ // onMenuClick(menu, index);
497
+ // }}
498
+
509
499
  onClick={() => {
510
- onMenuClick(menu, index);
500
+ onMenuClick({ ...menu, parentKey: menu.path || menu.caption, isRoot: true }, index);
511
501
  }}
512
- key={`${menu.id}-${randomIndex}`}
502
+ // key={`${menu.id}-${randomIndex}`}
503
+ key={menu.path || menu.caption}
513
504
  >
514
-
515
- <CollapsedIconMenu
516
-
517
- menu={menu}
518
-
519
- caption={menu.caption}
520
-
521
- icon={menu.icon_name || 'fa-solid fas fa-user'}
522
-
523
-
524
- collapsed={collapsed}
525
-
526
- />
527
-
528
-
505
+ <CollapsedIconMenu menu={menu} caption={menu.caption} icon={menu.icon_name || 'fa-solid fas fa-user'} collapsed={collapsed} />
529
506
  </Menu.Item>
530
-
531
507
  );
532
508
  }
533
509
  })}
534
510
 
535
-
536
- {
537
- loading ? <div class="skeleton-wrapper"></div> : <Menu.Item onClick={handleLogout} key="logout-button">
538
-
539
- <CollapsedIconMenu
540
-
541
- caption="Logout"
542
-
543
- icon='fa-solid fas fa-user'
544
-
545
- collapsed={collapsed}
546
-
547
- />
548
-
549
-
511
+ {loading ? (
512
+ <div class="skeleton-wrapper"></div>
513
+ ) : (
514
+ <Menu.Item onClick={handleLogout} key="logout-button">
515
+ <CollapsedIconMenu caption="Logout" icon="fa-solid fas fa-user" collapsed={collapsed} />
550
516
  </Menu.Item>
551
- }
552
-
553
-
554
-
555
-
517
+ )}
556
518
  </Menu>
557
-
558
519
  )}
559
520
 
560
521
  {/* Footer Logo */}
@@ -570,16 +531,14 @@ export default function SideMenu({
570
531
  * sub menus
571
532
  */
572
533
  function MenuItem({ menu, index, history }) {
573
-
574
534
  function renderMenus({ menu, index }) {
575
535
  let sub_menus = [];
576
536
 
577
537
  if (menu.sub_menus) {
578
- sub_menus = Menus.screenMenus(menu.sub_menus)
538
+ sub_menus = Menus.screenMenus(menu.sub_menus);
579
539
  }
580
540
 
581
541
  if (menu && sub_menus && sub_menus.length) {
582
-
583
542
  // return 'hello';
584
543
  return (
585
544
  <SubMenu
@@ -591,9 +550,7 @@ function MenuItem({ menu, index, history }) {
591
550
  }
592
551
  >
593
552
  {menu.sub_menus.map((menu, index) => {
594
- return (
595
- <MenuItem menu={menu} index={menu.id} />
596
- );
553
+ return <MenuItem menu={menu} index={menu.id} />;
597
554
  })}
598
555
  </SubMenu>
599
556
  );
@@ -611,7 +568,5 @@ function MenuItem({ menu, index, history }) {
611
568
  }
612
569
  }
613
570
 
614
-
615
571
  return renderMenus({ menu, index });
616
-
617
- }
572
+ }