vintasend 0.4.0 → 0.4.7

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 (284) hide show
  1. package/README.md +5 -0
  2. package/dist/examples/vintasend-medplum-example/.env.example +11 -0
  3. package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
  4. package/dist/examples/vintasend-medplum-example/README.md +190 -0
  5. package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
  6. package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
  7. package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
  8. package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
  9. package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
  10. package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
  11. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
  12. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
  13. package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
  14. package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
  15. package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
  16. package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
  17. package/dist/examples/vintasend-medplum-example/index.html +14 -0
  18. package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
  19. package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
  20. package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
  21. package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
  22. package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
  23. package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
  24. package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
  25. package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
  26. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
  27. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
  28. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
  29. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
  30. package/dist/examples/vintasend-medplum-example/package.json +75 -0
  31. package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
  32. package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
  33. package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
  34. package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
  35. package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
  36. package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
  37. package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
  38. package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
  39. package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
  40. package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
  41. package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
  42. package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
  43. package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
  44. package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
  45. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
  46. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
  47. package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
  48. package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
  49. package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
  50. package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
  51. package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
  52. package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
  53. package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
  54. package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
  55. package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
  56. package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
  57. package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
  58. package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
  59. package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
  60. package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
  61. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
  62. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
  63. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
  64. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
  65. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
  66. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
  67. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
  68. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
  69. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
  70. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
  71. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
  72. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
  73. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
  74. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
  75. package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
  76. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
  77. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
  78. package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
  79. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
  80. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
  81. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
  82. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
  83. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
  84. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
  85. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
  86. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
  87. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
  88. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
  89. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
  90. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
  91. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
  92. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
  93. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
  94. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
  95. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
  96. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
  97. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
  98. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
  99. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
  100. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
  101. package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
  102. package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
  103. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
  104. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
  105. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
  106. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
  107. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
  108. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
  109. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
  110. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
  111. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
  112. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
  113. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
  114. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
  115. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
  116. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
  117. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
  118. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
  119. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
  120. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
  121. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
  122. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
  123. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
  124. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
  125. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
  126. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
  127. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
  128. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
  129. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
  130. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
  131. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
  132. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
  133. package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
  134. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
  135. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
  136. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
  137. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
  138. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
  139. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
  140. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
  141. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
  142. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
  143. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
  144. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
  145. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
  146. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
  147. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
  148. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
  149. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
  150. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
  151. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
  152. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
  153. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
  154. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
  155. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
  156. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
  157. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
  158. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
  159. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
  160. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
  161. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
  162. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
  163. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
  164. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
  165. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
  166. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
  167. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
  168. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
  169. package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
  170. package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
  171. package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
  172. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
  173. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
  174. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
  175. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
  176. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
  177. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
  178. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
  179. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
  180. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
  181. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
  182. package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
  183. package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
  184. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
  185. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
  186. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
  187. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
  188. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
  189. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
  190. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
  191. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
  192. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
  193. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
  194. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
  195. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
  196. package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
  197. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
  198. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
  199. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
  200. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
  201. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
  202. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
  203. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
  204. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
  205. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
  206. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
  207. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
  208. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
  209. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
  210. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
  211. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
  212. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
  213. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
  214. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
  215. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
  216. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
  217. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
  218. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
  219. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
  220. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
  221. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
  222. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
  223. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
  224. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
  225. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
  226. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
  227. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
  228. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
  229. package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
  230. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
  231. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
  232. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
  233. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
  234. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
  235. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
  236. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
  237. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
  238. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
  239. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
  240. package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
  241. package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
  242. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
  243. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
  244. package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
  245. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
  246. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
  247. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
  248. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
  249. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
  250. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
  251. package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
  252. package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
  253. package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
  254. package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
  255. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
  256. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
  257. package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
  258. package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
  259. package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
  260. package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
  261. package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
  262. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
  263. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
  264. package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
  265. package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
  266. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
  267. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
  268. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
  269. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
  270. package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
  271. package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
  272. package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
  273. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
  274. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
  275. package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
  276. package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
  277. package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
  278. package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
  279. package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
  280. package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
  281. package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
  282. package/dist/services/notification-service.js +11 -1
  283. package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +5 -0
  284. package/package.json +1 -1
@@ -0,0 +1,356 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import type { ServiceRequest } from '@medplum/fhirtypes';
4
+ import type { LabOrganization, TestCoding } from '@medplum/health-gorilla-core';
5
+ import { HomerSimpson, MockClient } from '@medplum/mock';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import { showNotification } from '@mantine/notifications';
8
+ import { describe, expect, test, beforeEach, vi } from 'vitest';
9
+ import { OrderLabsPage } from './OrderLabsPage';
10
+ import { act, fireEvent, render, screen, waitFor } from '../../test-utils/render';
11
+ import * as notifications from '../../utils/notifications';
12
+
13
+ vi.mock('@medplum/health-gorilla-react', async () => {
14
+ const actual = await vi.importActual('@medplum/health-gorilla-react');
15
+ return {
16
+ ...actual,
17
+ useHealthGorillaLabOrder: vi.fn(),
18
+ };
19
+ });
20
+
21
+ vi.mock('react-router', async () => {
22
+ const actual = await vi.importActual('react-router');
23
+ return {
24
+ ...actual,
25
+ useParams: vi.fn(),
26
+ };
27
+ });
28
+
29
+ vi.mock('@mantine/notifications', () => ({
30
+ showNotification: vi.fn(),
31
+ }));
32
+
33
+ vi.mock('../../utils/notifications', () => ({
34
+ showErrorNotification: vi.fn(),
35
+ }));
36
+
37
+ import { useHealthGorillaLabOrder } from '@medplum/health-gorilla-react';
38
+ import { MemoryRouter, useParams } from 'react-router';
39
+
40
+ describe('OrderLabsPage', () => {
41
+ let medplum: MockClient;
42
+ let mockOnSubmitLabOrder: () => void;
43
+ let mockLabOrderReturn: ReturnType<typeof useHealthGorillaLabOrder>;
44
+
45
+ beforeEach(async () => {
46
+ vi.clearAllMocks();
47
+
48
+ medplum = new MockClient();
49
+ mockOnSubmitLabOrder = vi.fn();
50
+
51
+ const mockTest: TestCoding = {
52
+ code: 'TEST001',
53
+ display: 'Complete Blood Count',
54
+ system: 'http://loinc.org',
55
+ };
56
+
57
+ const mockLab: LabOrganization = {
58
+ resourceType: 'Organization',
59
+ id: 'lab-123',
60
+ name: 'Test Lab',
61
+ identifier: [
62
+ {
63
+ system: 'https://www.healthgorilla.com/fhir/StructureDefinition/organization-identifier',
64
+ value: 'HG123',
65
+ },
66
+ ],
67
+ } as LabOrganization;
68
+
69
+ mockLabOrderReturn = {
70
+ state: {
71
+ performingLab: undefined,
72
+ performingLabAccountNumber: undefined,
73
+ selectedTests: [],
74
+ testMetadata: {},
75
+ diagnoses: [],
76
+ billingInformation: {
77
+ billTo: 'patient',
78
+ },
79
+ specimenCollectedDateTime: undefined,
80
+ orderNotes: undefined,
81
+ },
82
+ removeDiagnosis: vi.fn(),
83
+ setDiagnoses: vi.fn(),
84
+ getActivePatientCoverages: vi.fn().mockResolvedValue([]),
85
+ updateBillingInformation: vi.fn(),
86
+ setSpecimenCollectedDateTime: vi.fn(),
87
+ setOrderNotes: vi.fn(),
88
+ validateOrder: vi.fn().mockReturnValue(undefined),
89
+ createOrderBundle: vi.fn().mockResolvedValue({
90
+ serviceRequest: {
91
+ resourceType: 'ServiceRequest',
92
+ id: 'order-123',
93
+ status: 'draft',
94
+ } as ServiceRequest,
95
+ }),
96
+ searchAvailableLabs: vi.fn().mockResolvedValue([mockLab]),
97
+ searchAvailableTests: vi.fn().mockResolvedValue([mockTest]),
98
+ setPerformingLab: vi.fn(),
99
+ setPerformingLabAccountNumber: vi.fn(),
100
+ addTest: vi.fn(),
101
+ removeTest: vi.fn(),
102
+ setTests: vi.fn(),
103
+ updateTestMetadata: vi.fn(),
104
+ addDiagnosis: vi.fn(),
105
+ };
106
+
107
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
108
+ vi.mocked(useParams).mockReturnValue({});
109
+ vi.spyOn(medplum, 'executeBot').mockResolvedValue({});
110
+ });
111
+
112
+ function setup(props: { patientId?: string } = {}): void {
113
+ vi.mocked(useParams).mockReturnValue({ patientId: props.patientId });
114
+
115
+ if (props.patientId) {
116
+ vi.spyOn(medplum, 'readResource').mockResolvedValue(HomerSimpson as typeof HomerSimpson & { id: string });
117
+ }
118
+
119
+ render(
120
+ <MemoryRouter>
121
+ <MedplumProvider medplum={medplum}>
122
+ <OrderLabsPage onSubmitLabOrder={mockOnSubmitLabOrder} />
123
+ </MedplumProvider>
124
+ </MemoryRouter>
125
+ );
126
+ }
127
+
128
+ test('Renders form fields', () => {
129
+ setup();
130
+
131
+ expect(screen.getByText('Requester')).toBeInTheDocument();
132
+ expect(screen.getByLabelText(/^Patient/)).toBeInTheDocument();
133
+ expect(screen.getByText('Selected tests')).toBeInTheDocument();
134
+ expect(screen.getByText('Diagnoses')).toBeInTheDocument();
135
+ expect(screen.getByText('Bill to')).toBeInTheDocument();
136
+ expect(screen.getByText('Order notes')).toBeInTheDocument();
137
+ expect(screen.getByText('Specimen collection time')).toBeInTheDocument();
138
+ expect(screen.getByRole('button', { name: 'Submit Order' })).toBeInTheDocument();
139
+ });
140
+
141
+ test('Loads patient when patientId is provided', async () => {
142
+ setup({ patientId: 'patient-123' });
143
+
144
+ await waitFor(() => {
145
+ expect(medplum.readResource).toHaveBeenCalledWith('Patient', 'patient-123');
146
+ });
147
+ });
148
+
149
+ test('Shows test metadata cards when tests are selected', () => {
150
+ const mockTest: TestCoding = {
151
+ code: 'TEST001',
152
+ display: 'Complete Blood Count',
153
+ system: 'http://loinc.org',
154
+ };
155
+
156
+ mockLabOrderReturn.state.selectedTests = [mockTest];
157
+ mockLabOrderReturn.state.testMetadata = {
158
+ TEST001: { aoeStatus: 'none' },
159
+ };
160
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
161
+
162
+ setup();
163
+
164
+ // Test metadata cards should be rendered (component will render them)
165
+ expect(screen.getByText('Selected tests')).toBeInTheDocument();
166
+ });
167
+
168
+ test('Shows coverage input when patient is set', async () => {
169
+ mockLabOrderReturn.state.performingLab = {
170
+ resourceType: 'Organization',
171
+ id: 'lab-123',
172
+ } as LabOrganization;
173
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
174
+
175
+ await act(async () => setup({ patientId: 'patient-123' }));
176
+ expect(screen.getByText('Bill to')).toBeInTheDocument();
177
+ });
178
+
179
+ test('Updates billing information when radio button is selected', async () => {
180
+ setup();
181
+
182
+ const insuranceRadio = screen.getByLabelText('Insurance');
183
+
184
+ await act(async () => {
185
+ fireEvent.click(insuranceRadio);
186
+ });
187
+
188
+ expect(mockLabOrderReturn.updateBillingInformation).toHaveBeenCalledWith({
189
+ billTo: 'insurance',
190
+ });
191
+ });
192
+
193
+ test('Updates order notes when text input changes', async () => {
194
+ setup();
195
+
196
+ const notesInput = screen.getByLabelText('Order notes');
197
+
198
+ await act(async () => {
199
+ fireEvent.change(notesInput, { target: { value: 'Test notes' } });
200
+ });
201
+
202
+ expect(mockLabOrderReturn.setOrderNotes).toHaveBeenCalledWith('Test notes');
203
+ });
204
+
205
+ test('Submits order successfully', async () => {
206
+ const mockServiceRequest: ServiceRequest = {
207
+ resourceType: 'ServiceRequest',
208
+ id: 'order-123',
209
+ status: 'draft',
210
+ } as ServiceRequest;
211
+
212
+ mockLabOrderReturn.createOrderBundle = vi.fn().mockResolvedValue({
213
+ serviceRequest: mockServiceRequest,
214
+ });
215
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
216
+ vi.spyOn(medplum, 'executeBot').mockResolvedValue({});
217
+
218
+ setup();
219
+
220
+ const submitButton = screen.getByRole('button', { name: 'Submit Order' });
221
+
222
+ await act(async () => {
223
+ fireEvent.click(submitButton);
224
+ });
225
+
226
+ await waitFor(() => {
227
+ expect(mockLabOrderReturn.createOrderBundle).toHaveBeenCalled();
228
+ });
229
+
230
+ await waitFor(() => {
231
+ expect(medplum.executeBot).toHaveBeenCalled();
232
+ });
233
+
234
+ const executeBotCall = (medplum.executeBot as ReturnType<typeof vi.fn>).mock.calls[0];
235
+ expect(executeBotCall[0]).toEqual({
236
+ system: 'https://www.medplum.com/integrations/bot-identifier',
237
+ value: 'health-gorilla-labs/send-to-health-gorilla',
238
+ });
239
+ expect(executeBotCall[1]).toEqual(mockServiceRequest);
240
+
241
+ expect(showNotification).toHaveBeenCalledWith({
242
+ title: 'Lab Order Submitted',
243
+ message: 'The lab order has been successfully submitted.',
244
+ color: 'green',
245
+ });
246
+
247
+ expect(mockOnSubmitLabOrder).toHaveBeenCalled();
248
+ });
249
+
250
+ test('Shows error when order submission fails', async () => {
251
+ const error = new Error('Submission failed');
252
+ mockLabOrderReturn.createOrderBundle = vi.fn().mockRejectedValue(error);
253
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
254
+
255
+ setup();
256
+
257
+ const submitButton = screen.getByRole('button', { name: 'Submit Order' });
258
+
259
+ await act(async () => {
260
+ fireEvent.click(submitButton);
261
+ });
262
+
263
+ await waitFor(() => {
264
+ expect(notifications.showErrorNotification).toHaveBeenCalledWith(error);
265
+ });
266
+
267
+ expect(showNotification).not.toHaveBeenCalledWith(
268
+ expect.objectContaining({
269
+ title: 'Lab Order Submitted',
270
+ })
271
+ );
272
+ expect(mockOnSubmitLabOrder).not.toHaveBeenCalled();
273
+ });
274
+
275
+ test('Shows error when bot execution fails', async () => {
276
+ const mockServiceRequest: ServiceRequest = {
277
+ resourceType: 'ServiceRequest',
278
+ id: 'order-123',
279
+ status: 'draft',
280
+ } as ServiceRequest;
281
+
282
+ const botError = new Error('Bot execution failed');
283
+ mockLabOrderReturn.createOrderBundle = vi.fn().mockResolvedValue({
284
+ serviceRequest: mockServiceRequest,
285
+ });
286
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
287
+ vi.spyOn(medplum, 'executeBot').mockRejectedValue(botError);
288
+
289
+ setup();
290
+
291
+ const submitButton = screen.getByRole('button', { name: 'Submit Order' });
292
+
293
+ await act(async () => {
294
+ fireEvent.click(submitButton);
295
+ });
296
+
297
+ await waitFor(() => {
298
+ expect(notifications.showErrorNotification).toHaveBeenCalledWith(botError);
299
+ });
300
+
301
+ expect(showNotification).not.toHaveBeenCalledWith(
302
+ expect.objectContaining({
303
+ title: 'Lab Order Submitted',
304
+ })
305
+ );
306
+ expect(mockOnSubmitLabOrder).not.toHaveBeenCalled();
307
+ });
308
+
309
+ test('Disables submit button while submitting', async () => {
310
+ const mockServiceRequest: ServiceRequest = {
311
+ resourceType: 'ServiceRequest',
312
+ id: 'order-123',
313
+ status: 'draft',
314
+ } as ServiceRequest;
315
+
316
+ let resolveCreateOrder: (() => void) | undefined;
317
+ const createOrderPromise = new Promise<{ serviceRequest: ServiceRequest }>((resolve) => {
318
+ resolveCreateOrder = () => resolve({ serviceRequest: mockServiceRequest });
319
+ });
320
+
321
+ mockLabOrderReturn.createOrderBundle = vi.fn().mockReturnValue(createOrderPromise);
322
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
323
+ vi.spyOn(medplum, 'executeBot').mockResolvedValue({});
324
+
325
+ setup();
326
+
327
+ const submitButton = screen.getByRole('button', { name: 'Submit Order' });
328
+
329
+ // Click button to start submission
330
+ fireEvent.click(submitButton);
331
+
332
+ // Wait for button to be disabled
333
+ await waitFor(() => {
334
+ expect(submitButton).toBeDisabled();
335
+ });
336
+
337
+ resolveCreateOrder?.();
338
+
339
+ await createOrderPromise;
340
+
341
+ // Wait for button to be enabled again
342
+ await waitFor(
343
+ () => {
344
+ expect(submitButton).not.toBeDisabled();
345
+ },
346
+ { timeout: 2000 }
347
+ );
348
+ });
349
+
350
+ test('Renders specimen collection date time input', () => {
351
+ setup();
352
+
353
+ expect(screen.getByText('Specimen collection time')).toBeInTheDocument();
354
+ expect(screen.getByText('Specimen collection time')).toBeInTheDocument();
355
+ });
356
+ });
@@ -0,0 +1,275 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Button, Container, Group, Input, Radio, Stack, TextInput } from '@mantine/core';
4
+ import { ContentType, createReference } from '@medplum/core';
5
+ import type { MedplumClient } from '@medplum/core';
6
+ import type { Encounter, Patient, Practitioner, Reference, ServiceRequest, Task } from '@medplum/fhirtypes';
7
+ import { NPI_SYSTEM } from '@medplum/health-gorilla-core';
8
+ import type {
9
+ BillingInformation,
10
+ DiagnosisCodeableConcept,
11
+ LabOrderInputErrors,
12
+ LabOrganization,
13
+ TestCoding,
14
+ } from '@medplum/health-gorilla-core';
15
+ import { HealthGorillaLabOrderProvider, useHealthGorillaLabOrder } from '@medplum/health-gorilla-react';
16
+ import {
17
+ AsyncAutocomplete,
18
+ DateTimeInput,
19
+ Panel,
20
+ ResourceInput,
21
+ useMedplum,
22
+ useResource,
23
+ ValueSetAutocomplete,
24
+ } from '@medplum/react';
25
+ import type { AsyncAutocompleteOption } from '@medplum/react';
26
+ import { PerformingLabInput } from '../../components/PerformingLabInput';
27
+ import { TestMetadataCardInput } from '../../components/labs/TestMetadataCardInput';
28
+ import { CoverageInput } from '../../components/labs/CoverageInput';
29
+ import { useState, useEffect, useRef } from 'react';
30
+ import type { JSX } from 'react';
31
+ import { useParams } from 'react-router';
32
+ import { showErrorNotification } from '../../utils/notifications';
33
+ import { showNotification } from '@mantine/notifications';
34
+
35
+ async function sendLabOrderToHealthGorilla(medplum: MedplumClient, labOrder: ServiceRequest): Promise<void> {
36
+ return medplum.executeBot(
37
+ {
38
+ system: 'https://www.medplum.com/integrations/bot-identifier',
39
+ value: 'health-gorilla-labs/send-to-health-gorilla',
40
+ },
41
+ labOrder,
42
+ ContentType.FHIR_JSON
43
+ );
44
+ }
45
+
46
+ export interface OrderLabsPageProps {
47
+ encounter?: Encounter | Reference<Encounter> | undefined;
48
+ task?: Task | Reference<Task> | undefined;
49
+ tests?: TestCoding[] | undefined;
50
+ performingLab?: LabOrganization | undefined;
51
+ onSubmitLabOrder: (serviceRequest?: ServiceRequest) => void;
52
+ }
53
+
54
+ export function OrderLabsPage(props: OrderLabsPageProps): JSX.Element {
55
+ const { encounter, task, tests, performingLab, onSubmitLabOrder } = props;
56
+ const medplum = useMedplum();
57
+ const { patientId } = useParams();
58
+ const [patient, setPatient] = useState<Patient | undefined>();
59
+ const [requester, setRequester] = useState<Practitioner | undefined>(medplum.getProfile() as Practitioner);
60
+ const encounterResource = useResource(encounter);
61
+ const taskResource = useResource(task);
62
+ const labOrderReturn = useHealthGorillaLabOrder({
63
+ patient,
64
+ requester,
65
+ });
66
+
67
+ const {
68
+ state,
69
+ searchAvailableTests,
70
+ setTests,
71
+ setDiagnoses,
72
+ updateBillingInformation,
73
+ setSpecimenCollectedDateTime,
74
+ setOrderNotes,
75
+ createOrderBundle,
76
+ } = labOrderReturn;
77
+
78
+ const [isSubmitting, setIsSubmitting] = useState(false);
79
+ const [createError, setCreateError] = useState<{ generic?: unknown; validation?: LabOrderInputErrors } | undefined>();
80
+ const prevTestsRef = useRef<TestCoding[] | undefined>(undefined);
81
+
82
+ useEffect(() => {
83
+ // Only set tests if they're provided and different from the previous value
84
+ // Compare by checking if arrays have different lengths or different test codes
85
+ const testsChanged =
86
+ tests &&
87
+ (prevTestsRef.current?.length !== tests.length ||
88
+ prevTestsRef.current.some((prevTest, index) => prevTest.code !== tests[index]?.code));
89
+
90
+ if (testsChanged) {
91
+ setTests(tests);
92
+ prevTestsRef.current = tests;
93
+ }
94
+ }, [tests, setTests]);
95
+
96
+ useEffect(() => {
97
+ if (patientId) {
98
+ medplum
99
+ .readResource('Patient', patientId)
100
+ .then((patient) => {
101
+ setPatient(patient);
102
+ })
103
+ .catch((error) => {
104
+ showErrorNotification(error);
105
+ });
106
+ }
107
+ }, [patientId, medplum]);
108
+
109
+ const submitOrder = async (): Promise<void> => {
110
+ try {
111
+ setIsSubmitting(true);
112
+ setCreateError(undefined);
113
+ let { serviceRequest } = await createOrderBundle();
114
+ if (encounterResource) {
115
+ serviceRequest = {
116
+ ...serviceRequest,
117
+ encounter: createReference(encounterResource),
118
+ };
119
+ }
120
+ if (taskResource) {
121
+ await medplum.updateResource({
122
+ ...taskResource,
123
+ output: [
124
+ ...(taskResource.output || []),
125
+ {
126
+ type: { text: 'ServiceRequest' },
127
+ valueReference: createReference(serviceRequest),
128
+ },
129
+ ],
130
+ focus: createReference(serviceRequest),
131
+ });
132
+ }
133
+ await sendLabOrderToHealthGorilla(medplum, serviceRequest);
134
+
135
+ showNotification({
136
+ title: 'Lab Order Submitted',
137
+ message: 'The lab order has been successfully submitted.',
138
+ color: 'green',
139
+ });
140
+ onSubmitLabOrder({ ...serviceRequest, status: 'active' });
141
+ } catch (error) {
142
+ setCreateError({ generic: error });
143
+ showErrorNotification(error);
144
+ } finally {
145
+ setIsSubmitting(false);
146
+ }
147
+ };
148
+
149
+ return (
150
+ <HealthGorillaLabOrderProvider {...labOrderReturn}>
151
+ <Container size="md">
152
+ <Panel>
153
+ <Stack gap="md">
154
+ <Input.Wrapper label="Requester" required error={createError?.validation?.requester?.message}>
155
+ <ResourceInput<Practitioner>
156
+ resourceType="Practitioner"
157
+ name="Requester"
158
+ onChange={setRequester}
159
+ defaultValue={requester}
160
+ searchCriteria={{ identifier: `${NPI_SYSTEM}|` }}
161
+ />
162
+ </Input.Wrapper>
163
+ <Input.Wrapper label="Patient" required error={createError?.validation?.patient?.message}>
164
+ <ResourceInput<Patient>
165
+ resourceType="Patient"
166
+ name="patient"
167
+ defaultValue={patient}
168
+ onChange={setPatient}
169
+ />
170
+ </Input.Wrapper>
171
+ <PerformingLabInput
172
+ patient={patient}
173
+ performingLab={performingLab}
174
+ error={createError?.validation?.performingLab}
175
+ />
176
+ <div>
177
+ <AsyncAutocomplete<TestCoding>
178
+ required
179
+ error={createError?.validation?.selectedTests?.message}
180
+ label="Selected tests"
181
+ disabled={!state.performingLab}
182
+ maxValues={10}
183
+ defaultValue={tests}
184
+ loadOptions={searchAvailableTests}
185
+ toOption={TestCodingToOption}
186
+ onChange={setTests}
187
+ />
188
+ {state.selectedTests.length > 0 && (
189
+ <Group mt="md" gap="md" align="flex-start" wrap="wrap">
190
+ {state.selectedTests.map((test: TestCoding) => (
191
+ <TestMetadataCardInput
192
+ key={test.code}
193
+ test={test}
194
+ metadata={state.testMetadata[test.code]}
195
+ error={createError?.validation?.testMetadata?.[test.code]}
196
+ />
197
+ ))}
198
+ </Group>
199
+ )}
200
+ </div>
201
+ <div>
202
+ <ValueSetAutocomplete
203
+ label="Diagnoses"
204
+ binding="http://hl7.org/fhir/sid/icd-10-cm/vs"
205
+ name="diagnoses"
206
+ maxValues={10}
207
+ onChange={(items) => {
208
+ const codeableConcepts = items.map((item) => ({ coding: [item] })) as DiagnosisCodeableConcept[];
209
+ setDiagnoses(codeableConcepts);
210
+ }}
211
+ />
212
+ </div>
213
+ <Group align="flex-start" gap={48}>
214
+ <div>
215
+ <Radio.Group
216
+ value={state.billingInformation.billTo}
217
+ error={createError?.validation?.billingInformation?.billTo?.message}
218
+ onChange={(newBillTo) => {
219
+ updateBillingInformation({ billTo: newBillTo as BillingInformation['billTo'] });
220
+ }}
221
+ label="Bill to"
222
+ withAsterisk
223
+ >
224
+ <Stack gap={4}>
225
+ <Radio value="patient" label="Patient" />
226
+ <Radio value="insurance" label="Insurance" />
227
+ <Radio value="customer-account" label="Customer" />
228
+ </Stack>
229
+ </Radio.Group>
230
+ </div>
231
+ {patient && (
232
+ <CoverageInput patient={patient} error={createError?.validation?.billingInformation?.patientCoverage} />
233
+ )}
234
+ </Group>
235
+ <TextInput label="Order notes" onChange={(e) => setOrderNotes(e.currentTarget.value)} />
236
+ <DateTimeInput
237
+ label="Specimen collection time"
238
+ name=""
239
+ onChange={(isoDateTimeString) => {
240
+ setSpecimenCollectedDateTime(isoDateTimeString ? new Date(isoDateTimeString) : undefined);
241
+ }}
242
+ />
243
+ <Group>
244
+ <Button onClick={submitOrder} loading={isSubmitting} disabled={isSubmitting}>
245
+ Submit Order
246
+ </Button>
247
+ </Group>
248
+ </Stack>
249
+ </Panel>
250
+ </Container>
251
+ </HealthGorillaLabOrderProvider>
252
+ );
253
+ }
254
+
255
+ function TestCodingToOption(element: TestCoding): AsyncAutocompleteOption<TestCoding> {
256
+ return {
257
+ value: toKey(element),
258
+ label: getDisplay(element),
259
+ resource: element,
260
+ };
261
+ }
262
+
263
+ function toKey(element: TestCoding): string {
264
+ if (typeof element.code === 'string') {
265
+ return element.code;
266
+ }
267
+ return JSON.stringify(element);
268
+ }
269
+
270
+ function getDisplay(item: TestCoding): string {
271
+ if (typeof item.display === 'string') {
272
+ return item.display;
273
+ }
274
+ return toKey(item);
275
+ }
@@ -0,0 +1,8 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: stretch;
5
+ height: calc(100vh - var(--app-shell-header-height, 0));
6
+ overflow: hidden;
7
+ flex: 1;
8
+ }