@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,206 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import dayjs from 'dayjs';
4
+ import { type Control, Controller, type FieldPath, useFormContext, useWatch } from 'react-hook-form';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { SelectItem, TimePicker, TimePickerSelect } from '@carbon/react';
7
+ import { type amPm } from '@openmrs/esm-patient-common-lib';
8
+ import { OpenmrsDatePicker, ResponsiveWrapper } from '@openmrs/esm-framework';
9
+ import { convertToDate, type VisitFormData } from './visit-form.resource';
10
+ import styles from './visit-form.scss';
11
+
12
+ // Helpers to safely compute min/max across optional values
13
+ const minOf = (...values: Array<number | undefined | null>) => {
14
+ const nums = values.filter((v): v is number => typeof v === 'number' && Number.isFinite(v));
15
+ return nums.length ? Math.min(...nums) : undefined;
16
+ };
17
+
18
+ const maxOf = (...values: Array<number | undefined | null>) => {
19
+ const nums = values.filter((v): v is number => typeof v === 'number' && Number.isFinite(v));
20
+ return nums.length ? Math.max(...nums) : undefined;
21
+ };
22
+
23
+ interface VisitDateTimeSectionProps {
24
+ control: Control<VisitFormData, any>;
25
+ earliestStartDate?: number;
26
+ firstEncounterDateTime: number;
27
+ lastEncounterDateTime: number;
28
+ }
29
+
30
+ /**
31
+ * The component conditionally renders the Visit start and end
32
+ * date / time fields based on the visit status (new / ongoing / past)
33
+ */
34
+ const VisitDateTimeSection: React.FC<VisitDateTimeSectionProps> = ({
35
+ control,
36
+ earliestStartDate,
37
+ firstEncounterDateTime,
38
+ lastEncounterDateTime,
39
+ }) => {
40
+ const { t } = useTranslation();
41
+ const [
42
+ visitStatus,
43
+ visitStartDate,
44
+ visitStartTime,
45
+ visitStartTimeFormat,
46
+ visitStopDate,
47
+ visitStopTime,
48
+ visitStopTimeFormat,
49
+ ] = useWatch({
50
+ control,
51
+ name: [
52
+ 'visitStatus',
53
+ 'visitStartDate',
54
+ 'visitStartTime',
55
+ 'visitStartTimeFormat',
56
+ 'visitStopDate',
57
+ 'visitStopTime',
58
+ 'visitStopTimeFormat',
59
+ ],
60
+ });
61
+
62
+ const hasStopTime = 'past' === visitStatus;
63
+ const selectedVisitStartDateTime = convertToDate(visitStartDate, visitStartTime, visitStartTimeFormat);
64
+ const selectedVisitStopDateTime = convertToDate(visitStopDate, visitStopTime, visitStopTimeFormat);
65
+
66
+ if (visitStatus === 'new') {
67
+ return null;
68
+ }
69
+
70
+ return (
71
+ <section>
72
+ <div className={styles.sectionTitle}>
73
+ {visitStatus === 'ongoing'
74
+ ? t('visitStartDate', 'Visit start date')
75
+ : t('visitStartAndEndDate', 'Visit start and end date')}
76
+ </div>
77
+ <VisitDateTimeField
78
+ dateField={{ name: 'visitStartDate', label: t('startDate', 'Start date') }}
79
+ timeField={{ name: 'visitStartTime', label: t('startTime', 'Start time') }}
80
+ timeFormatField={{ name: 'visitStartTimeFormat', label: t('startTimeFormat', 'Start time format') }}
81
+ minDate={earliestStartDate}
82
+ maxDate={minOf(Date.now(), firstEncounterDateTime, selectedVisitStopDateTime?.getTime())}
83
+ />
84
+ {hasStopTime && (
85
+ <VisitDateTimeField
86
+ dateField={{ name: 'visitStopDate', label: t('endDate', 'End date') }}
87
+ timeField={{ name: 'visitStopTime', label: t('endTime', 'End time') }}
88
+ timeFormatField={{ name: 'visitStopTimeFormat', label: t('endTimeFormat', 'End time format') }}
89
+ minDate={maxOf(lastEncounterDateTime, selectedVisitStartDateTime?.getTime())}
90
+ maxDate={Date.now()}
91
+ />
92
+ )}
93
+ </section>
94
+ );
95
+ };
96
+
97
+ interface VisitDateTimeFieldProps {
98
+ dateField: Field;
99
+ timeField: Field;
100
+ timeFormatField: Field;
101
+ minDate?: dayjs.ConfigType;
102
+ maxDate?: dayjs.ConfigType;
103
+ disabled?: boolean;
104
+ }
105
+
106
+ interface Field {
107
+ name: FieldPath<VisitFormData>;
108
+ label: string;
109
+ }
110
+
111
+ /**
112
+ * This components renders a DatePicker, TimePicker and AM / PM dropdown
113
+ * used to input a Date.
114
+ * It is used by the visit form for the start and end time inputs.
115
+ */
116
+ const VisitDateTimeField: React.FC<VisitDateTimeFieldProps> = ({
117
+ dateField,
118
+ timeField,
119
+ timeFormatField,
120
+ minDate,
121
+ maxDate,
122
+ disabled,
123
+ }) => {
124
+ const {
125
+ control,
126
+ formState: { errors },
127
+ } = useFormContext<VisitFormData>();
128
+ const { t } = useTranslation();
129
+
130
+ // Since we have the separate date and time fields, the full validation is done by zod.
131
+ // We are just using minDateObj and maxDateObj to restrict the bounds of the DatePicker.
132
+ const minDateObj = minDate ? dayjs(minDate).startOf('day') : null;
133
+ const maxDateObj = maxDate ? dayjs(maxDate).endOf('day') : null;
134
+
135
+ return (
136
+ <div className={classNames(styles.dateTimeSection, styles.sectionField)}>
137
+ <Controller
138
+ name={dateField.name}
139
+ control={control}
140
+ render={({ field, fieldState }) => (
141
+ <ResponsiveWrapper>
142
+ <OpenmrsDatePicker
143
+ {...field}
144
+ value={field.value as Date}
145
+ className={styles.datePicker}
146
+ id={`${dateField.name}Input`}
147
+ data-testid={`${dateField.name}Input`}
148
+ maxDate={maxDateObj}
149
+ minDate={minDateObj}
150
+ labelText={dateField.label}
151
+ invalid={Boolean(fieldState?.error?.message)}
152
+ invalidText={fieldState?.error?.message}
153
+ />
154
+ </ResponsiveWrapper>
155
+ )}
156
+ />
157
+ <ResponsiveWrapper>
158
+ <Controller
159
+ name={timeField.name}
160
+ control={control}
161
+ render={({ field: { onBlur, onChange, value } }) => (
162
+ <div className={styles.timePickerContainer}>
163
+ <TimePicker
164
+ className={styles.timePicker}
165
+ disabled={disabled}
166
+ id={timeField.name}
167
+ invalid={Boolean(errors[timeField.name])}
168
+ invalidText={errors[timeField.name]?.message}
169
+ labelText={timeField.label}
170
+ onBlur={onBlur}
171
+ onChange={(event) => onChange(event.target.value)}
172
+ pattern="^(0[1-9]|1[0-2]):([0-5][0-9])$"
173
+ value={value as string}
174
+ >
175
+ <Controller
176
+ name={timeFormatField.name}
177
+ control={control}
178
+ render={({ field: { onChange, value } }) => (
179
+ <TimePickerSelect
180
+ aria-label={timeFormatField.label}
181
+ className={classNames({
182
+ [styles.timePickerSelectError]: errors[timeFormatField.name],
183
+ })}
184
+ disabled={disabled}
185
+ id={`${timeFormatField.name}Input`}
186
+ onChange={(event) => onChange(event.target.value as amPm)}
187
+ value={value as amPm}
188
+ >
189
+ <SelectItem value="AM" text={t('AM', 'AM')} />
190
+ <SelectItem value="PM" text={t('PM', 'PM')} />
191
+ </TimePickerSelect>
192
+ )}
193
+ />
194
+ </TimePicker>
195
+ {errors[timeFormatField.name] && (
196
+ <div className={styles.timerPickerError}>{errors[timeFormatField.name]?.message}</div>
197
+ )}
198
+ </div>
199
+ )}
200
+ />
201
+ </ResponsiveWrapper>
202
+ </div>
203
+ );
204
+ };
205
+
206
+ export default VisitDateTimeSection;
@@ -0,0 +1,401 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import type { TFunction } from 'i18next';
4
+ import dayjs from 'dayjs';
5
+ import useSWRImmutable from 'swr/immutable';
6
+ import { z } from 'zod';
7
+ import {
8
+ type FetchResponse,
9
+ openmrsFetch,
10
+ restBaseUrl,
11
+ useConfig,
12
+ useConnectivity,
13
+ useEmrConfiguration,
14
+ useFeatureFlag,
15
+ useSession,
16
+ useVisitTypes,
17
+ type Visit,
18
+ } from '@openmrs/esm-framework';
19
+ import { time12HourFormatRegex, type amPm } from '@openmrs/esm-patient-common-lib';
20
+ import { useDefaultVisitLocation } from '../hooks/useDefaultVisitLocation';
21
+ import { useOfflineVisitType } from '../hooks/useOfflineVisitType';
22
+ import { type ChartConfig } from '../../config-schema';
23
+
24
+ export const visitStatuses = ['new', 'ongoing', 'past'] as const;
25
+ export type VisitStatus = (typeof visitStatuses)[number];
26
+
27
+ export type VisitFormData = {
28
+ visitStatus: VisitStatus;
29
+ visitStartDate: Date; // Date object that only contains info for year, month, day
30
+ visitStartTime: string; // hh:mm (note that hh is from 01 to 12, NOT 00 to 23)
31
+ visitStartTimeFormat: amPm;
32
+ visitStopDate: Date; // Date object that only contains info for year, month, day
33
+ visitStopTime: string; // hh:mm (note that hh is from 01 to 12, NOT 00 to 23)
34
+ visitStopTimeFormat: amPm;
35
+ programType: string;
36
+ visitType: string;
37
+ visitLocation: {
38
+ display?: string;
39
+ uuid?: string;
40
+ };
41
+ visitAttributes: {
42
+ [x: string]: string;
43
+ };
44
+ };
45
+
46
+ // *****************
47
+ // Copied from form-submission.service.ts
48
+ // TODO: consolidate logic for parsing errors from REST API calls
49
+ export type FieldError = {
50
+ [key: string]: Array<{ code: string; message: string }>;
51
+ };
52
+
53
+ export type ErrorObject = {
54
+ error: {
55
+ code: string;
56
+ message: string;
57
+ detail: string;
58
+ fieldErrors?: FieldError;
59
+ globalErrors?: Array<{ code: string; message: string }>;
60
+ };
61
+ };
62
+
63
+ export function extractErrorMessagesFromResponse(errorObject: ErrorObject, t: TFunction) {
64
+ const { fieldErrors, globalErrors, message, code } = errorObject?.error ?? {};
65
+
66
+ if (fieldErrors && Object.keys(fieldErrors).length > 0) {
67
+ return Object.values(fieldErrors)
68
+ .flatMap((errors) => errors.map((error) => error.message))
69
+ .join('\n');
70
+ }
71
+
72
+ if (globalErrors?.length > 0) {
73
+ return globalErrors.map((error) => error.message).join('\n');
74
+ }
75
+
76
+ return message ?? code ?? t('unknownError', 'Unknown error');
77
+ }
78
+ // *****************
79
+
80
+ export function useConditionalVisitTypes() {
81
+ const isOnline = useConnectivity();
82
+
83
+ const visitTypesHook = isOnline ? useVisitTypes : useOfflineVisitType;
84
+
85
+ return visitTypesHook();
86
+ }
87
+
88
+ interface PatientPersonResponse {
89
+ person: {
90
+ birthdate: string | null;
91
+ birthdateEstimated: boolean;
92
+ age: number | null;
93
+ };
94
+ }
95
+
96
+ const patientPersonCustomRep = 'custom:(person:(birthdate,birthdateEstimated,age))';
97
+
98
+ /**
99
+ * Parses a date string into a local-midnight Date,
100
+ * avoiding timezone drift from `new Date(str)` or dayjs(str).
101
+ * Accepts both 'YYYY-MM-DD' and full ISO datetime strings
102
+ * like '1979-12-08T00:00:00.000+0530' (as returned by the REST API).
103
+ */
104
+ function parseLocalDate(dateStr: string): Date | null {
105
+ const datePart = dateStr.split('T')[0];
106
+ const parts = datePart.split('-');
107
+ if (parts.length !== 3) {
108
+ return null;
109
+ }
110
+ const [y, m, d] = parts.map(Number);
111
+ return new Date(y, m - 1, d);
112
+ }
113
+
114
+ /**
115
+ * Computes the earliest allowed visit start date based on the patient's birthdate.
116
+ * For estimated birthdates, a grace period shifts the boundary earlier,
117
+ * matching the backend's VisitValidator logic.
118
+ */
119
+ export function computeEarliestAllowedStartDate(
120
+ birthdate: string | null,
121
+ birthdateEstimated: boolean,
122
+ age: number | null,
123
+ ): Date | null {
124
+ if (!birthdate) {
125
+ return null;
126
+ }
127
+
128
+ const earliest = parseLocalDate(birthdate);
129
+ if (!earliest) {
130
+ return null;
131
+ }
132
+
133
+ // When the birthdate is estimated, the backend's VisitValidator applies a grace period
134
+ // that shifts the earliest allowed date further into the past. The grace is half the
135
+ // patient's age (in years), with a minimum of 1 year.
136
+ // @see https://github.com/openmrs/openmrs-core/blob/master/api/src/main/java/org/openmrs/validator/VisitValidator.java
137
+ if (birthdateEstimated && age != null) {
138
+ const graceYears = Math.max(1, Math.floor(age * 0.5));
139
+ earliest.setFullYear(earliest.getFullYear() - graceYears);
140
+ }
141
+
142
+ return earliest;
143
+ }
144
+
145
+ // We need a separate REST call here because the FHIR Patient resource doesn't
146
+ // include `birthdateEstimated` or `age`, which are required for the grace period
147
+ // calculation. The FHIR patient's `birthDate` alone isn't sufficient.
148
+ export function useEarliestAllowedVisitStartDate(patientUuid: string) {
149
+ const { data, isLoading } = useSWRImmutable<FetchResponse<PatientPersonResponse>>(
150
+ `${restBaseUrl}/patient/${patientUuid}?v=${patientPersonCustomRep}`,
151
+ openmrsFetch,
152
+ );
153
+
154
+ const earliestAllowedStartDate = useMemo(() => {
155
+ if (!data?.data?.person) {
156
+ return null;
157
+ }
158
+ const { birthdate, birthdateEstimated, age } = data.data.person;
159
+ return computeEarliestAllowedStartDate(birthdate, birthdateEstimated, age);
160
+ }, [data]);
161
+
162
+ return { earliestAllowedStartDate, isLoading };
163
+ }
164
+
165
+ export function useAllowOverlappingVisits() {
166
+ const isOnline = useConnectivity();
167
+ const { data, error, isLoading } = useSWRImmutable<FetchResponse<{ value: string }>>(
168
+ isOnline ? `${restBaseUrl}/systemsetting/visits.allowOverlappingVisits?v=custom:(value)` : null,
169
+ openmrsFetch,
170
+ );
171
+ return {
172
+ allowOverlappingVisits: error || !data ? true : (data.data.value ?? 'true').toLowerCase() === 'true',
173
+ isLoading,
174
+ };
175
+ }
176
+
177
+ export interface VisitFormCallbacks {
178
+ onVisitCreatedOrUpdated: (visit: Visit) => Promise<any>;
179
+ }
180
+
181
+ export function useVisitFormCallbacks() {
182
+ return useState<Map<string, VisitFormCallbacks>>(new Map());
183
+ }
184
+
185
+ export function createVisitAttribute(visitUuid: string, attributeType: string, value: string) {
186
+ return openmrsFetch(`${restBaseUrl}/visit/${visitUuid}/attribute`, {
187
+ method: 'POST',
188
+ headers: { 'Content-type': 'application/json' },
189
+ body: { attributeType, value },
190
+ });
191
+ }
192
+
193
+ export function updateVisitAttribute(visitUuid: string, visitAttributeUuid: string, value: string) {
194
+ return openmrsFetch(`${restBaseUrl}/visit/${visitUuid}/attribute/${visitAttributeUuid}`, {
195
+ method: 'POST',
196
+ headers: { 'Content-type': 'application/json' },
197
+ body: { value },
198
+ });
199
+ }
200
+
201
+ export function deleteVisitAttribute(visitUuid: string, visitAttributeUuid: string) {
202
+ return openmrsFetch(`${restBaseUrl}/visit/${visitUuid}/attribute/${visitAttributeUuid}`, {
203
+ method: 'DELETE',
204
+ });
205
+ }
206
+
207
+ export function useVisitFormSchemaAndDefaultValues(visitToEdit: Visit, earliestAllowedStartDate?: Date | null) {
208
+ const { t } = useTranslation();
209
+ const { visitAttributeTypes, restrictByVisitLocationTag } = useConfig<ChartConfig>();
210
+ const isEmrApiModuleInstalled = useFeatureFlag('emrapi-module');
211
+ const sessionUser = useSession();
212
+ const sessionLocation = sessionUser?.sessionLocation;
213
+ const defaultVisitLocation = useDefaultVisitLocation(
214
+ sessionLocation,
215
+ restrictByVisitLocationTag && isEmrApiModuleInstalled,
216
+ );
217
+ const { emrConfiguration } = useEmrConfiguration();
218
+
219
+ return useMemo(() => {
220
+ const now = new Date();
221
+
222
+ const allEncounterDateTimes = (visitToEdit?.encounters ?? []).map(({ encounterDatetime }) =>
223
+ Date.parse(encounterDatetime),
224
+ );
225
+
226
+ const firstEncounterDateTime: number = Math.min(...allEncounterDateTimes);
227
+ const lastEncounterDateTime: number = Math.max(...allEncounterDateTimes);
228
+
229
+ const startDateTime = convertToDateTimeFields(visitToEdit?.startDatetime ?? now);
230
+ const stopDateTime = convertToDateTimeFields(visitToEdit?.stopDatetime ?? now);
231
+
232
+ const visitStatus: VisitStatus =
233
+ visitToEdit == null ? 'new' : visitToEdit.stopDatetime === null ? 'ongoing' : 'past';
234
+
235
+ const defaultValues: Partial<VisitFormData> = {
236
+ visitStatus,
237
+ visitStartDate: startDateTime.date,
238
+ visitStartTime: startDateTime.time,
239
+ visitStartTimeFormat: startDateTime.timeFormat,
240
+ visitStopDate: stopDateTime.date,
241
+ visitStopTime: stopDateTime.time,
242
+ visitStopTimeFormat: stopDateTime.timeFormat,
243
+ visitType: visitToEdit?.visitType?.uuid ?? emrConfiguration?.atFacilityVisitType?.uuid,
244
+ visitLocation: visitToEdit?.location ?? defaultVisitLocation ?? {},
245
+ visitAttributes:
246
+ visitToEdit?.attributes.reduce(
247
+ (acc, curr) => ({
248
+ ...acc,
249
+ [curr.attributeType.uuid]: typeof curr.value === 'object' ? curr?.value?.uuid : `${curr.value ?? ''}`,
250
+ }),
251
+ {},
252
+ ) ?? {},
253
+ };
254
+
255
+ const visitAttributes = (visitAttributeTypes ?? [])?.reduce(
256
+ (acc, { uuid, required }) => ({
257
+ ...acc,
258
+ [uuid]: required
259
+ ? z.string({ required_error: t('fieldRequired', 'This field is required') })
260
+ : z.string().optional(),
261
+ }),
262
+ {},
263
+ );
264
+
265
+ const visitStatusEnum = z.enum(visitStatuses);
266
+ const visitFormSchema = z
267
+ .object({
268
+ visitStatus: visitToEdit ? visitStatusEnum.exclude(['new']) : visitStatusEnum,
269
+ visitStartDate: z.date().optional(),
270
+ visitStartTime: z.string().regex(time12HourFormatRegex).optional(),
271
+ visitStartTimeFormat: z.enum(['PM', 'AM']).optional(),
272
+ visitStopDate: z.date().optional(),
273
+ visitStopTime: z.string().regex(time12HourFormatRegex).optional(),
274
+ visitStopTimeFormat: z.enum(['PM', 'AM']).optional(),
275
+ programType: z.string().optional(),
276
+ visitType: z.string({ required_error: t('visitTypeRequired', 'Visit type is required') }),
277
+ visitLocation: z.object({
278
+ display: z.string(),
279
+ uuid: z.string({ required_error: t('visitLocationRequired', 'Visit location is required') }),
280
+ }),
281
+ visitAttributes: z.object(visitAttributes),
282
+ })
283
+ .superRefine((data, ctx) => {
284
+ const {
285
+ visitStatus,
286
+ visitStartDate,
287
+ visitStartTime,
288
+ visitStartTimeFormat,
289
+ visitStopDate,
290
+ visitStopTime,
291
+ visitStopTimeFormat,
292
+ } = data;
293
+
294
+ const visitStartDateTime = convertToDate(visitStartDate, visitStartTime, visitStartTimeFormat);
295
+ const visitStopDateTime = convertToDate(visitStopDate, visitStopTime, visitStopTimeFormat);
296
+
297
+ if (visitStatus === 'ongoing' || visitStatus === 'past') {
298
+ if (visitStartDateTime === null) {
299
+ ctx.addIssue({
300
+ code: z.ZodIssueCode.custom,
301
+ message: t('visitStartDateTimeRequired', 'Start date and time are required'),
302
+ path: ['visitStartDate'],
303
+ });
304
+ } else if (earliestAllowedStartDate && visitStartDateTime < earliestAllowedStartDate) {
305
+ ctx.addIssue({
306
+ code: z.ZodIssueCode.custom,
307
+ message: t('visitStartDateBeforeBirthdate', "Start date cannot be before the patient's birth date"),
308
+ path: ['visitStartDate'],
309
+ });
310
+ } else if (visitStartDateTime > now) {
311
+ ctx.addIssue({
312
+ code: z.ZodIssueCode.custom,
313
+ message: t('futureStartTime', 'Start time cannot be in the future'),
314
+ path: ['visitStartTime'],
315
+ });
316
+ } else if (visitStartDateTime.getTime() > firstEncounterDateTime) {
317
+ ctx.addIssue({
318
+ code: z.ZodIssueCode.custom,
319
+ message: t(
320
+ 'visitStartDateMustBeBeforeEarliestEncounter',
321
+ 'Start time must be on or before {{firstEncounterDatetime}}',
322
+ {
323
+ firstEncounterDatetime: new Date(firstEncounterDateTime).toLocaleString(),
324
+ interpolation: {
325
+ escapeValue: false,
326
+ },
327
+ },
328
+ ),
329
+ path: ['visitStartTime'],
330
+ });
331
+ }
332
+ }
333
+
334
+ if (visitStatus === 'past') {
335
+ if (visitStopDateTime === null) {
336
+ ctx.addIssue({
337
+ code: z.ZodIssueCode.custom,
338
+ message: t('endDateTimeRequired', 'End date and time are required'),
339
+ path: ['visitStopDate'],
340
+ });
341
+ } else if (visitStopDateTime > now) {
342
+ ctx.addIssue({
343
+ code: z.ZodIssueCode.custom,
344
+ message: t('futureEndTime', 'End time cannot be in the future'),
345
+ path: ['visitStopTime'],
346
+ });
347
+ } else if (visitStopDateTime < visitStartDateTime) {
348
+ ctx.addIssue({
349
+ code: z.ZodIssueCode.custom,
350
+ message: t('endTimeMustBeAfterStartTime', 'End time must be after start time'),
351
+ path: ['visitStopDate'],
352
+ });
353
+ } else if (visitStopDateTime.getTime() < lastEncounterDateTime) {
354
+ ctx.addIssue({
355
+ code: z.ZodIssueCode.custom,
356
+ message: t(
357
+ 'endTimeMustBeAfterMostRecentEncounter',
358
+ 'End time must be on or after {{lastEncounterDatetime}}',
359
+ {
360
+ lastEncounterDatetime: new Date(lastEncounterDateTime).toLocaleString(),
361
+ interpolation: {
362
+ escapeValue: false,
363
+ },
364
+ },
365
+ ),
366
+ path: ['visitStopTime'],
367
+ });
368
+ }
369
+ }
370
+ });
371
+
372
+ return { visitFormSchema, defaultValues, firstEncounterDateTime, lastEncounterDateTime };
373
+ }, [t, visitAttributeTypes, visitToEdit, defaultVisitLocation, emrConfiguration, earliestAllowedStartDate]);
374
+ }
375
+
376
+ // Returns a Date object based on date, time and am/pm inputs from user.
377
+ // Note that the inputs are expected to be in local time.
378
+ // Returns a non-null Date only is the inputs are valid
379
+ export const convertToDate = (
380
+ date: Date, // Date object that only contains info for year, month, day
381
+ time12h: string, // hh:mm, where hh is 01 to 12
382
+ timeFormat: amPm, // AM / PM
383
+ ): Date | null => {
384
+ if (!date || !time12h || !timeFormat) {
385
+ return null;
386
+ }
387
+ const dateStr = dayjs(date).format('YYYY-MM-DD');
388
+ const ret = dayjs(`${dateStr} ${time12h} ${timeFormat}`, 'YYYY-MM-DD hh:mm A');
389
+ return ret.isValid() ? ret.toDate() : null;
390
+ };
391
+
392
+ // The inverse of `convertToDate`. Takes a Date-like value and returns
393
+ // the date (with hour / minute / seconds truncated), time12h and timeFormat
394
+ export const convertToDateTimeFields = (dateTime: dayjs.ConfigType) => {
395
+ const dateTimeDayjs = dayjs(dateTime);
396
+ return {
397
+ date: dateTimeDayjs.startOf('day').toDate(),
398
+ time: dateTimeDayjs.format('hh:mm'),
399
+ timeFormat: dateTimeDayjs.format('A') as amPm,
400
+ };
401
+ };