@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,39 @@
1
+ import React, { useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { showModal, useVisit } from '@openmrs/esm-framework';
4
+ import { OverflowMenuItem } from '@carbon/react';
5
+ import styles from './action-button.scss';
6
+
7
+ interface StopVisitOverflowMenuItemProps {
8
+ patientUuid: string;
9
+ closeMenu?: () => void;
10
+ }
11
+
12
+ /**
13
+ * This button shows up in the patient banner action menu, but only when the patient has an active visit.
14
+ * On click, it opens the modal in end-visit-dialog.component.tsx to END the visit
15
+ */
16
+ const StopVisitOverflowMenuItem: React.FC<StopVisitOverflowMenuItemProps> = ({ patientUuid, closeMenu }) => {
17
+ const { t } = useTranslation();
18
+ const { activeVisit } = useVisit(patientUuid);
19
+
20
+ const handleLaunchModal = useCallback(() => {
21
+ const dispose = showModal('end-visit-dialog', {
22
+ closeModal: () => dispose(),
23
+ patientUuid,
24
+ });
25
+ }, [patientUuid]);
26
+
27
+ return (
28
+ activeVisit && (
29
+ <OverflowMenuItem
30
+ className={styles.menuitem}
31
+ itemText={`${t('endActiveVisit', 'End active visit')}`}
32
+ onClick={handleLaunchModal}
33
+ closeMenu={closeMenu}
34
+ />
35
+ )
36
+ );
37
+ };
38
+
39
+ export default StopVisitOverflowMenuItem;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { vi, describe, it, expect } from 'vitest';
3
+ import StopVisitOverflowMenuItem from './stop-visit.component';
4
+ import { screen, render } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { showModal, useVisit } from '@openmrs/esm-framework';
7
+ import { mockCurrentVisit } from '__mocks__';
8
+ import { mockPatient } from 'tools';
9
+
10
+ const mockUseVisit = vi.mocked(useVisit);
11
+ const mockShowModal = vi.mocked(showModal);
12
+
13
+ describe('StopVisitOverflowMenuItem', () => {
14
+ it('should be able to stop active visit', async () => {
15
+ const user = userEvent.setup();
16
+
17
+ mockUseVisit.mockReturnValue({ activeVisit: mockCurrentVisit } as ReturnType<typeof useVisit>);
18
+
19
+ render(<StopVisitOverflowMenuItem patientUuid={mockPatient.id} />);
20
+
21
+ const endVisitButton = screen.getByRole('menuitem', { name: /End active visit/i });
22
+ expect(endVisitButton).toBeInTheDocument();
23
+
24
+ await user.click(endVisitButton);
25
+ expect(mockShowModal).toHaveBeenCalledTimes(1);
26
+ });
27
+ });
@@ -0,0 +1,78 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Tabs, Tab, TabList, TabPanels, TabPanel } from '@carbon/react';
4
+ import { useConfig } from '@openmrs/esm-framework';
5
+ import { EncounterList } from './encounter-list.component';
6
+ import { getMenuItemTabsConfiguration } from '../utils/encounter-list-config-builder';
7
+ import styles from './encounter-list-tabs.scss';
8
+ import { filter } from '../utils/helpers';
9
+ import { type Encounter } from '../types';
10
+ import { usePatientChartStore } from '@openmrs/esm-patient-common-lib/src';
11
+
12
+ interface EncounterListTabsComponentProps {
13
+ patientUuid: string;
14
+ patient: fhir.Patient;
15
+ }
16
+
17
+ /**
18
+ * This extension is not used in the refapp.
19
+ * This extension uses the patient chart store and SHOULD only be mounted within the patient chart
20
+ */
21
+ const EncounterListTabsExtension: React.FC<EncounterListTabsComponentProps> = ({ patientUuid, patient }) => {
22
+ const { t } = useTranslation();
23
+ const { visitContext } = usePatientChartStore(patientUuid);
24
+
25
+ const config = useConfig();
26
+ const { tabDefinitions = [] } = config;
27
+
28
+ const configConcepts = {
29
+ trueConceptUuid: config.trueConceptUuid,
30
+ falseConceptUuid: config.falseConceptUuid,
31
+ otherConceptUuid: config.otherConceptUuid,
32
+ };
33
+
34
+ const tabsConfig = getMenuItemTabsConfiguration(tabDefinitions, configConcepts, t);
35
+
36
+ const tabFilters = useMemo(() => {
37
+ return tabsConfig.reduce((result, tab) => {
38
+ if (tab.hasFilter) {
39
+ result[tab.name] = (encounter: Encounter) => filter(encounter, tab.formList?.[0]?.uuid);
40
+ }
41
+ return result;
42
+ }, {});
43
+ }, [tabsConfig]);
44
+
45
+ const isDead = patient.deceasedBoolean ?? Boolean(patient.deceasedDateTime);
46
+
47
+ return (
48
+ <div className={styles.tabContainer}>
49
+ <Tabs>
50
+ <TabList contained>
51
+ {tabsConfig.map((tab) => (
52
+ <Tab key={tab.name}>{t(tab.name)}</Tab>
53
+ ))}
54
+ </TabList>
55
+ <TabPanels>
56
+ {tabsConfig.map((tab) => (
57
+ <TabPanel key={tab.name}>
58
+ <EncounterList
59
+ filter={tabFilters[tab.name]}
60
+ patientUuid={patientUuid}
61
+ formList={tab.formList}
62
+ columns={tab.columns}
63
+ encounterType={tab.encounterType}
64
+ launchOptions={tab.launchOptions}
65
+ headerTitle={tab.headerTitle}
66
+ description={tab.description}
67
+ visit={visitContext}
68
+ deathStatus={isDead}
69
+ />
70
+ </TabPanel>
71
+ ))}
72
+ </TabPanels>
73
+ </Tabs>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default EncounterListTabsExtension;
@@ -0,0 +1,7 @@
1
+ .tabContainer div[role='tabpanel'] {
2
+ padding: 0 !important;
3
+ }
4
+
5
+ .tabContainer li button {
6
+ width: 100% !important;
7
+ }
@@ -0,0 +1,306 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import classNames from 'classnames';
4
+ import { Button, Link, OverflowMenu, OverflowMenuItem, DataTableSkeleton, Pagination } from '@carbon/react';
5
+ import {
6
+ AddIcon,
7
+ navigate,
8
+ NumericObservation,
9
+ showModal,
10
+ showSnackbar,
11
+ useConfig,
12
+ type Visit,
13
+ } from '@openmrs/esm-framework';
14
+ import { EmptyState } from '@openmrs/esm-patient-common-lib';
15
+ import { EncounterListDataTable } from './table.component';
16
+ import { type LaunchAction, launchEncounterForm } from '../utils/helpers';
17
+ import { deleteEncounter } from '../utils/encounter-list.resource';
18
+ import { useEncounterRows, useFormsJson } from '../hooks';
19
+ import type { TableRow, Encounter, FormattedColumn, Action } from '../types';
20
+ import type { ChartConfig } from '../../config-schema';
21
+ import styles from './encounter-list.scss';
22
+
23
+ export interface EncounterListProps {
24
+ patientUuid: string;
25
+ encounterType: string;
26
+ columns: Array<FormattedColumn>;
27
+ headerTitle: string;
28
+ description: string;
29
+ formList?: Array<{
30
+ name?: string;
31
+ uuid: string;
32
+ excludedIntents?: Array<string>;
33
+ fixedIntent?: string;
34
+ isDefault?: boolean;
35
+ }>;
36
+ launchOptions: {
37
+ hideFormLauncher?: boolean;
38
+ displayText?: string;
39
+ workspaceWindowSize?: 'minimized' | 'maximized';
40
+ };
41
+ filter?: (encounter: Encounter) => boolean;
42
+ afterFormSaveAction?: () => void;
43
+ deathStatus?: boolean;
44
+ visit: Visit;
45
+ }
46
+
47
+ export const EncounterList: React.FC<EncounterListProps> = ({
48
+ patientUuid,
49
+ encounterType,
50
+ columns,
51
+ headerTitle,
52
+ description,
53
+ formList,
54
+ filter,
55
+ launchOptions,
56
+ afterFormSaveAction,
57
+ visit,
58
+ deathStatus,
59
+ }) => {
60
+ const { t } = useTranslation();
61
+ const { requireActiveVisitForEncounterTile } = useConfig<Pick<ChartConfig, 'requireActiveVisitForEncounterTile'>>();
62
+
63
+ const [currentPage, setCurrentPage] = useState(1);
64
+ const [pageSize, setPageSize] = useState(10);
65
+
66
+ const { formsJson, isLoading: isLoadingFormsJson } = useFormsJson(formList?.[0]?.uuid);
67
+ const { encounters, total, isLoading, onFormSave, mutate } = useEncounterRows(
68
+ patientUuid,
69
+ encounterType,
70
+ filter,
71
+ afterFormSaveAction,
72
+ pageSize,
73
+ currentPage,
74
+ );
75
+
76
+ const { displayText, hideFormLauncher } = launchOptions;
77
+
78
+ const defaultActions = useMemo(
79
+ () =>
80
+ [
81
+ {
82
+ label: t('viewEncounter', 'View'),
83
+ form: {
84
+ name: formsJson?.name,
85
+ },
86
+ mode: 'view',
87
+ intent: '*',
88
+ },
89
+ {
90
+ label: t('editEncounter', 'Edit'),
91
+ form: {
92
+ name: formsJson?.name,
93
+ },
94
+ mode: 'edit',
95
+ intent: '*',
96
+ },
97
+ {
98
+ label: t('deleteEncounter', 'Delete'),
99
+ form: {
100
+ name: formsJson?.name,
101
+ },
102
+ mode: 'delete',
103
+ intent: '*',
104
+ },
105
+ ] as Array<Action>,
106
+ [formsJson, t],
107
+ );
108
+
109
+ const createLaunchFormAction = useCallback(
110
+ (encounter: Encounter, mode: LaunchAction) => () => {
111
+ launchEncounterForm(formsJson, mode, '*', requireActiveVisitForEncounterTile, visit, encounter.uuid);
112
+ },
113
+ [formsJson, visit, requireActiveVisitForEncounterTile],
114
+ );
115
+
116
+ const handleDeleteEncounter = useCallback(
117
+ (encounterUuid: string, encounterTypeName: string) => {
118
+ const close = showModal('delete-encounter-modal', {
119
+ close: () => close(),
120
+ encounterTypeName: encounterTypeName || '',
121
+ onConfirmation: () => {
122
+ const abortController = new AbortController();
123
+ deleteEncounter(encounterUuid, abortController)
124
+ .then(() => {
125
+ onFormSave();
126
+ mutate();
127
+ showSnackbar({
128
+ isLowContrast: true,
129
+ title: t('encounterDeleted', 'Encounter deleted'),
130
+ subtitle: t('encounterSuccessfullyDeleted', 'The encounter has been deleted successfully'),
131
+ kind: 'success',
132
+ });
133
+ })
134
+ .catch(() => {
135
+ showSnackbar({
136
+ isLowContrast: false,
137
+ title: t('error', 'Error'),
138
+ subtitle: t(
139
+ 'encounterWithError',
140
+ 'The encounter could not be deleted successfully. If the error persists, please contact your system administrator.',
141
+ ),
142
+ kind: 'error',
143
+ });
144
+ })
145
+ .finally(() => {
146
+ close();
147
+ });
148
+ },
149
+ });
150
+ },
151
+ [onFormSave, t, mutate],
152
+ );
153
+
154
+ const tableRows = useMemo(() => {
155
+ return encounters.map((encounter: Encounter) => {
156
+ const tableRow: TableRow = { id: encounter.uuid };
157
+
158
+ encounter['launchFormActions'] = {
159
+ editEncounter: createLaunchFormAction(encounter, 'edit'),
160
+ viewEncounter: createLaunchFormAction(encounter, 'view'),
161
+ };
162
+
163
+ columns.forEach((column) => {
164
+ let val = column?.getValue(encounter);
165
+ if (column.link) {
166
+ val = (
167
+ <Link
168
+ onClick={(e) => {
169
+ e.preventDefault();
170
+ if (column.link.handleNavigate) {
171
+ column.link.handleNavigate(encounter);
172
+ } else if (column.link?.getUrl) {
173
+ navigate({ to: column.link.getUrl(encounter) });
174
+ }
175
+ }}
176
+ >
177
+ {typeof val === 'string' ? val : ''}
178
+ </Link>
179
+ );
180
+ } else if (typeof val === 'number' && column.concept) {
181
+ val = (
182
+ <NumericObservation value={val} conceptUuid={column.concept} patientUuid={patientUuid} variant="cell" />
183
+ );
184
+ }
185
+ tableRow[column.key] = val;
186
+ });
187
+
188
+ const actions =
189
+ Array.isArray(tableRow.actions) && tableRow.actions.length > 0 ? tableRow.actions : defaultActions;
190
+
191
+ tableRow['actions'] = (
192
+ <OverflowMenu align="left" flipped className={styles.flippedOverflowMenu} data-testid="actions-id">
193
+ {actions.map((actionItem: Action, index: number) => {
194
+ const form = formsJson && actionItem?.form?.name ? formsJson.name === actionItem.form.name : null;
195
+
196
+ return (
197
+ form && (
198
+ <OverflowMenuItem
199
+ key={index}
200
+ index={index}
201
+ itemText={t(actionItem.label)}
202
+ onClick={(e) => {
203
+ e.preventDefault();
204
+ if (actionItem.mode === 'delete') {
205
+ handleDeleteEncounter(encounter.uuid, encounter.encounterType.name);
206
+ } else {
207
+ launchEncounterForm(
208
+ formsJson,
209
+ actionItem.mode,
210
+ actionItem.intent,
211
+ requireActiveVisitForEncounterTile,
212
+ visit,
213
+ encounter.uuid,
214
+ );
215
+ }
216
+ }}
217
+ />
218
+ )
219
+ );
220
+ })}
221
+ </OverflowMenu>
222
+ );
223
+
224
+ return tableRow;
225
+ });
226
+ }, [
227
+ encounters,
228
+ createLaunchFormAction,
229
+ columns,
230
+ defaultActions,
231
+ formsJson,
232
+ t,
233
+ handleDeleteEncounter,
234
+ visit,
235
+ requireActiveVisitForEncounterTile,
236
+ patientUuid,
237
+ ]);
238
+
239
+ const headers = useMemo(() => {
240
+ if (columns) {
241
+ return columns.map((column) => {
242
+ return { key: column.key, header: t(column.header) };
243
+ });
244
+ }
245
+ return [];
246
+ }, [columns, t]);
247
+
248
+ const formLauncher = useMemo(() => {
249
+ if (formsJson) {
250
+ return (
251
+ <Button
252
+ kind="ghost"
253
+ renderIcon={() => <AddIcon className={styles.headerIcon} />}
254
+ iconDescription="Add"
255
+ onClick={(e) => {
256
+ e.preventDefault();
257
+ launchEncounterForm(formsJson, 'add', '*', requireActiveVisitForEncounterTile, visit);
258
+ }}
259
+ >
260
+ {t(displayText)}
261
+ </Button>
262
+ );
263
+ }
264
+ return null;
265
+ }, [formsJson, displayText, t, visit, requireActiveVisitForEncounterTile]);
266
+
267
+ if (isLoading === true || isLoadingFormsJson === true) {
268
+ return <DataTableSkeleton rowCount={10} />;
269
+ }
270
+
271
+ return (
272
+ <>
273
+ {tableRows?.length > 0 || encounters.length > 0 ? (
274
+ <>
275
+ <div className={styles.widgetContainer}>
276
+ <div className={styles.widgetHeaderContainer}>
277
+ <h4 className={classNames(styles.productiveHeading03)}>{t(headerTitle)}</h4>
278
+ {!(hideFormLauncher ?? deathStatus) && <div className={styles.toggleButtons}>{formLauncher}</div>}
279
+ </div>
280
+ <EncounterListDataTable tableHeaders={headers} tableRows={tableRows} />
281
+ <Pagination
282
+ page={currentPage}
283
+ pageSizes={[10, 20, 30, 40, 50]}
284
+ onChange={({ page, pageSize }) => {
285
+ setCurrentPage(page);
286
+ setPageSize(pageSize);
287
+ }}
288
+ pageSize={pageSize}
289
+ totalItems={total}
290
+ />
291
+ </div>
292
+ </>
293
+ ) : (
294
+ <EmptyState
295
+ displayText={t(description)}
296
+ headerTitle={t(headerTitle)}
297
+ launchForm={
298
+ hideFormLauncher || deathStatus
299
+ ? null
300
+ : () => launchEncounterForm(formsJson, 'add', '*', requireActiveVisitForEncounterTile, visit)
301
+ }
302
+ />
303
+ )}
304
+ </>
305
+ );
306
+ };
@@ -0,0 +1,36 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .widgetContainer {
6
+ background-color: $ui-background;
7
+ border: 1px solid #e0e0e0;
8
+ margin-bottom: layout.$spacing-05;
9
+ }
10
+
11
+ .widgetHeaderContainer {
12
+ display: flex;
13
+ justify-content: space-between;
14
+ align-items: center;
15
+ padding: layout.$spacing-04 0 layout.$spacing-04 layout.$spacing-05;
16
+ }
17
+
18
+ .widgetHeaderContainer > h4:after {
19
+ content: '';
20
+ display: block;
21
+ width: 2rem;
22
+ padding-top: 0.188rem;
23
+ border-bottom: 0.375rem solid var(--brand-03);
24
+ }
25
+
26
+ .headerIcon {
27
+ fill: var(--cds-link-primary, #0f62fe) !important;
28
+ }
29
+
30
+ .widgetContainer :global(.cds--data-table) thead th button span {
31
+ height: unset !important;
32
+ }
33
+
34
+ .productiveHeading03 {
35
+ @include type.type-style('heading-03');
36
+ }
@@ -0,0 +1,63 @@
1
+ import React, { isValidElement } from 'react';
2
+ import {
3
+ DataTable,
4
+ Table,
5
+ TableCell,
6
+ TableContainer,
7
+ TableBody,
8
+ TableHead,
9
+ TableHeader,
10
+ TableRow,
11
+ } from '@carbon/react';
12
+ import { NumericObservation } from '@openmrs/esm-framework';
13
+ import { type TableHeaderType, type TableRow as TableRowType } from '../types';
14
+ import styles from './table.scss';
15
+
16
+ interface EncounterListDataTableProps {
17
+ tableHeaders: Array<TableHeaderType>;
18
+ tableRows: Array<TableRowType>;
19
+ }
20
+
21
+ export const EncounterListDataTable: React.FC<EncounterListDataTableProps> = ({ tableHeaders, tableRows }) => {
22
+ return (
23
+ <TableContainer>
24
+ <DataTable rows={tableRows} headers={tableHeaders} isSortable={true} size="md">
25
+ {({ rows, headers, getHeaderProps, getTableProps }) => (
26
+ <Table {...getTableProps()}>
27
+ <TableHead>
28
+ <TableRow>
29
+ {headers.map((header, index) => (
30
+ <TableHeader
31
+ key={index}
32
+ className={`${styles.productiveHeading01} ${styles.text02}`}
33
+ {...getHeaderProps({
34
+ header,
35
+ })}
36
+ >
37
+ {header.header}
38
+ </TableHeader>
39
+ ))}
40
+ </TableRow>
41
+ </TableHead>
42
+ <TableBody>
43
+ {rows.map((row) => (
44
+ <TableRow key={row.id}>
45
+ {row.cells.map((cell) => {
46
+ const cellContent = cell.value?.content ?? cell.value;
47
+ const isNumericObs =
48
+ isValidElement(cellContent) && cellContent.type === NumericObservation;
49
+ return (
50
+ <TableCell key={cell.id} className={isNumericObs ? styles.numericObsCell : undefined}>
51
+ {cellContent}
52
+ </TableCell>
53
+ );
54
+ })}
55
+ </TableRow>
56
+ ))}
57
+ </TableBody>
58
+ </Table>
59
+ )}
60
+ </DataTable>
61
+ </TableContainer>
62
+ );
63
+ };
@@ -0,0 +1,11 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .productiveHeading01 {
6
+ @include type.type-style('heading-compact-01');
7
+ }
8
+
9
+ .numericObsCell {
10
+ padding: 0 !important;
11
+ }