ui-soxo-bootstrap-core 2.6.40-dev.1 → 2.6.40-dev.12

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 (420) hide show
  1. package/.babelrc +8 -8
  2. package/.github/workflows/npm-publish.yml +5 -4
  3. package/.husky/pre-commit +11 -11
  4. package/.prettierrc.json +10 -10
  5. package/DEVELOPER_GUIDE.md +323 -323
  6. package/PUBLISHING.md +333 -0
  7. package/babel.config.js +2 -2
  8. package/core/components/component-loader/component-loader.js +125 -125
  9. package/core/components/component-wrapper/component-wrapper.js +121 -121
  10. package/core/components/external-window/DEVELOPER_GUIDE.md +705 -705
  11. package/core/components/external-window/external-window.js +236 -236
  12. package/core/components/external-window/external-window.test.js +80 -80
  13. package/core/components/extra-info/extra-info-details.js +155 -155
  14. package/core/components/extra-info/extra-info-details.scss +26 -26
  15. package/core/components/extra-info/extra-info.js +134 -134
  16. package/core/components/index.js +12 -12
  17. package/core/components/landing-api/landing-api.js +707 -707
  18. package/core/components/landing-api/landing-api.scss +41 -41
  19. package/core/components/license-management/license-alert.js +97 -97
  20. package/core/components/menu-template-api/menu-template-api.js +321 -321
  21. package/core/components/root-application-api/root-application-api.js +174 -174
  22. package/core/index.js +13 -13
  23. package/core/lib/Store.js +369 -369
  24. package/core/lib/components/application-bootstrap/application-bootstrap.js +115 -115
  25. package/core/lib/components/approval-form/approval-form.js +280 -280
  26. package/core/lib/components/approval-form/approval-form.scss +183 -183
  27. package/core/lib/components/approval-list/approval-list.js +143 -143
  28. package/core/lib/components/approval-list/approval-list.scss +2 -2
  29. package/core/lib/components/approval-list/components/request-card/request-card.js +42 -42
  30. package/core/lib/components/approval-list/components/request-card/request-card.scss +30 -30
  31. package/core/lib/components/camera/camera.js +230 -230
  32. package/core/lib/components/camera/camera.scss +86 -86
  33. package/core/lib/components/comment-block/comment-block.js +138 -138
  34. package/core/lib/components/comment-block/comment-block.scss +3 -3
  35. package/core/lib/components/confirm-modal/confirm-modal.js +82 -82
  36. package/core/lib/components/confirm-modal/confirm-modal.scss +2 -2
  37. package/core/lib/components/consent/consent.js +67 -67
  38. package/core/lib/components/consent/pdf-signature.js +299 -299
  39. package/core/lib/components/consent/signature-pad.js +90 -90
  40. package/core/lib/components/consent/signature-pad.scss +14 -14
  41. package/core/lib/components/file-upload/file-upload.js +133 -133
  42. package/core/lib/components/finger-print-reader/finger-print-reader.js +295 -295
  43. package/core/lib/components/finger-print-reader/finger-print-reader.scss +47 -47
  44. package/core/lib/components/finger-print-search/finger-print-search.js +200 -200
  45. package/core/lib/components/finger-print-search/finger-print-search.scss +47 -47
  46. package/core/lib/components/global-header/animations.js +18 -18
  47. package/core/lib/components/global-header/global-header.js +286 -286
  48. package/core/lib/components/global-header/global-header.scss +397 -397
  49. package/core/lib/components/header/generic-header.js +76 -76
  50. package/core/lib/components/header/generic-header.scss +99 -99
  51. package/core/lib/components/image-preview/image-preview.js +33 -33
  52. package/core/lib/components/image-wrapper/image-wrapper.js +108 -108
  53. package/core/lib/components/image-wrapper/image-wrapper.scss +12 -12
  54. package/core/lib/components/index.js +206 -206
  55. package/core/lib/components/landing/landing.js +403 -403
  56. package/core/lib/components/language-switcher/language-switcher.js +49 -49
  57. package/core/lib/components/menu-context/menu-context.js +69 -69
  58. package/core/lib/components/menu-template/menu-template.js +249 -249
  59. package/core/lib/components/menu-template/menu-template.scss +9 -9
  60. package/core/lib/components/modal-search/modal-search.js +153 -153
  61. package/core/lib/components/modal-search/modal-search.scss +78 -78
  62. package/core/lib/components/modal-wrapper/modal-manager.js +15 -15
  63. package/core/lib/components/modal-wrapper/modal-wrapper.js +108 -108
  64. package/core/lib/components/modal-wrapper/modal-wrapper.scss +13 -13
  65. package/core/lib/components/notice-board/notice-board.js +132 -132
  66. package/core/lib/components/notice-board/notice-board.scss +65 -65
  67. package/core/lib/components/page-container/page-container.js +55 -55
  68. package/core/lib/components/page-container/page-container.scss +8 -8
  69. package/core/lib/components/page-header/page-header.js +23 -23
  70. package/core/lib/components/page-header/page-header.scss +17 -17
  71. package/core/lib/components/pdf-viewer/pdf-viewer.js +56 -56
  72. package/core/lib/components/portlet-table/components/table-actions/table-actions.js +58 -58
  73. package/core/lib/components/portlet-table/components/table-actions/table-actions.scss +1 -1
  74. package/core/lib/components/portlet-table/components/table-data/table-data.js +106 -106
  75. package/core/lib/components/portlet-table/portlet-table.js +63 -63
  76. package/core/lib/components/portlet-table/portlet-table.scss +90 -90
  77. package/core/lib/components/progress-bar/progress-bar.js +58 -58
  78. package/core/lib/components/progress-bar/progress-bar.scss +15 -15
  79. package/core/lib/components/request-form/request-form.js +110 -110
  80. package/core/lib/components/root-application/root-application.js +70 -70
  81. package/core/lib/components/rupee/rupee.js +14 -14
  82. package/core/lib/components/script-input/script-input.js +169 -169
  83. package/core/lib/components/script-input/script-input.scss +8 -8
  84. package/core/lib/components/sidemenu/animations.js +51 -51
  85. package/core/lib/components/sidemenu/sidemenu.js +713 -713
  86. package/core/lib/components/sidemenu/sidemenu.scss +314 -314
  87. package/core/lib/components/spotlight-search/spotlight-search.component.js +635 -635
  88. package/core/lib/components/spotlight-search/spotlight-search.component.scss +78 -78
  89. package/core/lib/components/table-wrapper/table-wrapper.js +135 -135
  90. package/core/lib/components/table-wrapper/table-wrapper.scss +72 -72
  91. package/core/lib/components/ui_elements/Loader.js +12 -12
  92. package/core/lib/components/ui_elements/Notify.js +12 -12
  93. package/core/lib/components/ui_elements/PlaceHolder.js +33 -33
  94. package/core/lib/components/web-camera/web-camera.js +161 -161
  95. package/core/lib/components/web-camera/web-camera.scss +28 -28
  96. package/core/lib/core.md +9 -9
  97. package/core/lib/elements/Elements.md +2 -2
  98. package/core/lib/elements/basic/LoggedUserRedirect.js +21 -21
  99. package/core/lib/elements/basic/PrivateRoute.js +16 -16
  100. package/core/lib/elements/basic/button/Button.md +43 -43
  101. package/core/lib/elements/basic/button/button.js +170 -170
  102. package/core/lib/elements/basic/card/Card.md +15 -15
  103. package/core/lib/elements/basic/card/card.js +40 -40
  104. package/core/lib/elements/basic/card/card.scss +13 -13
  105. package/core/lib/elements/basic/checkbox/checkbox.js +23 -23
  106. package/core/lib/elements/basic/col/col.js +15 -15
  107. package/core/lib/elements/basic/copy-to-clipboard/Readme.md +40 -40
  108. package/core/lib/elements/basic/copy-to-clipboard/copy-to-clipboard.js +61 -61
  109. package/core/lib/elements/basic/country-phone-input/Readme.md +98 -98
  110. package/core/lib/elements/basic/country-phone-input/country-phone-input.js +81 -81
  111. package/core/lib/elements/basic/country-phone-input/phone-input.scss +75 -75
  112. package/core/lib/elements/basic/datepicker/datepicker.js +33 -33
  113. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +203 -203
  114. package/core/lib/elements/basic/empty/empty.js +14 -14
  115. package/core/lib/elements/basic/fingerprint-protrected/fingerprint-protected.js +118 -118
  116. package/core/lib/elements/basic/fingerprint-protrected/fingerprint-protected.scss +10 -10
  117. package/core/lib/elements/basic/form/form.js +70 -70
  118. package/core/lib/elements/basic/form/form.scss +3 -3
  119. package/core/lib/elements/basic/image/image.js +45 -45
  120. package/core/lib/elements/basic/image/image.scss +17 -17
  121. package/core/lib/elements/basic/image/readme.md +26 -26
  122. package/core/lib/elements/basic/image-viewer/image-viewer.js +108 -108
  123. package/core/lib/elements/basic/image-viewer/image-viewer.scss +7 -7
  124. package/core/lib/elements/basic/input/input.js +81 -81
  125. package/core/lib/elements/basic/input/readme.md +77 -77
  126. package/core/lib/elements/basic/json-input/json-input.js +51 -51
  127. package/core/lib/elements/basic/menu-dashboard/menu-dashboard.js +216 -216
  128. package/core/lib/elements/basic/menu-dashboard/menu-dashboard.scss +28 -28
  129. package/core/lib/elements/basic/menu-tree/menu-tree.js +127 -127
  130. package/core/lib/elements/basic/modal/modal.js +64 -64
  131. package/core/lib/elements/basic/modal/readme.md +62 -62
  132. package/core/lib/elements/basic/popconfirm/popconfirm.js +17 -17
  133. package/core/lib/elements/basic/popover/popover.js +12 -12
  134. package/core/lib/elements/basic/radio/radio.js +18 -18
  135. package/core/lib/elements/basic/rangepicker/rangepicker.js +141 -141
  136. package/core/lib/elements/basic/rangepicker/rangepicker.scss +24 -24
  137. package/core/lib/elements/basic/rangepicker/readme.md +81 -81
  138. package/core/lib/elements/basic/reference-select/readme.md +18 -18
  139. package/core/lib/elements/basic/reference-select/reference-select.js +337 -337
  140. package/core/lib/elements/basic/row/row.js +15 -15
  141. package/core/lib/elements/basic/select/select.js +46 -46
  142. package/core/lib/elements/basic/select-box/readme.md +52 -52
  143. package/core/lib/elements/basic/select-box/select-box.js +63 -63
  144. package/core/lib/elements/basic/skeleton/readme.md +35 -35
  145. package/core/lib/elements/basic/skeleton/skeleton.js +35 -35
  146. package/core/lib/elements/basic/skeleton/skeleton.scss +53 -53
  147. package/core/lib/elements/basic/space/space.js +12 -12
  148. package/core/lib/elements/basic/switch/readme.md +29 -29
  149. package/core/lib/elements/basic/switch/switch.js +67 -67
  150. package/core/lib/elements/basic/tab/tab.js +14 -14
  151. package/core/lib/elements/basic/table/readme.md +8 -8
  152. package/core/lib/elements/basic/table/table.js +95 -95
  153. package/core/lib/elements/basic/tag/tag.js +63 -63
  154. package/core/lib/elements/basic/tag/tag.scss +2 -2
  155. package/core/lib/elements/basic/timeline/timeline.js +13 -13
  156. package/core/lib/elements/basic/title/readme.md +20 -20
  157. package/core/lib/elements/basic/title/title.js +37 -37
  158. package/core/lib/elements/basic/user-search/user-search.js +192 -192
  159. package/core/lib/elements/complex/barcode/barcode.js +27 -27
  160. package/core/lib/elements/complex/bargraph/bar-graph.js +262 -262
  161. package/core/lib/elements/complex/basic-table/basic-table.js +110 -110
  162. package/core/lib/elements/complex/basic-table/basic-table.scss +4 -4
  163. package/core/lib/elements/complex/date-display/date-display.js +37 -37
  164. package/core/lib/elements/complex/error-boundary/error-boundary.js +29 -29
  165. package/core/lib/elements/complex/google-location-input/map-container-library-load.js +92 -92
  166. package/core/lib/elements/complex/google-map/google-map.js +230 -230
  167. package/core/lib/elements/complex/google-map/google-map.scss +13 -13
  168. package/core/lib/elements/complex/line-graph/line-graph.js +108 -108
  169. package/core/lib/elements/complex/location-search-input/location-search-input.js +100 -100
  170. package/core/lib/elements/complex/pie-chart/pie-chart.js +202 -202
  171. package/core/lib/elements/complex/qr-code/qr-code.js +27 -27
  172. package/core/lib/elements/complex/qrscanner/qrscanner.js +57 -57
  173. package/core/lib/elements/complex/search-debounce/search-debounce.js +37 -37
  174. package/core/lib/elements/complex/statistic-card/dashboard-statistic-card.js +75 -75
  175. package/core/lib/elements/complex/statistic-card/statistic-card.js +28 -28
  176. package/core/lib/elements/index.js +226 -226
  177. package/core/lib/hooks/device-detect.js +25 -25
  178. package/core/lib/hooks/index.js +9 -9
  179. package/core/lib/hooks/use-location.js +33 -33
  180. package/core/lib/hooks/use-otp-timer.js +80 -80
  181. package/core/lib/hooks/use-window-size.js +34 -34
  182. package/core/lib/i18n.js +69 -69
  183. package/core/lib/index.js +106 -106
  184. package/core/lib/introduction.md +73 -73
  185. package/core/lib/js-styleguide.md +4112 -4112
  186. package/core/lib/models/actions/actions.js +127 -127
  187. package/core/lib/models/actions/components/action-detail/action-detail.js +190 -190
  188. package/core/lib/models/actions/components/custom-actions/custom-actions.js +185 -185
  189. package/core/lib/models/attachments/attachments.js +231 -231
  190. package/core/lib/models/base-loader.js +99 -99
  191. package/core/lib/models/base.js +716 -716
  192. package/core/lib/models/branches/branches.js +125 -125
  193. package/core/lib/models/checklists/checklists.js +114 -114
  194. package/core/lib/models/columns/columns.js +169 -169
  195. package/core/lib/models/columns/components/columns-add/columns-add.js +171 -171
  196. package/core/lib/models/comments/comments.js +213 -213
  197. package/core/lib/models/departments/departments.js +107 -107
  198. package/core/lib/models/financial-years/financial_years.js +127 -127
  199. package/core/lib/models/forms/components/form-creator/form-creator.js +665 -665
  200. package/core/lib/models/forms/components/form-creator/form-creator.scss +39 -39
  201. package/core/lib/models/forms/components/form-detail/form-detail.js +224 -224
  202. package/core/lib/models/forms/forms.js +121 -121
  203. package/core/lib/models/index.js +203 -203
  204. package/core/lib/models/invoice-numbers/invoice_numbers.js +204 -204
  205. package/core/lib/models/lookup-types/components/lookup-detail/lookup-detail.js +145 -145
  206. package/core/lib/models/lookup-types/lookup-types.js +113 -113
  207. package/core/lib/models/lookup-values/components/lookup-values-add/lookup-values-add.js +126 -126
  208. package/core/lib/models/lookup-values/lookup-values.js +107 -107
  209. package/core/lib/models/menu-roles/menu-roles.js +127 -127
  210. package/core/lib/models/menus/components/menu-add/menu-add.js +228 -228
  211. package/core/lib/models/menus/components/menu-detail/menu-detail.js +170 -170
  212. package/core/lib/models/menus/components/menu-list/menu-list.js +550 -550
  213. package/core/lib/models/menus/components/menu-list/menu-list.scss +5 -5
  214. package/core/lib/models/menus/components/menu-roles-add/menu-roles-add.js +183 -183
  215. package/core/lib/models/menus/menus.js +499 -499
  216. package/core/lib/models/models/components/model-detail/model-detail.js +137 -137
  217. package/core/lib/models/models/components/models.js +128 -128
  218. package/core/lib/models/modules/modules.js +204 -204
  219. package/core/lib/models/outbox/outbox.js +73 -73
  220. package/core/lib/models/pages/pages.js +107 -107
  221. package/core/lib/models/permissions/permissions.js +71 -71
  222. package/core/lib/models/process/components/process-add/process-add.js +181 -181
  223. package/core/lib/models/process/components/process-dashboard/process-dashboard.js +1068 -1068
  224. package/core/lib/models/process/components/process-dashboard/process-dashboard.scss +66 -66
  225. package/core/lib/models/process/components/process-detail/process-detail.js +140 -140
  226. package/core/lib/models/process/components/process-timeline/process-timeline.js +139 -139
  227. package/core/lib/models/process/components/task-detail/task-detail.js +240 -240
  228. package/core/lib/models/process/components/task-detail/task-detail.scss +27 -27
  229. package/core/lib/models/process/components/task-form/task-form.js +528 -528
  230. package/core/lib/models/process/components/task-form/task-form.scss +7 -7
  231. package/core/lib/models/process/components/task-list/task-list.js +221 -221
  232. package/core/lib/models/process/components/task-list/task-list.scss +14 -14
  233. package/core/lib/models/process/components/task-overview/task-overview.js +299 -299
  234. package/core/lib/models/process/components/task-overview-legacy/task-overview-legacy.js +192 -192
  235. package/core/lib/models/process/components/task-routes/task-routes.js +45 -45
  236. package/core/lib/models/process/components/task-status/task-status.js +175 -175
  237. package/core/lib/models/process/components/task-status/task-status.scss +11 -11
  238. package/core/lib/models/process/process.js +780 -780
  239. package/core/lib/models/process-transactions/process-transactions.js +123 -123
  240. package/core/lib/models/roles/roles.js +106 -106
  241. package/core/lib/models/scripts/scripts.js +111 -111
  242. package/core/lib/models/step-transactions/step-transcations.js +147 -147
  243. package/core/lib/models/steps/components/step-add/step-add.js +261 -261
  244. package/core/lib/models/steps/components/step-detail/step-detail.js +157 -157
  245. package/core/lib/models/steps/steps.js +356 -356
  246. package/core/lib/models/user-preferences/user-preferences.js +83 -83
  247. package/core/lib/models/users/components/user-add/user-add.js +226 -226
  248. package/core/lib/models/users/users.js +119 -119
  249. package/core/lib/modules/business/launch-page/launch-page.js +29 -29
  250. package/core/lib/modules/business/launch-page/launch-page.scss +5 -5
  251. package/core/lib/modules/business/slots/slots.js +231 -231
  252. package/core/lib/modules/business/slots/slots.scss +108 -108
  253. package/core/lib/modules/forms/components/field-customizer/field-customizer.js +138 -138
  254. package/core/lib/modules/forms/components/field-selector/field-selector.js +157 -157
  255. package/core/lib/modules/forms/components/field-selector/field-selector.scss +25 -25
  256. package/core/lib/modules/forms/components/form-display/form-display.js +203 -203
  257. package/core/lib/modules/forms/components/form-display/form-display.scss +9 -9
  258. package/core/lib/modules/forms/components/tab-customizer/tab-customizer.js +124 -124
  259. package/core/lib/modules/generic/generic-add/generic-add.js +213 -213
  260. package/core/lib/modules/generic/generic-detail/generic-detail.js +199 -199
  261. package/core/lib/modules/generic/generic-edit/generic-edit.js +120 -120
  262. package/core/lib/modules/generic/generic-list/ExportReactCSV.js +414 -414
  263. package/core/lib/modules/generic/generic-list/generic-list.js +705 -705
  264. package/core/lib/modules/generic/generic-list/generic-list.scss +68 -68
  265. package/core/lib/modules/generic/generic-upload/generic-upload.js +483 -483
  266. package/core/lib/modules/generic/table-settings/table-settings.js +226 -226
  267. package/core/lib/modules/generic/table-settings/table-settings.scss +37 -37
  268. package/core/lib/modules/index.js +52 -52
  269. package/core/lib/modules/modules-routes/module-routes.js +35 -35
  270. package/core/lib/pages/change-password/change-password.js +204 -204
  271. package/core/lib/pages/change-password/change-password.scss +73 -73
  272. package/core/lib/pages/homepage/homepage.js +53 -53
  273. package/core/lib/pages/index.js +19 -19
  274. package/core/lib/pages/login/commnication-mode-selection.js +46 -46
  275. package/core/lib/pages/login/communication-mode-selection.scss +60 -60
  276. package/core/lib/pages/login/login.js +872 -872
  277. package/core/lib/pages/login/login.scss +353 -353
  278. package/core/lib/pages/login/reset-password.js +124 -124
  279. package/core/lib/pages/login/reset-password.scss +31 -31
  280. package/core/lib/pages/manage-users/manage-users.js +429 -429
  281. package/core/lib/pages/manage-users/manage-users.scss +25 -25
  282. package/core/lib/pages/profile/profile.js +247 -247
  283. package/core/lib/pages/profile/profile.scss +107 -107
  284. package/core/lib/pages/profile/theme-config.js +18 -18
  285. package/core/lib/pages/profile/themes-backup.json +310 -310
  286. package/core/lib/pages/profile/themes.json +254 -254
  287. package/core/lib/pages/register/register.js +176 -176
  288. package/core/lib/pages/register/register.scss +128 -128
  289. package/core/lib/react-styleguide.md +756 -756
  290. package/core/lib/utils/api/api.utils.js +207 -207
  291. package/core/lib/utils/api/readme.md +426 -426
  292. package/core/lib/utils/async.js +35 -35
  293. package/core/lib/utils/common/common.utils.js +237 -237
  294. package/core/lib/utils/common/readme.md +30 -30
  295. package/core/lib/utils/date/date.utils.js +295 -295
  296. package/core/lib/utils/date/readme.md +2 -2
  297. package/core/lib/utils/firebase.support.utils.js +98 -98
  298. package/core/lib/utils/firebase.utils.js +808 -808
  299. package/core/lib/utils/font-awesome.utils.js +168 -168
  300. package/core/lib/utils/form/form.utils.js +255 -255
  301. package/core/lib/utils/generic/generic.utils.js +70 -70
  302. package/core/lib/utils/http/auth.helper.js +95 -95
  303. package/core/lib/utils/http/http.utils.js +186 -186
  304. package/core/lib/utils/http/readme.md +14 -14
  305. package/core/lib/utils/index.js +43 -43
  306. package/core/lib/utils/location/location.utils.js +137 -137
  307. package/core/lib/utils/location/readme.md +18 -18
  308. package/core/lib/utils/modal.utils.js +15 -15
  309. package/core/lib/utils/notification.utils.js +34 -34
  310. package/core/lib/utils/pwa/pwa.utils.js +88 -88
  311. package/core/lib/utils/script.utils.js +235 -235
  312. package/core/lib/utils/setting.utils.js +68 -68
  313. package/core/lib/utils/upload.utils.js +29 -29
  314. package/core/models/Preference/Preferences.js +46 -46
  315. package/core/models/base/base.js +403 -403
  316. package/core/models/base-clone-loader.js +107 -107
  317. package/core/models/base-clone.js +187 -187
  318. package/core/models/base-loader.js +97 -97
  319. package/core/models/core-scripts/core-scripts.js +179 -179
  320. package/core/models/dashboard/dashboard.js +201 -201
  321. package/core/models/detail-loader.js +88 -88
  322. package/core/models/doctor/components/doctor-add/doctor-add.js +432 -432
  323. package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -32
  324. package/core/models/groups.js +82 -82
  325. package/core/models/index.js +100 -100
  326. package/core/models/lookup-types/components/lookup-detail/lookup-detail.js +129 -129
  327. package/core/models/lookup-types/lookup-types.js +96 -96
  328. package/core/models/lookup-values/components/lookup-values-modal/lookup-values-modal.js +95 -95
  329. package/core/models/lookup-values/lookup-values.js +92 -92
  330. package/core/models/menu-roles/components/menu-roles-add/menu-roles-add.js +153 -153
  331. package/core/models/menu-roles/menu-roles.js +158 -158
  332. package/core/models/menus/components/menu-add/menu-add.js +288 -288
  333. package/core/models/menus/components/menu-add/menu-add.scss +31 -31
  334. package/core/models/menus/components/menu-detail/menu-detail.js +263 -263
  335. package/core/models/menus/components/menu-list/menu-list.js +392 -392
  336. package/core/models/menus/components/menu-lists/menu-lists.js +635 -584
  337. package/core/models/menus/components/menu-lists/menu-lists.scss +46 -46
  338. package/core/models/menus/menus.js +338 -338
  339. package/core/models/model-columns.js +121 -121
  340. package/core/models/models/components/model-detail/model-add.js +120 -120
  341. package/core/models/models/components/model-detail/model-detail.js +133 -133
  342. package/core/models/models/models.js +154 -154
  343. package/core/models/pages/components/page-add/page-add.js +163 -163
  344. package/core/models/pages/components/page-add/page-add.scss +30 -30
  345. package/core/models/pages/components/page-details/page-details.js +209 -209
  346. package/core/models/pages/components/page-list/page-list.js +248 -248
  347. package/core/models/pages/pages.js +142 -142
  348. package/core/models/pages.js +142 -142
  349. package/core/models/roles/components/role-add/menu-label.js +14 -14
  350. package/core/models/roles/components/role-add/menu-tree.js +127 -127
  351. package/core/models/roles/components/role-add/role-add.js +222 -222
  352. package/core/models/roles/components/role-add/role-add.scss +4 -4
  353. package/core/models/roles/components/role-list/role-list.js +406 -406
  354. package/core/models/roles/roles.js +196 -196
  355. package/core/models/staff/components/staff-add/staff-add.js +455 -455
  356. package/core/models/user-roles/components/user-roles-add/user-roles-add.js +149 -149
  357. package/core/models/user-roles/user-roles.js +113 -113
  358. package/core/models/users/components/assign-role/assign-role.js +428 -428
  359. package/core/models/users/components/assign-role/assign-role.scss +281 -281
  360. package/core/models/users/components/assign-role/avatar-props.js +45 -45
  361. package/core/models/users/components/user-add/user-add.js +847 -847
  362. package/core/models/users/components/user-add/user-edit.js +110 -110
  363. package/core/models/users/components/user-detail/user-detail.js +236 -236
  364. package/core/models/users/components/user-list/user-list.js +397 -397
  365. package/core/models/users/users.js +379 -379
  366. package/core/modules/Informations/change-info/change-info.js +618 -618
  367. package/core/modules/Informations/change-info/change-info.scss +134 -134
  368. package/core/modules/dashboard/components/dashboard-card/animations.js +64 -64
  369. package/core/modules/dashboard/components/dashboard-card/dashboard-card.js +197 -197
  370. package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +430 -430
  371. package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.scss +59 -59
  372. package/core/modules/dashboard/components/pop-query-dashboard/pop-query-dashboard.js +66 -66
  373. package/core/modules/generic/components/generic-add/generic-add.js +121 -121
  374. package/core/modules/generic/components/generic-add/generic-add.scss +13 -13
  375. package/core/modules/generic/components/generic-add-modal/generic-add-modal.js +125 -125
  376. package/core/modules/generic/components/generic-add-modal/generic-add-modal.scss +13 -13
  377. package/core/modules/generic/components/generic-detail/generic-detail.js +184 -184
  378. package/core/modules/generic/components/generic-detail/generic-detail.scss +25 -25
  379. package/core/modules/generic/components/generic-edit/generic-edit.js +123 -123
  380. package/core/modules/generic/components/generic-list/generic-list.js +335 -335
  381. package/core/modules/generic/components/generic-list/generic-list.scss +35 -35
  382. package/core/modules/index.js +42 -42
  383. package/core/modules/module-routes/module-routes.js +37 -37
  384. package/core/modules/reporting/components/index.js +6 -6
  385. package/core/modules/reporting/components/reporting-dashboard/README.md +316 -316
  386. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +271 -271
  387. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.scss +76 -76
  388. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +90 -90
  389. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -74
  390. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +449 -449
  391. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +199 -199
  392. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +1116 -1116
  393. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +215 -215
  394. package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -519
  395. package/core/modules/steps/action-buttons.js +92 -92
  396. package/core/modules/steps/action-buttons.scss +62 -62
  397. package/core/modules/steps/chat-assistant.js +141 -141
  398. package/core/modules/steps/narration.js +192 -192
  399. package/core/modules/steps/openai-realtime.js +275 -275
  400. package/core/modules/steps/progress-storage.js +140 -140
  401. package/core/modules/steps/readme.md +167 -167
  402. package/core/modules/steps/steps.js +1567 -1567
  403. package/core/modules/steps/steps.scss +907 -907
  404. package/core/modules/steps/timeline.js +56 -56
  405. package/core/modules/steps/voice-navigation.js +709 -709
  406. package/core/pages/homepage-api/homepage-api.js +106 -106
  407. package/core/pages/homepage-api/homepage-api.scss +233 -233
  408. package/core/pages/homepage-api/menu-dashboard.js +169 -169
  409. package/core/pages/homepage-api/menu-dashboard.scss +11 -11
  410. package/core/translation.json +53 -53
  411. package/core/translations.json +19 -19
  412. package/core/utils/script.utils.js +129 -129
  413. package/core/utils/settings.utils.js +25 -25
  414. package/eslint.config.mjs +79 -79
  415. package/index.js +35 -35
  416. package/jest.config.js +7 -7
  417. package/jest.setup.js +1 -1
  418. package/package.json +124 -124
  419. package/tsconfig.json +26 -26
  420. package/webpack.config.js +173 -173
@@ -1,1116 +1,1116 @@
1
- import React, { useState, useEffect, useContext, useRef } from 'react';
2
-
3
- import { Table, Skeleton, Input, Modal, message, Pagination, Tag } from 'antd';
4
-
5
- import { QrcodeOutlined } from '@ant-design/icons';
6
-
7
- import { Location, FormCreator, GlobalContext, ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
8
-
9
- import { CoreScripts } from './../../../../models/';
10
-
11
- import moment from 'moment-timezone';
12
-
13
- import Button from '../../../../lib/elements/basic/button/button';
14
-
15
- import './reporting-dashboard.scss';
16
-
17
- // import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
18
- import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboard/menu-dashboard';
19
- import { useHistory } from 'react-router-dom';
20
- import * as ReportingDashboardComp from '../index';
21
- import buildDisplayColumns from './display-columns/build-display-columns';
22
- import { getRedirectLink } from './display-columns/display-cell-renderer';
23
- import AdvancedSearchSelect from './adavance-search/advance-search';
24
-
25
- // import { isPdfFile } from 'pdfjs-dist';
26
-
27
- var genericComponents = require('./../../../../lib');
28
-
29
- const { Search } = Input;
30
-
31
- /**
32
- * ReportingDashboard component renders the dashboard and handles patient details,
33
- * configuration, and form layout for generating reports.
34
- *
35
- * @param {Object} props - The component's props.
36
- * @param {Object} props.match - The match object containing the URL parameters.
37
- * @param {Object} props.CustomComponents - Custom components for rendering.
38
- * @param {string} props.reportId - The report ID.
39
- * @param {boolean} props.isFixedIndex - Determines if the index is fixed.
40
- * @param {Array<string>} props.dashBoardIds - The list of dashboard IDs.
41
- *
42
- * @returns {JSX.Element} The rendered ReportingDashboard component.
43
- */
44
- export default function ReportingDashboard({
45
- match,
46
- scope,
47
- CustomComponents,
48
- reportId,
49
- isFixedIndex,
50
- dashBoardIds,
51
- barcodeFilterKey,
52
- showScanner,
53
- dbPtr,
54
- attributes,
55
- }) {
56
- const [config, setConfig] = useState({});
57
-
58
- // State to manage the layout of the form
59
- const [formLayout, setFormLayout] = useState('vertical');
60
-
61
- const [loading, setLoading] = useState(true);
62
-
63
- const [cardLoading, setCardLoading] = useState(true);
64
-
65
- const [searchParameters, setSearchParameters] = useState([]);
66
-
67
- const [searchValues, setSearchValues] = useState({});
68
-
69
- const [dashboardVisible, setDashBoardVisible] = useState(false);
70
-
71
- const [formContents, setformContents] = useState({});
72
- const [liveFormContents, setLiveFormContents] = useState({});
73
- const [reportMailRequest, setReportMailRequest] = useState({
74
- scriptId: null,
75
- inputParameters: {},
76
- });
77
-
78
- const scriptId = useRef(null);
79
-
80
- //In case of reports from core_script , there will be id from params
81
- // In case of normal menu we need to take id from props
82
- let id = reportId ? reportId : match.params.id;
83
-
84
- const { CustomModels = {} } = useContext(GlobalContext);
85
-
86
- const [details, setDetails] = useState([]);
87
-
88
- const [columns, setColumns] = useState([]); // To set columns
89
-
90
- const [summaryColumns, setSummaryColumns] = useState([]);
91
-
92
- const [patients, setPatients] = useState([]); //Patients list array
93
-
94
- const urlParams = Location.search();
95
-
96
- // Pagination
97
- const [pagination, setPagination] = useState({
98
- current: 1,
99
- pageSize: 20,
100
- total: 0,
101
- });
102
-
103
- // Settings db pointer
104
- if (!dbPtr) dbPtr = localStorage.db_ptr;
105
-
106
- /**
107
- * Fetches patient details from the server and updates the relevant state.
108
- * This includes input parameters, columns, summary columns, and configuration.
109
- *
110
- * @returns {Promise<void>} A promise that resolves when the patient details have been fetched and the state has been updated.
111
- */
112
- async function getPatientDetails(idOverride) {
113
- setPatients([]);
114
- const fetchId = idOverride || id;
115
- await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
116
- // Check if display columns are provided from backend
117
- let parsedColumns = [];
118
-
119
- if (result.display_columns) {
120
- parsedColumns = JSON.parse(result.display_columns);
121
- }
122
- setColumns(parsedColumns);
123
-
124
- await prepareInputParameters(result, parsedColumns, fetchId);
125
-
126
- if (result.summary_columns) {
127
- setSummaryColumns(JSON.parse(result.summary_columns));
128
- }
129
-
130
- setConfig({ ...result });
131
- });
132
- }
133
-
134
- // useEffect(() => {
135
- // if (!dashboardVisible) {
136
- // refresh(false);
137
- // }
138
- // }, [dashboardVisible]);
139
-
140
- /**
141
- *
142
- *
143
- * Manage initial render
144
- */
145
-
146
- useEffect(() => {
147
- getPatientDetails();
148
- }, []);
149
-
150
- /**
151
- * Effect hook that triggers the `getPatientDetails` function
152
- * whenever the `match.params.id` value changes.
153
- *
154
- * @returns {void} This hook does not return anything.
155
- */
156
- // useEffect(() => {
157
- // getPatientDetails();
158
- // }, [match.params.id]);
159
-
160
- let parameters;
161
-
162
- /**
163
- * Some parameters would need binding with the model
164
- *
165
- * @param {*} inputParameters Input parameters from the report configuration.
166
- */
167
-
168
- //Prepare input parameters by mapping default values and binding models if needed
169
- async function prepareInputParameters(record, parsedColumns, fetchId) {
170
- setLoading(true);
171
- let urlParams = Location.search();
172
-
173
- // If script id exist set into variable
174
- if (urlParams.script_id) scriptId.current = urlParams.script_id;
175
-
176
- let otherDetails = record.other_details1 ? JSON.parse(record.other_details1) : null;
177
-
178
- parameters = record.input_parameters ? JSON.parse(record.input_parameters) : [];
179
-
180
- let formContent = {};
181
- const searchFields = (parameters || []).filter((p) => p.type === 'search' && p.search_enabled === 'yes');
182
- setSearchParameters([...searchFields]);
183
-
184
- parameters = await parameters?.map((record) => {
185
- // Only if the url params does have a matching value ,
186
- // we should not consider the default value
187
-
188
- if (urlParams[record.field]) {
189
- if (record.type === 'date') {
190
- formContent[record.field] = moment.utc(urlParams[record.field]);
191
- }
192
-
193
- // return formContent;
194
- } else {
195
- // let StartDate, EndDate;
196
- switch (record.default) {
197
- case 'startOfDay':
198
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).startOf('day');
199
- break;
200
-
201
- case 'endOfDay':
202
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).endOf('day');
203
- break;
204
-
205
- case 'startOfWeek':
206
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).startOf('week');
207
- break;
208
-
209
- case 'endOfWeek':
210
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).endOf('week');
211
- break;
212
-
213
- case 'currentDate':
214
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
215
- break;
216
-
217
- default:
218
- break;
219
- }
220
- }
221
-
222
- // If it is a date field and still no value, set it to today's date
223
- if (record.type === 'date' && !formContent[record.field]) {
224
- formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
225
- }
226
- if (record.type === 'search') {
227
- if (!formContent[record.field]) formContent[record.field] = [];
228
- return {
229
- ...record,
230
- reportId: fetchId,
231
- onReset: () => getPatientDetails(fetchId),
232
- required: record.required,
233
- };
234
- }
235
- if (['reference-select', 'reference-search', 'select'].indexOf(record.type) !== -1) {
236
- // let model = "";
237
- let model = CustomModels[record.modelName];
238
-
239
- return {
240
- ...record,
241
- model,
242
- required: record.required,
243
- };
244
- } else {
245
- return {
246
- ...record,
247
- required: true,
248
- };
249
- }
250
- });
251
- // Update form content state
252
- setformContents(formContent);
253
- setLiveFormContents(formContent);
254
-
255
- // Trigger form submission
256
- onFinish(formContent, null, record.input_parameters, parsedColumns);
257
-
258
- setLoading(false);
259
-
260
- // Check if input parameters are enabled or disabled in otherDetails
261
- if (otherDetails?.isDisableInputParameters) {
262
- // If enabled, clear the details array
263
- setDetails([]);
264
- } else {
265
- // Keep all parameters with a type (including search) to render in FormCreator
266
- setDetails([...parameters.filter((ele) => ele.type)]);
267
- }
268
- }
269
-
270
- // Refresh patient details.
271
-
272
- function refresh() {
273
- getPatientDetails();
274
- }
275
-
276
- const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns) => {
277
- const { current, pageSize } = pagination || {};
278
- // If card script id is exist load that id otherwise load id
279
- const coreScriptId = scriptId.current ? scriptId.current : id;
280
- const normalizedColumns = Array.isArray(parsedColumns) ? parsedColumns : Array.isArray(columns) ? columns : [];
281
-
282
- setLoading(true);
283
- try {
284
- // Prepare payload for backend: format only here
285
- const formattedValues = {};
286
- Object.keys(values).forEach((key) => {
287
- const val = values[key];
288
- formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
289
- });
290
- // Pagination Data
291
- const paginationData = {
292
- page: pagination.current || 1,
293
- limit: pagination.pageSize || 10,
294
- };
295
- // Combine form data + pagination
296
- let formBody = {
297
- body: {
298
- ...formattedValues,
299
- ...paginationData,
300
- },
301
- };
302
- // Optional override if `scope` exists
303
- if (scope) {
304
- formBody = { body: { ...scope, ...paginationData } };
305
- }
306
-
307
- setReportMailRequest({
308
- scriptId: coreScriptId,
309
- inputParameters: formBody,
310
- });
311
-
312
- // Fetch result
313
- const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
314
-
315
- const apiData = Array.isArray(result) ? result : Array.isArray(result?.result) ? result.result : [];
316
-
317
- // Handle both result formats
318
- let resultDetails = apiData[0] || [];
319
- if (result?.result && result?.result[0]) {
320
- resultDetails = result.result[0];
321
- }
322
- // Update patients
323
- setPatients(resultDetails || []);
324
-
325
- // When display_columns is missing, build columns from the response keys.
326
- if (normalizedColumns.length === 0 && resultDetails.length > 0) {
327
- // Create columns dynamically from resultDetails keys
328
- setColumns((prev) => {
329
- if (prev.length > 0) return prev;
330
- return Object.keys(resultDetails[0]).map((key) => ({
331
- title: key,
332
- field: key,
333
- }));
334
- });
335
- }
336
-
337
- if (result.length) {
338
- // Set Pgination data into URL
339
- Location.search({ ...Location.search(), current, pageSize });
340
-
341
- setPagination((prev) => ({
342
- ...prev,
343
- current: pagination.current,
344
- pageSize: pagination.pageSize,
345
- total: resultDetails?.[0]?.TotalCount ?? pagination?.total,
346
- }));
347
- }
348
- } catch (error) {
349
- console.error('Error fetching report data:', error);
350
- } finally {
351
- // Always runs, success or error
352
- setLoading(false);
353
- }
354
- };
355
-
356
- const handleSubmit = (values) => {
357
- // Extract search fields from the form values
358
- const searchKeys = searchParameters.map((p) => p.field);
359
- const currentSearchValues = {};
360
- const formValues = {};
361
-
362
- Object.keys(values).forEach((key) => {
363
- if (searchKeys.includes(key)) {
364
- currentSearchValues[key] = values[key];
365
- } else {
366
- formValues[key] = values[key];
367
- }
368
- });
369
-
370
- const hasSearchValues = Object.values(currentSearchValues).some((v) => Array.isArray(v) && v.length > 0);
371
-
372
- if (!hasSearchValues) {
373
- runSubmit(formValues);
374
- return;
375
- }
376
-
377
- // Check if main form values (non-search) changed
378
- const formChanged = Object.keys(formValues).some((key) => {
379
- const newVal = formValues[key];
380
- const oldVal = formContents[key];
381
-
382
- if (moment.isMoment(newVal) && moment.isMoment(oldVal)) {
383
- return !newVal.isSame(oldVal, 'day');
384
- }
385
-
386
- return newVal !== oldVal;
387
- });
388
-
389
- if (formChanged) {
390
- Modal.confirm({
391
- title: 'Filters changed',
392
- content: 'You changed some filters. Do you want to search using these filters also?',
393
- okText: 'Yes',
394
- cancelText: 'No',
395
-
396
- onOk() {
397
- // YES → send form values + search condition
398
- const finalValues = {
399
- ...formValues,
400
- search_values: currentSearchValues,
401
- };
402
-
403
- runSubmit(finalValues);
404
- },
405
-
406
- onCancel() {
407
- // NO → reset form values
408
- const resetValues = {};
409
- Object.keys(formValues).forEach((key) => {
410
- resetValues[key] = null;
411
- });
412
-
413
- const finalValues = {
414
- ...resetValues,
415
- search_values: currentSearchValues,
416
- };
417
-
418
- runSubmit(finalValues);
419
- },
420
- });
421
- } else {
422
- // no form change
423
- const resetValues = {};
424
- Object.keys(formValues).forEach((key) => {
425
- resetValues[key] = null;
426
- });
427
-
428
- const finalValues = {
429
- ...resetValues,
430
- search_values: currentSearchValues,
431
- };
432
-
433
- runSubmit(finalValues);
434
- }
435
- };
436
-
437
- const runSubmit = (finalValues) => {
438
- const { pageSize } = pagination;
439
- const resetPage = 1;
440
-
441
- scriptId.current = null;
442
-
443
- const hasSearchValues = finalValues.search_values && Object.values(finalValues.search_values).some((v) => Array.isArray(v) && v.length > 0);
444
-
445
- if (!hasSearchValues) {
446
- const currentUrlParams = Location.search();
447
- const searchKeys = new Set(searchParameters.map((parameter) => parameter.field));
448
- const cleanParams = Object.fromEntries(
449
- Object.entries(currentUrlParams).filter(
450
- ([key]) => key !== 'script_id' && key !== 'selected_card' && !searchKeys.has(key)
451
- )
452
- );
453
-
454
- const newParams = new URLSearchParams({
455
- ...cleanParams,
456
- current: resetPage,
457
- pageSize,
458
- });
459
-
460
- const newUrl = `${window.location.pathname}?${newParams.toString()}`;
461
- window.history.replaceState({}, '', newUrl);
462
- }
463
-
464
- onFinish(finalValues, resetPage);
465
- };
466
-
467
- const selectedSearchFields = details.filter((field) => {
468
- if (field.type !== 'search') return false;
469
-
470
- const value = liveFormContents[field.field];
471
- return Array.isArray(value) ? value.length > 0 : !!value;
472
- });
473
-
474
- const handleRemoveSearchField = (fieldName) => {
475
- const updatedValues = {
476
- ...liveFormContents,
477
- [fieldName]: [],
478
- };
479
-
480
- setLiveFormContents(updatedValues);
481
- handleSubmit(updatedValues);
482
- };
483
-
484
- /**
485
- *
486
- * @param {*} values
487
- */
488
-
489
- const onFinish = async (values, resetPage, inputParamsString, parsedColumns) => {
490
- setLoading(true);
491
- setCardLoading(true);
492
- // // Check if the dashboard is visible and both start and end values are provided
493
- // If true, proceed with hiding the dashboard by setting dashboardVisible to false
494
- if (dashboardVisible && values.start && values.end) {
495
- setDashBoardVisible((prev) => {
496
- if (!prev) return prev; // Prevent unnecessary updates
497
-
498
- return false;
499
- });
500
- }
501
- const paramsString = inputParamsString || (config.input_parameters ? config.input_parameters : null);
502
- // Cheacking if there is data and making the data url friendly, ie, if there is date in url
503
- if (paramsString) {
504
- let urlsToUpdate = {};
505
-
506
- let parameterValue = JSON.parse(paramsString);
507
-
508
- // Variable to store/update the form initial Values
509
-
510
- let formContent = {};
511
-
512
- parameters = parameterValue.map((parameter) => {
513
- //Getting url friendly value by matching the url values and input_parameter values
514
- let value = values[parameter.field];
515
-
516
- // If the value is missing at the root level (which happens when search fields are moved
517
- // into search_values during submission), try to retrieve it from the nested object.
518
- if (value === undefined && values.search_values && values.search_values[parameter.field] !== undefined) {
519
- value = values.search_values[parameter.field];
520
- }
521
-
522
- // Keep Moment object in state for picker
523
- if (parameter.type === 'date' && value) {
524
- formContent[parameter.field] = value.isValid ? value : moment(value); // ensure Moment
525
- // Format for URL
526
- urlsToUpdate[parameter.field] = moment(value).format('YYYY-MM-DD');
527
- } else {
528
- formContent[parameter.field] = value;
529
- if (parameter.type) {
530
- urlsToUpdate[parameter.field] = value;
531
- }
532
- }
533
-
534
- return parameter;
535
- });
536
-
537
- const currentParams = Location.search();
538
- // Mark existing empty values as null so Location.search removes stale query params.
539
- const filteredParams = Object.fromEntries(
540
- Object.entries(urlsToUpdate).flatMap(([key, value]) => {
541
- const shouldRemove = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0);
542
- if (shouldRemove && !Object.prototype.hasOwnProperty.call(currentParams, key)) {
543
- return [];
544
- }
545
-
546
- return [[key, shouldRemove ? null : value]];
547
- })
548
- );
549
-
550
- setformContents(formContent);
551
- setLiveFormContents(formContent);
552
- Location.search({ ...currentParams, ...filteredParams });
553
- }
554
-
555
- // Reset Pagination Data
556
- const latestUrlParams = Location.search();
557
- const paginationData = {
558
- current: resetPage || Number(latestUrlParams.current) || 1,
559
- pageSize: Number(latestUrlParams.pageSize) || pagination.pageSize,
560
- };
561
-
562
- // Call API
563
- try {
564
- await fetchReportData(id, values, dbPtr, paginationData, parsedColumns);
565
- } finally {
566
- setLoading(false);
567
- setCardLoading(false);
568
- }
569
- };
570
-
571
- // Pagination Handler
572
- const handlePagination = async (newPagination) => {
573
- try {
574
- await fetchReportData(id, formContents, dbPtr, newPagination);
575
- } finally {
576
- setLoading(false);
577
- setCardLoading(false);
578
- }
579
- };
580
-
581
- let model = {
582
- fields: details,
583
- };
584
-
585
- return (
586
- <Card className="reporting-dashboard card card-shadow">
587
- {/** If dashBoardIds exist and contain elements, render MenuDashBoard*/}
588
-
589
- {dashBoardIds?.length > 0 ? (
590
- <MenuDashBoardComponent
591
- dashBoardIds={dashBoardIds} //Pass the available dashboard IDs to the componen
592
- activeId={Number(urlParams?.selected_card || 0)}
593
- dbPtr={dbPtr}
594
- callback={(record) => {
595
- const selectedCard = record?.id;
596
- if (record.other_details) {
597
- // Convert the other_details string to a JSON object
598
- let json = JSON.parse(record.other_details);
599
- const newScriptId = json.script_id;
600
-
601
- scriptId.current = newScriptId;
602
-
603
- // Reset Pagination on card click
604
- setPagination((prev) => ({
605
- ...prev,
606
- current: 1,
607
- total: 0,
608
- }));
609
-
610
- //Update URL
611
- Location.search({
612
- ...Location.search(),
613
- current: 1,
614
- pageSize: pagination.pageSize,
615
- script_id: newScriptId,
616
- selected_card: selectedCard,
617
- });
618
-
619
- // menudashBoardVisible = true;
620
- // Show the dashboard component
621
- setDashBoardVisible(true);
622
- // Fetch and update patient details based on the selected item
623
- getPatientDetails(newScriptId);
624
- // setDashBoard(true);
625
- }
626
- }}
627
- ></MenuDashBoardComponent>
628
- ) : null}
629
-
630
- {/* Content Below */}
631
- {loading ? (
632
- <>
633
- <Skeleton active />
634
- </>
635
- ) : (
636
- <>
637
- <div>
638
- {/* <Card className="form-card" style={{ padding: '10px 10px' }}> */}
639
- {details && details[0] && !loading ? (
640
- <FormCreator
641
- {...formLayout}
642
- layout={formLayout}
643
- initialValues={{
644
- layout: formLayout,
645
- }}
646
- styles={{ paddingRight: '15px', alignItems: 'center' }}
647
- fields={details}
648
- reportId={id}
649
- formContent={formContents}
650
- // formContent={{ [model]: {} }}
651
- modelIndex="requestId"
652
- model={model}
653
- onSubmit={handleSubmit}
654
- onFormValuesChange={setLiveFormContents}
655
- callback={() => {
656
- // history.goBack();
657
- }}
658
- />
659
- ) : null}
660
- </div>
661
-
662
- {/** GuestList component start*/}
663
- <GuestList
664
- patients={patients}
665
- columns={columns}
666
- summaryColumns={summaryColumns}
667
- isFixedIndex={isFixedIndex}
668
- showScanner={showScanner}
669
- barcodeFilterKey={barcodeFilterKey}
670
- CustomComponents={{ ...CustomComponents, ...genericComponents, ...ReportingDashboardComp }}
671
- refresh={refresh}
672
- config={config}
673
- loading={cardLoading}
674
- pagination={pagination}
675
- handlePagination={handlePagination}
676
- attributes={attributes}
677
- selectedSearchFields={selectedSearchFields}
678
- handleRemoveSearchField={handleRemoveSearchField}
679
- fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
680
- reportScriptId={reportMailRequest.scriptId}
681
- reportInputParameters={reportMailRequest.inputParameters}
682
- />
683
- {/** GuestList component end*/}
684
- </>
685
- )}
686
- </Card>
687
- );
688
- }
689
-
690
- /**
691
- *
692
- * @param root0
693
- * @param root0.patients
694
- * @param root0.CustomComponents
695
- * @param root0.summaryColumns
696
- * @param root0.refresh
697
- * @param root0.isFixedIndex
698
- * @returns {*}
699
- */
700
- //Renders a table displaying a list of patients with dynamic columns
701
- function GuestList({
702
- patients,
703
- columns,
704
- loading,
705
- CustomComponents,
706
- refresh,
707
- isFixedIndex,
708
- barcodeFilterKey,
709
- showScanner,
710
- config,
711
- pagination,
712
- handlePagination,
713
- attributes,
714
- selectedSearchFields,
715
- handleRemoveSearchField,
716
- fetchReportData,
717
- reportScriptId,
718
- reportInputParameters,
719
- }) {
720
- /**
721
- * @param {*} propValues
722
- */
723
- const propValues = (attributes && JSON.parse(attributes)) || {};
724
-
725
- const { buttonAttributes = [] } = propValues;
726
-
727
- var [query, setQuery] = useState('');
728
-
729
- const [exportData, setExportData] = useState({});
730
-
731
- // const [data, setData] = useState([]);
732
-
733
- //visibility of the QR scanner modal.
734
- const [isScannerVisible, setScannerVisible] = useState(false);
735
-
736
- // Stores the patients filtered specifically by QR scan match.
737
- const [filteredPatients, setFilteredPatients] = useState([]); // Show all initially
738
-
739
- // patient object to redirect to upon successful QR scan.
740
- const [redirectPatient, setRedirectPatient] = useState(null);
741
-
742
- const [visible, setVisible] = useState(false);
743
-
744
- const [ActiveComponent, setActiveComponent] = useState(null);
745
-
746
- let history = useHistory();
747
-
748
- const { isMobile, dispatch } = useContext(GlobalContext);
749
- const [single, setSingle] = useState({});
750
- const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
751
-
752
- // const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
753
- // const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
754
- const cols = buildDisplayColumns({
755
- columns,
756
- patients,
757
- isFixedIndex,
758
- CustomComponents,
759
- refresh,
760
- otherDetails,
761
- });
762
-
763
- /**
764
- *
765
- * @param {*} result
766
- */
767
-
768
- // function changeView(result) {
769
- // setView(result);
770
- // }
771
-
772
- /**
773
- *
774
- * @param {*} event
775
- */
776
-
777
- function onSearch(event) {
778
- setQuery(event.target.value);
779
- }
780
-
781
- /**
782
- *
783
- */
784
-
785
- useEffect(() => {
786
- //Cheaking if there is patient data exists
787
- if (patients) {
788
- // let data = patients?.map((entry) => {
789
- // entry.rowIndex = entry.opb_id;
790
-
791
- // entry.dispatch = dispatch;
792
-
793
- // return entry;
794
- // });
795
-
796
- // setData(data);
797
-
798
- // Define export data
799
- // Sanitize cols for export to ensure titles are strings
800
- const exportCols = cols.map((col) => {
801
- if (col.title && typeof col.title === 'object' && col.title.props) {
802
- return { ...col, title: col.title.props.title };
803
- }
804
- return col;
805
- });
806
- const summaryCols = columns.filter((col) => col.enable_summary);
807
- let dataToExport = [...patients];
808
-
809
- if (summaryCols.length > 0) {
810
- // Build one synthetic row for CSV export that mirrors the table layout:
811
- // numeric summary cells are populated from `calculateSummaryValues`, while
812
- // non-summary columns stay blank unless a configured caption should be shown.
813
- const summaryValues = calculateSummaryValues(summaryCols, patients);
814
- const summaryRow = { isSummaryRow: true };
815
-
816
- cols.forEach((col, index) => {
817
- // Start each export column empty so the appended row keeps the same shape
818
- // as the data rows and does not leak index/helper values into the export.
819
- const colKey = col.field || col.key || col.dataIndex;
820
- if (colKey && !summaryRow[colKey]) {
821
- summaryRow[colKey] = '';
822
- }
823
-
824
- if (summaryValues[col.field] !== undefined) {
825
- // Fill columns that have an aggregate configured (sum, count, avg, etc.).
826
- summaryRow[col.field] = summaryValues[col.field];
827
- } else {
828
- // If this column is marked as the caption target for a summary column,
829
- // place the configured label (for example "Total") into that cell.
830
- const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
831
- if (captionConfig) {
832
- summaryRow[col.field] = captionConfig.summary_caption || '';
833
- }
834
- }
835
- });
836
- dataToExport.push(summaryRow);
837
- }
838
- let exportDatas = getExportData(dataToExport, exportCols);
839
-
840
- if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
841
- setExportData({ exportDatas });
842
- }
843
- }
844
- }, [patients, columns]);
845
-
846
- let filtered;
847
-
848
- if (patients) {
849
- filtered = patients.filter((record) => {
850
- if (query) {
851
- // Keys
852
- let keys = Object.keys(record);
853
-
854
- let flag = false;
855
-
856
- keys.forEach((key) => {
857
- let ele = record[key];
858
-
859
- if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
860
- flag = true;
861
- }
862
- });
863
-
864
- /**Will return flag */
865
- return flag;
866
- } else {
867
- return true;
868
- }
869
- });
870
- }
871
-
872
- /**
873
- * Checks for a match in the filtered patient list based on a scanned code,
874
- * updates the relevant state if a match is found, and redirects to the
875
- * patient's detail page. Displays a warning if no match is found.
876
- *
877
- * @param {string} code - The scanned code to match against a specific field of each patient.
878
- */
879
-
880
- const handleScanSuccess = (code) => {
881
- // Filters patients based on the scanned code and the selected barcode key(using attributes 'barcodeFilterKey')
882
- const matched = filtered.filter((patient) => patient[barcodeFilterKey] === code);
883
-
884
- if (matched.length) {
885
- const patient = matched[0];
886
- setFilteredPatients(matched);
887
- setRedirectPatient(matched);
888
- message.success(`Match found for ${code}, redirecting...`);
889
-
890
- const actionColumn = columns.find((col) => col.field === 'action') || columns.find((col) => col.type === 'action');
891
- if (actionColumn) {
892
- const redirectLink = getRedirectLink(actionColumn, patient);
893
- // history.push(redirectLink);
894
- window.location.href = redirectLink;
895
- }
896
- } else {
897
- Modal.warning({
898
- title: 'No matching records.',
899
- content: `No match for scanned code: ${code}`,
900
- });
901
- }
902
- };
903
-
904
- //open the edit modal
905
- const handleOpenEdit = (button) => {
906
- const componentName = button.component;
907
- const ComponentToRender = ReportingDashboardComp[componentName];
908
-
909
- if (!ComponentToRender) {
910
- console.error(`Component ${componentName} not found!`);
911
- return;
912
- }
913
-
914
- setSingle({});
915
- setActiveComponent(() => ComponentToRender);
916
- setVisible(true);
917
- };
918
-
919
- // close the edit modal
920
- const handleCloseEdit = () => {
921
- setShowEdit(false);
922
- };
923
- /**
924
- * Calculates aggregate values for the configured summary columns.
925
- *
926
- * Each summary definition contributes one value keyed by its `field`. Missing
927
- * row values are treated as `0` for numeric operations so the table summary and
928
- * export summary row can be built from the same result object.
929
- *
930
- * Supported functions:
931
- * `sum` - totals all numeric values in the field.
932
- * `count` - returns the number of rows in the current dataset.
933
- * `avg` - returns the arithmetic mean of the field values.
934
- * `min` - returns the smallest numeric value in the field.
935
- * `max` - returns the largest numeric value in the field.
936
- *
937
- * @param {Array<Object>} summaryCols - Column configs with `field` and `function`.
938
- * @param {Array<Object>} pageData - Rows currently being summarized.
939
- * @returns {Object} Aggregate values keyed by field name.
940
- */
941
- function calculateSummaryValues(summaryCols, pageData) {
942
- const summaryValues = {};
943
-
944
- summaryCols.forEach((col) => {
945
- const field = col.field;
946
-
947
- if (col.function === 'sum') {
948
- summaryValues[field] = pageData.reduce((total, row) => total + Number(row[field] || 0), 0);
949
- }
950
-
951
- if (col.function === 'count') {
952
- summaryValues[field] = pageData.length;
953
- }
954
-
955
- if (col.function === 'avg') {
956
- const total = pageData.reduce((sum, row) => sum + Number(row[field] || 0), 0);
957
- summaryValues[field] = pageData.length ? total / pageData.length : 0;
958
- }
959
- if (col.function === 'min') {
960
- const values = pageData.map((row) => Number(row[field] || 0));
961
- summaryValues[field] = values.length ? Math.min(...values) : 0;
962
- }
963
-
964
- if (col.function === 'max') {
965
- const values = pageData.map((row) => Number(row[field] || 0));
966
- summaryValues[field] = values.length ? Math.max(...values) : 0;
967
- }
968
- });
969
-
970
- return summaryValues;
971
- }
972
- return (
973
- <>
974
- <div className="table-header">
975
- <div className="table-left">
976
- {/* {selectedSearchFields?.length > 0 ? (
977
- <div className="search-tags-container">
978
- {selectedSearchFields.map((field) => (
979
- <Tag key={field.field} closable color="blue" onClose={() => handleRemoveSearchField(field.field)}>
980
- {field.caption}
981
- </Tag>
982
- ))}
983
- </div>
984
- ) : null} */}
985
- </div>
986
-
987
- <div className="table-right">
988
- {/* shwoing caption is not correct so this commented */}
989
- {/* <span className="menu-caption">{config.caption}</span> */}
990
- <Search className="table-search-input" placeholder="Enter Search Value" allowClear onChange={onSearch} />
991
- <div className="table-export-button">
992
- {exportData.exportDatas && (
993
- <>
994
- <ExportReactCSV
995
- title={config.caption}
996
- headers={exportData.exportDatas.exportDataHeaders}
997
- csvData={exportData.exportDatas.exportDataColumns}
998
- fileName={`${config.caption || 'Report'}.xlsx`}
999
- pdfFileName={`${config.caption || 'Report'}.pdf`}
1000
- scriptId={reportScriptId}
1001
- inputParameters={reportInputParameters}
1002
- dropdown
1003
- />
1004
- </>
1005
- )}
1006
- </div>
1007
-
1008
- {/* QR Scan start */}
1009
- {showScanner ? (
1010
- <Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
1011
- Scan QR
1012
- </Button>
1013
- ) : null}
1014
- {/** Add User button */}
1015
- {Array.isArray(buttonAttributes) &&
1016
- buttonAttributes.map((btn, index) => (
1017
- <Button key={index} size="small" type="primary" style={{ marginLeft: 8 }} onClick={() => handleOpenEdit(btn)}>
1018
- {btn.title}
1019
- </Button>
1020
- ))}
1021
-
1022
- <Modal open={visible} onCancel={() => setVisible(false)} footer={null} destroyOnClose width={950} style={{ top: 10 }}>
1023
- {ActiveComponent && (
1024
- <ActiveComponent
1025
- formContent={single}
1026
- callback={() => {
1027
- setVisible(false);
1028
- refresh();
1029
- setVisible(false);
1030
- fetchReportData();
1031
- }}
1032
- // {...dynamicProps}
1033
- />
1034
- )}
1035
- </Modal>
1036
-
1037
- <Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
1038
- <QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
1039
- </Modal>
1040
- {/* QR Scan End */}
1041
- </div>
1042
- </div>
1043
-
1044
- <div>
1045
- <Card>
1046
- {loading ? (
1047
- <>
1048
- <Skeleton active paragraph={{ rows: 6 }} />
1049
- </>
1050
- ) : (
1051
- <TableComponent
1052
- size="small"
1053
- // scroll={{ x: 'max-content' }}
1054
- scroll={{ x: 'max-content', y: '60vh' }}
1055
- rowKey={(record) => record.OpNo}
1056
- dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
1057
- columns={cols}
1058
- sticky
1059
- pagination={false}
1060
- summary={(pageData) => {
1061
- const summaryCols = columns.filter((col) => col.enable_summary);
1062
- if (!summaryCols.length) return null;
1063
- /** calculate summary*/
1064
-
1065
- const summaryValues = calculateSummaryValues(summaryCols, pageData);
1066
-
1067
-
1068
- return (
1069
- <Table.Summary.Row className="report-summary-row">
1070
- {cols.map((col, index) => {
1071
- if (summaryValues[col.field] !== undefined) {
1072
- return (
1073
- <Table.Summary.Cell key={index}>
1074
- <strong style={{ fontWeight: 900 }}>{summaryValues[col.field]}</strong>
1075
- </Table.Summary.Cell>
1076
- );
1077
- }
1078
-
1079
- const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
1080
- if (captionConfig) {
1081
- return (
1082
- <Table.Summary.Cell key={index}>
1083
- <strong style={{ fontWeight: 900 }}>{captionConfig.summary_caption || ''}</strong>
1084
- </Table.Summary.Cell>
1085
- );
1086
- }
1087
-
1088
- return <Table.Summary.Cell key={index} />;
1089
- })}
1090
- </Table.Summary.Row>
1091
- );
1092
- }}
1093
- />
1094
- )}
1095
-
1096
- {/* Pagination aligned to the right */}
1097
- <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
1098
- <Pagination
1099
- showSizeChanger
1100
- current={pagination.current}
1101
- pageSize={pagination.pageSize}
1102
- total={pagination.total}
1103
- pageSizeOptions={[20, 30, 50, 100]}
1104
- onChange={(page, pageSize) => handlePagination({ current: page, pageSize })}
1105
- />
1106
- </div>
1107
-
1108
- {/*If patient data exists show the number else to 0 */}
1109
- <p className="size-hint">{patients ? patients.length : 0} records. </p>
1110
- </Card>
1111
- {/* </> */}
1112
- {/* )} */}
1113
- </div>
1114
- </>
1115
- );
1116
- }
1
+ import React, { useState, useEffect, useContext, useRef } from 'react';
2
+
3
+ import { Table, Skeleton, Input, Modal, message, Pagination, Tag } from 'antd';
4
+
5
+ import { QrcodeOutlined } from '@ant-design/icons';
6
+
7
+ import { Location, FormCreator, GlobalContext, ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
8
+
9
+ import { CoreScripts } from './../../../../models/';
10
+
11
+ import moment from 'moment-timezone';
12
+
13
+ import Button from '../../../../lib/elements/basic/button/button';
14
+
15
+ import './reporting-dashboard.scss';
16
+
17
+ // import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
18
+ import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboard/menu-dashboard';
19
+ import { useHistory } from 'react-router-dom';
20
+ import * as ReportingDashboardComp from '../index';
21
+ import buildDisplayColumns from './display-columns/build-display-columns';
22
+ import { getRedirectLink } from './display-columns/display-cell-renderer';
23
+ import AdvancedSearchSelect from './adavance-search/advance-search';
24
+
25
+ // import { isPdfFile } from 'pdfjs-dist';
26
+
27
+ var genericComponents = require('./../../../../lib');
28
+
29
+ const { Search } = Input;
30
+
31
+ /**
32
+ * ReportingDashboard component renders the dashboard and handles patient details,
33
+ * configuration, and form layout for generating reports.
34
+ *
35
+ * @param {Object} props - The component's props.
36
+ * @param {Object} props.match - The match object containing the URL parameters.
37
+ * @param {Object} props.CustomComponents - Custom components for rendering.
38
+ * @param {string} props.reportId - The report ID.
39
+ * @param {boolean} props.isFixedIndex - Determines if the index is fixed.
40
+ * @param {Array<string>} props.dashBoardIds - The list of dashboard IDs.
41
+ *
42
+ * @returns {JSX.Element} The rendered ReportingDashboard component.
43
+ */
44
+ export default function ReportingDashboard({
45
+ match,
46
+ scope,
47
+ CustomComponents,
48
+ reportId,
49
+ isFixedIndex,
50
+ dashBoardIds,
51
+ barcodeFilterKey,
52
+ showScanner,
53
+ dbPtr,
54
+ attributes,
55
+ }) {
56
+ const [config, setConfig] = useState({});
57
+
58
+ // State to manage the layout of the form
59
+ const [formLayout, setFormLayout] = useState('vertical');
60
+
61
+ const [loading, setLoading] = useState(true);
62
+
63
+ const [cardLoading, setCardLoading] = useState(true);
64
+
65
+ const [searchParameters, setSearchParameters] = useState([]);
66
+
67
+ const [searchValues, setSearchValues] = useState({});
68
+
69
+ const [dashboardVisible, setDashBoardVisible] = useState(false);
70
+
71
+ const [formContents, setformContents] = useState({});
72
+ const [liveFormContents, setLiveFormContents] = useState({});
73
+ const [reportMailRequest, setReportMailRequest] = useState({
74
+ scriptId: null,
75
+ inputParameters: {},
76
+ });
77
+
78
+ const scriptId = useRef(null);
79
+
80
+ //In case of reports from core_script , there will be id from params
81
+ // In case of normal menu we need to take id from props
82
+ let id = reportId ? reportId : match.params.id;
83
+
84
+ const { CustomModels = {} } = useContext(GlobalContext);
85
+
86
+ const [details, setDetails] = useState([]);
87
+
88
+ const [columns, setColumns] = useState([]); // To set columns
89
+
90
+ const [summaryColumns, setSummaryColumns] = useState([]);
91
+
92
+ const [patients, setPatients] = useState([]); //Patients list array
93
+
94
+ const urlParams = Location.search();
95
+
96
+ // Pagination
97
+ const [pagination, setPagination] = useState({
98
+ current: 1,
99
+ pageSize: 20,
100
+ total: 0,
101
+ });
102
+
103
+ // Settings db pointer
104
+ if (!dbPtr) dbPtr = localStorage.db_ptr;
105
+
106
+ /**
107
+ * Fetches patient details from the server and updates the relevant state.
108
+ * This includes input parameters, columns, summary columns, and configuration.
109
+ *
110
+ * @returns {Promise<void>} A promise that resolves when the patient details have been fetched and the state has been updated.
111
+ */
112
+ async function getPatientDetails(idOverride) {
113
+ setPatients([]);
114
+ const fetchId = idOverride || id;
115
+ await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
116
+ // Check if display columns are provided from backend
117
+ let parsedColumns = [];
118
+
119
+ if (result.display_columns) {
120
+ parsedColumns = JSON.parse(result.display_columns);
121
+ }
122
+ setColumns(parsedColumns);
123
+
124
+ await prepareInputParameters(result, parsedColumns, fetchId);
125
+
126
+ if (result.summary_columns) {
127
+ setSummaryColumns(JSON.parse(result.summary_columns));
128
+ }
129
+
130
+ setConfig({ ...result });
131
+ });
132
+ }
133
+
134
+ // useEffect(() => {
135
+ // if (!dashboardVisible) {
136
+ // refresh(false);
137
+ // }
138
+ // }, [dashboardVisible]);
139
+
140
+ /**
141
+ *
142
+ *
143
+ * Manage initial render
144
+ */
145
+
146
+ useEffect(() => {
147
+ getPatientDetails();
148
+ }, []);
149
+
150
+ /**
151
+ * Effect hook that triggers the `getPatientDetails` function
152
+ * whenever the `match.params.id` value changes.
153
+ *
154
+ * @returns {void} This hook does not return anything.
155
+ */
156
+ // useEffect(() => {
157
+ // getPatientDetails();
158
+ // }, [match.params.id]);
159
+
160
+ let parameters;
161
+
162
+ /**
163
+ * Some parameters would need binding with the model
164
+ *
165
+ * @param {*} inputParameters Input parameters from the report configuration.
166
+ */
167
+
168
+ //Prepare input parameters by mapping default values and binding models if needed
169
+ async function prepareInputParameters(record, parsedColumns, fetchId) {
170
+ setLoading(true);
171
+ let urlParams = Location.search();
172
+
173
+ // If script id exist set into variable
174
+ if (urlParams.script_id) scriptId.current = urlParams.script_id;
175
+
176
+ let otherDetails = record.other_details1 ? JSON.parse(record.other_details1) : null;
177
+
178
+ parameters = record.input_parameters ? JSON.parse(record.input_parameters) : [];
179
+
180
+ let formContent = {};
181
+ const searchFields = (parameters || []).filter((p) => p.type === 'search' && p.search_enabled === 'yes');
182
+ setSearchParameters([...searchFields]);
183
+
184
+ parameters = await parameters?.map((record) => {
185
+ // Only if the url params does have a matching value ,
186
+ // we should not consider the default value
187
+
188
+ if (urlParams[record.field]) {
189
+ if (record.type === 'date') {
190
+ formContent[record.field] = moment.utc(urlParams[record.field]);
191
+ }
192
+
193
+ // return formContent;
194
+ } else {
195
+ // let StartDate, EndDate;
196
+ switch (record.default) {
197
+ case 'startOfDay':
198
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).startOf('day');
199
+ break;
200
+
201
+ case 'endOfDay':
202
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).endOf('day');
203
+ break;
204
+
205
+ case 'startOfWeek':
206
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).startOf('week');
207
+ break;
208
+
209
+ case 'endOfWeek':
210
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE).endOf('week');
211
+ break;
212
+
213
+ case 'currentDate':
214
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
215
+ break;
216
+
217
+ default:
218
+ break;
219
+ }
220
+ }
221
+
222
+ // If it is a date field and still no value, set it to today's date
223
+ if (record.type === 'date' && !formContent[record.field]) {
224
+ formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
225
+ }
226
+ if (record.type === 'search') {
227
+ if (!formContent[record.field]) formContent[record.field] = [];
228
+ return {
229
+ ...record,
230
+ reportId: fetchId,
231
+ onReset: () => getPatientDetails(fetchId),
232
+ required: record.required,
233
+ };
234
+ }
235
+ if (['reference-select', 'reference-search', 'select'].indexOf(record.type) !== -1) {
236
+ // let model = "";
237
+ let model = CustomModels[record.modelName];
238
+
239
+ return {
240
+ ...record,
241
+ model,
242
+ required: record.required,
243
+ };
244
+ } else {
245
+ return {
246
+ ...record,
247
+ required: true,
248
+ };
249
+ }
250
+ });
251
+ // Update form content state
252
+ setformContents(formContent);
253
+ setLiveFormContents(formContent);
254
+
255
+ // Trigger form submission
256
+ onFinish(formContent, null, record.input_parameters, parsedColumns);
257
+
258
+ setLoading(false);
259
+
260
+ // Check if input parameters are enabled or disabled in otherDetails
261
+ if (otherDetails?.isDisableInputParameters) {
262
+ // If enabled, clear the details array
263
+ setDetails([]);
264
+ } else {
265
+ // Keep all parameters with a type (including search) to render in FormCreator
266
+ setDetails([...parameters.filter((ele) => ele.type)]);
267
+ }
268
+ }
269
+
270
+ // Refresh patient details.
271
+
272
+ function refresh() {
273
+ getPatientDetails();
274
+ }
275
+
276
+ const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns) => {
277
+ const { current, pageSize } = pagination || {};
278
+ // If card script id is exist load that id otherwise load id
279
+ const coreScriptId = scriptId.current ? scriptId.current : id;
280
+ const normalizedColumns = Array.isArray(parsedColumns) ? parsedColumns : Array.isArray(columns) ? columns : [];
281
+
282
+ setLoading(true);
283
+ try {
284
+ // Prepare payload for backend: format only here
285
+ const formattedValues = {};
286
+ Object.keys(values).forEach((key) => {
287
+ const val = values[key];
288
+ formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
289
+ });
290
+ // Pagination Data
291
+ const paginationData = {
292
+ page: pagination.current || 1,
293
+ limit: pagination.pageSize || 10,
294
+ };
295
+ // Combine form data + pagination
296
+ let formBody = {
297
+ body: {
298
+ ...formattedValues,
299
+ ...paginationData,
300
+ },
301
+ };
302
+ // Optional override if `scope` exists
303
+ if (scope) {
304
+ formBody = { body: { ...scope, ...paginationData } };
305
+ }
306
+
307
+ setReportMailRequest({
308
+ scriptId: coreScriptId,
309
+ inputParameters: formBody,
310
+ });
311
+
312
+ // Fetch result
313
+ const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
314
+
315
+ const apiData = Array.isArray(result) ? result : Array.isArray(result?.result) ? result.result : [];
316
+
317
+ // Handle both result formats
318
+ let resultDetails = apiData[0] || [];
319
+ if (result?.result && result?.result[0]) {
320
+ resultDetails = result.result[0];
321
+ }
322
+ // Update patients
323
+ setPatients(resultDetails || []);
324
+
325
+ // When display_columns is missing, build columns from the response keys.
326
+ if (normalizedColumns.length === 0 && resultDetails.length > 0) {
327
+ // Create columns dynamically from resultDetails keys
328
+ setColumns((prev) => {
329
+ if (prev.length > 0) return prev;
330
+ return Object.keys(resultDetails[0]).map((key) => ({
331
+ title: key,
332
+ field: key,
333
+ }));
334
+ });
335
+ }
336
+
337
+ if (result.length) {
338
+ // Set Pgination data into URL
339
+ Location.search({ ...Location.search(), current, pageSize });
340
+
341
+ setPagination((prev) => ({
342
+ ...prev,
343
+ current: pagination.current,
344
+ pageSize: pagination.pageSize,
345
+ total: resultDetails?.[0]?.TotalCount ?? pagination?.total,
346
+ }));
347
+ }
348
+ } catch (error) {
349
+ console.error('Error fetching report data:', error);
350
+ } finally {
351
+ // Always runs, success or error
352
+ setLoading(false);
353
+ }
354
+ };
355
+
356
+ const handleSubmit = (values) => {
357
+ // Extract search fields from the form values
358
+ const searchKeys = searchParameters.map((p) => p.field);
359
+ const currentSearchValues = {};
360
+ const formValues = {};
361
+
362
+ Object.keys(values).forEach((key) => {
363
+ if (searchKeys.includes(key)) {
364
+ currentSearchValues[key] = values[key];
365
+ } else {
366
+ formValues[key] = values[key];
367
+ }
368
+ });
369
+
370
+ const hasSearchValues = Object.values(currentSearchValues).some((v) => Array.isArray(v) && v.length > 0);
371
+
372
+ if (!hasSearchValues) {
373
+ runSubmit(formValues);
374
+ return;
375
+ }
376
+
377
+ // Check if main form values (non-search) changed
378
+ const formChanged = Object.keys(formValues).some((key) => {
379
+ const newVal = formValues[key];
380
+ const oldVal = formContents[key];
381
+
382
+ if (moment.isMoment(newVal) && moment.isMoment(oldVal)) {
383
+ return !newVal.isSame(oldVal, 'day');
384
+ }
385
+
386
+ return newVal !== oldVal;
387
+ });
388
+
389
+ if (formChanged) {
390
+ Modal.confirm({
391
+ title: 'Filters changed',
392
+ content: 'You changed some filters. Do you want to search using these filters also?',
393
+ okText: 'Yes',
394
+ cancelText: 'No',
395
+
396
+ onOk() {
397
+ // YES → send form values + search condition
398
+ const finalValues = {
399
+ ...formValues,
400
+ search_values: currentSearchValues,
401
+ };
402
+
403
+ runSubmit(finalValues);
404
+ },
405
+
406
+ onCancel() {
407
+ // NO → reset form values
408
+ const resetValues = {};
409
+ Object.keys(formValues).forEach((key) => {
410
+ resetValues[key] = null;
411
+ });
412
+
413
+ const finalValues = {
414
+ ...resetValues,
415
+ search_values: currentSearchValues,
416
+ };
417
+
418
+ runSubmit(finalValues);
419
+ },
420
+ });
421
+ } else {
422
+ // no form change
423
+ const resetValues = {};
424
+ Object.keys(formValues).forEach((key) => {
425
+ resetValues[key] = null;
426
+ });
427
+
428
+ const finalValues = {
429
+ ...resetValues,
430
+ search_values: currentSearchValues,
431
+ };
432
+
433
+ runSubmit(finalValues);
434
+ }
435
+ };
436
+
437
+ const runSubmit = (finalValues) => {
438
+ const { pageSize } = pagination;
439
+ const resetPage = 1;
440
+
441
+ scriptId.current = null;
442
+
443
+ const hasSearchValues = finalValues.search_values && Object.values(finalValues.search_values).some((v) => Array.isArray(v) && v.length > 0);
444
+
445
+ if (!hasSearchValues) {
446
+ const currentUrlParams = Location.search();
447
+ const searchKeys = new Set(searchParameters.map((parameter) => parameter.field));
448
+ const cleanParams = Object.fromEntries(
449
+ Object.entries(currentUrlParams).filter(
450
+ ([key]) => key !== 'script_id' && key !== 'selected_card' && !searchKeys.has(key)
451
+ )
452
+ );
453
+
454
+ const newParams = new URLSearchParams({
455
+ ...cleanParams,
456
+ current: resetPage,
457
+ pageSize,
458
+ });
459
+
460
+ const newUrl = `${window.location.pathname}?${newParams.toString()}`;
461
+ window.history.replaceState({}, '', newUrl);
462
+ }
463
+
464
+ onFinish(finalValues, resetPage);
465
+ };
466
+
467
+ const selectedSearchFields = details.filter((field) => {
468
+ if (field.type !== 'search') return false;
469
+
470
+ const value = liveFormContents[field.field];
471
+ return Array.isArray(value) ? value.length > 0 : !!value;
472
+ });
473
+
474
+ const handleRemoveSearchField = (fieldName) => {
475
+ const updatedValues = {
476
+ ...liveFormContents,
477
+ [fieldName]: [],
478
+ };
479
+
480
+ setLiveFormContents(updatedValues);
481
+ handleSubmit(updatedValues);
482
+ };
483
+
484
+ /**
485
+ *
486
+ * @param {*} values
487
+ */
488
+
489
+ const onFinish = async (values, resetPage, inputParamsString, parsedColumns) => {
490
+ setLoading(true);
491
+ setCardLoading(true);
492
+ // // Check if the dashboard is visible and both start and end values are provided
493
+ // If true, proceed with hiding the dashboard by setting dashboardVisible to false
494
+ if (dashboardVisible && values.start && values.end) {
495
+ setDashBoardVisible((prev) => {
496
+ if (!prev) return prev; // Prevent unnecessary updates
497
+
498
+ return false;
499
+ });
500
+ }
501
+ const paramsString = inputParamsString || (config.input_parameters ? config.input_parameters : null);
502
+ // Cheacking if there is data and making the data url friendly, ie, if there is date in url
503
+ if (paramsString) {
504
+ let urlsToUpdate = {};
505
+
506
+ let parameterValue = JSON.parse(paramsString);
507
+
508
+ // Variable to store/update the form initial Values
509
+
510
+ let formContent = {};
511
+
512
+ parameters = parameterValue.map((parameter) => {
513
+ //Getting url friendly value by matching the url values and input_parameter values
514
+ let value = values[parameter.field];
515
+
516
+ // If the value is missing at the root level (which happens when search fields are moved
517
+ // into search_values during submission), try to retrieve it from the nested object.
518
+ if (value === undefined && values.search_values && values.search_values[parameter.field] !== undefined) {
519
+ value = values.search_values[parameter.field];
520
+ }
521
+
522
+ // Keep Moment object in state for picker
523
+ if (parameter.type === 'date' && value) {
524
+ formContent[parameter.field] = value.isValid ? value : moment(value); // ensure Moment
525
+ // Format for URL
526
+ urlsToUpdate[parameter.field] = moment(value).format('YYYY-MM-DD');
527
+ } else {
528
+ formContent[parameter.field] = value;
529
+ if (parameter.type) {
530
+ urlsToUpdate[parameter.field] = value;
531
+ }
532
+ }
533
+
534
+ return parameter;
535
+ });
536
+
537
+ const currentParams = Location.search();
538
+ // Mark existing empty values as null so Location.search removes stale query params.
539
+ const filteredParams = Object.fromEntries(
540
+ Object.entries(urlsToUpdate).flatMap(([key, value]) => {
541
+ const shouldRemove = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0);
542
+ if (shouldRemove && !Object.prototype.hasOwnProperty.call(currentParams, key)) {
543
+ return [];
544
+ }
545
+
546
+ return [[key, shouldRemove ? null : value]];
547
+ })
548
+ );
549
+
550
+ setformContents(formContent);
551
+ setLiveFormContents(formContent);
552
+ Location.search({ ...currentParams, ...filteredParams });
553
+ }
554
+
555
+ // Reset Pagination Data
556
+ const latestUrlParams = Location.search();
557
+ const paginationData = {
558
+ current: resetPage || Number(latestUrlParams.current) || 1,
559
+ pageSize: Number(latestUrlParams.pageSize) || pagination.pageSize,
560
+ };
561
+
562
+ // Call API
563
+ try {
564
+ await fetchReportData(id, values, dbPtr, paginationData, parsedColumns);
565
+ } finally {
566
+ setLoading(false);
567
+ setCardLoading(false);
568
+ }
569
+ };
570
+
571
+ // Pagination Handler
572
+ const handlePagination = async (newPagination) => {
573
+ try {
574
+ await fetchReportData(id, formContents, dbPtr, newPagination);
575
+ } finally {
576
+ setLoading(false);
577
+ setCardLoading(false);
578
+ }
579
+ };
580
+
581
+ let model = {
582
+ fields: details,
583
+ };
584
+
585
+ return (
586
+ <Card className="reporting-dashboard card card-shadow">
587
+ {/** If dashBoardIds exist and contain elements, render MenuDashBoard*/}
588
+
589
+ {dashBoardIds?.length > 0 ? (
590
+ <MenuDashBoardComponent
591
+ dashBoardIds={dashBoardIds} //Pass the available dashboard IDs to the componen
592
+ activeId={Number(urlParams?.selected_card || 0)}
593
+ dbPtr={dbPtr}
594
+ callback={(record) => {
595
+ const selectedCard = record?.id;
596
+ if (record.other_details) {
597
+ // Convert the other_details string to a JSON object
598
+ let json = JSON.parse(record.other_details);
599
+ const newScriptId = json.script_id;
600
+
601
+ scriptId.current = newScriptId;
602
+
603
+ // Reset Pagination on card click
604
+ setPagination((prev) => ({
605
+ ...prev,
606
+ current: 1,
607
+ total: 0,
608
+ }));
609
+
610
+ //Update URL
611
+ Location.search({
612
+ ...Location.search(),
613
+ current: 1,
614
+ pageSize: pagination.pageSize,
615
+ script_id: newScriptId,
616
+ selected_card: selectedCard,
617
+ });
618
+
619
+ // menudashBoardVisible = true;
620
+ // Show the dashboard component
621
+ setDashBoardVisible(true);
622
+ // Fetch and update patient details based on the selected item
623
+ getPatientDetails(newScriptId);
624
+ // setDashBoard(true);
625
+ }
626
+ }}
627
+ ></MenuDashBoardComponent>
628
+ ) : null}
629
+
630
+ {/* Content Below */}
631
+ {loading ? (
632
+ <>
633
+ <Skeleton active />
634
+ </>
635
+ ) : (
636
+ <>
637
+ <div>
638
+ {/* <Card className="form-card" style={{ padding: '10px 10px' }}> */}
639
+ {details && details[0] && !loading ? (
640
+ <FormCreator
641
+ {...formLayout}
642
+ layout={formLayout}
643
+ initialValues={{
644
+ layout: formLayout,
645
+ }}
646
+ styles={{ paddingRight: '15px', alignItems: 'center' }}
647
+ fields={details}
648
+ reportId={id}
649
+ formContent={formContents}
650
+ // formContent={{ [model]: {} }}
651
+ modelIndex="requestId"
652
+ model={model}
653
+ onSubmit={handleSubmit}
654
+ onFormValuesChange={setLiveFormContents}
655
+ callback={() => {
656
+ // history.goBack();
657
+ }}
658
+ />
659
+ ) : null}
660
+ </div>
661
+
662
+ {/** GuestList component start*/}
663
+ <GuestList
664
+ patients={patients}
665
+ columns={columns}
666
+ summaryColumns={summaryColumns}
667
+ isFixedIndex={isFixedIndex}
668
+ showScanner={showScanner}
669
+ barcodeFilterKey={barcodeFilterKey}
670
+ CustomComponents={{ ...CustomComponents, ...genericComponents, ...ReportingDashboardComp }}
671
+ refresh={refresh}
672
+ config={config}
673
+ loading={cardLoading}
674
+ pagination={pagination}
675
+ handlePagination={handlePagination}
676
+ attributes={attributes}
677
+ selectedSearchFields={selectedSearchFields}
678
+ handleRemoveSearchField={handleRemoveSearchField}
679
+ fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
680
+ reportScriptId={reportMailRequest.scriptId}
681
+ reportInputParameters={reportMailRequest.inputParameters}
682
+ />
683
+ {/** GuestList component end*/}
684
+ </>
685
+ )}
686
+ </Card>
687
+ );
688
+ }
689
+
690
+ /**
691
+ *
692
+ * @param root0
693
+ * @param root0.patients
694
+ * @param root0.CustomComponents
695
+ * @param root0.summaryColumns
696
+ * @param root0.refresh
697
+ * @param root0.isFixedIndex
698
+ * @returns {*}
699
+ */
700
+ //Renders a table displaying a list of patients with dynamic columns
701
+ function GuestList({
702
+ patients,
703
+ columns,
704
+ loading,
705
+ CustomComponents,
706
+ refresh,
707
+ isFixedIndex,
708
+ barcodeFilterKey,
709
+ showScanner,
710
+ config,
711
+ pagination,
712
+ handlePagination,
713
+ attributes,
714
+ selectedSearchFields,
715
+ handleRemoveSearchField,
716
+ fetchReportData,
717
+ reportScriptId,
718
+ reportInputParameters,
719
+ }) {
720
+ /**
721
+ * @param {*} propValues
722
+ */
723
+ const propValues = (attributes && JSON.parse(attributes)) || {};
724
+
725
+ const { buttonAttributes = [] } = propValues;
726
+
727
+ var [query, setQuery] = useState('');
728
+
729
+ const [exportData, setExportData] = useState({});
730
+
731
+ // const [data, setData] = useState([]);
732
+
733
+ //visibility of the QR scanner modal.
734
+ const [isScannerVisible, setScannerVisible] = useState(false);
735
+
736
+ // Stores the patients filtered specifically by QR scan match.
737
+ const [filteredPatients, setFilteredPatients] = useState([]); // Show all initially
738
+
739
+ // patient object to redirect to upon successful QR scan.
740
+ const [redirectPatient, setRedirectPatient] = useState(null);
741
+
742
+ const [visible, setVisible] = useState(false);
743
+
744
+ const [ActiveComponent, setActiveComponent] = useState(null);
745
+
746
+ let history = useHistory();
747
+
748
+ const { isMobile, dispatch } = useContext(GlobalContext);
749
+ const [single, setSingle] = useState({});
750
+ const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
751
+
752
+ // const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
753
+ // const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
754
+ const cols = buildDisplayColumns({
755
+ columns,
756
+ patients,
757
+ isFixedIndex,
758
+ CustomComponents,
759
+ refresh,
760
+ otherDetails,
761
+ });
762
+
763
+ /**
764
+ *
765
+ * @param {*} result
766
+ */
767
+
768
+ // function changeView(result) {
769
+ // setView(result);
770
+ // }
771
+
772
+ /**
773
+ *
774
+ * @param {*} event
775
+ */
776
+
777
+ function onSearch(event) {
778
+ setQuery(event.target.value);
779
+ }
780
+
781
+ /**
782
+ *
783
+ */
784
+
785
+ useEffect(() => {
786
+ //Cheaking if there is patient data exists
787
+ if (patients) {
788
+ // let data = patients?.map((entry) => {
789
+ // entry.rowIndex = entry.opb_id;
790
+
791
+ // entry.dispatch = dispatch;
792
+
793
+ // return entry;
794
+ // });
795
+
796
+ // setData(data);
797
+
798
+ // Define export data
799
+ // Sanitize cols for export to ensure titles are strings
800
+ const exportCols = cols.map((col) => {
801
+ if (col.title && typeof col.title === 'object' && col.title.props) {
802
+ return { ...col, title: col.title.props.title };
803
+ }
804
+ return col;
805
+ });
806
+ const summaryCols = columns.filter((col) => col.enable_summary);
807
+ let dataToExport = [...patients];
808
+
809
+ if (summaryCols.length > 0) {
810
+ // Build one synthetic row for CSV export that mirrors the table layout:
811
+ // numeric summary cells are populated from `calculateSummaryValues`, while
812
+ // non-summary columns stay blank unless a configured caption should be shown.
813
+ const summaryValues = calculateSummaryValues(summaryCols, patients);
814
+ const summaryRow = { isSummaryRow: true };
815
+
816
+ cols.forEach((col, index) => {
817
+ // Start each export column empty so the appended row keeps the same shape
818
+ // as the data rows and does not leak index/helper values into the export.
819
+ const colKey = col.field || col.key || col.dataIndex;
820
+ if (colKey && !summaryRow[colKey]) {
821
+ summaryRow[colKey] = '';
822
+ }
823
+
824
+ if (summaryValues[col.field] !== undefined) {
825
+ // Fill columns that have an aggregate configured (sum, count, avg, etc.).
826
+ summaryRow[col.field] = summaryValues[col.field];
827
+ } else {
828
+ // If this column is marked as the caption target for a summary column,
829
+ // place the configured label (for example "Total") into that cell.
830
+ const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
831
+ if (captionConfig) {
832
+ summaryRow[col.field] = captionConfig.summary_caption || '';
833
+ }
834
+ }
835
+ });
836
+ dataToExport.push(summaryRow);
837
+ }
838
+ let exportDatas = getExportData(dataToExport, exportCols);
839
+
840
+ if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
841
+ setExportData({ exportDatas });
842
+ }
843
+ }
844
+ }, [patients, columns]);
845
+
846
+ let filtered;
847
+
848
+ if (patients) {
849
+ filtered = patients.filter((record) => {
850
+ if (query) {
851
+ // Keys
852
+ let keys = Object.keys(record);
853
+
854
+ let flag = false;
855
+
856
+ keys.forEach((key) => {
857
+ let ele = record[key];
858
+
859
+ if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
860
+ flag = true;
861
+ }
862
+ });
863
+
864
+ /**Will return flag */
865
+ return flag;
866
+ } else {
867
+ return true;
868
+ }
869
+ });
870
+ }
871
+
872
+ /**
873
+ * Checks for a match in the filtered patient list based on a scanned code,
874
+ * updates the relevant state if a match is found, and redirects to the
875
+ * patient's detail page. Displays a warning if no match is found.
876
+ *
877
+ * @param {string} code - The scanned code to match against a specific field of each patient.
878
+ */
879
+
880
+ const handleScanSuccess = (code) => {
881
+ // Filters patients based on the scanned code and the selected barcode key(using attributes 'barcodeFilterKey')
882
+ const matched = filtered.filter((patient) => patient[barcodeFilterKey] === code);
883
+
884
+ if (matched.length) {
885
+ const patient = matched[0];
886
+ setFilteredPatients(matched);
887
+ setRedirectPatient(matched);
888
+ message.success(`Match found for ${code}, redirecting...`);
889
+
890
+ const actionColumn = columns.find((col) => col.field === 'action') || columns.find((col) => col.type === 'action');
891
+ if (actionColumn) {
892
+ const redirectLink = getRedirectLink(actionColumn, patient);
893
+ // history.push(redirectLink);
894
+ window.location.href = redirectLink;
895
+ }
896
+ } else {
897
+ Modal.warning({
898
+ title: 'No matching records.',
899
+ content: `No match for scanned code: ${code}`,
900
+ });
901
+ }
902
+ };
903
+
904
+ //open the edit modal
905
+ const handleOpenEdit = (button) => {
906
+ const componentName = button.component;
907
+ const ComponentToRender = ReportingDashboardComp[componentName];
908
+
909
+ if (!ComponentToRender) {
910
+ console.error(`Component ${componentName} not found!`);
911
+ return;
912
+ }
913
+
914
+ setSingle({});
915
+ setActiveComponent(() => ComponentToRender);
916
+ setVisible(true);
917
+ };
918
+
919
+ // close the edit modal
920
+ const handleCloseEdit = () => {
921
+ setShowEdit(false);
922
+ };
923
+ /**
924
+ * Calculates aggregate values for the configured summary columns.
925
+ *
926
+ * Each summary definition contributes one value keyed by its `field`. Missing
927
+ * row values are treated as `0` for numeric operations so the table summary and
928
+ * export summary row can be built from the same result object.
929
+ *
930
+ * Supported functions:
931
+ * `sum` - totals all numeric values in the field.
932
+ * `count` - returns the number of rows in the current dataset.
933
+ * `avg` - returns the arithmetic mean of the field values.
934
+ * `min` - returns the smallest numeric value in the field.
935
+ * `max` - returns the largest numeric value in the field.
936
+ *
937
+ * @param {Array<Object>} summaryCols - Column configs with `field` and `function`.
938
+ * @param {Array<Object>} pageData - Rows currently being summarized.
939
+ * @returns {Object} Aggregate values keyed by field name.
940
+ */
941
+ function calculateSummaryValues(summaryCols, pageData) {
942
+ const summaryValues = {};
943
+
944
+ summaryCols.forEach((col) => {
945
+ const field = col.field;
946
+
947
+ if (col.function === 'sum') {
948
+ summaryValues[field] = pageData.reduce((total, row) => total + Number(row[field] || 0), 0);
949
+ }
950
+
951
+ if (col.function === 'count') {
952
+ summaryValues[field] = pageData.length;
953
+ }
954
+
955
+ if (col.function === 'avg') {
956
+ const total = pageData.reduce((sum, row) => sum + Number(row[field] || 0), 0);
957
+ summaryValues[field] = pageData.length ? total / pageData.length : 0;
958
+ }
959
+ if (col.function === 'min') {
960
+ const values = pageData.map((row) => Number(row[field] || 0));
961
+ summaryValues[field] = values.length ? Math.min(...values) : 0;
962
+ }
963
+
964
+ if (col.function === 'max') {
965
+ const values = pageData.map((row) => Number(row[field] || 0));
966
+ summaryValues[field] = values.length ? Math.max(...values) : 0;
967
+ }
968
+ });
969
+
970
+ return summaryValues;
971
+ }
972
+ return (
973
+ <>
974
+ <div className="table-header">
975
+ <div className="table-left">
976
+ {/* {selectedSearchFields?.length > 0 ? (
977
+ <div className="search-tags-container">
978
+ {selectedSearchFields.map((field) => (
979
+ <Tag key={field.field} closable color="blue" onClose={() => handleRemoveSearchField(field.field)}>
980
+ {field.caption}
981
+ </Tag>
982
+ ))}
983
+ </div>
984
+ ) : null} */}
985
+ </div>
986
+
987
+ <div className="table-right">
988
+ {/* shwoing caption is not correct so this commented */}
989
+ {/* <span className="menu-caption">{config.caption}</span> */}
990
+ <Search className="table-search-input" placeholder="Enter Search Value" allowClear onChange={onSearch} />
991
+ <div className="table-export-button">
992
+ {exportData.exportDatas && (
993
+ <>
994
+ <ExportReactCSV
995
+ title={config.caption}
996
+ headers={exportData.exportDatas.exportDataHeaders}
997
+ csvData={exportData.exportDatas.exportDataColumns}
998
+ fileName={`${config.caption || 'Report'}.xlsx`}
999
+ pdfFileName={`${config.caption || 'Report'}.pdf`}
1000
+ scriptId={reportScriptId}
1001
+ inputParameters={reportInputParameters}
1002
+ dropdown
1003
+ />
1004
+ </>
1005
+ )}
1006
+ </div>
1007
+
1008
+ {/* QR Scan start */}
1009
+ {showScanner ? (
1010
+ <Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
1011
+ Scan QR
1012
+ </Button>
1013
+ ) : null}
1014
+ {/** Add User button */}
1015
+ {Array.isArray(buttonAttributes) &&
1016
+ buttonAttributes.map((btn, index) => (
1017
+ <Button key={index} size="small" type="primary" style={{ marginLeft: 8 }} onClick={() => handleOpenEdit(btn)}>
1018
+ {btn.title}
1019
+ </Button>
1020
+ ))}
1021
+
1022
+ <Modal open={visible} onCancel={() => setVisible(false)} footer={null} destroyOnClose width={950} style={{ top: 10 }}>
1023
+ {ActiveComponent && (
1024
+ <ActiveComponent
1025
+ formContent={single}
1026
+ callback={() => {
1027
+ setVisible(false);
1028
+ refresh();
1029
+ setVisible(false);
1030
+ fetchReportData();
1031
+ }}
1032
+ // {...dynamicProps}
1033
+ />
1034
+ )}
1035
+ </Modal>
1036
+
1037
+ <Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
1038
+ <QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
1039
+ </Modal>
1040
+ {/* QR Scan End */}
1041
+ </div>
1042
+ </div>
1043
+
1044
+ <div>
1045
+ <Card>
1046
+ {loading ? (
1047
+ <>
1048
+ <Skeleton active paragraph={{ rows: 6 }} />
1049
+ </>
1050
+ ) : (
1051
+ <TableComponent
1052
+ size="small"
1053
+ // scroll={{ x: 'max-content' }}
1054
+ scroll={{ x: 'max-content', y: '60vh' }}
1055
+ rowKey={(record) => record.OpNo}
1056
+ dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
1057
+ columns={cols}
1058
+ sticky
1059
+ pagination={false}
1060
+ summary={(pageData) => {
1061
+ const summaryCols = columns.filter((col) => col.enable_summary);
1062
+ if (!summaryCols.length) return null;
1063
+ /** calculate summary*/
1064
+
1065
+ const summaryValues = calculateSummaryValues(summaryCols, pageData);
1066
+
1067
+
1068
+ return (
1069
+ <Table.Summary.Row className="report-summary-row">
1070
+ {cols.map((col, index) => {
1071
+ if (summaryValues[col.field] !== undefined) {
1072
+ return (
1073
+ <Table.Summary.Cell key={index}>
1074
+ <strong style={{ fontWeight: 900 }}>{summaryValues[col.field]}</strong>
1075
+ </Table.Summary.Cell>
1076
+ );
1077
+ }
1078
+
1079
+ const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
1080
+ if (captionConfig) {
1081
+ return (
1082
+ <Table.Summary.Cell key={index}>
1083
+ <strong style={{ fontWeight: 900 }}>{captionConfig.summary_caption || ''}</strong>
1084
+ </Table.Summary.Cell>
1085
+ );
1086
+ }
1087
+
1088
+ return <Table.Summary.Cell key={index} />;
1089
+ })}
1090
+ </Table.Summary.Row>
1091
+ );
1092
+ }}
1093
+ />
1094
+ )}
1095
+
1096
+ {/* Pagination aligned to the right */}
1097
+ <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
1098
+ <Pagination
1099
+ showSizeChanger
1100
+ current={pagination.current}
1101
+ pageSize={pagination.pageSize}
1102
+ total={pagination.total}
1103
+ pageSizeOptions={[20, 30, 50, 100]}
1104
+ onChange={(page, pageSize) => handlePagination({ current: page, pageSize })}
1105
+ />
1106
+ </div>
1107
+
1108
+ {/*If patient data exists show the number else to 0 */}
1109
+ <p className="size-hint">{patients ? patients.length : 0} records. </p>
1110
+ </Card>
1111
+ {/* </> */}
1112
+ {/* )} */}
1113
+ </div>
1114
+ </>
1115
+ );
1116
+ }