@argusoft/medplat-app-shell 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) 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/store/slices/dashboardSlice.js +14 -0
  161. package/src/utils/.gitkeep +0 -0
  162. package/src/utils/FormConstants.js +2629 -0
  163. package/src/utils/GujaratTopoChart.jsx +483 -0
  164. package/src/utils/UUIDgenerator.js +8 -0
  165. package/src/utils/appointment-utils/appointment-utils.js +123 -0
  166. package/src/utils/feature.js +42 -0
  167. package/src/utils/getThemeColor.js +12 -0
  168. package/src/utils/localStorageHelper.js +11 -0
  169. package/src/utils/notifications/enable-push-notifications.js +27 -0
  170. package/src/utils/resolveAppliedStyle.js +11 -0
  171. package/src/utils/themeConfigs.js +1483 -0
  172. package/src/views/custom-components/.gitkeep +0 -0
  173. package/src/views/custom-components/AgIconButton/RIf.jsx +14 -0
  174. package/src/views/custom-components/AgIconButton/button.jsx +108 -0
  175. package/src/views/custom-components/AgIconButton/waterDrop.jsx +95 -0
  176. package/src/views/custom-components/AgIconButton/waterDrop.scss +37 -0
  177. package/src/views/custom-components/AlertPlaceholder.jsx +32 -0
  178. package/src/views/custom-components/AllFaIconsSelector.jsx +56 -0
  179. package/src/views/custom-components/CkEditor/CkEditor.js +102 -0
  180. package/src/views/custom-components/CustomAccordion.jsx +72 -0
  181. package/src/views/custom-components/CustomActionIcons.jsx +118 -0
  182. package/src/views/custom-components/CustomAutoComplete.jsx +188 -0
  183. package/src/views/custom-components/CustomCheckBox.jsx +60 -0
  184. package/src/views/custom-components/CustomConfirmationModal.jsx +118 -0
  185. package/src/views/custom-components/CustomCountrySelect.jsx +129 -0
  186. package/src/views/custom-components/CustomDatePicker.jsx +122 -0
  187. package/src/views/custom-components/CustomDropdown.jsx +191 -0
  188. package/src/views/custom-components/CustomFileUpload.jsx +387 -0
  189. package/src/views/custom-components/CustomFullCalendar.jsx +514 -0
  190. package/src/views/custom-components/CustomInfiniteScroll.jsx +126 -0
  191. package/src/views/custom-components/CustomRadioComponent.jsx +65 -0
  192. package/src/views/custom-components/CustomStatsComponent.jsx +114 -0
  193. package/src/views/custom-components/CustomSvgUpload.jsx +170 -0
  194. package/src/views/custom-components/CustomSwitch.jsx +37 -0
  195. package/src/views/custom-components/CustomTabPanel.jsx +19 -0
  196. package/src/views/custom-components/CustomTextArea.jsx +62 -0
  197. package/src/views/custom-components/CustomTextArea.scss +27 -0
  198. package/src/views/custom-components/CustomTextField.jsx +116 -0
  199. package/src/views/custom-components/CustomToggleSwitch.jsx +138 -0
  200. package/src/views/custom-components/CustomTooltip.jsx +51 -0
  201. package/src/views/custom-components/CustomZoomImage.jsx +134 -0
  202. package/src/views/custom-components/CustomizedTable/CustomizedTableV2.jsx +1407 -0
  203. package/src/views/custom-components/CustomizedTable/VirtualizeTableBody.jsx +295 -0
  204. package/src/views/custom-components/CustomizedTable/helper.jsx +159 -0
  205. package/src/views/custom-components/CustomizedTable.jsx +532 -0
  206. package/src/views/custom-components/EditInputField.jsx +174 -0
  207. package/src/views/custom-components/FieldDescription.jsx +22 -0
  208. package/src/views/custom-components/FileDisplayComponent.jsx +138 -0
  209. package/src/views/custom-components/FormItem.jsx +53 -0
  210. package/src/views/custom-components/GenericChart.jsx +80 -0
  211. package/src/views/custom-components/InfoBadge.jsx +60 -0
  212. package/src/views/custom-components/PostgresEditor.jsx +801 -0
  213. package/src/views/custom-components/ResizableEditAutocompleteField.jsx +249 -0
  214. package/src/views/custom-components/ResizableEditInputField.jsx +215 -0
  215. package/src/views/custom-components/ResizeableEditSelectField.jsx +197 -0
  216. package/src/views/custom-components/SideOverlay.jsx +113 -0
  217. package/src/views/custom-components/SideOverlay.scss +42 -0
  218. package/src/views/custom-components/calendar.scss +571 -0
  219. package/src/views/feature-components/.gitkeep +0 -0
  220. package/src/views/feature-components/Dashboard/DashboardUI.jsx +1043 -0
  221. package/src/views/feature-components/Dashboard/DhnddModal/AshaDataQualityVerificationModal.jsx +278 -0
  222. package/src/views/feature-components/Dashboard/PinFeatureModal.jsx +143 -0
  223. package/src/views/feature-components/Dashboard/QuickLinks.jsx +163 -0
  224. package/src/views/feature-components/Dashboard/Taskbar.jsx +56 -0
  225. package/src/views/feature-components/Dashboard/WebtasksFilterForm.jsx +109 -0
  226. package/src/views/feature-components/Dashboard/WidgetCard.jsx +161 -0
  227. package/src/views/feature-components/Dashboard/actionModal.jsx +263 -0
  228. package/src/views/feature-components/Dashboard/ekavachModal/HealthWorkerIncorrectDetailsModal.jsx +332 -0
  229. package/src/views/feature-components/Dashboard/ekavachModal/MoMaternalDeathVerifcationModal.jsx +275 -0
  230. package/src/views/feature-components/Dashboard/ekavachModal/MoVerficationForChildScreeningMoadal.jsx +566 -0
  231. package/src/views/feature-components/FeatureUsageAnalytics/FeatureUsageAnalytics.jsx +989 -0
  232. package/src/views/feature-components/Features/NewServerManagement.jsx +217 -0
  233. package/src/views/feature-components/Features/ServerManagement.scss +120 -0
  234. package/src/views/feature-components/ForgotPassword/ForgotPassword.jsx +226 -0
  235. package/src/views/feature-components/LocationDirective/LocationDirective.jsx +992 -0
  236. package/src/views/feature-components/LocationDirective/LocationDirectiveV2.jsx +909 -0
  237. package/src/views/feature-components/NotFound.jsx +66 -0
  238. package/src/views/feature-components/Onboarding/Onboarding.jsx +1400 -0
  239. package/src/views/feature-components/Skeletons.js +115 -0
  240. package/src/views/feature-components/Unauthorized.jsx +48 -0
  241. package/src/views/feature-components/VerifyRoute.jsx +88 -0
  242. package/src/views/feature-components/YearlyRecap/YearlyRecap.jsx +357 -0
  243. package/src/views/feature-components/YearlyRecap/components/RecapSlide.jsx +183 -0
  244. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationContext.js +5 -0
  245. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationProvider.jsx +26 -0
  246. package/src/views/feature-components/YearlyRecap/languageTranslator/i18n.js +46 -0
  247. package/src/views/feature-components/YearlyRecap/languageTranslator/translations.json +167 -0
  248. package/src/views/feature-components/YearlyRecap/slides/IntroSlide.jsx +233 -0
  249. package/src/views/feature-components/YearlyRecap/slides/MaternalHealthSlide.jsx +146 -0
  250. package/src/views/feature-components/YearlyRecap/slides/MetricSlide.jsx +227 -0
  251. package/src/views/feature-components/YearlyRecap/slides/OutroSlide.jsx +701 -0
  252. package/src/views/feature-components/YearlyRecap/slides/ReachSlide.jsx +273 -0
  253. package/src/views/feature-components/login/Login.jsx +840 -0
  254. package/src/views/feature-components/login/Login.scss +154 -0
  255. package/src/views/feature-components/login/LoginConfigurator.jsx +1149 -0
  256. package/src/views/feature-components/login/TwoFactorSetupModal.jsx +411 -0
  257. package/src/views/feature-components/login/simplifyMenu.js +45 -0
  258. package/src/views/feature-components/system-config/ManageSystemConfigs.jsx +284 -0
  259. package/src/views/feature-components/system-config/SystemConfig.jsx +299 -0
  260. package/src/views/feature-components/users/ChangePasswordModal.jsx +243 -0
  261. package/src/views/feature-components/users/PasswordField.jsx +56 -0
  262. package/dist/index.css +0 -1
  263. package/dist/index.js +0 -32001
  264. package/dist/index.js.map +0 -1
@@ -0,0 +1,483 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import { Loader2 } from "lucide-react";
3
+ import { ComposableMap, Geographies, Geography, Marker } from 'react-simple-maps';
4
+ import topoData from '@/assets/Gujarat-Topo.json';
5
+ import Grid from '@mui/material/Grid2';
6
+ import { Paper, Typography } from '@mui/material';
7
+ import * as topojson from 'topojson-client';
8
+ import { colorMap, CORPORATION, stateData1 } from '@/common/constants/PerformanceDashboard';
9
+
10
+ /**
11
+ * Returns the mapped name for a district if available, else returns the original name.
12
+ * @param {string} name - District name from topo data.
13
+ * @returns {string} - Mapped district name.
14
+ */
15
+ const NAME_MAPPING_MAP = {
16
+ 'Jam Kandorna': 'Jamkandorna',
17
+ Vinchhiya: 'Vinchchiya',
18
+ 'Kotda Sangani': 'Kotada sangani',
19
+ 'Gir Somnath': 'Gir Somnath',
20
+ Kachchh: 'Kutch',
21
+ Mahesana: 'mehsana',
22
+ 'Chhota Udepur': 'Chhota udepur',
23
+ 'Devbhumi Dwarka': 'Devbhumi Dwarka',
24
+ umerpada: 'Umarpada',
25
+ mandavi: 'Mandvi',
26
+ Baradoli: 'Bardoli',
27
+ UMARALA: 'Umrala',
28
+ GARIYADHAR: 'Gariadhar',
29
+ Kukavav: 'Kunkavav vadia',
30
+ Liliya: 'Lilia',
31
+ Savarkundla: 'Savar kundla',
32
+ Detroj: 'Detroj-rampura',
33
+ LAKHATAR: 'Lakhtar',
34
+ WADHVAN: 'Wadhwan',
35
+ mulee: 'Muli',
36
+ thanagadha: 'Thangadh',
37
+ DHRANGDHRA: 'Dhrangadhra',
38
+ Kaparada: 'Kaprada',
39
+ ANKLESHWAR: 'Anklesvar',
40
+ Dediyapada: 'Dediapada',
41
+ Dahegam: 'Dehgam',
42
+ Sarasvati: 'Sarsvati(Patan)',
43
+ Shankheshvar: 'Sankheswar',
44
+ Vansada: 'Bansda',
45
+ 'Morva-Hadaf': 'Morwa hadaf',
46
+ GODHARA: 'Godhra',
47
+ Shahera: 'Shehera',
48
+ NAKHTRANA: 'Nakhatrana',
49
+ GALTESWAR: 'Galteshwar',
50
+ KAPDWANJ: 'Kapadvanj',
51
+ MAHEMDAWAD: 'Mehmedabad',
52
+ Nizer: 'Nizar',
53
+ botada: 'Botad',
54
+ ranapura: 'Ranpur',
55
+ baravala: 'Barwala',
56
+ Vankaner: 'Wankaner',
57
+ MEGHARAJ: 'Meghraj',
58
+ DANTIVADA: 'Dantiwada',
59
+ LAKHNI: 'Lakhani',
60
+ WAV: 'Vav',
61
+ Bhesan: 'Bhesana',
62
+ 'Maliya hatina': 'Malia',
63
+ Mendarada: 'Mendarda',
64
+ SHINOR: 'Sinor',
65
+ WAGHODIA: 'Vaghodia',
66
+ Devgadhbariya: 'Devgadh baria',
67
+ };
68
+
69
+ /**
70
+ * Returns the mapped name for a location if available, else returns the original name.
71
+ * @param {string} name - Location name from topo data.
72
+ * @returns {string} - Mapped location name.
73
+ */
74
+ const getMappingName = (name) => {
75
+ return NAME_MAPPING_MAP[name] || name;
76
+ };
77
+
78
+ const norm = (s) => (s || '').toString().trim().toLowerCase();
79
+
80
+ /**
81
+ * GujaratTopoChart functional component
82
+ * @component
83
+ * @returns {JSX.Element} - A React component rendering Gujarat districts and corporations on a map.
84
+ */
85
+ const GujaratTopoChart = ({
86
+ chartData = stateData1,
87
+ userLocationLevel = 1,
88
+ userLocationName = '',
89
+ topoData: externalTopoData,
90
+ onClick,
91
+ loading = false,
92
+ tooltipConfig = null,
93
+ }) => {
94
+ const [tooltipContent, setTooltipContent] = useState(null);
95
+ const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
96
+ const [showTooltip, setShowTooltip] = useState(false);
97
+
98
+ const currentTopo = externalTopoData || topoData;
99
+
100
+ const resolveLayerPlan = (topo) => {
101
+ if (topo && topo.type === 'FeatureCollection') {
102
+ return {
103
+ objectKey: 'dynamic',
104
+ propertyName: 'english_name',
105
+ filterProperty: null,
106
+ filterValue: null,
107
+ labelProp: 'english_name',
108
+ layerType: userLocationLevel === 4 ? 'PHC' : userLocationLevel === 5 ? 'SC' : 'VILLAGE',
109
+ isDynamic: true,
110
+ };
111
+ }
112
+
113
+ const objects = (topo && topo.objects) || {};
114
+
115
+ try {
116
+ const dynObj = objects && objects.collection;
117
+ const g0 = dynObj && dynObj.geometries && dynObj.geometries[0];
118
+ const p0 = (g0 && g0.properties) || {};
119
+ if (dynObj && p0 && p0.area_type != null) {
120
+ return {
121
+ objectKey: 'collection',
122
+ propertyName: 'english_name',
123
+ filterProperty: null,
124
+ filterValue: null,
125
+ labelProp: 'english_name',
126
+ layerType: userLocationLevel === 4 ? 'PHC' : userLocationLevel === 5 ? 'SC' : 'VILLAGE',
127
+ isDynamic: true,
128
+ };
129
+ }
130
+ } catch (e) {}
131
+
132
+ const pickKey = (keys) => {
133
+ for (let i = 0; i < keys.length; i++) {
134
+ if (objects[keys[i]]) return keys[i];
135
+ }
136
+ return Object.keys(objects)[0];
137
+ };
138
+
139
+ const detectNameProp = (objectKey, fallbacks) => {
140
+ try {
141
+ const obj = objects[objectKey];
142
+ const g0 = obj && obj.geometries && obj.geometries[0];
143
+ const p = (g0 && g0.properties) || {};
144
+ for (let i = 0; i < fallbacks.length; i++) {
145
+ if (p[fallbacks[i]] != null) return fallbacks[i];
146
+ }
147
+ } catch (e) {}
148
+ return fallbacks[0];
149
+ };
150
+
151
+ if (userLocationLevel === 1 || userLocationLevel === 2) {
152
+ const dKey = pickKey(['District', 'districts', 'polygons', 'collection']);
153
+ const dNameProp = detectNameProp(dKey, ['District', 'district', 'english_name', 'name']);
154
+ return {
155
+ objectKey: dKey,
156
+ propertyName: dNameProp,
157
+ filterProperty: null,
158
+ filterValue: null,
159
+ labelProp: dNameProp,
160
+ layerType: 'DISTRICT',
161
+ isDynamic: false,
162
+ };
163
+ }
164
+
165
+ if (userLocationLevel === 3) {
166
+ const tKey = pickKey(['Taluka', 'taluka', 'Block', 'block', 'collection']);
167
+ const tNameProp = detectNameProp(tKey, ['Taluka', 'taluka', 'Block', 'block', 'english_name', 'name']);
168
+ return {
169
+ objectKey: tKey,
170
+ propertyName: tNameProp,
171
+ filterProperty: 'District',
172
+ filterValue: norm(getMappingName(userLocationName)),
173
+ labelProp: tNameProp,
174
+ layerType: 'TALUKA',
175
+ isDynamic: false,
176
+ };
177
+ }
178
+
179
+ if (userLocationLevel === 4) {
180
+ const pKey = pickKey(['PHC', 'phc', 'collection']);
181
+ const pNameProp = detectNameProp(pKey, ['english_name', 'PHC', 'phc', 'name']);
182
+ return {
183
+ objectKey: pKey,
184
+ propertyName: pNameProp,
185
+ filterProperty: 'Taluka',
186
+ filterValue: norm(getMappingName(userLocationName)),
187
+ labelProp: pNameProp,
188
+ layerType: 'PHC',
189
+ isDynamic: false,
190
+ };
191
+ }
192
+
193
+ if (userLocationLevel === 5) {
194
+ const scKey = pickKey(['SubCenter', 'Sub_Center', 'SC', 'subcenter', 'collection', 'sub']);
195
+ const scNameProp = detectNameProp(scKey, ['english_name', 'SC', 'SubCenter', 'name', 'sub']);
196
+ return {
197
+ objectKey: scKey,
198
+ propertyName: scNameProp,
199
+ filterProperty: 'PHC',
200
+ filterValue: norm(getMappingName(userLocationName)),
201
+ labelProp: scNameProp,
202
+ layerType: 'SC',
203
+ isDynamic: false,
204
+ };
205
+ }
206
+
207
+ const vKey = pickKey(['Village', 'village', 'collection']);
208
+ const vNameProp = detectNameProp(vKey, ['Village', 'village', 'english_name', 'name']);
209
+ return {
210
+ objectKey: vKey,
211
+ propertyName: vNameProp,
212
+ filterProperty: 'SC',
213
+ filterValue: norm(getMappingName(userLocationName)),
214
+ labelProp: vNameProp,
215
+ layerType: 'VILLAGE',
216
+ isDynamic: false,
217
+ };
218
+ };
219
+
220
+ const findChartRowByFeature = (feature, plan) => {
221
+ if (!feature || !feature.properties) return null;
222
+ if (plan && plan.isDynamic && feature.properties.area_type === 'main') return null;
223
+
224
+ const locId = feature.properties.loc_id || feature.properties.id || null;
225
+ if (locId != null) {
226
+ const byId = (chartData || []).find((row) => String(row.id) === String(locId));
227
+ if (byId) return byId;
228
+ }
229
+
230
+ const featureName = feature.properties[plan.propertyName];
231
+ if (!featureName) return null;
232
+
233
+ const mappedFeatureName = norm(getMappingName(featureName));
234
+ return (chartData || []).find((row) => norm(getMappingName(row.x_axis_label)) === mappedFeatureName);
235
+ };
236
+
237
+ const handleMouseMove = (event) => {
238
+ const rect = event.currentTarget.getBoundingClientRect();
239
+ setTooltipPosition({
240
+ x: event.clientX - rect.left + 12,
241
+ y: event.clientY - rect.top + 12,
242
+ });
243
+ };
244
+
245
+ const plan = resolveLayerPlan(currentTopo);
246
+
247
+ const isGeoJson = currentTopo && currentTopo.type === 'FeatureCollection';
248
+ let features = [];
249
+
250
+ if (isGeoJson) {
251
+ features = currentTopo.features || [];
252
+ } else {
253
+ // Filter geographies if needed (for non-dynamic but filtered layers)
254
+ let topoToDraw = currentTopo;
255
+ if (!plan.isDynamic && plan.filterProperty && plan.filterValue) {
256
+ const obj = currentTopo.objects[plan.objectKey];
257
+ if (obj && obj.geometries) {
258
+ const filteredGeoms = obj.geometries.filter((g) => {
259
+ const p = g.properties || {};
260
+ return norm(p[plan.filterProperty]) === plan.filterValue;
261
+ });
262
+ topoToDraw = {
263
+ ...currentTopo,
264
+ objects: {
265
+ ...currentTopo.objects,
266
+ [plan.objectKey]: { ...obj, geometries: filteredGeoms },
267
+ },
268
+ };
269
+ }
270
+ }
271
+ const featureColl = topojson.feature(topoToDraw, topoToDraw.objects[plan.objectKey]);
272
+ features = featureColl && featureColl.features ? featureColl.features : [];
273
+ }
274
+
275
+ const getGeographyFill = (geo, plan) => {
276
+ const row = findChartRowByFeature(geo, plan);
277
+ if (!row) return 'white';
278
+ return colorMap[row.color] || row.color || 'white';
279
+ };
280
+
281
+ const getGeographyOpacity = (geo, plan) => {
282
+ const row = findChartRowByFeature(geo, plan);
283
+ return row ? 1 : 0.1;
284
+ };
285
+
286
+ const projectionConfig = useMemo(() => {
287
+ if (!features.length) {
288
+ return { scale: 5000, center: [71.4, 22.3] };
289
+ }
290
+
291
+ let minLng = 180, maxLng = -180, minLat = 90, maxLat = -90;
292
+ let found = false;
293
+
294
+ features.forEach(f => {
295
+ if (!f || !f.geometry) return;
296
+ const processCoords = (coords) => {
297
+ coords.forEach(p => {
298
+ const lng = p[0], lat = p[1];
299
+ if (lng < minLng) minLng = lng;
300
+ if (lng > maxLng) maxLng = lng;
301
+ if (lat < minLat) minLat = lat;
302
+ if (lat > maxLat) maxLat = lat;
303
+ found = true;
304
+ });
305
+ };
306
+
307
+ if (f.geometry.type === 'Polygon') {
308
+ f.geometry.coordinates.forEach(processCoords);
309
+ } else if (f.geometry.type === 'MultiPolygon') {
310
+ f.geometry.coordinates.forEach(poly => poly.forEach(processCoords));
311
+ }
312
+ });
313
+
314
+ if (!found) return { scale: 5000, center: [71.4, 22.3] };
315
+
316
+ const centerLng = (minLng + maxLng) / 2;
317
+ const centerLat = (minLat + maxLat) / 2;
318
+ const dLng = maxLng - minLng || 0.1;
319
+ const dLat = maxLat - minLat || 0.1;
320
+
321
+ // Gujarat aspect ratio is roughly 7.5 deg wide (68-75.5) and 5.5 deg high (20-25.5).
322
+ // The scale heuristic: Gujarat state view (full width) fits at ~5000.
323
+ // We calculate based on both width and height constraints.
324
+ const scale = Math.min(7.5 / dLng, 5.5 / dLat) * 5000 * 0.7;
325
+
326
+ return { scale, center: [centerLng, centerLat] };
327
+ }, [features]);
328
+
329
+ return (
330
+ <Grid className="w-full relative" onMouseMove={handleMouseMove} data-testid="gujarat-topo-chart" sx={{ height: 400 }}>
331
+ {showTooltip && tooltipContent && (
332
+ <Paper
333
+ className="absolute px-2.5 py-1.5 text-sm pointer-events-none bg-[var(--color-background2)] text-[var(--color-text)] border-[var(--color-gray3)] z-[9999] shadow-md"
334
+ sx={{
335
+ top: tooltipPosition.y,
336
+ left: tooltipPosition.x,
337
+ backgroundColor: "rgba(255, 255, 255, 0.98)",
338
+ backdropFilter: "blur(4px)"
339
+ }}
340
+ data-testid="tooltip"
341
+ >
342
+ <Typography className="text-[13px] font-bold text-gray-800 mb-2 border-b border-gray-100 pb-1.5">{tooltipContent.name}</Typography>
343
+ {tooltipContent.data ? (
344
+ <div className="space-y-1.5">
345
+ {tooltipConfig ? (
346
+ tooltipConfig.map((configItem, idx) => {
347
+ const val = tooltipContent.data[configItem.key];
348
+ return (
349
+ <div key={idx} className={`flex justify-between items-center gap-4 ${configItem.highlight ? 'pt-1.5 border-t border-gray-100 mt-1' : ''}`}>
350
+ <span className={`text-[11px] ${configItem.highlight ? 'text-gray-600 font-bold uppercase tracking-wider' : 'text-gray-500 font-medium'}`}>{configItem.label}:</span>
351
+ <span className={`text-[11px] font-bold`} style={{ color: configItem.valueColor || (configItem.highlight ? '#6961CE' : '#1f2937'), fontSize: configItem.highlight ? '13px' : '11px', fontWeight: configItem.highlight ? 900 : 700 }}>
352
+ {val !== undefined ? val.toLocaleString() : 0}{configItem.suffix || ''}
353
+ </span>
354
+ </div>
355
+ );
356
+ })
357
+ ) : (
358
+ <div className="flex justify-between items-center gap-4">
359
+ <span className="text-[11px] text-gray-500 font-medium">Value:</span>
360
+ <span className="text-[11px] text-gray-800 font-bold">{tooltipContent.data.series_label !== undefined ? tooltipContent.data.series_label : 'N/A'}</span>
361
+ </div>
362
+ )}
363
+ </div>
364
+ ) : (
365
+ <div className="text-[11px] text-gray-400 italic">No data available</div>
366
+ )}
367
+ </Paper>
368
+ )}
369
+
370
+ {loading && (
371
+ <div className="absolute inset-0 z-10 flex flex-col items-center justify-center bg-white/60 backdrop-blur-[1px] rounded-lg">
372
+ <Loader2 className="w-8 h-8 text-[#00546D] animate-spin mb-2" />
373
+ <span className="text-[10px] font-bold text-[#00546D] uppercase tracking-widest">Updating Map...</span>
374
+ </div>
375
+ )}
376
+
377
+ <ComposableMap
378
+ projectionConfig={projectionConfig}
379
+ height={400}
380
+ className="w-full h-full"
381
+ data-testid="composable-map"
382
+ >
383
+ <Geographies geography={features} data-testid="geographies">
384
+ {({ geographies }) =>
385
+ geographies.map((geo) => {
386
+ if (plan.isDynamic && geo.properties.area_type === 'main') {
387
+ return (
388
+ <Geography
389
+ key={geo.rsmKey}
390
+ geography={geo}
391
+ style={{
392
+ default: { outline: 'none', fill: 'none', stroke: '#000', strokeWidth: 1.5 },
393
+ hover: { outline: 'none', fill: 'none', stroke: '#000', strokeWidth: 1.5 },
394
+ pressed: { outline: 'none', fill: 'none', stroke: '#000', strokeWidth: 1.5 },
395
+ }}
396
+ />
397
+ );
398
+ }
399
+
400
+ const row = findChartRowByFeature(geo, plan);
401
+ const name = geo.properties[plan.propertyName] || '';
402
+
403
+ return (
404
+ <Geography
405
+ key={geo.rsmKey}
406
+ geography={geo}
407
+ style={{
408
+ default: { outline: 'none', stroke: '#000', strokeWidth: plan.layerType === 'PHC' ? 0.4 : 0.2 },
409
+ hover: { outline: 'none', stroke: '#000', strokeWidth: 0.8, cursor: row ? 'pointer' : 'default' },
410
+ pressed: { outline: 'none', stroke: '#000', strokeWidth: 0.5 },
411
+ }}
412
+ fill={getGeographyFill(geo, plan)}
413
+ fillOpacity={getGeographyOpacity(geo, plan)}
414
+ onMouseEnter={() => {
415
+ if (row) {
416
+ setTooltipContent({
417
+ name: row.x_axis_label || name,
418
+ data: row
419
+ });
420
+ } else {
421
+ setTooltipContent({ name });
422
+ }
423
+ setShowTooltip(true);
424
+ }}
425
+ onMouseLeave={() => {
426
+ setShowTooltip(false);
427
+ setTooltipContent(null);
428
+ }}
429
+ onClick={() => {
430
+ if (row && onClick) {
431
+ onClick(row);
432
+ }
433
+ }}
434
+ data-testid={`geography-${name}`}
435
+ />
436
+ );
437
+ })
438
+ }
439
+ </Geographies>
440
+
441
+ {userLocationLevel <= 2 &&
442
+ CORPORATION.map((corp, i) => {
443
+ const corpData = chartData.find(
444
+ (item) => getMappingName(item.x_axis_label).toLowerCase() === corp.name.toLowerCase()
445
+ );
446
+ const color = corpData ? colorMap[corpData.color] || corpData.color || '#CCCCCC' : '#CCCCCC';
447
+
448
+ return (
449
+ <Marker
450
+ key={i}
451
+ coordinates={[corp.location.longitude, corp.location.latitude]}
452
+ data-testid={`marker-${corp.name}`}
453
+ >
454
+ <circle
455
+ r={8}
456
+ fill={color}
457
+ className="stroke-white stroke-[1px] cursor-pointer"
458
+ onMouseEnter={() => {
459
+ setTooltipContent({
460
+ name: corp.name,
461
+ data: corpData || null
462
+ });
463
+ setShowTooltip(true);
464
+ }}
465
+ onMouseLeave={() => {
466
+ setShowTooltip(false);
467
+ setTooltipContent(null);
468
+ }}
469
+ onClick={() => {
470
+ if (corpData && onClick) {
471
+ onClick(corpData);
472
+ }
473
+ }}
474
+ />
475
+ </Marker>
476
+ );
477
+ })}
478
+ </ComposableMap>
479
+ </Grid>
480
+ );
481
+ };
482
+
483
+ export default GujaratTopoChart;
@@ -0,0 +1,8 @@
1
+ export const generateUUID = () => {
2
+ function s4() {
3
+ return Math.floor((1 + Math.random()) * 0x10000)
4
+ .toString(16)
5
+ .substring(1);
6
+ }
7
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
8
+ };
@@ -0,0 +1,123 @@
1
+ import dayjs from 'dayjs';
2
+ const CLINIC_VISIT = 'CLINIC_VISIT';
3
+ const DATE_FORMAT = 'YYYY-MM-DD';
4
+ // const MONTH_GRID_CALENDAR = 'dayGridMonth';
5
+ const TELE_CONSULTATION = 'TELE_CONSULTATION';
6
+ export const DAY_GRID_CALENDAR = 'timeGridDay';
7
+ export const WEEK_GRID_CALENDAR = 'timeGridWeek';
8
+ export const MONTH_GRID_CALENDAR = 'dayGridMonth';
9
+ // export const DAY_GRID_CALENDAR = "timeGridDay";
10
+ export const updateFilteredAppointment = (allEvents, viewInfo) => {
11
+ let filteredAppointments = [];
12
+ let monthViewEvents;
13
+ if (viewInfo) {
14
+ const selectedStartDate = dayjs(viewInfo.start).format(DATE_FORMAT);
15
+ const selectedEndDate = dayjs(viewInfo.end).format(DATE_FORMAT);
16
+
17
+ if (viewInfo.view.type === DAY_GRID_CALENDAR) {
18
+ filteredAppointments = allEvents.filter((event) => dayjs(event.start).format(DATE_FORMAT) === selectedStartDate);
19
+ } else if (viewInfo.view.type === WEEK_GRID_CALENDAR) {
20
+ filteredAppointments = allEvents.filter(
21
+ (event) =>
22
+ dayjs(event.start).format(DATE_FORMAT) >= selectedStartDate &&
23
+ dayjs(event.end).format(DATE_FORMAT) < selectedEndDate
24
+ );
25
+ } else if (viewInfo.view.type === MONTH_GRID_CALENDAR) {
26
+ const monthStartDate = dayjs(viewInfo.view.currentStart).format(DATE_FORMAT);
27
+ const monthEndDate = dayjs(viewInfo.view.currentEnd).format(DATE_FORMAT);
28
+
29
+ filteredAppointments = allEvents.filter(
30
+ (event) =>
31
+ dayjs(event.start).format(DATE_FORMAT) >= monthStartDate &&
32
+ dayjs(event.end).format(DATE_FORMAT) < monthEndDate
33
+ );
34
+ const monthEvents = [];
35
+
36
+ const events = filteredAppointments.reduce((acc, appointment) => {
37
+ const date = dayjs(appointment.start).format(DATE_FORMAT);
38
+
39
+ if (!acc[date]) {
40
+ acc[date] = {
41
+ clinicVisitCount: 0,
42
+ teleconsultationCount: 0,
43
+ start: appointment.start,
44
+ end: appointment.end,
45
+ teleAppointment: [],
46
+ clinicAppointment: [],
47
+ };
48
+ }
49
+
50
+ if (appointment.appointmentType === 'CLINIC_VISIT') {
51
+ acc[date].clinicVisitCount++;
52
+ acc[date].clinicAppointment.push({
53
+ id: appointment.appointmentId,
54
+ title: appointment.title,
55
+ patientId: appointment.patientId,
56
+ });
57
+ } else if (appointment.appointmentType === 'TELE_CONSULTATION') {
58
+ acc[date].teleconsultationCount++;
59
+ acc[date].teleAppointment.push({
60
+ ...appointment,
61
+ });
62
+ }
63
+
64
+ return acc;
65
+ }, {});
66
+
67
+ Object.keys(events).forEach((date) => {
68
+ monthEvents.push({
69
+ start: events[date].start,
70
+ end: events[date].end,
71
+ appointmentType: TELE_CONSULTATION,
72
+ count: events[date].teleconsultationCount,
73
+ teleAppointment: events[date].teleAppointment,
74
+ });
75
+ monthEvents.push({
76
+ start: events[date].start,
77
+ end: events[date].end,
78
+ appointmentType: CLINIC_VISIT,
79
+ count: events[date].clinicVisitCount,
80
+ clinicAppointment: events[date].clinicAppointment,
81
+ });
82
+ });
83
+ monthViewEvents = monthEvents;
84
+ } else {
85
+ const monthStartDate = dayjs(viewInfo.view.currentStart).format(DATE_FORMAT);
86
+ const monthEndDate = dayjs(viewInfo.view.currentEnd).format(DATE_FORMAT);
87
+ filteredAppointments = allEvents.filter(
88
+ (event) =>
89
+ dayjs(event.start).format(DATE_FORMAT) >= monthStartDate &&
90
+ dayjs(event.end).format(DATE_FORMAT) < monthEndDate
91
+ );
92
+ }
93
+ } else {
94
+ const today = dayjs();
95
+ const formattedToday = today.format(DATE_FORMAT);
96
+ filteredAppointments = allEvents.filter((event) => dayjs(event.start).format(DATE_FORMAT) === formattedToday);
97
+ }
98
+ return { filteredAppointments, monthViewEvents };
99
+ };
100
+
101
+ // when doctor click on appointment count on month view of calendar
102
+ // export const handleMonthViewMoreClick = async (eventInfo, doctorAppointments) => {
103
+ // const selectedDate = eventInfo.event.start.toISOString().split("T")[0];
104
+ // const appointmentOnSelectedDate = doctorAppointments.filter((appointment) => {
105
+ // return (
106
+ // appointment.appointmentStatus !== CANCELLED &&
107
+ // appointment.appointmentStatus !== NO_SHOW &&
108
+ // appointment.appointmentDate === selectedDate &&
109
+ // appointment.type === eventInfo.event.extendedProps.appointmentType
110
+ // );
111
+ // });
112
+ // const appointmentDetails = await Promise.all(
113
+ // appointmentOnSelectedDate.map(async (appointment) => {
114
+ // try {
115
+ // const response = await getAppointmentById(appointment.id);
116
+ // return response.data;
117
+ // } catch (error) {
118
+ // console.error("error fetching patient appointments");
119
+ // }
120
+ // })
121
+ // );
122
+ // return appointmentDetails;
123
+ // };
@@ -0,0 +1,42 @@
1
+ import { getFeaturesV2 } from '@/common/services/AuthenticateService';
2
+ import { simplifyMenu } from '@/views/feature-components/login/simplifyMenu';
3
+ import { getAssignedFeature } from '@/common/getAssignedFeature/getAssignedFeature';
4
+ import { execute } from '@/common/services/QueryBuilder';
5
+ import GeneralUtil from '@/common/services/util/GeneralUtil';
6
+ import store from '@/store';
7
+
8
+ export const getAssignedFeatures2 = async (value) => {
9
+ try {
10
+ const { data: features } = await getFeaturesV2();
11
+ let linearMenuItems = {};
12
+ Object.entries(features || {}).forEach(([, element]) => {
13
+ simplifyMenu(element, linearMenuItems);
14
+ });
15
+ const response = await getAssignedFeature(value, linearMenuItems);
16
+ return { data: response?.featureJson };
17
+ } catch (error) {
18
+ console.error(error);
19
+ }
20
+ };
21
+
22
+ export const getAssignedFeatures = async (value) => {
23
+ try {
24
+ if (GeneralUtil.isTecho) {
25
+ const user = store.getState().Authenticate.user;
26
+ let queryDto = {
27
+ code: 'FETCH_PERMISSIONS',
28
+ parameters: {
29
+ roleId: user?.roleId,
30
+ userId: user?.id,
31
+ navigationState: value
32
+ }
33
+ };
34
+ const res = await execute(queryDto);
35
+ return { data: res?.data?.result?.[0]?.featurejson };
36
+ } else {
37
+ return getAssignedFeatures2(value);
38
+ }
39
+ } catch (error) {
40
+ console.error(error);
41
+ }
42
+ };
@@ -0,0 +1,12 @@
1
+ import { useTheme } from '@mui/material';
2
+
3
+ export const getThemeColor = (colorKey) => {
4
+ const theme = useTheme();
5
+ if (!colorKey) return undefined;
6
+
7
+ if (theme.palette[colorKey]?.main) {
8
+ return theme.palette[colorKey].main;
9
+ }
10
+
11
+ return colorKey;
12
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Clears every entry in localStorage except the one named "pinnedFeatures".
3
+ * This function is safe to call on logout – it preserves the user's pinned UI state.
4
+ */
5
+ export function clearAllExceptPinned() {
6
+ const pinned = localStorage.getItem('pinnedFeatures');
7
+ localStorage.clear();
8
+ if (pinned !== null) {
9
+ localStorage.setItem('pinnedFeatures', pinned);
10
+ }
11
+ }