@bathiran212/esm-patient-chart-app 7.0.0

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 (382) hide show
  1. package/README.md +3 -0
  2. package/dist/108.js +1 -0
  3. package/dist/108.js.map +1 -0
  4. package/dist/1339.js +1 -0
  5. package/dist/1339.js.map +1 -0
  6. package/dist/1480.js +1 -0
  7. package/dist/1480.js.map +1 -0
  8. package/dist/1543.js +1 -0
  9. package/dist/1543.js.map +1 -0
  10. package/dist/1582.js +1 -0
  11. package/dist/1582.js.map +1 -0
  12. package/dist/1646.js +1 -0
  13. package/dist/1646.js.map +1 -0
  14. package/dist/1797.js +1 -0
  15. package/dist/1797.js.map +1 -0
  16. package/dist/1869.js +1 -0
  17. package/dist/1869.js.map +1 -0
  18. package/dist/1877.js +1 -0
  19. package/dist/1877.js.map +1 -0
  20. package/dist/2020.js +1 -0
  21. package/dist/2020.js.map +1 -0
  22. package/dist/2246.js +1 -0
  23. package/dist/2246.js.map +1 -0
  24. package/dist/2317.js +1 -0
  25. package/dist/2317.js.map +1 -0
  26. package/dist/2416.js +1 -0
  27. package/dist/2416.js.map +1 -0
  28. package/dist/2790.js +1 -0
  29. package/dist/2790.js.map +1 -0
  30. package/dist/282.js +1 -0
  31. package/dist/282.js.map +1 -0
  32. package/dist/2881.js +1 -0
  33. package/dist/2881.js.map +1 -0
  34. package/dist/3137.js +1 -0
  35. package/dist/3137.js.map +1 -0
  36. package/dist/3378.js +1 -0
  37. package/dist/3378.js.map +1 -0
  38. package/dist/3390.js +1 -0
  39. package/dist/3390.js.map +1 -0
  40. package/dist/3536.js +1 -0
  41. package/dist/3536.js.map +1 -0
  42. package/dist/3720.js +1 -0
  43. package/dist/3720.js.map +1 -0
  44. package/dist/3857.js +1 -0
  45. package/dist/3857.js.map +1 -0
  46. package/dist/3925.js +1 -0
  47. package/dist/3925.js.map +1 -0
  48. package/dist/3963.js +1 -0
  49. package/dist/3963.js.map +1 -0
  50. package/dist/3989.js +1 -0
  51. package/dist/3989.js.map +1 -0
  52. package/dist/4092.js +1 -0
  53. package/dist/4092.js.map +1 -0
  54. package/dist/4106.js +1 -0
  55. package/dist/4106.js.map +1 -0
  56. package/dist/4111.js +1 -0
  57. package/dist/4111.js.map +1 -0
  58. package/dist/4145.js +1 -0
  59. package/dist/4145.js.map +1 -0
  60. package/dist/434.js +1 -0
  61. package/dist/434.js.map +1 -0
  62. package/dist/4348.js +1 -0
  63. package/dist/4348.js.map +1 -0
  64. package/dist/4383.js +1 -0
  65. package/dist/4383.js.map +1 -0
  66. package/dist/4540.js +1 -0
  67. package/dist/4540.js.map +1 -0
  68. package/dist/4658.js +1 -0
  69. package/dist/4658.js.map +1 -0
  70. package/dist/466.js +1 -0
  71. package/dist/466.js.map +1 -0
  72. package/dist/4913.js +1 -0
  73. package/dist/4913.js.map +1 -0
  74. package/dist/4928.js +1 -0
  75. package/dist/4928.js.map +1 -0
  76. package/dist/5069.js +1 -0
  77. package/dist/5069.js.map +1 -0
  78. package/dist/5117.js +1 -0
  79. package/dist/5117.js.map +1 -0
  80. package/dist/5132.js +1 -0
  81. package/dist/5132.js.map +1 -0
  82. package/dist/5145.js +1 -0
  83. package/dist/5145.js.map +1 -0
  84. package/dist/52.js +1 -0
  85. package/dist/52.js.map +1 -0
  86. package/dist/5422.js +1 -0
  87. package/dist/5422.js.map +1 -0
  88. package/dist/5503.js +1 -0
  89. package/dist/5503.js.map +1 -0
  90. package/dist/5549.js +1 -0
  91. package/dist/5549.js.map +1 -0
  92. package/dist/556.js +1 -0
  93. package/dist/556.js.map +1 -0
  94. package/dist/5644.js +1 -0
  95. package/dist/5644.js.map +1 -0
  96. package/dist/5697.js +1 -0
  97. package/dist/5697.js.map +1 -0
  98. package/dist/5793.js +1 -0
  99. package/dist/5793.js.map +1 -0
  100. package/dist/5940.js +1 -0
  101. package/dist/5940.js.map +1 -0
  102. package/dist/5952.js +1 -0
  103. package/dist/5952.js.map +1 -0
  104. package/dist/6047.js +1 -0
  105. package/dist/6047.js.map +1 -0
  106. package/dist/6371.js +1 -0
  107. package/dist/6371.js.map +1 -0
  108. package/dist/6377.js +1 -0
  109. package/dist/6377.js.map +1 -0
  110. package/dist/6444.js +1 -0
  111. package/dist/6444.js.map +1 -0
  112. package/dist/6479.js +1 -0
  113. package/dist/6479.js.map +1 -0
  114. package/dist/6508.js +1 -0
  115. package/dist/6508.js.map +1 -0
  116. package/dist/6724.js +1 -0
  117. package/dist/6724.js.map +1 -0
  118. package/dist/6759.js +27 -0
  119. package/dist/6759.js.map +1 -0
  120. package/dist/689.js +1 -0
  121. package/dist/689.js.map +1 -0
  122. package/dist/6904.js +1 -0
  123. package/dist/6904.js.map +1 -0
  124. package/dist/7045.js +1 -0
  125. package/dist/7045.js.map +1 -0
  126. package/dist/7175.js +1 -0
  127. package/dist/7175.js.map +1 -0
  128. package/dist/7182.js +1 -0
  129. package/dist/7182.js.map +1 -0
  130. package/dist/7302.js +1 -0
  131. package/dist/7302.js.map +1 -0
  132. package/dist/7646.js +17 -0
  133. package/dist/7646.js.map +1 -0
  134. package/dist/7742.js +1 -0
  135. package/dist/7742.js.map +1 -0
  136. package/dist/7912.js +1 -0
  137. package/dist/7912.js.map +1 -0
  138. package/dist/8105.js +21 -0
  139. package/dist/8105.js.map +1 -0
  140. package/dist/8202.js +1 -0
  141. package/dist/8202.js.map +1 -0
  142. package/dist/8349.js +1 -0
  143. package/dist/8349.js.map +1 -0
  144. package/dist/8358.js +1 -0
  145. package/dist/8358.js.map +1 -0
  146. package/dist/8359.js +1 -0
  147. package/dist/8359.js.map +1 -0
  148. package/dist/8695.js +1 -0
  149. package/dist/8695.js.map +1 -0
  150. package/dist/8702.js +1 -0
  151. package/dist/8702.js.map +1 -0
  152. package/dist/8894.js +1 -0
  153. package/dist/8894.js.map +1 -0
  154. package/dist/8958.js +1 -0
  155. package/dist/8958.js.map +1 -0
  156. package/dist/903.js +1 -0
  157. package/dist/903.js.map +1 -0
  158. package/dist/9061.js +1 -0
  159. package/dist/9061.js.map +1 -0
  160. package/dist/9072.js +1 -0
  161. package/dist/9072.js.map +1 -0
  162. package/dist/9105.js +1 -0
  163. package/dist/9105.js.map +1 -0
  164. package/dist/9107.js +1 -0
  165. package/dist/9107.js.map +1 -0
  166. package/dist/9456.js +1 -0
  167. package/dist/9456.js.map +1 -0
  168. package/dist/9586.js +1 -0
  169. package/dist/9586.js.map +1 -0
  170. package/dist/9712.js +1 -0
  171. package/dist/9712.js.map +1 -0
  172. package/dist/9771.js +1 -0
  173. package/dist/9771.js.map +1 -0
  174. package/dist/9806.js +1 -0
  175. package/dist/9806.js.map +1 -0
  176. package/dist/9873.js +1 -0
  177. package/dist/9873.js.map +1 -0
  178. package/dist/9927.js +1 -0
  179. package/dist/9927.js.map +1 -0
  180. package/dist/main.js +6 -0
  181. package/dist/main.js.map +1 -0
  182. package/dist/openmrs-esm-patient-chart-app.js +6 -0
  183. package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +2386 -0
  184. package/dist/openmrs-esm-patient-chart-app.js.map +1 -0
  185. package/dist/routes.json +1 -0
  186. package/package.json +63 -0
  187. package/rspack.config.js +1 -0
  188. package/src/actions-buttons/action-button.scss +3 -0
  189. package/src/actions-buttons/delete-visit.component.tsx +41 -0
  190. package/src/actions-buttons/delete-visit.test.tsx +26 -0
  191. package/src/actions-buttons/mark-patient-alive.component.tsx +42 -0
  192. package/src/actions-buttons/mark-patient-deceased.component.tsx +35 -0
  193. package/src/actions-buttons/start-visit.component.tsx +41 -0
  194. package/src/actions-buttons/start-visit.test.tsx +44 -0
  195. package/src/actions-buttons/stop-visit.component.tsx +39 -0
  196. package/src/actions-buttons/stop-visit.test.tsx +27 -0
  197. package/src/clinical-views/encounter-list/encounter-list-tabs.extension.tsx +78 -0
  198. package/src/clinical-views/encounter-list/encounter-list-tabs.scss +7 -0
  199. package/src/clinical-views/encounter-list/encounter-list.component.tsx +306 -0
  200. package/src/clinical-views/encounter-list/encounter-list.scss +36 -0
  201. package/src/clinical-views/encounter-list/table.component.tsx +63 -0
  202. package/src/clinical-views/encounter-list/table.scss +11 -0
  203. package/src/clinical-views/encounter-list/tag.component.test.tsx +307 -0
  204. package/src/clinical-views/encounter-list/tag.component.tsx +43 -0
  205. package/src/clinical-views/encounter-tile/clinical-views-summary.component.tsx +40 -0
  206. package/src/clinical-views/encounter-tile/encounter-tile.component.tsx +94 -0
  207. package/src/clinical-views/encounter-tile/tile.scss +82 -0
  208. package/src/clinical-views/hooks/index.ts +3 -0
  209. package/src/clinical-views/hooks/useEncounterRows.ts +60 -0
  210. package/src/clinical-views/hooks/useEncountersByVisit.ts +13 -0
  211. package/src/clinical-views/hooks/useFormsJson.ts +15 -0
  212. package/src/clinical-views/hooks/useLastEncounter.ts +29 -0
  213. package/src/clinical-views/types.ts +305 -0
  214. package/src/clinical-views/utils/concept-utils.ts +24 -0
  215. package/src/clinical-views/utils/encounter-list-config-builder.ts +160 -0
  216. package/src/clinical-views/utils/encounter-list.resource.ts +26 -0
  217. package/src/clinical-views/utils/helpers.ts +226 -0
  218. package/src/clinical-views/utils/index.ts +90 -0
  219. package/src/config-schema.ts +235 -0
  220. package/src/constants.ts +11 -0
  221. package/src/dashboard.meta.ts +15 -0
  222. package/src/data.resource.ts +117 -0
  223. package/src/declarations.d.ts +4 -0
  224. package/src/index.ts +204 -0
  225. package/src/loader/loader.component.tsx +11 -0
  226. package/src/loader/loader.scss +9 -0
  227. package/src/mark-patient-alive/mark-patient-alive.modal.tsx +54 -0
  228. package/src/mark-patient-deceased/mark-patient-deceased-form.scss +175 -0
  229. package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +203 -0
  230. package/src/mark-patient-deceased/mark-patient-deceased-form.workspace.tsx +295 -0
  231. package/src/offline.ts +41 -0
  232. package/src/patient-banner-tags/visit-attribute-tags.extension.tsx +62 -0
  233. package/src/patient-banner-tags/visit-attribute-tags.scss +8 -0
  234. package/src/patient-chart/chart-review/chart-review.component.tsx +138 -0
  235. package/src/patient-chart/chart-review/chart-review.test.tsx +77 -0
  236. package/src/patient-chart/chart-review/dashboard-view.component.tsx +85 -0
  237. package/src/patient-chart/chart-review/dashboard-view.scss +84 -0
  238. package/src/patient-chart/patient-chart.component.tsx +71 -0
  239. package/src/patient-chart/patient-chart.resources.test.ts +238 -0
  240. package/src/patient-chart/patient-chart.resources.ts +231 -0
  241. package/src/patient-chart/patient-chart.scss +65 -0
  242. package/src/patient-details-tile/patient-details-tile.component.tsx +25 -0
  243. package/src/patient-details-tile/patient-details-tile.scss +24 -0
  244. package/src/root.component.tsx +35 -0
  245. package/src/root.scss +54 -0
  246. package/src/routes.json +267 -0
  247. package/src/side-nav/side-menu.component.tsx +10 -0
  248. package/src/side-nav/side-menu.scss +38 -0
  249. package/src/side-nav/side-menu.test.tsx +27 -0
  250. package/src/utils.test.ts +17 -0
  251. package/src/utils.ts +5 -0
  252. package/src/visit/hooks/useDefaultFacilityLocation.tsx +15 -0
  253. package/src/visit/hooks/useDefaultVisitLocation.tsx +24 -0
  254. package/src/visit/hooks/useDeleteVisit.test.tsx +267 -0
  255. package/src/visit/hooks/useDeleteVisit.tsx +103 -0
  256. package/src/visit/hooks/useOfflineVisitType.tsx +18 -0
  257. package/src/visit/hooks/useRecommendedVisitTypes.tsx +34 -0
  258. package/src/visit/hooks/useVisitAttributeType.tsx +102 -0
  259. package/src/visit/start-visit-button.component.tsx +47 -0
  260. package/src/visit/start-visit-button.test.tsx +32 -0
  261. package/src/visit/visit-action-items/delete-visit-action-item.component.tsx +60 -0
  262. package/src/visit/visit-action-items/delete-visit-action-item.test.tsx +48 -0
  263. package/src/visit/visit-action-items/edit-visit-details.component.tsx +79 -0
  264. package/src/visit/visit-form/base-visit-type.component.tsx +121 -0
  265. package/src/visit/visit-form/base-visit-type.scss +75 -0
  266. package/src/visit/visit-form/base-visit-type.test.tsx +153 -0
  267. package/src/visit/visit-form/exported-visit-form.workspace.tsx +755 -0
  268. package/src/visit/visit-form/location-selector.component.tsx +86 -0
  269. package/src/visit/visit-form/location-selector.test.tsx +146 -0
  270. package/src/visit/visit-form/recommended-visit-type.component.tsx +32 -0
  271. package/src/visit/visit-form/visit-attribute-type.component.tsx +258 -0
  272. package/src/visit/visit-form/visit-attribute-type.scss +5 -0
  273. package/src/visit/visit-form/visit-date-time.component.tsx +206 -0
  274. package/src/visit/visit-form/visit-form.resource.ts +401 -0
  275. package/src/visit/visit-form/visit-form.scss +167 -0
  276. package/src/visit/visit-form/visit-form.test.tsx +1233 -0
  277. package/src/visit/visit-form/visit-form.workspace.tsx +61 -0
  278. package/src/visit/visit-form/visit-type.test.tsx +88 -0
  279. package/src/visit/visit-history-table/visit-actions-cell.component.tsx +20 -0
  280. package/src/visit/visit-history-table/visit-actions-cell.scss +4 -0
  281. package/src/visit/visit-history-table/visit-date-cell.component.tsx +19 -0
  282. package/src/visit/visit-history-table/visit-diagnoses-cell.component.tsx +18 -0
  283. package/src/visit/visit-history-table/visit-history-table.component.tsx +145 -0
  284. package/src/visit/visit-history-table/visit-history-table.scss +25 -0
  285. package/src/visit/visit-history-table/visit-type-cell.component.tsx +15 -0
  286. package/src/visit/visit-prompt/delete-visit-dialog.modal.tsx +46 -0
  287. package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +79 -0
  288. package/src/visit/visit-prompt/end-visit-dialog.modal.tsx +82 -0
  289. package/src/visit/visit-prompt/end-visit-dialog.scss +7 -0
  290. package/src/visit/visit-prompt/end-visit-dialog.test.tsx +131 -0
  291. package/src/visit/visit-prompt/modify-visit-date.modal.tsx +40 -0
  292. package/src/visit/visit-prompt/start-visit-dialog.modal.tsx +64 -0
  293. package/src/visit/visit-prompt/start-visit-dialog.scss +10 -0
  294. package/src/visit/visit-prompt/start-visit-dialog.test.tsx +40 -0
  295. package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.scss +7 -0
  296. package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.tsx +178 -0
  297. package/src/visit/visits-widget/current-visit-summary.extension.tsx +48 -0
  298. package/src/visit/visits-widget/current-visit-summary.scss +10 -0
  299. package/src/visit/visits-widget/current-visit-summary.test.tsx +85 -0
  300. package/src/visit/visits-widget/encounter-observations/encounter-observations.component.tsx +67 -0
  301. package/src/visit/visits-widget/encounter-observations/index.ts +3 -0
  302. package/src/visit/visits-widget/encounter-observations/styles.scss +22 -0
  303. package/src/visit/visits-widget/past-visits-components/delete-encounter.modal.tsx +47 -0
  304. package/src/visit/visits-widget/past-visits-components/delete-encounter.scss +9 -0
  305. package/src/visit/visits-widget/past-visits-components/encounters-table/all-encounters-table.component.tsx +49 -0
  306. package/src/visit/visits-widget/past-visits-components/encounters-table/completed-forms-table.component.tsx +67 -0
  307. package/src/visit/visits-widget/past-visits-components/encounters-table/completed-forms-table.test.tsx +146 -0
  308. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +452 -0
  309. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.test.ts +156 -0
  310. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +215 -0
  311. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.scss +113 -0
  312. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.test.tsx +432 -0
  313. package/src/visit/visits-widget/past-visits-components/encounters-table/visit-completed-forms-table.component.tsx +61 -0
  314. package/src/visit/visits-widget/past-visits-components/encounters-table/visit-completed-forms-table.test.tsx +125 -0
  315. package/src/visit/visits-widget/past-visits-components/encounters-table/visit-encounters-table.component.tsx +47 -0
  316. package/src/visit/visits-widget/past-visits-components/medications-summary.component.tsx +163 -0
  317. package/src/visit/visits-widget/past-visits-components/notes-summary.component.tsx +66 -0
  318. package/src/visit/visits-widget/past-visits-components/patient-notes-summary.component.tsx +318 -0
  319. package/src/visit/visits-widget/past-visits-components/tests-summary.component.tsx +16 -0
  320. package/src/visit/visits-widget/past-visits-components/visit-summary.component.tsx +192 -0
  321. package/src/visit/visits-widget/past-visits-components/visit-summary.scss +72 -0
  322. package/src/visit/visits-widget/past-visits-components/visit-summary.test.tsx +105 -0
  323. package/src/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.component.tsx +94 -0
  324. package/src/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.scss +60 -0
  325. package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/restrospective-date-time-picker.scss +35 -0
  326. package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +140 -0
  327. package/src/visit/visits-widget/visit-context/visit-context-header.extension.tsx +61 -0
  328. package/src/visit/visits-widget/visit-context/visit-context-header.scss +45 -0
  329. package/src/visit/visits-widget/visit-context/visit-context-header.test.tsx +59 -0
  330. package/src/visit/visits-widget/visit-context/visit-context-info.component.tsx +37 -0
  331. package/src/visit/visits-widget/visit-context/visit-context-info.scss +12 -0
  332. package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +166 -0
  333. package/src/visit/visits-widget/visit-context/visit-context-switcher.scss +83 -0
  334. package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +79 -0
  335. package/src/visit/visits-widget/visit-detail-overview.component.tsx +67 -0
  336. package/src/visit/visits-widget/visit-detail-overview.scss +301 -0
  337. package/src/visit/visits-widget/visit-detail-overview.test.tsx +205 -0
  338. package/src/visit/visits-widget/visit.resource.tsx +146 -0
  339. package/translations/am.json +209 -0
  340. package/translations/ar.json +209 -0
  341. package/translations/ar_SY.json +209 -0
  342. package/translations/bn.json +209 -0
  343. package/translations/cs.json +209 -0
  344. package/translations/de.json +209 -0
  345. package/translations/en.json +209 -0
  346. package/translations/en_US.json +209 -0
  347. package/translations/es.json +209 -0
  348. package/translations/es_MX.json +209 -0
  349. package/translations/fr.json +209 -0
  350. package/translations/he.json +209 -0
  351. package/translations/hi.json +209 -0
  352. package/translations/hi_IN.json +209 -0
  353. package/translations/id.json +209 -0
  354. package/translations/it.json +209 -0
  355. package/translations/ka.json +209 -0
  356. package/translations/km.json +209 -0
  357. package/translations/ku.json +209 -0
  358. package/translations/ky.json +209 -0
  359. package/translations/lg.json +209 -0
  360. package/translations/ne.json +209 -0
  361. package/translations/pl.json +209 -0
  362. package/translations/pt.json +209 -0
  363. package/translations/pt_BR.json +209 -0
  364. package/translations/qu.json +209 -0
  365. package/translations/ro_RO.json +209 -0
  366. package/translations/ru_RU.json +209 -0
  367. package/translations/si.json +209 -0
  368. package/translations/sq.json +209 -0
  369. package/translations/sw.json +209 -0
  370. package/translations/sw_KE.json +209 -0
  371. package/translations/tr.json +209 -0
  372. package/translations/tr_TR.json +209 -0
  373. package/translations/uk.json +209 -0
  374. package/translations/uz.json +209 -0
  375. package/translations/uz@Latn.json +209 -0
  376. package/translations/uz_UZ.json +209 -0
  377. package/translations/vi.json +209 -0
  378. package/translations/zh.json +209 -0
  379. package/translations/zh_CN.json +209 -0
  380. package/translations/zh_TW.json +209 -0
  381. package/tsconfig.json +4 -0
  382. package/vitest.config.ts +4 -0
@@ -0,0 +1,295 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import fuzzy from 'fuzzy';
4
+ import { useTranslation } from 'react-i18next';
5
+ import {
6
+ Button,
7
+ ButtonSet,
8
+ DatePickerSkeleton,
9
+ Form,
10
+ InlineLoading,
11
+ RadioButton,
12
+ RadioButtonGroup,
13
+ Row,
14
+ Search,
15
+ StructuredListSkeleton,
16
+ TextInput,
17
+ Tile,
18
+ } from '@carbon/react';
19
+ import { Controller, useForm, type SubmitHandler } from 'react-hook-form';
20
+ import { z } from 'zod';
21
+ import { zodResolver } from '@hookform/resolvers/zod';
22
+ import { WarningFilled } from '@carbon/react/icons';
23
+ import {
24
+ type PatientWorkspace2DefinitionProps,
25
+ EmptyState,
26
+ invalidateCurrentVisit,
27
+ invalidateVisitAndEncounterData,
28
+ } from '@openmrs/esm-patient-common-lib';
29
+ import {
30
+ ExtensionSlot,
31
+ useLayoutType,
32
+ showSnackbar,
33
+ ResponsiveWrapper,
34
+ useConfig,
35
+ OpenmrsDatePicker,
36
+ Workspace2,
37
+ } from '@openmrs/esm-framework';
38
+ import { useSWRConfig } from 'swr';
39
+ import { markPatientDeceased, useCausesOfDeath } from '../data.resource';
40
+ import { type ChartConfig } from '../config-schema';
41
+ import styles from './mark-patient-deceased-form.scss';
42
+
43
+ const MarkPatientDeceasedForm: React.FC<PatientWorkspace2DefinitionProps<{}, {}>> = ({
44
+ closeWorkspace,
45
+ groupProps: { patientUuid, patient },
46
+ }) => {
47
+ const { t } = useTranslation();
48
+ const isTablet = useLayoutType() === 'tablet';
49
+ const memoizedState = useMemo(() => ({ patientUuid, patient }), [patientUuid, patient]);
50
+ const [searchTerm, setSearchTerm] = useState('');
51
+ const { causesOfDeath, isLoading: isLoadingCausesOfDeath } = useCausesOfDeath();
52
+ const { freeTextFieldConceptUuid } = useConfig<ChartConfig>();
53
+ const { mutate: globalMutate } = useSWRConfig();
54
+ const patientBirthDate = useMemo(() => (patient?.birthDate ? new Date(patient.birthDate) : undefined), [patient]);
55
+
56
+ const filteredCausesOfDeath = useMemo(() => {
57
+ if (!searchTerm) {
58
+ return causesOfDeath;
59
+ }
60
+ return searchTerm
61
+ ? fuzzy
62
+ .filter(searchTerm, causesOfDeath, {
63
+ extract: (causeOfDeathConcept) => causeOfDeathConcept.display,
64
+ })
65
+ .sort((r1, r2) => r1.score - r2.score)
66
+ .map((result) => result.original)
67
+ : causesOfDeath;
68
+ }, [searchTerm, causesOfDeath]);
69
+
70
+ const handleSearchTermChange = (event) => {
71
+ setSearchTerm(event.target.value);
72
+ };
73
+
74
+ const schema = z
75
+ .object({
76
+ causeOfDeath: z.string().refine((causeOfDeath) => !!causeOfDeath, {
77
+ message: t('causeOfDeathIsRequired', 'Please select the cause of death'),
78
+ }),
79
+ deathDate: z
80
+ .date()
81
+ .refine((date) => !!date, {
82
+ message: t('deathDateRequired', 'Please select the date of death'),
83
+ })
84
+ .refine((date) => !patientBirthDate || date >= patientBirthDate, {
85
+ message: t('deathDateBeforeBirthDate', 'Death date cannot be before the date of birth'),
86
+ }),
87
+ nonCodedCauseOfDeath: z.string().optional(),
88
+ })
89
+ .refine((data) => !(data.causeOfDeath === freeTextFieldConceptUuid && !data.nonCodedCauseOfDeath), {
90
+ message: t('nonCodedCauseOfDeathRequired', 'Please enter the non-coded cause of death'),
91
+ path: ['nonCodedCauseOfDeath'],
92
+ });
93
+
94
+ type MarkPatientDeceasedFormSchema = z.infer<typeof schema>;
95
+
96
+ const {
97
+ control,
98
+ formState: { errors, isSubmitting, isDirty },
99
+ handleSubmit,
100
+ watch,
101
+ } = useForm<MarkPatientDeceasedFormSchema>({
102
+ mode: 'onSubmit',
103
+ resolver: zodResolver(schema),
104
+ defaultValues: {
105
+ causeOfDeath: '',
106
+ deathDate: new Date(),
107
+ nonCodedCauseOfDeath: '',
108
+ },
109
+ });
110
+
111
+ const causeOfDeathValue = watch('causeOfDeath');
112
+
113
+ const onSubmit: SubmitHandler<MarkPatientDeceasedFormSchema> = useCallback(
114
+ (data) => {
115
+ const { causeOfDeath, deathDate, nonCodedCauseOfDeath } = data;
116
+
117
+ return markPatientDeceased(deathDate, patientUuid, causeOfDeath, nonCodedCauseOfDeath)
118
+ .then(() => {
119
+ globalMutate((key) => Array.isArray(key) && key[0] === 'patient' && key[1] === patientUuid);
120
+ invalidateCurrentVisit(globalMutate, patientUuid);
121
+ invalidateVisitAndEncounterData(globalMutate, patientUuid);
122
+
123
+ showSnackbar({
124
+ title: t('markDeceasedSuccessfully', 'Patient marked deceased successfully'),
125
+ });
126
+
127
+ closeWorkspace({ discardUnsavedChanges: true });
128
+ })
129
+ .catch((error) => {
130
+ showSnackbar({
131
+ kind: 'error',
132
+ isLowContrast: false,
133
+ subtitle: error?.message,
134
+ title: t('errorMarkingPatientDeceased', 'Error marking patient deceased'),
135
+ });
136
+ });
137
+ },
138
+ [closeWorkspace, globalMutate, patientUuid, t],
139
+ );
140
+
141
+ const onError = (errors) => console.error(errors);
142
+
143
+ return (
144
+ <Workspace2 title={t('markPatientDeceased', 'Mark patient deceased')} hasUnsavedChanges={isDirty}>
145
+ <Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
146
+ <div>
147
+ {isTablet && (
148
+ <Row className={styles.headerGridRow}>
149
+ <ExtensionSlot className={styles.dataGridRow} name="visit-form-header-slot" state={memoizedState} />
150
+ </Row>
151
+ )}
152
+ <div className={styles.container}>
153
+ <span className={styles.warningContainer}>
154
+ <WarningFilled aria-label={t('warning', 'Warning')} className={styles.warningIcon} size={20} />
155
+ <span className={styles.warningText}>
156
+ {t('markDeceasedWarning', "Marking the patient as deceased updates this patient's death information")}
157
+ </span>
158
+ </span>
159
+ <section>
160
+ <div className={styles.sectionTitle}>{t('dateOfDeath', 'Date of death')}</div>
161
+ {causesOfDeath?.length ? (
162
+ <ResponsiveWrapper>
163
+ <Controller
164
+ name="deathDate"
165
+ control={control}
166
+ render={({ field, fieldState }) => (
167
+ <OpenmrsDatePicker
168
+ {...field}
169
+ className={styles.datePicker}
170
+ id="deceasedDate"
171
+ data-testid="deceasedDate"
172
+ labelText={t('date', 'Date')}
173
+ minDate={patientBirthDate}
174
+ maxDate={new Date()}
175
+ invalid={Boolean(fieldState?.error?.message)}
176
+ invalidText={fieldState?.error?.message}
177
+ />
178
+ )}
179
+ />
180
+ </ResponsiveWrapper>
181
+ ) : (
182
+ <DatePickerSkeleton />
183
+ )}
184
+ </section>
185
+ <section>
186
+ <div className={styles.sectionTitle}>{t('causeOfDeath', 'Cause of death')}</div>
187
+ <div
188
+ className={classNames(styles.conceptAnswerOverviewWrapper, {
189
+ [styles.conceptAnswerOverviewWrapperTablet]: isTablet,
190
+ [styles.conceptAnswerOverviewWrapperDesktop]: !isTablet,
191
+ [styles.errorOutline]: errors?.causeOfDeath?.message,
192
+ })}
193
+ >
194
+ {isLoadingCausesOfDeath ? <StructuredListSkeleton /> : null}
195
+
196
+ {causesOfDeath?.length ? (
197
+ <ResponsiveWrapper>
198
+ <Search
199
+ labelText={t('searchForCauseOfDeath', 'Search for a cause of death')}
200
+ onChange={handleSearchTermChange}
201
+ placeholder={t('searchForCauseOfDeath', 'Search for a cause of death')}
202
+ />
203
+ </ResponsiveWrapper>
204
+ ) : null}
205
+
206
+ {causesOfDeath?.length && filteredCausesOfDeath.length > 0 ? (
207
+ <Controller
208
+ name="causeOfDeath"
209
+ control={control}
210
+ render={({ field: { onChange } }) => (
211
+ <RadioButtonGroup
212
+ className={styles.radioButtonGroup}
213
+ name={
214
+ causeOfDeathValue === freeTextFieldConceptUuid
215
+ ? 'freeTextFieldCauseOfDeath'
216
+ : 'codedCauseOfDeath'
217
+ }
218
+ orientation="vertical"
219
+ onChange={onChange}
220
+ >
221
+ {filteredCausesOfDeath.map(({ uuid, display, name }) => (
222
+ <RadioButton
223
+ className={styles.radioButton}
224
+ id={name}
225
+ key={uuid}
226
+ labelText={display}
227
+ value={uuid}
228
+ />
229
+ ))}
230
+ </RadioButtonGroup>
231
+ )}
232
+ />
233
+ ) : null}
234
+
235
+ {searchTerm && filteredCausesOfDeath.length === 0 && (
236
+ <div className={styles.tileContainer}>
237
+ <Tile className={styles.tile}>
238
+ <div className={styles.tileContent}>
239
+ <p className={styles.content}>
240
+ {t('noMatchingCodedCausesOfDeath', 'No matching coded causes of death')}
241
+ </p>
242
+ <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
243
+ </div>
244
+ </Tile>
245
+ </div>
246
+ )}
247
+
248
+ {!isLoadingCausesOfDeath && !causesOfDeath?.length ? (
249
+ <EmptyState
250
+ displayText={t('causeOfDeath_lower', 'cause of death concepts configured in the system')}
251
+ headerTitle={t('causeOfDeath', 'Cause of death')}
252
+ />
253
+ ) : null}
254
+ </div>
255
+ {errors?.causeOfDeath && <p className={styles.errorMessage}>{errors?.causeOfDeath?.message}</p>}
256
+ </section>
257
+ </div>
258
+ {causeOfDeathValue === freeTextFieldConceptUuid && (
259
+ <div className={styles.nonCodedCauseOfDeath}>
260
+ <Controller
261
+ name="nonCodedCauseOfDeath"
262
+ control={control}
263
+ render={({ field: { onChange, value } }) => (
264
+ <TextInput
265
+ id="freeTextCauseOfDeath"
266
+ invalid={!!errors?.nonCodedCauseOfDeath}
267
+ invalidText={errors?.nonCodedCauseOfDeath?.message}
268
+ labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
269
+ onChange={onChange}
270
+ placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
271
+ value={value}
272
+ />
273
+ )}
274
+ />
275
+ </div>
276
+ )}
277
+ </div>
278
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
279
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
280
+ {t('discard', 'Discard')}
281
+ </Button>
282
+ <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
283
+ {isSubmitting ? (
284
+ <InlineLoading description={t('saving', 'Saving') + '...'} role="progressbar" />
285
+ ) : (
286
+ t('saveAndClose', 'Save and close')
287
+ )}
288
+ </Button>
289
+ </ButtonSet>
290
+ </Form>
291
+ </Workspace2>
292
+ );
293
+ };
294
+
295
+ export default MarkPatientDeceasedForm;
package/src/offline.ts ADDED
@@ -0,0 +1,41 @@
1
+ import {
2
+ fhirBaseUrl,
3
+ messageOmrsServiceWorker,
4
+ restBaseUrl,
5
+ saveVisit,
6
+ setupOfflineSync,
7
+ } from '@openmrs/esm-framework';
8
+ import { type OfflineVisit, visitSyncType } from '@openmrs/esm-patient-common-lib';
9
+
10
+ export function setupCacheableRoutes() {
11
+ messageOmrsServiceWorker({
12
+ type: 'registerDynamicRoute',
13
+ pattern: `.+${fhirBaseUrl}/R4/Patient/.+`,
14
+ });
15
+
16
+ messageOmrsServiceWorker({
17
+ type: 'registerDynamicRoute',
18
+ pattern: `.+${restBaseUrl}/visit.+`,
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Sets up the offline synchronization for offline visits.
24
+ */
25
+ export function setupOfflineVisitsSync() {
26
+ setupOfflineSync<OfflineVisit>(visitSyncType, ['patient-registration'], async (visit, options) => {
27
+ const visitPayload = {
28
+ ...visit,
29
+ stopDatetime: new Date(),
30
+ };
31
+
32
+ const res = await saveVisit(visitPayload, options.abort);
33
+ if (!res.ok) {
34
+ throw new Error(
35
+ `Failed to synchronize offline visit with the UUID: ${visit.uuid}. Error: ${JSON.stringify(res.data)}`,
36
+ );
37
+ }
38
+
39
+ return res.data;
40
+ });
41
+ }
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { Tag } from '@carbon/react';
3
+ import { formatDate, useConfig, useVisit } from '@openmrs/esm-framework';
4
+ import { type ChartConfig } from '../config-schema';
5
+ import styles from './visit-attribute-tags.scss';
6
+
7
+ interface VisitAttributeTagsProps {
8
+ patientUuid: string;
9
+ }
10
+
11
+ const getAttributeValue = (attributeType, value) => {
12
+ switch (attributeType?.datatypeClassname) {
13
+ case 'org.openmrs.customdatatype.datatype.ConceptDatatype':
14
+ return value?.display;
15
+ case 'org.openmrs.customdatatype.datatype.FloatDatatype':
16
+ case 'org.openmrs.customdatatype.datatype.FreeTextDatatype':
17
+ case 'org.openmrs.customdatatype.datatype.LongFreeTextDatatype':
18
+ case 'org.openmrs.customdatatype.datatype.BooleanDatatype':
19
+ return value;
20
+ case 'org.openmrs.customdatatype.datatype.DateDatatype':
21
+ return formatDate(new Date(value), {
22
+ mode: 'wide',
23
+ });
24
+ default:
25
+ return value;
26
+ }
27
+ };
28
+
29
+ /**
30
+ * This extension slots to the patient-banner-tags-slot by default.
31
+ */
32
+ const VisitAttributeTags: React.FC<VisitAttributeTagsProps> = ({ patientUuid }) => {
33
+ const { activeVisit } = useVisit(patientUuid);
34
+ const { visitAttributeTypes } = useConfig<ChartConfig>();
35
+
36
+ const displayableAttributes = activeVisit?.attributes
37
+ ?.filter(
38
+ (attribute) =>
39
+ visitAttributeTypes?.find(({ uuid }) => attribute?.attributeType?.uuid === uuid)?.displayInThePatientBanner,
40
+ )
41
+ .map((attribute) => ({
42
+ attribute,
43
+ value: getAttributeValue(attribute?.attributeType, attribute?.value),
44
+ }))
45
+ .filter(({ value }) => value != null && value !== '');
46
+
47
+ if (!displayableAttributes?.length) {
48
+ return null;
49
+ }
50
+
51
+ return (
52
+ <div className={styles.tagsContainer}>
53
+ {displayableAttributes.map(({ attribute, value }) => (
54
+ <Tag key={attribute?.attributeType?.uuid} type="gray">
55
+ {value}
56
+ </Tag>
57
+ ))}
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default VisitAttributeTags;
@@ -0,0 +1,8 @@
1
+ @use '@carbon/layout';
2
+
3
+ .tagsContainer {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ gap: layout.$spacing-02;
7
+ margin: 0 layout.$spacing-02;
8
+ }
@@ -0,0 +1,138 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { Navigate } from 'react-router-dom';
3
+ import { type ConfigObject, useExtensionStore, registerExtensionSlot } from '@openmrs/esm-framework';
4
+ import { DashboardView, type DashboardConfig, type LayoutMode } from './dashboard-view.component';
5
+ import { basePath } from '../../constants';
6
+
7
+ function makePath(target: DashboardConfig, params: Record<string, string> = {}) {
8
+ const parts = `${basePath}/${encodeURIComponent(target.path)}`.split('/');
9
+
10
+ Object.keys(params).forEach((key) => {
11
+ for (let i = 0; i < parts.length; i++) {
12
+ if (parts[i][0] === ':' && parts[i].indexOf(key) === 1) {
13
+ parts[i] = params[key];
14
+ }
15
+ }
16
+ });
17
+
18
+ return parts.join('/');
19
+ }
20
+
21
+ /**
22
+ * Maps old dashboard URL path segments to their new lowercase/hyphenated equivalents.
23
+ * This ensures that existing bookmarks, shared links, and external references
24
+ * continue to resolve to the correct dashboard after the path rename.
25
+ */
26
+ const legacyPathAliases: Record<string, string> = {
27
+ 'Patient Summary': 'patient-summary',
28
+ 'Vitals & Biometrics': 'vitals-and-biometrics',
29
+ 'Vitals %26 Biometrics': 'vitals-and-biometrics',
30
+ Allergies: 'allergies',
31
+ Appointments: 'appointments',
32
+ Attachments: 'attachments',
33
+ 'Billing history': 'billing-history',
34
+ Conditions: 'conditions',
35
+ Encounters: 'encounters',
36
+ Immunizations: 'immunizations',
37
+ Medications: 'medications',
38
+ Orders: 'orders',
39
+ Programs: 'programs',
40
+ Results: 'results',
41
+ Visits: 'visits',
42
+ };
43
+
44
+ function resolveView(view: string): string {
45
+ return legacyPathAliases[view] ?? view;
46
+ }
47
+
48
+ function isDashboardConfig(obj: unknown): obj is DashboardConfig {
49
+ return (
50
+ typeof obj === 'object' &&
51
+ obj !== null &&
52
+ 'path' in obj &&
53
+ typeof obj.path === 'string' &&
54
+ 'slot' in obj &&
55
+ typeof obj.slot === 'string'
56
+ );
57
+ }
58
+
59
+ const seenMessages = new Set<string>();
60
+
61
+ function getDashboardDefinition(meta: object, config: ConfigObject, moduleName: string, name: string) {
62
+ const mergedDefinition = { ...meta, ...config, moduleName };
63
+ if (isDashboardConfig(mergedDefinition)) {
64
+ return mergedDefinition;
65
+ } else {
66
+ const msg = `Could not find a valid dashboard definition for the dashboard ${name} located in ${moduleName}`;
67
+ if (!seenMessages.has(msg)) {
68
+ console.error(msg);
69
+ seenMessages.add(msg);
70
+ }
71
+ return null;
72
+ }
73
+ }
74
+
75
+ interface ChartReviewProps {
76
+ patientUuid: string;
77
+ patient: fhir.Patient;
78
+ view: string;
79
+ setDashboardLayoutMode?: (layoutMode: LayoutMode) => void;
80
+ }
81
+
82
+ const ChartReview: React.FC<ChartReviewProps> = ({ patientUuid, patient, view, setDashboardLayoutMode }) => {
83
+ const extensionStore = useExtensionStore();
84
+
85
+ const dashboards = extensionStore.slots['patient-chart-dashboard-slot'].assignedExtensions
86
+ .flatMap((e) => {
87
+ if (e.config?.slotName) {
88
+ if (e.config.slotName in extensionStore.slots) {
89
+ return extensionStore.slots[e.config.slotName].assignedExtensions.map((e) =>
90
+ getDashboardDefinition(e.meta, e.config, e.moduleName, e.name),
91
+ );
92
+ } else {
93
+ registerExtensionSlot('@openmrs/esm-patient-chart-app', e.config.slotName);
94
+ // Since we register the new extension slot, we need to force a re-render with the
95
+ // update extensionStore. Throwing the promise will trigger suspense and since the
96
+ // promise immediately resolves, it should re-render on the next tic.
97
+ throw Promise.resolve();
98
+ }
99
+ }
100
+
101
+ return getDashboardDefinition(e.meta, e.config, e.moduleName, e.name);
102
+ })
103
+ .filter(Boolean);
104
+
105
+ const defaultDashboard = dashboards.filter((dashboard) => Boolean(dashboard.path))?.[0];
106
+ const resolvedView = resolveView(view);
107
+ const dashboard = useMemo(() => {
108
+ return dashboards.find((dashboard) => dashboard.path === resolvedView);
109
+ }, [dashboards, resolvedView]);
110
+
111
+ useEffect(() => {
112
+ const activeDashboard = dashboard ?? defaultDashboard;
113
+ if (setDashboardLayoutMode) {
114
+ setDashboardLayoutMode(activeDashboard.layoutMode ?? 'contained');
115
+ }
116
+ }, [dashboard, defaultDashboard, setDashboardLayoutMode]);
117
+
118
+ if (!('patient-chart-dashboard-slot' in extensionStore.slots)) {
119
+ return null;
120
+ }
121
+
122
+ if (!defaultDashboard) {
123
+ return null;
124
+ } else if (!dashboard) {
125
+ return (
126
+ <Navigate
127
+ to={makePath(defaultDashboard, {
128
+ patientUuid,
129
+ })}
130
+ replace
131
+ />
132
+ );
133
+ } else {
134
+ return <DashboardView dashboard={dashboard} patientUuid={patientUuid} patient={patient} />;
135
+ }
136
+ };
137
+
138
+ export default ChartReview;
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { vi, describe, expect, test } from 'vitest';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import { screen, render } from '@testing-library/react';
5
+ import {
6
+ type AssignedExtension,
7
+ type ExtensionSlotState,
8
+ useExtensionStore,
9
+ useExtensionSlotMeta,
10
+ } from '@openmrs/esm-framework';
11
+ import { mockPatient } from 'tools';
12
+ import ChartReview from './chart-review.component';
13
+
14
+ const mockUseExtensionStore = vi.mocked(useExtensionStore);
15
+ const mockUseExtensionSlotMeta = vi.mocked(useExtensionSlotMeta);
16
+
17
+ vi.mock('react-router-dom', async () => ({
18
+ ...((await vi.importActual('react-router-dom')) as object),
19
+ Redirect: vi.fn(),
20
+ useMatch: vi.fn().mockReturnValue({
21
+ params: {
22
+ url: '/patient/8673ee4f-e2ab-4077-ba55-4980f408773e/chart',
23
+ view: 'patient-summary',
24
+ },
25
+ }),
26
+ }));
27
+
28
+ function slotMetaFromStore(store, slotName) {
29
+ return Object.fromEntries(
30
+ store.slots[slotName].assignedExtensions.map((e) => {
31
+ return [e.name, e.meta];
32
+ }),
33
+ );
34
+ }
35
+
36
+ describe('ChartReview', () => {
37
+ test('renders a grid-based layout', () => {
38
+ const mockStore = {
39
+ slots: {
40
+ 'patient-chart-dashboard-slot': {
41
+ assignedExtensions: [
42
+ {
43
+ name: 'charts-summary-dashboard',
44
+ meta: {
45
+ slot: 'patient-chart-summary-dashboard-slot',
46
+ path: 'patient-summary',
47
+ title: 'Patient Summary',
48
+ },
49
+ },
50
+ {
51
+ name: 'test-results-summary-dashboard',
52
+ meta: {
53
+ slot: 'patient-chart-test-results-dashboard-slot',
54
+ path: 'Test Results',
55
+ title: 'Test Results',
56
+ },
57
+ },
58
+ ] as unknown as AssignedExtension[],
59
+ },
60
+ 'patient-chart-summary-dashboard-slot': {
61
+ assignedExtensions: [],
62
+ },
63
+ } as Record<string, ExtensionSlotState>,
64
+ };
65
+
66
+ mockUseExtensionStore.mockReturnValue(mockStore as unknown as ReturnType<typeof useExtensionStore>);
67
+ mockUseExtensionSlotMeta.mockImplementation((slotName) => slotMetaFromStore(mockStore, slotName));
68
+
69
+ render(
70
+ <BrowserRouter>
71
+ <ChartReview patient={mockPatient} patientUuid={mockPatient.id} view="patient-summary" />
72
+ </BrowserRouter>,
73
+ );
74
+
75
+ expect(screen.getByRole('heading')).toHaveTextContent(/Patient summary/i);
76
+ });
77
+ });