@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,61 @@
1
+ import React from 'react';
2
+ import { useSWRConfig } from 'swr';
3
+ import { useVisit, type Visit } from '@openmrs/esm-framework';
4
+ import {
5
+ invalidateVisitByUuid,
6
+ type PatientWorkspace2DefinitionProps,
7
+ usePatientChartStore,
8
+ } from '@openmrs/esm-patient-common-lib';
9
+ import ExportedVisitForm from './exported-visit-form.workspace';
10
+
11
+ export interface VisitFormProps {
12
+ /**
13
+ * A unique string identifying where the visit form is opened from.
14
+ * This string is passed into various extensions within the form to
15
+ * affect how / if they should be rendered.
16
+ */
17
+ openedFrom: string;
18
+ showPatientHeader?: boolean;
19
+ }
20
+
21
+ /**
22
+ * This form is used for starting a new visit and for editing
23
+ * an existing visit
24
+ *
25
+ * This workspace must only be used within the patient chart.
26
+ * @see exported-visit-form.workspace.tsx
27
+ */
28
+ const VisitForm: React.FC<PatientWorkspace2DefinitionProps<VisitFormProps, {}>> = ({
29
+ workspaceProps: { openedFrom, showPatientHeader = false },
30
+ groupProps: { patient, patientUuid, visitContext },
31
+ ...rest
32
+ }) => {
33
+ const { mutate: mutateActiveVisit } = useVisit(patientUuid);
34
+ const { mutate: globalMutate } = useSWRConfig();
35
+ const { setVisitContext } = usePatientChartStore(patientUuid);
36
+
37
+ const onVisitStarted = (visit: Visit) => {
38
+ // For visit creation, we need to update:
39
+ // 1. Current visit data (for critical components like visit summary, action buttons)
40
+ // 2. Visit history table (for the paginated visit list)
41
+ const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
42
+ mutateActiveVisit();
43
+ setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
44
+ };
45
+ return (
46
+ <ExportedVisitForm
47
+ {...rest}
48
+ workspaceProps={{
49
+ openedFrom,
50
+ showPatientHeader,
51
+ onVisitStarted,
52
+ patient,
53
+ patientUuid,
54
+ visitContext,
55
+ }}
56
+ groupProps={{}}
57
+ />
58
+ );
59
+ };
60
+
61
+ export default VisitForm;
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import { vi, describe, it, expect, test } from 'vitest';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { useVisitTypes } from '@openmrs/esm-framework';
5
+ import { screen, render } from '@testing-library/react';
6
+ import { mockVisitTypes } from '__mocks__';
7
+ import BaseVisitType from './base-visit-type.component';
8
+
9
+ const mockUseVisitTypes = vi.mocked(useVisitTypes);
10
+
11
+ vi.mock('lodash-es/debounce', () => vi.fn((fn) => fn));
12
+ vi.mock('react-hook-form', async () => ({
13
+ ...((await vi.importActual('react-hook-form')) as object),
14
+ useFormContext: vi.fn().mockImplementation(() => ({
15
+ handleSubmit: () => vi.fn(),
16
+ control: {
17
+ register: vi.fn(),
18
+ unregister: vi.fn(),
19
+ getFieldState: vi.fn(),
20
+ _names: {
21
+ array: new Set('test'),
22
+ mount: new Set('test'),
23
+ unMount: new Set('test'),
24
+ watch: new Set('test'),
25
+ focus: 'test',
26
+ watchAll: false,
27
+ },
28
+ _subjects: {
29
+ watch: vi.fn(),
30
+ array: vi.fn(),
31
+ state: vi.fn(),
32
+ },
33
+ _getWatch: vi.fn(),
34
+ _formValues: [],
35
+ _defaultValues: [],
36
+ },
37
+ getValues: () => {
38
+ return [];
39
+ },
40
+ setValue: () => vi.fn(),
41
+ formState: () => vi.fn(),
42
+ watch: () => vi.fn(),
43
+ })),
44
+ Controller: ({ render }) =>
45
+ render({
46
+ field: {
47
+ onChange: vi.fn(),
48
+ onBlur: vi.fn(),
49
+ value: '',
50
+ ref: vi.fn(),
51
+ },
52
+ formState: {
53
+ isSubmitted: false,
54
+ },
55
+ fieldState: {
56
+ isTouched: false,
57
+ },
58
+ }),
59
+ useSubscribe: () => ({
60
+ r: { current: { subject: { subscribe: () => vi.fn() } } },
61
+ }),
62
+ }));
63
+
64
+ describe('VisitTypeOverview', () => {
65
+ const renderVisitTypeOverview = () => {
66
+ mockUseVisitTypes.mockReturnValue(mockVisitTypes);
67
+
68
+ render(<BaseVisitType visitTypes={mockVisitTypes} />);
69
+ };
70
+
71
+ it('should be able to search for a visit type', async () => {
72
+ const user = userEvent.setup();
73
+
74
+ renderVisitTypeOverview();
75
+
76
+ const hivVisit = screen.getByRole('radio', { name: /HIV Return Visit/i });
77
+ const outpatientVisit = screen.getByRole('radio', { name: /Outpatient Visit/i });
78
+
79
+ expect(outpatientVisit).toBeInTheDocument();
80
+ expect(hivVisit).toBeInTheDocument();
81
+
82
+ const searchInput = screen.getByRole('searchbox');
83
+ await user.type(searchInput, 'HIV');
84
+
85
+ expect(outpatientVisit).toBeEmptyDOMElement();
86
+ expect(hivVisit).toBeInTheDocument();
87
+ });
88
+ });
@@ -0,0 +1,20 @@
1
+ import { ExtensionSlot, type Visit } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import styles from './visit-actions-cell.scss';
4
+
5
+ interface Props {
6
+ visit: Visit;
7
+ patient: fhir.Patient;
8
+ }
9
+
10
+ const VisitActionsCell: React.FC<Props> = ({ visit, patient }) => {
11
+ return (
12
+ <ExtensionSlot
13
+ name="visit-detail-overview-actions"
14
+ className={styles.visitActions}
15
+ state={{ patientUuid: visit.patient.uuid, patient, visit, compact: true }}
16
+ />
17
+ );
18
+ };
19
+
20
+ export default VisitActionsCell;
@@ -0,0 +1,4 @@
1
+ .visitActions {
2
+ display: flex;
3
+ align-items: center;
4
+ }
@@ -0,0 +1,19 @@
1
+ import { formatDate, type Visit } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ interface Props {
6
+ visit: Visit;
7
+ patient: fhir.Patient;
8
+ }
9
+
10
+ const VisitDateCell: React.FC<Props> = ({ visit }) => {
11
+ const { t } = useTranslation();
12
+ const { startDatetime, stopDatetime } = visit;
13
+ const fromDate = formatDate(new Date(startDatetime));
14
+ const toDate = stopDatetime ? formatDate(new Date(stopDatetime)) : null;
15
+
16
+ return <>{toDate ? t('fromDateToDate', '{{fromDate}} - {{toDate}}', { fromDate, toDate }) : fromDate}</>;
17
+ };
18
+
19
+ export default VisitDateCell;
@@ -0,0 +1,18 @@
1
+ import { DiagnosisTags, type Visit } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+
4
+ interface Props {
5
+ visit: Visit;
6
+ patient: fhir.Patient;
7
+ }
8
+
9
+ const VisitDiagnosisCell: React.FC<Props> = ({ visit }) => {
10
+ const diagnoses = visit.encounters
11
+ .flatMap((encounter) => encounter.diagnoses)
12
+ .filter((diagnosis) => !diagnosis.voided)
13
+ .sort((a, b) => a.rank - b.rank);
14
+
15
+ return <DiagnosisTags diagnoses={diagnoses} />;
16
+ };
17
+
18
+ export default VisitDiagnosisCell;
@@ -0,0 +1,145 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTable,
5
+ DataTableSkeleton,
6
+ Pagination,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableExpandedRow,
12
+ TableExpandHeader,
13
+ TableExpandRow,
14
+ TableHead,
15
+ TableHeader,
16
+ TableRow,
17
+ } from '@carbon/react';
18
+ import { ErrorState, isDesktop, useLayoutType } from '@openmrs/esm-framework';
19
+ import { EmptyState } from '@openmrs/esm-patient-common-lib';
20
+ import { usePaginatedVisits } from '../visits-widget/visit.resource';
21
+ import VisitActionsCell from './visit-actions-cell.component';
22
+ import VisitDateCell from './visit-date-cell.component';
23
+ import VisitDiagnosisCell from './visit-diagnoses-cell.component';
24
+ import VisitSummary from '../visits-widget/past-visits-components/visit-summary.component';
25
+ import VisitTypeCell from './visit-type-cell.component';
26
+ import styles from './visit-history-table.scss';
27
+
28
+ interface VisitHistoryTableProps {
29
+ patientUuid: string;
30
+ patient: fhir.Patient;
31
+ }
32
+
33
+ /**
34
+ * This show a list of visit histories in the visit tab in patient chart
35
+ */
36
+ const VisitHistoryTable: React.FC<VisitHistoryTableProps> = ({ patientUuid, patient }) => {
37
+ const defaultPageSize = 10;
38
+ const [pageSize, setPageSize] = useState(defaultPageSize);
39
+ const pageSizes = [10, 20, 30, 40, 50];
40
+
41
+ const { data: visits, currentPage, error, isLoading, totalCount, goTo } = usePaginatedVisits(patientUuid, pageSize);
42
+ const { t } = useTranslation();
43
+ const desktopLayout = isDesktop(useLayoutType());
44
+
45
+ // TODO: make this configurable
46
+ const columns = [
47
+ { key: 'visitDate', header: t('date', 'Date'), CellComponent: VisitDateCell },
48
+ { key: 'visitType', header: t('visitType', 'Visit type'), CellComponent: VisitTypeCell },
49
+ { key: 'diagnoses', header: t('diagnoses', 'Diagnoses'), CellComponent: VisitDiagnosisCell },
50
+ { key: 'actions', header: '', CellComponent: VisitActionsCell },
51
+ ];
52
+
53
+ const layout = useLayoutType();
54
+
55
+ const rowData = visits?.map((visit) => {
56
+ const row: Record<string, JSX.Element | string> = { id: visit.uuid };
57
+ for (const { key, CellComponent } of columns) {
58
+ row[key] = <CellComponent key={key} visit={visit} patient={patient} />;
59
+ }
60
+ return row;
61
+ });
62
+
63
+ if (isLoading) {
64
+ return <DataTableSkeleton role="progressbar" zebra />;
65
+ }
66
+
67
+ if (error) {
68
+ return <ErrorState error={error} headerTitle={t('pastVisits', 'Past visits')} />;
69
+ }
70
+
71
+ if (visits.length === 0) {
72
+ return (
73
+ <div className={styles.emptyStateContainer}>
74
+ <EmptyState headerTitle={t('pastVisits', 'Past visits')} displayText={t('visits', 'visits')} />
75
+ </div>
76
+ );
77
+ }
78
+ return (
79
+ <div className={styles.container}>
80
+ {/* @ts-ignore */}
81
+ <DataTable headers={columns} rows={rowData} size={desktopLayout ? 'sm' : 'lg'} useZebraStyles>
82
+ {({ rows, headers, getTableProps, getHeaderProps, getExpandHeaderProps, getRowProps, getExpandedRowProps }) => (
83
+ <>
84
+ <TableContainer>
85
+ <Table {...getTableProps()}>
86
+ <TableHead>
87
+ <TableRow>
88
+ <TableExpandHeader enableToggle {...getExpandHeaderProps()} />
89
+ {headers.map((header) => (
90
+ <TableHeader
91
+ {...getHeaderProps({
92
+ header,
93
+ className: header.key === 'actions' ? styles.actionsColumn : '',
94
+ })}
95
+ >
96
+ {header.header}
97
+ </TableHeader>
98
+ ))}
99
+ </TableRow>
100
+ </TableHead>
101
+ <TableBody>
102
+ {rows.map((row, i) => {
103
+ const visit = visits[i];
104
+ return (
105
+ <React.Fragment key={row.id}>
106
+ <TableExpandRow {...getRowProps({ row })}>
107
+ {row.cells.map((cell) => {
108
+ return <TableCell key={cell.id}>{cell?.value}</TableCell>;
109
+ })}
110
+ </TableExpandRow>
111
+ {row.isExpanded ? (
112
+ <TableExpandedRow {...getExpandedRowProps({ row })} colSpan={headers.length + 2}>
113
+ <VisitSummary visit={visit} patientUuid={patientUuid} />
114
+ </TableExpandedRow>
115
+ ) : (
116
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
117
+ )}
118
+ </React.Fragment>
119
+ );
120
+ })}
121
+ </TableBody>
122
+ </Table>
123
+ </TableContainer>
124
+ <Pagination
125
+ forwardText={t('nextPage', 'Next page')}
126
+ backwardText={t('previousPage', 'Previous page')}
127
+ page={currentPage}
128
+ pageSize={pageSize}
129
+ pageSizes={pageSizes}
130
+ totalItems={totalCount}
131
+ onChange={({ pageSize, page }) => {
132
+ setPageSize(pageSize);
133
+ if (page !== currentPage) {
134
+ goTo(page);
135
+ }
136
+ }}
137
+ />
138
+ </>
139
+ )}
140
+ </DataTable>
141
+ </div>
142
+ );
143
+ };
144
+
145
+ export default VisitHistoryTable;
@@ -0,0 +1,25 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .container {
5
+ margin: layout.$spacing-05 0;
6
+ border: 1px solid $ui-03;
7
+
8
+ // fixes padding of encounters table within the expanded visit table row
9
+ :global(tr.cds--parent-row.cds--expandable-row + tr[data-child-row] td) {
10
+ padding-inline-start: layout.$spacing-05;
11
+ }
12
+ }
13
+
14
+ .emptyStateContainer {
15
+ text-align: center;
16
+ margin: layout.$spacing-05 0;
17
+ }
18
+
19
+ .actionsColumn {
20
+ width: 1% !important; // fixes the width of action column to fit content
21
+ }
22
+
23
+ .hiddenRow {
24
+ display: none;
25
+ }
@@ -0,0 +1,15 @@
1
+ import { type Visit } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+
4
+ interface Props {
5
+ visit: Visit;
6
+ patient: fhir.Patient;
7
+ }
8
+
9
+ const VisitTypeCell: React.FC<Props> = ({ visit }) => {
10
+ const { visitType } = visit;
11
+
12
+ return <>{visitType.display}</>;
13
+ };
14
+
15
+ export default VisitTypeCell;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalHeader, ModalBody, ModalFooter, InlineLoading } from '@carbon/react';
4
+ import { type Visit } from '@openmrs/esm-framework';
5
+ import { useDeleteVisit } from '../hooks/useDeleteVisit';
6
+ import styles from './start-visit-dialog.scss';
7
+
8
+ interface DeleteVisitDialogProps {
9
+ closeModal: () => void;
10
+ visit: Visit;
11
+ }
12
+
13
+ const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({ closeModal, visit }) => {
14
+ const { t } = useTranslation();
15
+ const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(visit, closeModal);
16
+
17
+ return (
18
+ <div>
19
+ <ModalHeader
20
+ closeModal={closeModal}
21
+ title={t('deleteVisitDialogHeader', 'Are you sure you want to delete this visit?')}
22
+ />
23
+ <ModalBody>
24
+ <p className={styles.body}>
25
+ {t('confirmDeleteVisitText', 'Deleting this {{visit}} will delete its associated encounters.', {
26
+ visit: visit?.visitType?.display ?? t('visit', 'Visit'),
27
+ })}
28
+ </p>
29
+ </ModalBody>
30
+ <ModalFooter>
31
+ <Button kind="secondary" onClick={closeModal}>
32
+ {t('cancel', 'Cancel')}
33
+ </Button>
34
+ <Button kind="danger" onClick={initiateDeletingVisit} disabled={isDeletingVisit}>
35
+ {!isDeletingVisit ? (
36
+ t('deleteVisit', 'Delete visit')
37
+ ) : (
38
+ <InlineLoading description={t('deletingVisit', 'Deleting visit')} />
39
+ )}
40
+ </Button>
41
+ </ModalFooter>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default DeleteVisitDialog;
@@ -0,0 +1,79 @@
1
+ import { openmrsFetch, showSnackbar, type FetchResponse } from '@openmrs/esm-framework';
2
+ import { vi, describe, it, expect } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { mockCurrentVisit } from '__mocks__';
6
+ import React from 'react';
7
+
8
+ import DeleteVisitDialog from './delete-visit-dialog.modal';
9
+
10
+ const mockCloseModal = vi.fn();
11
+ const mockOpenmrsFetch = vi.mocked(openmrsFetch);
12
+ const mockShowSnackbar = vi.mocked(showSnackbar);
13
+
14
+ describe('Delete visit', () => {
15
+ it('voids the visit and voids its associated encounters', async () => {
16
+ const user = userEvent.setup();
17
+
18
+ const response: Partial<FetchResponse> = {
19
+ statusText: 'ok',
20
+ status: 200,
21
+ };
22
+
23
+ mockOpenmrsFetch.mockResolvedValue(response as FetchResponse);
24
+
25
+ render(<DeleteVisitDialog visit={mockCurrentVisit} closeModal={mockCloseModal} />);
26
+
27
+ const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
28
+ const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
29
+ const closeModalButton = screen.getByRole('button', { name: /close/i });
30
+
31
+ expect(cancelButton).toBeInTheDocument();
32
+ expect(deleteVisitButton).toBeInTheDocument();
33
+ expect(closeModalButton).toBeInTheDocument();
34
+
35
+ expect(screen.getByRole('heading', { name: /Are you sure you want to delete this visit?/i })).toBeInTheDocument();
36
+ expect(screen.getByText(/Deleting this Facility Visit will delete its associated encounters/i)).toBeInTheDocument();
37
+
38
+ await user.click(deleteVisitButton);
39
+
40
+ expect(mockOpenmrsFetch).toHaveBeenCalledWith(`/ws/rest/v1/visit/${mockCurrentVisit.uuid}`, {
41
+ method: 'DELETE',
42
+ });
43
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
44
+ expect.objectContaining({
45
+ kind: 'success',
46
+ title: 'Facility Visit deleted',
47
+ subtitle: 'Facility Visit deleted successfully',
48
+ }),
49
+ );
50
+ });
51
+
52
+ it('displays an error notification if there was problem with deleting a visit', async () => {
53
+ const user = userEvent.setup();
54
+
55
+ mockOpenmrsFetch.mockRejectedValueOnce({ message: 'Internal server error', status: 500 });
56
+
57
+ render(<DeleteVisitDialog visit={mockCurrentVisit} closeModal={mockCloseModal} />);
58
+
59
+ const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
60
+ const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
61
+ const closeModalButton = screen.getByRole('button', { name: /close/i });
62
+
63
+ expect(cancelButton).toBeInTheDocument();
64
+ expect(deleteVisitButton).toBeInTheDocument();
65
+ expect(closeModalButton).toBeInTheDocument();
66
+ expect(screen.getByText(/Deleting this Facility Visit will delete its associated encounters/i)).toBeInTheDocument();
67
+
68
+ await user.click(deleteVisitButton);
69
+
70
+ expect(mockOpenmrsFetch).toHaveBeenCalledWith(`/ws/rest/v1/visit/${mockCurrentVisit.uuid}`, {
71
+ method: 'DELETE',
72
+ });
73
+ expect(mockShowSnackbar).toHaveBeenCalledWith({
74
+ subtitle: 'An error occurred when deleting visit',
75
+ kind: 'error',
76
+ title: 'Error deleting visit',
77
+ });
78
+ });
79
+ });
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework';
5
+ import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
6
+ import styles from './end-visit-dialog.scss';
7
+
8
+ interface EndVisitDialogProps {
9
+ patientUuid: string;
10
+ closeModal: () => void;
11
+ }
12
+
13
+ /**
14
+ * This modal shows up when user clicks on the "End visit" button in the action menu within the
15
+ * patient banner. It should only show when the patient has an active visit. See stop-visit.component.tsx
16
+ * for the button.
17
+ *
18
+ * This dialog uses the patient chart store and SHOULD only be mounted within the patient chart
19
+ */
20
+ const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal }) => {
21
+ const { t } = useTranslation();
22
+ const { activeVisit, mutate } = useVisit(patientUuid);
23
+ const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
24
+
25
+ const handleEndVisit = () => {
26
+ if (activeVisit) {
27
+ const endVisitPayload = {
28
+ stopDatetime: new Date(),
29
+ };
30
+
31
+ const abortController = new AbortController();
32
+
33
+ updateVisit(activeVisit.uuid, endVisitPayload, abortController)
34
+ .then((response) => {
35
+ mutate();
36
+ window.dispatchEvent(new CustomEvent('queue-entry-updated'));
37
+ closeModal();
38
+ if (visitContext?.uuid == activeVisit.uuid) {
39
+ setVisitContext(null, null);
40
+ }
41
+ showSnackbar({
42
+ isLowContrast: true,
43
+ kind: 'success',
44
+ subtitle: t('visitEndSuccessfully', `${response?.data?.visitType?.display} ended successfully`),
45
+ title: t('visitEnded', 'Visit ended'),
46
+ });
47
+ })
48
+ .catch((error) => {
49
+ showSnackbar({
50
+ title: t('errorEndingVisit', 'Error ending visit'),
51
+ kind: 'error',
52
+ isLowContrast: false,
53
+ subtitle: error?.message,
54
+ });
55
+ });
56
+ }
57
+ };
58
+
59
+ return (
60
+ <div>
61
+ <ModalHeader
62
+ closeModal={closeModal}
63
+ title={t('endActiveVisitConfirmation', 'Are you sure you want to end this active visit?')}
64
+ />
65
+ <ModalBody>
66
+ <p className={styles.bodyShort02}>
67
+ {t('youCanAddAdditionalEncounters', 'You can add additional encounters to this visit in the visit summary.')}
68
+ </p>
69
+ </ModalBody>
70
+ <ModalFooter>
71
+ <Button kind="secondary" onClick={closeModal}>
72
+ {t('cancel', 'Cancel')}
73
+ </Button>
74
+ <Button kind="danger" onClick={handleEndVisit}>
75
+ {t('endVisit_title', 'End Visit')}
76
+ </Button>
77
+ </ModalFooter>
78
+ </div>
79
+ );
80
+ };
81
+
82
+ export default EndVisitDialog;
@@ -0,0 +1,7 @@
1
+ @use '@carbon/type';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .bodyShort02 {
5
+ @include type.type-style('body-compact-02');
6
+ color: $ui-05;
7
+ }