@argusoft/medplat-app-shell 1.0.5 → 1.0.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.
Files changed (263) hide show
  1. package/package.json +139 -141
  2. package/src/GlobalErrorBoundary.jsx +31 -0
  3. package/src/SilentErrorFallback.jsx +68 -0
  4. package/src/TrackingProviderWrapper.jsx +40 -0
  5. package/src/_tests_/__mocks__/MockTranslationProvider.jsx +21 -0
  6. package/src/_tests_/__mocks__/ckeditor.js +45 -0
  7. package/src/_tests_/__mocks__/fileMock.js +1 -0
  8. package/src/_tests_/__mocks__/useranalytics.js +5 -0
  9. package/src/_tests_/views/components/Dashboard/DashboardUI.test.jsx +137 -0
  10. package/src/_tests_/views/components/Dashboard/DashboardUIMock.js +877 -0
  11. package/src/_tests_/views/components/ForgotPassword/ForgotPassword.test.jsx +314 -0
  12. package/src/_tests_/views/components/LocationDirective/LocationDirective.test.jsx.disable +229 -0
  13. package/src/_tests_/views/components/LocationDirective/mockLocationDirective.js +810 -0
  14. package/src/_tests_/views/components/LocationType/MockLocationType.js +42259 -0
  15. package/src/_tests_/views/components/LocationType/addlocationtype.test.jsx.disable +276 -0
  16. package/src/_tests_/views/components/LocationType/editlocationtype.test.jsx.disable +262 -0
  17. package/src/_tests_/views/components/LocationType/locationtype.test.jsx.disable +148 -0
  18. package/src/_tests_/views/components/Profile/UpdateProfileModalData.js +4396 -0
  19. package/src/_tests_/views/components/Profile/updateprofilemodal.test.jsx +282 -0
  20. package/src/_tests_/views/components/SideBar/MockSideBar.js +1379 -0
  21. package/src/_tests_/views/components/SideBar/SideBar.test.jsx +98 -0
  22. package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/AddManageSystemConfig.test.jsx.disable +164 -0
  23. package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/UpdateManageSystemConfig.test.jsx.disable +157 -0
  24. package/src/_tests_/views/components/SystemConfig/MockSystemConfig.js +1280 -0
  25. package/src/_tests_/views/components/SystemConfig/SystemConfig.test.jsx.disable +165 -0
  26. package/src/_tests_/views/components/login/Login.test.jsx +276 -0
  27. package/src/_tests_/views/components/login/MockAuthorise.js +2414 -0
  28. package/src/_tests_/views/components/login/ServiceResponse.js +595 -0
  29. package/src/_tests_/views/components/user/MockManageUser.js +7965 -0
  30. package/src/_tests_/views/components/user/manageuser.test.jsx.disable +989 -0
  31. package/src/_tests_/views/components/user/mockUsersData.js +582 -0
  32. package/src/assets/img/OASISLogin.png +0 -0
  33. package/src/assets/img/bahaarNew.png +0 -0
  34. package/src/assets/img/dnhdd4K.png +0 -0
  35. package/src/assets/img/govtofup.png +0 -0
  36. package/src/assets/img/sewarural4K.png +0 -0
  37. package/src/assets/img/techo4K.png +0 -0
  38. package/src/assets/img/up4K.png +0 -0
  39. package/src/common/HolidayList.jsx +573 -0
  40. package/src/common/MalaciaousInputUtil.js +23 -0
  41. package/src/common/SafeHtml.jsx +17 -0
  42. package/src/common/VersionManager.jsx +109 -0
  43. package/src/common/constants/PerformanceDashboard.js +514 -0
  44. package/src/common/constants/app.constant.js +781 -0
  45. package/src/common/constants/cccVerificationConstants.js +18 -0
  46. package/src/common/constants/fhsrConstant.js +33 -0
  47. package/src/common/constants/gvk-verification.constant.js +76 -0
  48. package/src/common/constants/search.constant.js +23 -0
  49. package/src/common/constants/teleconsulatationConstant.jsx +1339 -0
  50. package/src/common/directives/SearchTemplate.jsx +784 -0
  51. package/src/common/directives/SearchTemplate.scss +14 -0
  52. package/src/common/dynamicView/DynamicView.jsx +353 -0
  53. package/src/common/dynamicView/InputFieldComponent.jsx +1501 -0
  54. package/src/common/dynamicView/InputViewComponent.jsx +298 -0
  55. package/src/common/dynamicView/InputViewComponent.scss +15 -0
  56. package/src/common/env.js +5 -0
  57. package/src/common/filters/locationNameFilter.js +26 -0
  58. package/src/common/fontAwesomeIcons/FontAwesomeIcons.jsx +27 -0
  59. package/src/common/fontAwesomeIcons/FontAwesomeIconsNames.js +1968 -0
  60. package/src/common/fontPreferences/fontSizeProvider.jsx +34 -0
  61. package/src/common/fontPreferences/fontSizeSelector.jsx +116 -0
  62. package/src/common/getAssignedFeature/getAssignedFeature.js +32 -0
  63. package/src/common/interceptors/AxiosInterceptor.js +216 -0
  64. package/src/common/languageTranslator/TranslationContext.js +5 -0
  65. package/src/common/languageTranslator/TranslationProvider.jsx +24 -0
  66. package/src/common/languageTranslator/i18n.js +49 -0
  67. package/src/common/services/AuthenticateService.js +116 -0
  68. package/src/common/services/DownloadFile.js +35 -0
  69. package/src/common/services/ForgotPassword.js +18 -0
  70. package/src/common/services/FormConfiguratorService.js +195 -0
  71. package/src/common/services/GlobalApis.js +84 -0
  72. package/src/common/services/InterceptorNavigationService.js +17 -0
  73. package/src/common/services/LocationService.js +65 -0
  74. package/src/common/services/LocationType.js +11 -0
  75. package/src/common/services/QueryBuilder.js +36 -0
  76. package/src/common/services/Roles.js +28 -0
  77. package/src/common/services/SyncWithServer.js +15 -0
  78. package/src/common/services/SystemConfig.js +15 -0
  79. package/src/common/services/TranslationService.js +70 -0
  80. package/src/common/services/TwoFactorService.js +7 -0
  81. package/src/common/services/Users.js +91 -0
  82. package/src/common/services/Webtasks.js +27 -0
  83. package/src/common/services/util/Convert-pad-data-to-API-format.jsx +167 -0
  84. package/src/common/services/util/Convert-to-UI-format.jsx +82 -0
  85. package/src/common/services/util/EmptyPrescriptionPadData.jsx +11 -0
  86. package/src/common/services/util/GeneralUtil.js +456 -0
  87. package/src/common/services/util/Prescription-pad-util.js +339 -0
  88. package/src/common/services/util/PrescriptionPadData.js +67 -0
  89. package/src/common/services/util/PrescriptionpadCommonUtil.js +96 -0
  90. package/src/common/services/util/ReportFieldUtil.jsx +398 -0
  91. package/src/common/services/util/WebSocketContext.jsx +261 -0
  92. package/src/common/syncWithServer/SyncWithServerDialog.jsx +170 -0
  93. package/src/common/syncWithServer/SyncWithServerDialogSkeleton.jsx +67 -0
  94. package/src/common/tests/CustomWrapper.jsx +49 -0
  95. package/src/common/tests/TranslationWrapper.jsx +38 -0
  96. package/src/common/themeProvider/ColorInputs.jsx +97 -0
  97. package/src/common/themeProvider/EditableColorInput.jsx +128 -0
  98. package/src/common/themeProvider/ThemeEditor.jsx +319 -0
  99. package/src/common/themeProvider/ThemeProvider.jsx +210 -0
  100. package/src/common/themeProvider/themeConfig.js +558 -0
  101. package/src/common/toaster/toaster.jsx +30 -0
  102. package/src/firebaseConfig.js +24 -0
  103. package/src/global.scss +221 -0
  104. package/src/hooks/.gitkeep +0 -0
  105. package/src/hooks/useAESEncryption.js +56 -0
  106. package/src/hooks/useCaching.js +43 -0
  107. package/src/hooks/useDebounce.js +34 -0
  108. package/src/hooks/useDebounceFn.js +50 -0
  109. package/src/hooks/useDownloadPdf.js +358 -0
  110. package/src/hooks/useDownloadXlsx.js +55 -0
  111. package/src/hooks/useListValueFieldValues.js +30 -0
  112. package/src/hooks/useLocationHierarchies.js +63 -0
  113. package/src/hooks/useLocationHierarchyTranslate.js +16 -0
  114. package/src/hooks/useOnline.js +27 -0
  115. package/src/hooks/usePagination.js +63 -0
  116. package/src/hooks/useRefreshToken.js +87 -0
  117. package/src/hooks/useScript.js +25 -0
  118. package/src/hooks/useStopwatch.js +75 -0
  119. package/src/hooks/useTrackEvent.js +22 -0
  120. package/src/hooks/useWebAudioRecorder.js +115 -0
  121. package/src/layout/LoaderComponet.jsx +22 -0
  122. package/src/layout/LoaderContext.jsx +29 -0
  123. package/src/layout/mainLayout/AdaptiveZoom.jsx +27 -0
  124. package/src/layout/mainLayout/Chatbot.jsx +243 -0
  125. package/src/layout/mainLayout/Layout.jsx +445 -0
  126. package/src/layout/mainLayout/Profile/UpdateProfileModal.jsx +684 -0
  127. package/src/layout/mainLayout/header/LogoutModal.jsx +131 -0
  128. package/src/layout/mainLayout/header/Navbar.jsx +1677 -0
  129. package/src/layout/mainLayout/header/Navbar.scss +4 -0
  130. package/src/layout/mainLayout/header/index.js +0 -0
  131. package/src/layout/mainLayout/sidebar/SideBar.jsx +1402 -0
  132. package/src/layout/mainLayout/sidebar/Sidebar.css +159 -0
  133. package/src/layout/mainLayout/sidebar/index.js +0 -0
  134. package/src/logo.svg +1 -0
  135. package/src/reportWebVitals.js +13 -0
  136. package/src/setupFirebaseMessaging.js +28 -0
  137. package/src/setupTests.js +8 -0
  138. package/src/store/actions/AuthenticationActions.js +0 -0
  139. package/src/store/actions/ReportsActions.js +0 -0
  140. package/src/store/actions/TranslationAction.js +0 -0
  141. package/src/store/index.js +8 -0
  142. package/src/store/reducer.js +46 -0
  143. package/src/store/reducers/AuthenticationReducer.js +50 -0
  144. package/src/store/reducers/CalendarEventReducer.js +41 -0
  145. package/src/store/reducers/ConditionClipboardReducer.js +45 -0
  146. package/src/store/reducers/FeatureReducer.js +27 -0
  147. package/src/store/reducers/FormConfiguratorReducer.js +38 -0
  148. package/src/store/reducers/LoadingReducer.js +20 -0
  149. package/src/store/reducers/MembersAuthenticationReducer.js +28 -0
  150. package/src/store/reducers/PrescriptionPadReducer.js +329 -0
  151. package/src/store/reducers/QuestionaireReducer.js +29 -0
  152. package/src/store/reducers/ReportsReducer.js +24 -0
  153. package/src/store/reducers/SkeletonReducer.js +20 -0
  154. package/src/store/reducers/ThemeReducer.js +106 -0
  155. package/src/store/reducers/TranslationReducer.js +126 -0
  156. package/src/store/reducers/dashboardEditorSlice.js +77 -0
  157. package/src/store/reducers/districtHealthDashboardSlice.js +58 -0
  158. package/src/store/reducers/immunizationSlice.js +234 -0
  159. package/src/store/slices/dashboardPagesSlice.js +51 -0
  160. package/src/utils/.gitkeep +0 -0
  161. package/src/utils/FormConstants.js +2629 -0
  162. package/src/utils/GujaratTopoChart.jsx +483 -0
  163. package/src/utils/UUIDgenerator.js +8 -0
  164. package/src/utils/appointment-utils/appointment-utils.js +123 -0
  165. package/src/utils/feature.js +42 -0
  166. package/src/utils/getThemeColor.js +12 -0
  167. package/src/utils/localStorageHelper.js +11 -0
  168. package/src/utils/notifications/enable-push-notifications.js +27 -0
  169. package/src/utils/resolveAppliedStyle.js +11 -0
  170. package/src/utils/themeConfigs.js +1483 -0
  171. package/src/views/custom-components/.gitkeep +0 -0
  172. package/src/views/custom-components/AgIconButton/RIf.jsx +14 -0
  173. package/src/views/custom-components/AgIconButton/button.jsx +108 -0
  174. package/src/views/custom-components/AgIconButton/waterDrop.jsx +95 -0
  175. package/src/views/custom-components/AgIconButton/waterDrop.scss +37 -0
  176. package/src/views/custom-components/AlertPlaceholder.jsx +32 -0
  177. package/src/views/custom-components/AllFaIconsSelector.jsx +56 -0
  178. package/src/views/custom-components/CkEditor/CkEditor.js +102 -0
  179. package/src/views/custom-components/CustomAccordion.jsx +72 -0
  180. package/src/views/custom-components/CustomActionIcons.jsx +118 -0
  181. package/src/views/custom-components/CustomAutoComplete.jsx +188 -0
  182. package/src/views/custom-components/CustomCheckBox.jsx +60 -0
  183. package/src/views/custom-components/CustomConfirmationModal.jsx +118 -0
  184. package/src/views/custom-components/CustomCountrySelect.jsx +129 -0
  185. package/src/views/custom-components/CustomDatePicker.jsx +122 -0
  186. package/src/views/custom-components/CustomDropdown.jsx +191 -0
  187. package/src/views/custom-components/CustomFileUpload.jsx +387 -0
  188. package/src/views/custom-components/CustomFullCalendar.jsx +514 -0
  189. package/src/views/custom-components/CustomInfiniteScroll.jsx +126 -0
  190. package/src/views/custom-components/CustomRadioComponent.jsx +65 -0
  191. package/src/views/custom-components/CustomStatsComponent.jsx +114 -0
  192. package/src/views/custom-components/CustomSvgUpload.jsx +170 -0
  193. package/src/views/custom-components/CustomSwitch.jsx +37 -0
  194. package/src/views/custom-components/CustomTabPanel.jsx +19 -0
  195. package/src/views/custom-components/CustomTextArea.jsx +62 -0
  196. package/src/views/custom-components/CustomTextArea.scss +27 -0
  197. package/src/views/custom-components/CustomTextField.jsx +116 -0
  198. package/src/views/custom-components/CustomToggleSwitch.jsx +138 -0
  199. package/src/views/custom-components/CustomTooltip.jsx +51 -0
  200. package/src/views/custom-components/CustomZoomImage.jsx +134 -0
  201. package/src/views/custom-components/CustomizedTable/CustomizedTableV2.jsx +1407 -0
  202. package/src/views/custom-components/CustomizedTable/VirtualizeTableBody.jsx +295 -0
  203. package/src/views/custom-components/CustomizedTable/helper.jsx +159 -0
  204. package/src/views/custom-components/CustomizedTable.jsx +532 -0
  205. package/src/views/custom-components/EditInputField.jsx +174 -0
  206. package/src/views/custom-components/FieldDescription.jsx +22 -0
  207. package/src/views/custom-components/FileDisplayComponent.jsx +138 -0
  208. package/src/views/custom-components/FormItem.jsx +53 -0
  209. package/src/views/custom-components/GenericChart.jsx +80 -0
  210. package/src/views/custom-components/InfoBadge.jsx +60 -0
  211. package/src/views/custom-components/PostgresEditor.jsx +801 -0
  212. package/src/views/custom-components/ResizableEditAutocompleteField.jsx +249 -0
  213. package/src/views/custom-components/ResizableEditInputField.jsx +215 -0
  214. package/src/views/custom-components/ResizeableEditSelectField.jsx +197 -0
  215. package/src/views/custom-components/SideOverlay.jsx +113 -0
  216. package/src/views/custom-components/SideOverlay.scss +42 -0
  217. package/src/views/custom-components/calendar.scss +571 -0
  218. package/src/views/feature-components/.gitkeep +0 -0
  219. package/src/views/feature-components/Dashboard/DashboardUI.jsx +1043 -0
  220. package/src/views/feature-components/Dashboard/DhnddModal/AshaDataQualityVerificationModal.jsx +278 -0
  221. package/src/views/feature-components/Dashboard/PinFeatureModal.jsx +143 -0
  222. package/src/views/feature-components/Dashboard/QuickLinks.jsx +163 -0
  223. package/src/views/feature-components/Dashboard/Taskbar.jsx +56 -0
  224. package/src/views/feature-components/Dashboard/WebtasksFilterForm.jsx +109 -0
  225. package/src/views/feature-components/Dashboard/WidgetCard.jsx +161 -0
  226. package/src/views/feature-components/Dashboard/actionModal.jsx +263 -0
  227. package/src/views/feature-components/Dashboard/ekavachModal/HealthWorkerIncorrectDetailsModal.jsx +332 -0
  228. package/src/views/feature-components/Dashboard/ekavachModal/MoMaternalDeathVerifcationModal.jsx +275 -0
  229. package/src/views/feature-components/Dashboard/ekavachModal/MoVerficationForChildScreeningMoadal.jsx +566 -0
  230. package/src/views/feature-components/FeatureUsageAnalytics/FeatureUsageAnalytics.jsx +989 -0
  231. package/src/views/feature-components/Features/NewServerManagement.jsx +217 -0
  232. package/src/views/feature-components/Features/ServerManagement.scss +120 -0
  233. package/src/views/feature-components/ForgotPassword/ForgotPassword.jsx +226 -0
  234. package/src/views/feature-components/LocationDirective/LocationDirective.jsx +992 -0
  235. package/src/views/feature-components/LocationDirective/LocationDirectiveV2.jsx +909 -0
  236. package/src/views/feature-components/NotFound.jsx +66 -0
  237. package/src/views/feature-components/Onboarding/Onboarding.jsx +1400 -0
  238. package/src/views/feature-components/Skeletons.js +115 -0
  239. package/src/views/feature-components/Unauthorized.jsx +48 -0
  240. package/src/views/feature-components/VerifyRoute.jsx +88 -0
  241. package/src/views/feature-components/YearlyRecap/YearlyRecap.jsx +357 -0
  242. package/src/views/feature-components/YearlyRecap/components/RecapSlide.jsx +183 -0
  243. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationContext.js +5 -0
  244. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationProvider.jsx +26 -0
  245. package/src/views/feature-components/YearlyRecap/languageTranslator/i18n.js +46 -0
  246. package/src/views/feature-components/YearlyRecap/languageTranslator/translations.json +167 -0
  247. package/src/views/feature-components/YearlyRecap/slides/IntroSlide.jsx +233 -0
  248. package/src/views/feature-components/YearlyRecap/slides/MaternalHealthSlide.jsx +146 -0
  249. package/src/views/feature-components/YearlyRecap/slides/MetricSlide.jsx +227 -0
  250. package/src/views/feature-components/YearlyRecap/slides/OutroSlide.jsx +701 -0
  251. package/src/views/feature-components/YearlyRecap/slides/ReachSlide.jsx +273 -0
  252. package/src/views/feature-components/login/Login.jsx +840 -0
  253. package/src/views/feature-components/login/Login.scss +154 -0
  254. package/src/views/feature-components/login/LoginConfigurator.jsx +1149 -0
  255. package/src/views/feature-components/login/TwoFactorSetupModal.jsx +411 -0
  256. package/src/views/feature-components/login/simplifyMenu.js +45 -0
  257. package/src/views/feature-components/system-config/ManageSystemConfigs.jsx +284 -0
  258. package/src/views/feature-components/system-config/SystemConfig.jsx +299 -0
  259. package/src/views/feature-components/users/ChangePasswordModal.jsx +243 -0
  260. package/src/views/feature-components/users/PasswordField.jsx +56 -0
  261. package/dist/index.css +0 -1
  262. package/dist/index.js +0 -32001
  263. package/dist/index.js.map +0 -1
@@ -0,0 +1,1677 @@
1
+ // src/components/Navbar.js
2
+
3
+ import '@/layout/mainLayout/header/Navbar.scss';
4
+ import UpdateUserModal from '@/layout/mainLayout/Profile/UpdateProfileModal';
5
+ import LogoutModal from '@/layout/mainLayout/header/LogoutModal';
6
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7
+ import { Backdrop } from '@mui/material';
8
+
9
+ import {
10
+ Autocomplete,
11
+ Avatar,
12
+ Box,
13
+ ButtonBase,
14
+ Divider,
15
+ ListItemIcon,
16
+ Modal,
17
+ Select,
18
+ TextField,
19
+ Tooltip,
20
+ useTheme,
21
+ Switch,
22
+ InputAdornment,
23
+ Popper,
24
+ Accordion,
25
+ AccordionSummary,
26
+ AccordionDetails,
27
+ } from '@mui/material';
28
+ import AppBar from '@mui/material/AppBar';
29
+ import Menu from '@mui/material/Menu';
30
+ import MenuItem from '@mui/material/MenuItem';
31
+ import Toolbar from '@mui/material/Toolbar';
32
+ import Typography from '@mui/material/Typography';
33
+ import { alpha } from '@mui/material/styles';
34
+ import { useTranslation } from 'react-i18next';
35
+ import { useDispatch, useSelector } from 'react-redux';
36
+ import useTrackEvent from '@/hooks/useTrackEvent';
37
+
38
+ import { useTranslateProvider } from '@/common/languageTranslator/TranslationContext';
39
+ import { execute } from '@/common/services/QueryBuilder';
40
+ import { getAllLanguages } from '@/common/services/TranslationService';
41
+ import { showToast } from '@/common/toaster/toaster';
42
+ import { resetStore } from '@/store/reducers/AuthenticationReducer';
43
+ import { setlanguageMap, setTranslation, setTranslationLanguage } from '@/store/reducers/TranslationReducer';
44
+ import { Grid } from '@mui/system';
45
+ import PropTypes from 'prop-types';
46
+ import { lazy, memo, Suspense, useCallback, useEffect, useRef, useState } from 'react';
47
+ import { Link, useNavigate } from 'react-router-dom';
48
+ import FontSizeSelectorWrapper from '@/common/fontPreferences/fontSizeSelector';
49
+ import { logout } from '@/common/services/AuthenticateService';
50
+ import {
51
+ updateCanSetPersonalizedTheme,
52
+ setPersonilizedThemes,
53
+ toggleTheme,
54
+ setTheme,
55
+ } from '@/store/reducers/ThemeReducer';
56
+ import { useMediaQuery, IconButton } from '@mui/material';
57
+ import { enablePushNotifications } from '@/utils/notifications/enable-push-notifications';
58
+ import {
59
+ // faBorderAll,
60
+ faChevronDown,
61
+ faChevronRight,
62
+ faGear,
63
+ faKey,
64
+ faMagnifyingGlass,
65
+ faRightFromBracket,
66
+ faQuestionCircle,
67
+ faCalendarDays,
68
+ faPhone,
69
+ faSliders,
70
+ } from '@fortawesome/free-solid-svg-icons';
71
+
72
+ import GeneralUtil from '@/common/services/util/GeneralUtil';
73
+ import { clearAllExceptPinned } from '@/utils/localStorageHelper';
74
+
75
+ const ChangePasswordModal = lazy(() => import('@/views/feature-components/users/ChangePasswordModal'));
76
+
77
+ const normalizeLanguageKey = (languageKey) => languageKey?.toString().toLowerCase();
78
+ const getLanguageKey = (languageItem) => normalizeLanguageKey(languageItem?.languageKey || languageItem?.language_key);
79
+
80
+ const getLanguageIdFromStorage = () => {
81
+ const savedLanguage = parseInt(localStorage.getItem('translation_language'));
82
+ return Number.isNaN(savedLanguage) ? null : savedLanguage;
83
+ };
84
+
85
+ const SearchModal = memo(function SearchModal({
86
+ open,
87
+ handleClose,
88
+ recentSearches = [],
89
+ allMenu = [],
90
+ navigate,
91
+ setRecentSearches,
92
+ }) {
93
+ const [searchText, setSearchText] = useState('');
94
+ const [searched, setSearched] = useState(allMenu);
95
+
96
+ useEffect(() => {
97
+ if (open) {
98
+ setSearchText('');
99
+ setSearched(allMenu);
100
+ }
101
+ }, [open, allMenu]);
102
+
103
+ const handleSearchActivity = (search) => {
104
+ const filtered = allMenu.filter((item) => item.name?.toLowerCase().includes(search.toLowerCase()));
105
+ setSearched(filtered);
106
+ };
107
+
108
+ useEffect(() => {
109
+ handleSearchActivity(searchText);
110
+ }, [searchText]);
111
+
112
+ const inputRef = useRef(null);
113
+
114
+ const handleBoxClick = () => {
115
+ inputRef.current?.focus();
116
+ };
117
+
118
+ const handleNavigation = (selectedOption) => {
119
+ if (!selectedOption) return;
120
+
121
+ const data = selectedOption;
122
+ let path = GeneralUtil.naviagationFunc(data);
123
+ // is ekavach then only open in new tab
124
+ if (path.includes('report/view/')) {
125
+ if (GeneralUtil.isEkavach) {
126
+ window.open(`/ui/${path}`, '_blank');
127
+ } else {
128
+ navigate(path);
129
+ }
130
+ } else {
131
+ navigate(path);
132
+ }
133
+ setSearchText('');
134
+ handleClose();
135
+ setRecentSearches((prev) => {
136
+ const isDuplicate = prev.some((item) => item.selectedOption?.navigationState === selectedOption?.navigationState);
137
+ if (isDuplicate) {
138
+ return prev;
139
+ }
140
+ return [
141
+ {
142
+ selectedOption,
143
+ title: selectedOption?.label,
144
+ description: selectedOption?.navigationState?.replace(/\./g, ' / '),
145
+ },
146
+ ...prev,
147
+ ];
148
+ });
149
+ };
150
+
151
+ return (
152
+ <Modal open={open} onClose={handleClose}>
153
+ <Box
154
+ className="absolute top-[10%] left-1/2 transform -translate-x-1/2 w-[90%] max-w-2xl p-2 pt-0 h-[50vh] outline-none flex flex-col"
155
+ sx={(theme) => ({
156
+ background:
157
+ theme.palette.mode === 'dark' ? alpha(theme.palette.background.paper, 0.96) : 'rgba(255, 255, 255, 0.96)',
158
+ border: `1px solid ${theme.palette.custom?.shellBorder}`,
159
+ borderRadius: '24px',
160
+ boxShadow:
161
+ theme.palette.mode === 'dark' ? '0 28px 60px rgba(2, 8, 23, 0.55)' : '0 28px 60px rgba(15, 23, 42, 0.18)',
162
+ backdropFilter: 'blur(20px)',
163
+ })}
164
+ >
165
+ {/* Search input */}
166
+ <Box className="flex items-center border-b cursor-pointer" onClick={handleBoxClick}>
167
+ <FontAwesomeIcon icon={faMagnifyingGlass} className="text-gray-400 mr-1" />
168
+ <Autocomplete
169
+ fullWidth
170
+ options={searched.map((data) => ({
171
+ ...data,
172
+ label: data.groupName
173
+ ? `${data.name} (${data.groupName}${data.subGroupName ? ' > ' + data.subGroupName : ''})`
174
+ : data.name,
175
+ }))}
176
+ value={searchText}
177
+ onInputChange={(_, newInputValue) => {
178
+ setSearchText(newInputValue);
179
+ }}
180
+ onChange={(_, selectedOption) => {
181
+ handleNavigation(selectedOption);
182
+ }}
183
+ disableClearable
184
+ popupIcon={null}
185
+ noOptionsText="No Results"
186
+ renderInput={(params) => (
187
+ <TextField
188
+ {...params}
189
+ placeholder="Search…"
190
+ size="small"
191
+ fullWidth
192
+ inputRef={inputRef}
193
+ variant="standard" // Or use "outlined" if you still want a minimal box
194
+ autoFocus
195
+ slotProps={{
196
+ input: {
197
+ ...params.InputProps,
198
+ disableUnderline: true, // removes underline for standard variant
199
+ sx: {
200
+ cursor: 'pointer',
201
+ '&:hover': {
202
+ cursor: 'pointer',
203
+ },
204
+ border: 'none',
205
+ outline: 'none',
206
+ boxShadow: 'none',
207
+ backgroundColor: 'tranparent',
208
+ },
209
+ },
210
+ }}
211
+ sx={{
212
+ border: 'none',
213
+ '& .MuiOutlinedInput-root': {
214
+ '& fieldset': { border: 'none' }, // for outlined variant
215
+ '&:hover fieldset': { border: 'none' },
216
+ },
217
+ }}
218
+ />
219
+ )}
220
+ renderOption={(props, data) => (
221
+ <MenuItem
222
+ {...props}
223
+ key={data.id}
224
+ value={data}
225
+ className="w-full whitespace-normal"
226
+ sx={{ padding: '8px 16px !important' }}
227
+ data-testid="searchbar-menuitem"
228
+ >
229
+ {data.groupName ? (
230
+ <Box className="">
231
+ {data.name}{' '}
232
+ <small>
233
+ ( {data.groupName}
234
+ {data.subGroupName && ' > ' + data.subGroupName} )
235
+ </small>
236
+ </Box>
237
+ ) : (
238
+ data.name
239
+ )}
240
+ </MenuItem>
241
+ )}
242
+ slotProps={{
243
+ paper: {
244
+ sx: {
245
+ width: '103.5%',
246
+ ml: '-3%',
247
+ // zIndex: 1300,
248
+ },
249
+ },
250
+ listbox: {
251
+ sx: {
252
+ padding: '0px !important',
253
+ },
254
+ },
255
+ popper: {
256
+ modifiers: [
257
+ {
258
+ name: 'offset',
259
+ options: {
260
+ offset: [0, 5],
261
+ },
262
+ },
263
+ ],
264
+ },
265
+ }}
266
+ />
267
+ </Box>
268
+
269
+ <Typography variant="subtitle1" className="mt-4 mb-2 text-gray-600 font-medium">
270
+ Recent Searches
271
+ </Typography>
272
+ <div className="flex-1 overflow-y-auto pr-1">
273
+ {recentSearches.map((item, index) => (
274
+ <Grid
275
+ container
276
+ alignItems="flex-start"
277
+ spacing={2}
278
+ key={index}
279
+ className="py-2 hover:bg-gray-100 rounded-lg cursor-pointer"
280
+ onClick={() => {
281
+ handleNavigation(item?.selectedOption);
282
+ }}
283
+ >
284
+ <Grid item>
285
+ <Avatar className="bg-gray-200 text-gray-600">
286
+ <FontAwesomeIcon icon={faMagnifyingGlass} className="text-gray-400" />
287
+ </Avatar>
288
+ </Grid>
289
+ <Grid item xs>
290
+ <Typography className="font-medium text-black">{item.title}</Typography>
291
+ <Typography className="text-sm text-gray-500">
292
+ {item.description ? item.description.split('(')[0].trim() : 'NA'}
293
+ </Typography>
294
+ </Grid>
295
+ </Grid>
296
+ ))}
297
+ </div>
298
+ </Box>
299
+ </Modal>
300
+ );
301
+ });
302
+
303
+ const CustomPopper = function CustomPopper(props) {
304
+ return (
305
+ <Popper
306
+ {...props}
307
+ placement="bottom-end"
308
+ sx={{
309
+ '& .MuiPaper-root': {
310
+ transformOrigin: 'top center',
311
+ // eslint-disable-next-line react/prop-types
312
+ animation: props.open ? 'searchResultsIn 220ms cubic-bezier(0.25, 0.9, 0.3, 1)' : 'none',
313
+ },
314
+ '@keyframes searchResultsIn': {
315
+ '0%': {
316
+ opacity: 0,
317
+ transform: 'translateY(-4px) scale(0.992)',
318
+ },
319
+ '100%': {
320
+ opacity: 1,
321
+ transform: 'translateY(0) scale(1)',
322
+ },
323
+ },
324
+ }}
325
+ />
326
+ );
327
+ };
328
+
329
+ const Navbar = ({ user, setOrientation, orientation }) => {
330
+ const searchContainerRef = useRef(null);
331
+ const { i18n } = useTranslation();
332
+ const [modalIndex, setModalIndex] = useState(false);
333
+ const personilizedThemes = useSelector((state) => state.theme.personilizedThemes);
334
+ const canUpdatePersonilzedTheme = useSelector((state) => state.theme.canSetPersonilisedTheme);
335
+ const trackEvent = useTrackEvent();
336
+ const [allMenu, setAllMenu] = useState([]);
337
+ const [language, setLanguage] = useState(getLanguageIdFromStorage());
338
+ const [availableLanguage, setAvailableLanguage] = useState([]);
339
+ const [searchText, setSearchText] = useState('');
340
+ const [searched, setSearched] = useState([]);
341
+ const [searchModalOpen, setSearchModalOpen] = useState(false);
342
+ const [profileModal, setProfileModal] = useState(false);
343
+ const [logoutOpen, setLogoutOpen] = useState(false);
344
+ const [recentSearches, setRecentSearches] = useState([]);
345
+ const [uiSettingsExpanded, setUiSettingsExpanded] = useState(false);
346
+ const dispatch = useDispatch();
347
+ const theme = useTheme();
348
+ const isDark = theme.palette.mode === 'dark';
349
+ const isMobile = useMediaQuery(theme.breakpoints.down('md'));
350
+ const navigate = useNavigate();
351
+ const [anchorEl, setAnchorEl] = useState(null);
352
+ const open = Boolean(anchorEl);
353
+ const { translate } = useTranslateProvider();
354
+ const { pageTitle, translations, languageMap } = useSelector((state) => state.Translation);
355
+ const { user: userData, headerTitle } = useSelector((state) => state.Authenticate);
356
+ console.log(userData);
357
+
358
+ const mode = useSelector((state) => state.theme.mode);
359
+ const inputRef = useRef(null);
360
+ const translationRequestRef = useRef(new Set());
361
+
362
+ const handleNavigation = (selectedOption) => {
363
+ if (!selectedOption) return;
364
+
365
+ const data = selectedOption;
366
+ let path = GeneralUtil.naviagationFunc(data);
367
+ // is ekavach then only open in new tab
368
+ if (path.includes('report/view/')) {
369
+ if (GeneralUtil.isEkavach) {
370
+ window.open(`/ui/${path}`, '_blank');
371
+ } else {
372
+ navigate(path);
373
+ }
374
+ } else {
375
+ navigate(path);
376
+ }
377
+ inputRef.current?.blur();
378
+ setIsExpanded(false);
379
+ setSearchText('');
380
+ handleClose();
381
+ setRecentSearches((prev) => {
382
+ const isDuplicate = prev.some((item) => item.selectedOption?.navigationState === selectedOption?.navigationState);
383
+ if (isDuplicate) {
384
+ return prev;
385
+ }
386
+ return [
387
+ {
388
+ selectedOption,
389
+ title: selectedOption?.label,
390
+ description: selectedOption?.navigationState?.replace(/\./g, ' / '),
391
+ },
392
+ ...prev,
393
+ ];
394
+ });
395
+ };
396
+
397
+ useEffect(() => {
398
+ const sendToken = async () => {
399
+ try {
400
+ const firebaseToken = await enablePushNotifications();
401
+ const savedToken = localStorage.getItem('firebaseToken');
402
+
403
+ // Insert only if it's a new or changed token
404
+ if (firebaseToken && firebaseToken !== savedToken && GeneralUtil.isBahaar) {
405
+ await execute({
406
+ code: 'TOKEN_INSERTION',
407
+ parameters: { token: firebaseToken },
408
+ });
409
+ localStorage.setItem('firebaseToken', firebaseToken);
410
+ }
411
+ } catch (error) {
412
+ console.error(error);
413
+ }
414
+ };
415
+ sendToken();
416
+ }, []);
417
+
418
+ const handleToggle = () => {
419
+ dispatch(toggleTheme());
420
+ };
421
+
422
+ const handleClick = (event) => {
423
+ setAnchorEl(event.currentTarget);
424
+ };
425
+ const openUpdateProfileModal = () => {
426
+ trackEvent('button_click', {
427
+ button_name: 'update_profile',
428
+ button_type: 'menu',
429
+ button_id: 'navbar-update-profile-button',
430
+ });
431
+ setProfileModal(true);
432
+ };
433
+ const handleCloseProfileModal = () => {
434
+ trackEvent('button_click', {
435
+ button_name: 'update_profile_cancel_button',
436
+ button_type: 'cancel',
437
+ button_id: 'updateprofilemodal-button-cancel',
438
+ });
439
+ setProfileModal(false);
440
+ };
441
+
442
+ const handleClose = () => {
443
+ setAnchorEl(null);
444
+ };
445
+ // Opens the logout confirmation modal (and closes the profile menu).
446
+ const handleLogout = () => {
447
+ setAnchorEl(null);
448
+ setLogoutOpen(true);
449
+ };
450
+
451
+ // Clears the session and returns to the landing page. Kept separate so it can run after the
452
+ // goodbye animation has had a moment to play.
453
+ const finishLogout = () => {
454
+ clearAllExceptPinned();
455
+ dispatch(resetStore());
456
+ dispatch(setTheme('light'));
457
+ sessionStorage.clear();
458
+ navigate('/');
459
+ };
460
+
461
+ // Confirmed from the modal: log the event, call the logout API while the token is still present,
462
+ // then let the goodbye animation play briefly before clearing the session and navigating away.
463
+ const confirmLogout = async () => {
464
+ try {
465
+ await trackEvent('session_end', { buttonName: 'Logout' });
466
+ await trackEvent('button_click', {
467
+ button_name: 'logout',
468
+ button_type: 'menu',
469
+ button_id: 'navbar-logout-button',
470
+ });
471
+ await logout({ clientId: 'imtecho-ui' });
472
+ } catch (error) {
473
+ console.error(error);
474
+ } finally {
475
+ setTimeout(finishLogout, 1600);
476
+ }
477
+ };
478
+
479
+ const handleSearchActivity = (search) => {
480
+ const searched = allMenu.filter((item) => item.name?.toLowerCase().includes(search.toLowerCase()));
481
+ debouncedTrackSearch(search, searched.length);
482
+ setSearched(searched);
483
+ };
484
+ useEffect(() => {
485
+ handleSearchActivity(searchText);
486
+ }, [searchText]);
487
+ const debounce = (func, delay) => {
488
+ let timeoutId;
489
+ return (...args) => {
490
+ clearTimeout(timeoutId);
491
+ timeoutId = setTimeout(() => func(...args), delay);
492
+ };
493
+ };
494
+ const debouncedTrackSearch = useCallback(
495
+ debounce((search, resultCount) => {
496
+ trackEvent('search_tracking', { searchTerm: search, results_count: resultCount, search_type: 'Global Search' });
497
+ }, 300),
498
+ [user] // Dependencies: empty because we don't want it to change
499
+ );
500
+ useEffect(() => {
501
+ let allMenu = [];
502
+ Object.entries(user?.features || {}).forEach(([key, element]) => {
503
+ // const isDuplicate = (name) => allMenu.some((item) => item.name === name);
504
+ const processItems = (items, groupName, subGroupName = null) => {
505
+ items.forEach((item) => {
506
+ if (item.isGroup || item.isSubGroup) {
507
+ if (item.subGroups && item.subGroups.length > 0) {
508
+ processItems(item.subGroups, groupName, item.name);
509
+ }
510
+ } else {
511
+ if (item.navigationState) {
512
+ allMenu.push({
513
+ ...item,
514
+ groupName: groupName,
515
+ ...(subGroupName && { subGroupName: subGroupName }),
516
+ });
517
+ }
518
+ }
519
+ });
520
+ };
521
+
522
+ processItems(element, key);
523
+ });
524
+ setAllMenu(allMenu);
525
+ setSearched(allMenu);
526
+ }, [user?.features]);
527
+
528
+ const loadTranslation = useCallback(
529
+ async (languageId, languages = availableLanguage) => {
530
+ if (!languageId || languages.length === 0 || !pageTitle) {
531
+ return;
532
+ }
533
+
534
+ try {
535
+ const selectedLanguage = languages.find((item) => Number(item.id) === Number(languageId));
536
+ const languageKey = getLanguageKey(selectedLanguage) || languageMap[languageId];
537
+
538
+ if (!languageKey) {
539
+ return;
540
+ }
541
+
542
+ const keys = Object.keys(translations[languageKey] || {});
543
+ if (keys.some((key) => key.includes(pageTitle))) {
544
+ dispatch(setTranslationLanguage(languageKey));
545
+ localStorage.setItem('translation_language', languageId);
546
+ return;
547
+ }
548
+
549
+ const requestKey = `${languageId}:${pageTitle}`;
550
+ if (translationRequestRef.current.has(requestKey)) {
551
+ return;
552
+ }
553
+ translationRequestRef.current.add(requestKey);
554
+
555
+ const { data } = await execute({
556
+ code: 'translation_from_language_key_namespace',
557
+ parameters: {
558
+ languageId,
559
+ appId: 2,
560
+ namespace: pageTitle,
561
+ },
562
+ });
563
+
564
+ const langData = data?.result?.reduce((acc, curr) => {
565
+ acc[curr.key] = curr.value;
566
+ return acc;
567
+ }, {});
568
+
569
+ dispatch(setTranslation({ langKey: languageKey, namespace: pageTitle, keys: langData }));
570
+ dispatch(setTranslationLanguage(languageKey));
571
+ localStorage.setItem('translation_language', languageId);
572
+ } catch (error) {
573
+ console.error(error);
574
+ translationRequestRef.current.delete(`${languageId}:${pageTitle}`);
575
+ }
576
+ },
577
+ [availableLanguage, dispatch, i18n, languageMap, pageTitle, translations]
578
+ );
579
+
580
+ useEffect(() => {
581
+ loadTranslation(language);
582
+ }, [language, loadTranslation]);
583
+
584
+ useEffect(() => {
585
+ const setAvailabelLang = async () => {
586
+ try {
587
+ const cachedLangs = localStorage.getItem('available_languages');
588
+ let activeLang;
589
+
590
+ if (cachedLangs) {
591
+ activeLang = JSON.parse(cachedLangs);
592
+ } else {
593
+ const res = await getAllLanguages();
594
+ activeLang = (res?.data || [])?.filter((lang) => {
595
+ return lang.active === true;
596
+ });
597
+ }
598
+
599
+ activeLang = activeLang?.map((lang) => ({
600
+ ...lang,
601
+ languageKey: lang.languageKey?.toLowerCase(),
602
+ }));
603
+ localStorage.setItem('available_languages', JSON.stringify(activeLang));
604
+
605
+ const langMap = activeLang.reduce((acc, el) => {
606
+ acc[el.id] = getLanguageKey(el);
607
+ return acc;
608
+ }, {});
609
+
610
+ if (Object.entries(languageMap).length === 0) {
611
+ dispatch(setlanguageMap(langMap));
612
+ }
613
+
614
+ const savedLanguage = getLanguageIdFromStorage();
615
+ setAvailableLanguage(activeLang);
616
+
617
+ const selectedLanguage = activeLang.find((item) => Number(item.id) === Number(savedLanguage));
618
+ const englishLanguage = activeLang.find((item) => getLanguageKey(item) === 'en');
619
+ const nextLanguage = selectedLanguage || englishLanguage;
620
+
621
+ if (nextLanguage) {
622
+ const nextLanguageId = Number(nextLanguage.id);
623
+ setLanguage(nextLanguageId);
624
+ if (!savedLanguage || Number(savedLanguage) !== nextLanguageId) {
625
+ localStorage.setItem('translation_language', nextLanguage.id);
626
+ }
627
+ await loadTranslation(nextLanguageId, activeLang);
628
+ }
629
+
630
+ if (Object.entries(languageMap).length !== 0) {
631
+ const hasLanguageMapChanged = Object.entries(langMap).some(
632
+ ([id, languageKey]) => languageMap[id] !== languageKey
633
+ );
634
+ if (hasLanguageMapChanged) {
635
+ dispatch(setlanguageMap(langMap));
636
+ }
637
+ }
638
+ } catch (error) {
639
+ console.error(error);
640
+ showToast({ message: 'Error in getting languages', type: 'error' });
641
+ }
642
+ };
643
+ setAvailabelLang();
644
+ const handleKeyDown = (event) => {
645
+ if (event.ctrlKey && event.key.toLowerCase() === 'k') {
646
+ event.preventDefault(); // prevent browser search
647
+ inputRef.current.focus();
648
+ // handleSearchOpen();
649
+ }
650
+ };
651
+
652
+ window.addEventListener('keydown', handleKeyDown);
653
+ return () => window.removeEventListener('keydown', handleKeyDown);
654
+ }, []);
655
+
656
+ // const handleSearchOpen = () => {
657
+ // setSearchModalOpen((curr) => !curr);
658
+ // };
659
+ const updateThemeConfigPreference = async (value) => {
660
+ try {
661
+ dispatch(setPersonilizedThemes(value));
662
+ await execute({
663
+ code: 'SET_PERSONALISED_THEME',
664
+ parameters: {
665
+ user_id: user?.id,
666
+ personlised: value,
667
+ },
668
+ });
669
+ } catch (error) {
670
+ console.error(error);
671
+ }
672
+ };
673
+
674
+ useEffect(() => {
675
+ const checkCanUpdatePersonalizedTheme = async () => {
676
+ try {
677
+ const cachedThemeExistence = localStorage.getItem('theme_existence');
678
+ if (cachedThemeExistence !== null) {
679
+ dispatch(updateCanSetPersonalizedTheme(JSON.parse(cachedThemeExistence)));
680
+ return;
681
+ }
682
+
683
+ const { data } = await execute({
684
+ code: 'GET_USER_THEME_EXIST',
685
+ parameters: {
686
+ user_id: user.id,
687
+ },
688
+ });
689
+
690
+ const userExists = data.result[0].user_exists;
691
+ dispatch(updateCanSetPersonalizedTheme(userExists));
692
+ localStorage.setItem('theme_existence', JSON.stringify(userExists));
693
+ } catch (error) {
694
+ console.error(error);
695
+ }
696
+ };
697
+
698
+ if (user?.id) {
699
+ checkCanUpdatePersonalizedTheme();
700
+ }
701
+ }, [user?.id]);
702
+
703
+ const [isExpanded, setIsExpanded] = useState(false);
704
+ const [isOpen, setIsOpen] = useState(false); // Controls the dropdown visibility
705
+ const timerRef = useRef(null);
706
+
707
+ useEffect(() => {
708
+ if (isExpanded) {
709
+ timerRef.current = setTimeout(() => {
710
+ setIsOpen(true);
711
+ }, 300);
712
+ } else {
713
+ setIsOpen(false);
714
+ }
715
+ return () => clearTimeout(timerRef.current);
716
+ }, [isExpanded]);
717
+
718
+ const textRef = useRef(null);
719
+ const [isTruncated, setIsTruncated] = useState(false);
720
+
721
+ const checkTruncation = () => {
722
+ if (textRef.current) {
723
+ const hasTruncated = textRef.current.scrollWidth > textRef.current.clientWidth;
724
+ setIsTruncated(hasTruncated);
725
+ }
726
+ };
727
+
728
+ useEffect(() => {
729
+ checkTruncation();
730
+ window.addEventListener('resize', checkTruncation);
731
+ return () => window.removeEventListener('resize', checkTruncation);
732
+ }, [headerTitle]);
733
+
734
+ const profileMenuItemSx = {
735
+ mx: 1,
736
+ my: 0.35,
737
+ minHeight: 46,
738
+ borderRadius: '14px',
739
+ color: theme.palette.text.primary,
740
+ transition: 'all 180ms ease',
741
+ '&:hover': {
742
+ background: isDark ? alpha('#fff', 0.06) : alpha(theme.palette.custom?.shellAccent || '#1c6fb7', 0.08),
743
+ },
744
+ };
745
+
746
+ const profileDividerSx = {
747
+ my: 0.8,
748
+ mx: 1.4,
749
+ borderColor: theme.palette.custom?.shellBorder,
750
+ opacity: 0.9,
751
+ };
752
+
753
+ const profileSelectSx = {
754
+ minWidth: 112,
755
+ borderRadius: '12px',
756
+ backgroundColor: isDark ? alpha('#fff', 0.04) : alpha('#0f172a', 0.03),
757
+ '& .MuiOutlinedInput-notchedOutline': {
758
+ borderColor: 'transparent',
759
+ },
760
+ '&:hover .MuiOutlinedInput-notchedOutline': {
761
+ borderColor: 'transparent',
762
+ },
763
+ '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
764
+ borderColor: alpha(theme.palette.custom?.shellAccent || '#1c6fb7', 0.28),
765
+ },
766
+ '& .MuiSelect-select': {
767
+ py: 1,
768
+ px: 1.25,
769
+ fontSize: '0.875rem',
770
+ color: theme.palette.text.primary,
771
+ },
772
+ '& .MuiSelect-icon': {
773
+ color: theme.palette.custom?.shellMuted,
774
+ },
775
+ };
776
+
777
+ const iconChipSx = {
778
+ minWidth: 34,
779
+ width: 34,
780
+ height: 34,
781
+ mr: 1.2,
782
+ borderRadius: '12px',
783
+ display: 'inline-flex',
784
+ alignItems: 'center',
785
+ justifyContent: 'center',
786
+ color: theme.palette.text.primary,
787
+ background: isDark
788
+ ? alpha(theme.palette.custom?.shellAccent || '#4da8da', 0.18)
789
+ : alpha(theme.palette.custom?.shellAccent || '#1c6fb7', 0.1),
790
+ };
791
+
792
+ const settingCardSx = {
793
+ mx: 1,
794
+ my: 0.35,
795
+ p: 1.35,
796
+ borderRadius: '16px',
797
+ border: `1px solid ${theme.palette.custom?.shellBorder}`,
798
+ background: isDark ? alpha('#fff', 0.03) : alpha('#0f172a', 0.025),
799
+ };
800
+
801
+ return (
802
+ <>
803
+ <Backdrop
804
+ sx={{
805
+ color: '#fff',
806
+ zIndex: 1201, // High enough to cover navbar/content
807
+ backdropFilter: 'blur(0px)', // The blur effect
808
+ backgroundColor: 'rgba(0, 0, 0, 0.5)', // The semi-transparent dark tint
809
+ }}
810
+ open={isExpanded} // Only show when search is focused
811
+ onClick={() => {
812
+ // Close search if they click the dark background
813
+ setIsExpanded(false);
814
+ setIsOpen(false);
815
+ inputRef.current?.blur();
816
+ }}
817
+ />
818
+ {profileModal && (
819
+ <UpdateUserModal open={profileModal} onClose={handleCloseProfileModal} data-testid="navbar-updateusermodal" />
820
+ )}
821
+
822
+ <LogoutModal
823
+ open={logoutOpen}
824
+ userName={user?.name}
825
+ onCancel={() => setLogoutOpen(false)}
826
+ onConfirm={confirmLogout}
827
+ />
828
+
829
+ {modalIndex && (
830
+ <Suspense>
831
+ <ChangePasswordModal
832
+ open={modalIndex}
833
+ onClose={() => setModalIndex(false)}
834
+ userId={user?.id}
835
+ data-testid="change-password-modal"
836
+ />
837
+ </Suspense>
838
+ )}
839
+ {
840
+ <SearchModal
841
+ open={searchModalOpen}
842
+ handleClose={() => setSearchModalOpen(false)}
843
+ allMenu={allMenu}
844
+ navigate={navigate}
845
+ recentSearches={recentSearches}
846
+ setRecentSearches={setRecentSearches}
847
+ anchorRef={searchContainerRef}
848
+ />
849
+ }
850
+
851
+ <AppBar
852
+ position="sticky"
853
+ className="h-[72px]"
854
+ sx={{
855
+ zIndex: isExpanded ? 1201 : 1100,
856
+ transition: 'z-index 0s linear',
857
+ background: GeneralUtil.isBahaar
858
+ ? 'linear-gradient(135deg, rgba(10, 31, 74, 0.96) 0%, rgba(16, 52, 112, 0.94) 100%)'
859
+ : theme.palette.mode === 'dark'
860
+ ? alpha(theme.palette.background.default, 0.96)
861
+ : 'linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(246, 250, 255, 0.96) 100%)',
862
+ borderBottom: `1px solid ${theme.palette.custom?.shellBorder}`,
863
+ boxShadow:
864
+ theme.palette.mode === 'dark'
865
+ ? '0 18px 40px rgba(2, 8, 23, 0.34)'
866
+ : '0 16px 36px rgba(148, 163, 184, 0.16)',
867
+ backdropFilter: 'blur(18px)',
868
+ }}
869
+ color={'inherit'}
870
+ >
871
+ <Toolbar
872
+ sx={{
873
+ minHeight: '72px !important',
874
+ gap: { xs: 0.75, sm: 1, md: 1.5 },
875
+ paddingLeft: { xs: '8px !important', sm: '10px !important', md: '16px !important' },
876
+ paddingRight: { xs: '8px !important', sm: '10px !important', md: '16px !important' },
877
+ }}
878
+ >
879
+ <Box className="transition-opacity duration-300" sx={{ flexShrink: 0 }}>
880
+ <Link
881
+ to="/ui/medplat/dashboard/webtasks"
882
+ className="self-center cursor-pointer"
883
+ onClick={(e) => {
884
+ e.preventDefault();
885
+ trackEvent('link_click', {
886
+ link_image_src: GeneralUtil.getNavbarLogo(),
887
+ link_text: '',
888
+ link_url: '/ui/medplat/dashboard/webtasks',
889
+ link_id: 'navbar-img-banner',
890
+ });
891
+ navigate('/ui/medplat/dashboard/webtasks');
892
+ }}
893
+ >
894
+ <img
895
+ src={GeneralUtil.getNavbarLogo()}
896
+ className="h-8 sm:h-10"
897
+ alt="banner"
898
+ data-testid="navbar-img-banner"
899
+ />
900
+ </Link>
901
+ </Box>
902
+
903
+ <Divider
904
+ orientation="vertical"
905
+ className="transition-opacity duration-300"
906
+ flexItem
907
+ sx={{
908
+ display: { xs: 'none', sm: 'block' },
909
+ mx: { sm: 1, md: 1.5 },
910
+ height: '42px',
911
+ borderColor: GeneralUtil.isBahaar ? 'rgba(255,255,255,0.14)' : theme.palette.custom?.shellBorder,
912
+ }}
913
+ />
914
+
915
+ <Tooltip
916
+ title={headerTitle || ''}
917
+ arrow
918
+ // This is the magic line: it disables the tooltip if not truncated
919
+ disableHoverListener={!isTruncated}
920
+ >
921
+ <Typography
922
+ ref={textRef}
923
+ variant="h6"
924
+ className="transition-opacity duration-300 truncate"
925
+ sx={{
926
+ display: { xs: 'none', sm: 'block' },
927
+ color: GeneralUtil.isBahaar ? '#fff' : isDark ? theme.palette.text.primary : '#20324d',
928
+ fontWeight: 700,
929
+ letterSpacing: '-0.02em',
930
+ width: { sm: '18vw', md: '18vw' },
931
+ minWidth: { sm: '120px', md: '180px' },
932
+ }}
933
+ >
934
+ {headerTitle}
935
+ </Typography>
936
+ </Tooltip>
937
+ {/* <Divider
938
+ orientation="vertical"
939
+ className="bg-white transition-opacity duration-300"
940
+ flexItem
941
+ sx={{ mx: 2, height: '59px', opacity: isExpanded ? 0.1 : 1 }}
942
+ /> */}
943
+ {/* {!GeneralUtil.isBahaar && (
944
+ <Grid
945
+ ref={searchContainerRef}
946
+ className="ml-5 mt-1 text-gray-500 italic cursor-pointer w-[20%] flex items-center"
947
+ onClick={handleSearchOpen}
948
+ >
949
+ <FontAwesomeIcon icon={faMagnifyingGlass} className="pr-2" />
950
+ <span>Ctrl+k or Search..</span>
951
+ </Grid>
952
+ )} */}
953
+
954
+ {!isMobile && (
955
+ <Box
956
+ className="flex justify-end mr-2 ml-2"
957
+ sx={{
958
+ position: 'relative',
959
+ flex: 1,
960
+ minWidth: 0,
961
+ maxWidth: { xs: 'none', md: '560px' },
962
+ marginLeft: { xs: 0.5, sm: 0.75, md: 0.5 },
963
+ marginRight: { xs: 0.5, sm: 0.75, md: 0.5 },
964
+ }}
965
+ >
966
+ <Autocomplete
967
+ className="transition-all duration-300 ease-in-out w-full"
968
+ open={isOpen}
969
+ onOpen={() => {
970
+ if (!isExpanded) setIsExpanded(true);
971
+ }}
972
+ onClose={(event, reason) => {
973
+ if (reason === 'blur' || reason === 'escape') {
974
+ setIsOpen(false);
975
+ if (!searchText) setIsExpanded(false);
976
+ }
977
+ }}
978
+ slots={{
979
+ popper: CustomPopper,
980
+ }}
981
+ fullWidth
982
+ options={searched.map((data) => ({
983
+ ...data,
984
+ label: data.groupName
985
+ ? `${data.name} (${data.groupName}${data.subGroupName ? ' > ' + data.subGroupName : ''})`
986
+ : data.name,
987
+ }))}
988
+ value={searchText}
989
+ onInputChange={(_, newInputValue) => {
990
+ setSearchText(newInputValue);
991
+ }}
992
+ onChange={(_, selectedOption) => {
993
+ handleNavigation(selectedOption);
994
+ }}
995
+ disableClearable
996
+ popupIcon={null}
997
+ noOptionsText="No Results"
998
+ renderInput={(params) => (
999
+ <TextField
1000
+ {...params}
1001
+ placeholder="Search…"
1002
+ size="small"
1003
+ fullWidth
1004
+ inputRef={inputRef}
1005
+ onFocus={() => setIsExpanded(true)}
1006
+ onBlur={() => {
1007
+ setSearchText('');
1008
+ setIsExpanded(false);
1009
+ }}
1010
+ slotProps={{
1011
+ input: {
1012
+ ...params.InputProps,
1013
+ disableUnderline: true,
1014
+ endAdornment: (
1015
+ <InputAdornment position="end" className="">
1016
+ <FontAwesomeIcon icon={faMagnifyingGlass} className="text-gray-400 " />
1017
+ </InputAdornment>
1018
+ ),
1019
+ sx: {
1020
+ zIndex: 9999,
1021
+ cursor: 'pointer',
1022
+ background: GeneralUtil.isBahaar
1023
+ ? 'rgba(255,255,255,0.96)'
1024
+ : theme.palette.mode === 'dark'
1025
+ ? alpha(theme.palette.common.white, 0.06)
1026
+ : alpha(theme.palette.common.white, 0.92),
1027
+ color: theme.palette.text.primary,
1028
+ borderRadius: '18px',
1029
+ minHeight: { xs: '40px', sm: '46px' },
1030
+ paddingRight: '14px !important',
1031
+ paddingLeft: '4px',
1032
+ boxShadow: isExpanded
1033
+ ? theme.palette.mode === 'dark'
1034
+ ? '0 10px 22px rgba(2, 8, 23, 0.22), inset 0 1px 0 rgba(255,255,255,0.05)'
1035
+ : '0 14px 28px rgba(148, 163, 184, 0.18)'
1036
+ : theme.palette.mode === 'dark'
1037
+ ? 'inset 0 1px 0 rgba(255,255,255,0.03)'
1038
+ : '0 14px 26px rgba(148, 163, 184, 0.16)',
1039
+ border: `1px solid ${theme.palette.custom?.shellBorder}`,
1040
+ transform: isExpanded ? 'translateY(-0.5px) scale(1.004)' : 'translateY(0) scale(1)',
1041
+ transition:
1042
+ 'transform 220ms ease, box-shadow 220ms ease, border-color 220ms ease, background 220ms ease',
1043
+ '&:hover': {
1044
+ cursor: 'pointer',
1045
+ },
1046
+ },
1047
+ },
1048
+ }}
1049
+ sx={{
1050
+ '& .MuiOutlinedInput-root': {
1051
+ borderRadius: '18px',
1052
+ '& fieldset': {
1053
+ border: 'none',
1054
+ },
1055
+ },
1056
+ '& .MuiInputBase-input::placeholder': {
1057
+ opacity: 1,
1058
+ color: theme.palette.custom?.shellMuted,
1059
+ },
1060
+ }}
1061
+ />
1062
+ )}
1063
+ renderOption={(props, data) => (
1064
+ <MenuItem
1065
+ {...props}
1066
+ key={data.id}
1067
+ value={data}
1068
+ className="w-full whitespace-normal border-b border-gray-300 last:border-none"
1069
+ sx={{
1070
+ padding: '8px 16px !important',
1071
+ transition: 'background-color 140ms ease',
1072
+ }}
1073
+ data-testid="searchbar-menuitem"
1074
+ >
1075
+ {data.groupName ? (
1076
+ <Box className="">
1077
+ {data.name}{' '}
1078
+ <small>
1079
+ ( {data.groupName}
1080
+ {data.subGroupName && ' > ' + data.subGroupName} )
1081
+ </small>
1082
+ </Box>
1083
+ ) : (
1084
+ data.name
1085
+ )}
1086
+ </MenuItem>
1087
+ )}
1088
+ slotProps={{
1089
+ paper: {
1090
+ sx: {
1091
+ marginTop: '8px !important',
1092
+ borderRadius: '18px !important',
1093
+ border: `1px solid ${theme.palette.custom?.shellBorder}`,
1094
+ background: theme.palette.mode === 'dark' ? theme.palette.background.paper : '#ffffff',
1095
+ boxShadow:
1096
+ theme.palette.mode === 'dark'
1097
+ ? '0 24px 48px rgba(2, 8, 23, 0.45)'
1098
+ : '0 20px 44px rgba(148, 163, 184, 0.22)',
1099
+ overflow: 'hidden',
1100
+ },
1101
+ },
1102
+ listbox: {
1103
+ sx: {
1104
+ padding: '0px !important',
1105
+ overflowX: 'hidden',
1106
+ },
1107
+ },
1108
+ }}
1109
+ />
1110
+ </Box>
1111
+ )}
1112
+ <Typography variant="h3" noWrap className="grow"></Typography>
1113
+ {isMobile && (
1114
+ <IconButton
1115
+ onClick={() => setSearchModalOpen(true)}
1116
+ sx={{
1117
+ width: 40,
1118
+ height: 40,
1119
+ borderRadius: '50%',
1120
+ background: isDark ? alpha('#fff', 0.12) : alpha('#000', 0.08),
1121
+ color: GeneralUtil.isBahaar ? '#fff' : theme.palette.text.primary,
1122
+ '&:hover': {
1123
+ background: isDark ? alpha('#fff', 0.1) : alpha('#000', 0.1),
1124
+ },
1125
+ ml: 1,
1126
+ }}
1127
+ >
1128
+ <FontAwesomeIcon icon={faMagnifyingGlass} size="sm" />
1129
+ </IconButton>
1130
+ )}
1131
+
1132
+ <Box className="flex flex-row items-center text-center" sx={{ opacity: isExpanded ? 0.1 : 1, flexShrink: 0 }}>
1133
+ <Tooltip title="Account settings">
1134
+ <ButtonBase
1135
+ onClick={handleClick}
1136
+ size="small"
1137
+ className="ml-1 sm:ml-2 p-2"
1138
+ aria-controls={open ? 'account-menu' : undefined}
1139
+ aria-haspopup="true"
1140
+ aria-expanded={open ? 'true' : undefined}
1141
+ data-testid="navbar-buttonbase-account"
1142
+ sx={{
1143
+ borderRadius: '999px',
1144
+ padding: { xs: '6px 8px', sm: '7px 10px' },
1145
+ background: GeneralUtil.isBahaar
1146
+ ? 'rgba(255,255,255,0.12)'
1147
+ : theme.palette.mode === 'dark'
1148
+ ? 'rgba(15, 23, 42, 0.7)'
1149
+ : 'rgba(255,255,255,0.94)',
1150
+ boxShadow:
1151
+ theme.palette.mode === 'dark'
1152
+ ? '0 10px 24px rgba(2, 8, 23, 0.18)'
1153
+ : '0 10px 24px rgba(148, 163, 184, 0.12)',
1154
+ backdropFilter: 'blur(14px)',
1155
+ transition: 'transform 180ms ease, box-shadow 180ms ease, background 180ms ease',
1156
+ '&:hover': {
1157
+ transform: 'translateY(-1px)',
1158
+ boxShadow:
1159
+ theme.palette.mode === 'dark'
1160
+ ? '0 14px 30px rgba(2, 8, 23, 0.22)'
1161
+ : '0 14px 30px rgba(148, 163, 184, 0.16)',
1162
+ },
1163
+ }}
1164
+ >
1165
+ <Typography
1166
+ variant="body2"
1167
+ className="mr-2"
1168
+ sx={{
1169
+ display: { xs: 'none', sm: 'block' },
1170
+ color: GeneralUtil.isBahaar ? '#ffffff' : theme.palette.text.primary,
1171
+ fontWeight: 700,
1172
+ maxWidth: '120px',
1173
+ overflow: 'hidden',
1174
+ textOverflow: 'ellipsis',
1175
+ whiteSpace: 'nowrap',
1176
+ }}
1177
+ >
1178
+ {user?.name}
1179
+ </Typography>
1180
+ <Avatar
1181
+ sx={{
1182
+ width: 34,
1183
+ height: 34,
1184
+ background: GeneralUtil.isBahaar
1185
+ ? 'linear-gradient(135deg, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.12) 100%)'
1186
+ : `linear-gradient(135deg, ${theme.palette.custom?.shellAccent} 0%, #3f8ec6 100%)`,
1187
+ color: '#fff',
1188
+ fontWeight: 700,
1189
+ boxShadow:
1190
+ theme.palette.mode === 'dark'
1191
+ ? '0 8px 18px rgba(2, 8, 23, 0.24)'
1192
+ : '0 8px 18px rgba(63, 142, 198, 0.22)',
1193
+ }}
1194
+ data-testid="navbar-avatar"
1195
+ >
1196
+ <Typography variant="body2">{user?.name?.charAt(0)}</Typography>
1197
+ </Avatar>
1198
+ <Box
1199
+ component="span"
1200
+ sx={{
1201
+ display: { xs: 'none', sm: 'inline-flex' },
1202
+ alignItems: 'center',
1203
+ }}
1204
+ >
1205
+ <FontAwesomeIcon
1206
+ icon={faChevronDown}
1207
+ className="ml-2"
1208
+ style={{
1209
+ color: GeneralUtil.isBahaar ? '#ffffff' : theme.palette.custom?.shellMuted,
1210
+ }}
1211
+ data-testid="navbar-expandmoreicon"
1212
+ />
1213
+ </Box>
1214
+ </ButtonBase>
1215
+ </Tooltip>
1216
+ </Box>
1217
+ <Menu
1218
+ anchorEl={anchorEl}
1219
+ id="account-menu"
1220
+ open={open}
1221
+ onClose={handleClose}
1222
+ onClick={handleClose}
1223
+ slotProps={{
1224
+ paper: {
1225
+ className: 'bg-secondary-theme text-primary-text',
1226
+ elevation: 0,
1227
+ sx: {
1228
+ minWidth: 320,
1229
+ maxHeight: '90vh',
1230
+ mt: 1.5,
1231
+ overflow: 'visible',
1232
+ borderRadius: '24px',
1233
+ border: `1px solid ${theme.palette.custom?.shellBorder}`,
1234
+ background:
1235
+ theme.palette.mode === 'dark'
1236
+ ? alpha(theme.palette.background.paper, 0.96)
1237
+ : 'rgba(255, 255, 255, 0.96)',
1238
+ boxShadow:
1239
+ theme.palette.mode === 'dark'
1240
+ ? '0 24px 48px rgba(2, 8, 23, 0.42)'
1241
+ : '0 20px 44px rgba(148, 163, 184, 0.2)',
1242
+ backdropFilter: 'blur(18px)',
1243
+ '& .MuiMenuItem-root': profileMenuItemSx,
1244
+ '& .MuiAvatar-root': {
1245
+ width: 30,
1246
+ height: 30,
1247
+ ml: -0.5,
1248
+ mr: 1,
1249
+ },
1250
+ },
1251
+ },
1252
+ list: {
1253
+ sx: {
1254
+ padding: '12px 0',
1255
+ },
1256
+ },
1257
+ }}
1258
+ transformOrigin={{ horizontal: 'right', vertical: 'top' }}
1259
+ anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
1260
+ >
1261
+ <div
1262
+ style={{
1263
+ maxHeight: '85vh',
1264
+ overflowY: 'auto',
1265
+ }}
1266
+ >
1267
+ <MenuItem data-testid="navbar-update-profile-button" onClick={openUpdateProfileModal}>
1268
+ <Avatar
1269
+ sx={{
1270
+ background: `linear-gradient(135deg, ${theme.palette.custom?.shellAccent} 0%, ${
1271
+ isDark ? '#3f8ec6' : '#6ab6e8'
1272
+ } 100%)`,
1273
+ color: '#fff',
1274
+ fontWeight: 700,
1275
+ }}
1276
+ >
1277
+ {user?.name?.charAt(0)}
1278
+ </Avatar>
1279
+ <Box>
1280
+ <Typography variant="body2" sx={{ fontWeight: 700 }}>
1281
+ {translate('Profile')}
1282
+ </Typography>
1283
+ <Typography variant="caption" sx={{ color: theme.palette.custom?.shellMuted }}>
1284
+ {user?.name}
1285
+ </Typography>
1286
+ </Box>
1287
+ </MenuItem>
1288
+ <Divider sx={profileDividerSx} />
1289
+ <MenuItem className="flex items-center gap-2">
1290
+ <Typography variant="body2" sx={{ fontWeight: 600, paddingLeft: '8px' }}>
1291
+ {'Language Preference'}
1292
+ </Typography>
1293
+ <Select
1294
+ fullWidth
1295
+ id="language-selector"
1296
+ value={language}
1297
+ onChange={(e) => {
1298
+ setLanguage(e.target.value);
1299
+ }}
1300
+ className="text-primary-text"
1301
+ sx={profileSelectSx}
1302
+ MenuProps={{
1303
+ MenuListProps: {
1304
+ className: 'bg-secondary-theme text-primary-text',
1305
+ },
1306
+ }}
1307
+ size="small"
1308
+ >
1309
+ {availableLanguage?.map((lang, key) => {
1310
+ return (
1311
+ <MenuItem key={key + 1} value={parseInt(lang?.id)}>
1312
+ <Typography variant="body2">{lang?.languageValue} </Typography>
1313
+ </MenuItem>
1314
+ );
1315
+ })}
1316
+ </Select>
1317
+ </MenuItem>
1318
+
1319
+ <Divider sx={profileDividerSx} />
1320
+
1321
+ <Accordion
1322
+ expanded={uiSettingsExpanded}
1323
+ onChange={(e, expanded) => setUiSettingsExpanded(expanded)}
1324
+ onClick={(e) => e.stopPropagation()}
1325
+ elevation={0}
1326
+ disableGutters
1327
+ sx={{
1328
+ background: 'transparent',
1329
+ '&:before': { display: 'none' },
1330
+ }}
1331
+ >
1332
+ <AccordionSummary
1333
+ expandIcon={<FontAwesomeIcon icon={faChevronDown} className="w-4 h-4" />}
1334
+ sx={{
1335
+ mx: 1,
1336
+ my: 0.35,
1337
+ minHeight: '46px !important',
1338
+ borderRadius: '14px',
1339
+ color: theme.palette.text.primary,
1340
+ transition: 'all 180ms ease',
1341
+ px: '16px !important',
1342
+ '&:hover': {
1343
+ background: isDark
1344
+ ? alpha('#fff', 0.06)
1345
+ : alpha(theme.palette.custom?.shellAccent || '#1c6fb7', 0.08),
1346
+ },
1347
+ '&.Mui-expanded': {
1348
+ minHeight: '46px !important',
1349
+ },
1350
+ '& .MuiAccordionSummary-content': {
1351
+ margin: '0 !important',
1352
+ display: 'flex',
1353
+ alignItems: 'center',
1354
+ },
1355
+ '& .MuiAccordionSummary-content.Mui-expanded': {
1356
+ margin: '0 !important',
1357
+ },
1358
+ '& .MuiAccordionSummary-expandIconWrapper': {
1359
+ color: theme.palette.custom?.shellMuted,
1360
+ marginRight: '20px',
1361
+ },
1362
+ }}
1363
+ >
1364
+ <ListItemIcon sx={iconChipSx}>
1365
+ <FontAwesomeIcon icon={faSliders} className="w-4.5 h-4.5" />
1366
+ </ListItemIcon>
1367
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
1368
+ {'Visual Preferences'}
1369
+ </Typography>
1370
+ </AccordionSummary>
1371
+
1372
+ <AccordionDetails sx={{ px: 0, py: 1 }}>
1373
+ <FontSizeSelectorWrapper />
1374
+
1375
+ {canUpdatePersonilzedTheme && (
1376
+ <>
1377
+ <Divider sx={profileDividerSx} />
1378
+ <Box
1379
+ sx={{
1380
+ display: 'flex',
1381
+ alignItems: 'center',
1382
+ justifyContent: 'space-between',
1383
+ px: 3,
1384
+ py: 1.5,
1385
+ }}
1386
+ >
1387
+ <Typography sx={{ fontWeight: 600, color: theme.palette.text.primary, fontSize: '0.875rem' }}>
1388
+ {'Personalized Theme'}
1389
+ </Typography>
1390
+ <Switch
1391
+ checked={personilizedThemes}
1392
+ onChange={(e) => updateThemeConfigPreference(e.target.checked)}
1393
+ color="primary"
1394
+ />
1395
+ </Box>
1396
+ </>
1397
+ )}
1398
+
1399
+ <Divider sx={profileDividerSx} />
1400
+
1401
+ <Box
1402
+ sx={{
1403
+ display: 'flex',
1404
+ flexDirection: 'column',
1405
+ gap: 1.2,
1406
+ px: 3,
1407
+ py: 1.5,
1408
+ }}
1409
+ >
1410
+ <Typography sx={{ fontWeight: 600, color: theme.palette.text.primary, fontSize: '0.875rem' }}>
1411
+ {'Sidebar Orientation'}
1412
+ </Typography>
1413
+ <Select
1414
+ fullWidth
1415
+ value={orientation}
1416
+ sx={profileSelectSx}
1417
+ size="small"
1418
+ onChange={(e) => {
1419
+ localStorage.setItem('sideBarOrientation', e.target.value);
1420
+ return setOrientation(e.target.value);
1421
+ }}
1422
+ className="text-primary-text"
1423
+ MenuProps={{
1424
+ MenuListProps: {
1425
+ className: 'bg-secondary-theme text-primary-text',
1426
+ },
1427
+ }}
1428
+ >
1429
+ <MenuItem value="horizontal">
1430
+ <Typography variant="body2">{'Horizontal'} </Typography>
1431
+ </MenuItem>
1432
+ <MenuItem value="vertical">
1433
+ <Typography variant="body2">{'Vertical'} </Typography>
1434
+ </MenuItem>
1435
+ </Select>
1436
+ </Box>
1437
+
1438
+ <Divider sx={profileDividerSx} />
1439
+
1440
+ <Box sx={{ ...settingCardSx, mt: 1.5 }}>
1441
+ <Box className="flex items-start justify-between gap-3">
1442
+ <Box sx={{ minWidth: 0 }}>
1443
+ <Typography
1444
+ variant="body2"
1445
+ sx={{ fontWeight: 700, color: theme.palette.text.primary, paddingLeft: '8px' }}
1446
+ >
1447
+ Theme
1448
+ </Typography>
1449
+ <Typography
1450
+ variant="caption"
1451
+ sx={{ color: theme.palette.custom?.shellMuted, paddingLeft: '8px' }}
1452
+ >
1453
+ Choose the interface appearance
1454
+ </Typography>
1455
+ </Box>
1456
+ {userData?.roleName === 'Argus Admin' && (
1457
+ <ListItemIcon
1458
+ data-testid="navbar-manage-theme-button"
1459
+ onClick={() => {
1460
+ handleClose();
1461
+ navigate('/ui/medplat/manage/theme');
1462
+ }}
1463
+ className="cursor-pointer min-w-0"
1464
+ sx={{ ...iconChipSx, mr: 0, flexShrink: 0 }}
1465
+ >
1466
+ <FontAwesomeIcon icon={faGear} />
1467
+ </ListItemIcon>
1468
+ )}
1469
+ </Box>
1470
+
1471
+ <Box className="flex items-center gap-2 mt-3">
1472
+ <Select
1473
+ fullWidth
1474
+ value={mode}
1475
+ size="small"
1476
+ onChange={handleToggle}
1477
+ className="bg-secondary-theme text-primary-text"
1478
+ sx={{ ...profileSelectSx, minWidth: 120 }}
1479
+ MenuProps={{
1480
+ MenuListProps: {
1481
+ className: 'bg-secondary-theme text-primary-text',
1482
+ },
1483
+ }}
1484
+ >
1485
+ <MenuItem value="dark">
1486
+ <Typography variant="body2">Dark Mode</Typography>
1487
+ </MenuItem>
1488
+ <MenuItem value="light">
1489
+ <Typography variant="body2">Light Mode</Typography>
1490
+ </MenuItem>
1491
+ </Select>
1492
+ <Box
1493
+ sx={{
1494
+ px: 1,
1495
+ py: 0.7,
1496
+ borderRadius: '12px',
1497
+ background: isDark
1498
+ ? alpha(theme.palette.custom?.shellAccent || '#4da8da', 0.18)
1499
+ : alpha(theme.palette.custom?.shellAccent || '#1c6fb7', 0.1),
1500
+ color: theme.palette.text.primary,
1501
+ fontSize: '0.72rem',
1502
+ fontWeight: 700,
1503
+ whiteSpace: 'nowrap',
1504
+ }}
1505
+ >
1506
+ {mode === 'dark' ? 'Dark' : 'Light'}
1507
+ </Box>
1508
+ </Box>
1509
+ </Box>
1510
+
1511
+ <Divider sx={profileDividerSx} />
1512
+ {['argusadmin', 'medplatadmin'].includes(userData?.roleCode) && (
1513
+ <Box
1514
+ sx={{
1515
+ display: 'flex',
1516
+ alignItems: 'center',
1517
+ justifyContent: 'space-between',
1518
+ px: 3,
1519
+ py: 1.5,
1520
+ cursor: 'pointer',
1521
+ '&:hover': {
1522
+ backgroundColor: isDark ? alpha('#fff', 0.04) : alpha('#0f172a', 0.03),
1523
+ }
1524
+ }}
1525
+ onClick={() => {
1526
+ handleClose();
1527
+ navigate('/ui/medplat/login-configurator');
1528
+ }}
1529
+ >
1530
+ <Typography sx={{ fontWeight: 600, color: theme.palette.text.primary, fontSize: '0.875rem' }}>
1531
+ {'Login Configurator'}
1532
+ </Typography>
1533
+ <FontAwesomeIcon icon={faChevronRight} style={{ color: theme.palette.custom?.shellMuted || '#a0aec0' }} />
1534
+ </Box>
1535
+ )}
1536
+ </AccordionDetails>
1537
+ </Accordion>
1538
+
1539
+ <Divider sx={profileDividerSx} />
1540
+ <MenuItem
1541
+ onClick={() => {
1542
+ setModalIndex(true);
1543
+ handleClose();
1544
+ }}
1545
+ >
1546
+ <ListItemIcon sx={iconChipSx}>
1547
+ <FontAwesomeIcon icon={faKey} className="w-4.5 h-4.5" />
1548
+ </ListItemIcon>
1549
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
1550
+ {translate('Change Password')}
1551
+ </Typography>
1552
+ </MenuItem>
1553
+
1554
+ {GeneralUtil.isTecho && (
1555
+ <>
1556
+ <Divider sx={profileDividerSx} />
1557
+ <MenuItem
1558
+ onClick={() => {
1559
+ handleClose();
1560
+ navigate('/ui/medplat/manage/holidayList');
1561
+ }}
1562
+ >
1563
+ <ListItemIcon sx={iconChipSx}>
1564
+ <FontAwesomeIcon icon={faCalendarDays} className="w-4.5 h-4.5" />
1565
+ </ListItemIcon>
1566
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
1567
+ {translate('Holiday List')}
1568
+ </Typography>
1569
+ </MenuItem>
1570
+ <MenuItem
1571
+ onClick={() => {
1572
+ handleClose();
1573
+ navigate('/ui/medplat/manage/faqList');
1574
+ }}
1575
+ >
1576
+ <ListItemIcon sx={iconChipSx}>
1577
+ <FontAwesomeIcon icon={faQuestionCircle} className="w-4.5 h-4.5" />
1578
+ </ListItemIcon>
1579
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
1580
+ {translate('FAQs')}
1581
+ </Typography>
1582
+ </MenuItem>
1583
+ <MenuItem
1584
+ sx={{
1585
+ display: 'flex',
1586
+ flexDirection: 'column',
1587
+ alignItems: 'flex-start',
1588
+ px: '16px !important',
1589
+ py: '12px !important',
1590
+ '&:hover': { backgroundColor: 'transparent !important' },
1591
+ cursor: 'default',
1592
+ }}
1593
+ >
1594
+ <Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 0.5 }}>
1595
+ <ListItemIcon sx={iconChipSx}>
1596
+ <FontAwesomeIcon icon={faPhone} className="w-4.5 h-4.5" />
1597
+ </ListItemIcon>
1598
+ <Typography variant="body2" sx={{ fontWeight: 600 }}>
1599
+ {translate('Contact Us')}
1600
+ </Typography>
1601
+ </Box>
1602
+ <Typography
1603
+ variant="caption"
1604
+ sx={{
1605
+ pl: '42px',
1606
+ color: theme.palette.custom?.shellMuted,
1607
+ whiteSpace: 'normal',
1608
+ maxWidth: '220px',
1609
+ lineHeight: 1.4,
1610
+ }}
1611
+ >
1612
+ Additional Director (FW) Office, Block No. 5/2, Dr. Jivraj Mehta Bhavan, Gandhinagar - 382010
1613
+ </Typography>
1614
+ </MenuItem>
1615
+ </>
1616
+ )}
1617
+ {user?.userLastLogin && (
1618
+ <>
1619
+ <Divider sx={profileDividerSx} />
1620
+ <MenuItem>
1621
+ <Typography variant="body2" sx={{ color: theme.palette.custom?.shellMuted }}>
1622
+ {translate('Last Login')} : {new Date(user.userLastLogin).toLocaleDateString('en-GB')}{' '}
1623
+ {new Date(user.userLastLogin).toLocaleTimeString('en-GB', { hour12: true })}
1624
+ </Typography>
1625
+ </MenuItem>
1626
+ </>
1627
+ )}
1628
+ <Divider sx={profileDividerSx} />
1629
+ <MenuItem data-testid="navbar-logout-button" onClick={handleLogout}>
1630
+ <ListItemIcon
1631
+ sx={{
1632
+ ...iconChipSx,
1633
+ color: isDark ? '#fecaca' : '#b91c1c',
1634
+ background: isDark ? alpha('#ef4444', 0.14) : alpha('#ef4444', 0.1),
1635
+ }}
1636
+ >
1637
+ <FontAwesomeIcon icon={faRightFromBracket} className="w-4.5 h-4.5" />
1638
+ </ListItemIcon>
1639
+ <Typography variant="body2" sx={{ fontWeight: 700 }}>
1640
+ {translate('Logout')}
1641
+ </Typography>
1642
+ </MenuItem>
1643
+ </div>
1644
+ </Menu>
1645
+ </Toolbar>
1646
+ </AppBar>
1647
+ </>
1648
+ );
1649
+ };
1650
+ SearchModal.propTypes = {
1651
+ allMenu: PropTypes.arrayOf(PropTypes.object),
1652
+ open: PropTypes.bool.isRequired,
1653
+ handleClose: PropTypes.func.isRequired,
1654
+ recentSearches: PropTypes.arrayOf(
1655
+ PropTypes.shape({
1656
+ selectedOption: PropTypes.object,
1657
+ title: PropTypes.string,
1658
+ description: PropTypes.string,
1659
+ })
1660
+ ),
1661
+ navigate: PropTypes.func.isRequired,
1662
+ setRecentSearches: PropTypes.func.isRequired,
1663
+ };
1664
+ Navbar.propTypes = {
1665
+ user: PropTypes.shape({
1666
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
1667
+ name: PropTypes.string,
1668
+ userLastLogin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
1669
+ languagePreference: PropTypes.string,
1670
+ features: PropTypes.object,
1671
+ }),
1672
+ orientation: PropTypes.oneOf(['vertical', 'horizontal']).isRequired,
1673
+ setShowSideBar: PropTypes.func,
1674
+ showSideBar: PropTypes.bool,
1675
+ setOrientation: PropTypes.func.isRequired,
1676
+ };
1677
+ export default Navbar;