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,104 @@
1
+ # Lab Order Requisition PDF Display
2
+
3
+ This feature displays Lab Order Requisition PDFs in the Orders content section when a ServiceRequest is selected.
4
+
5
+ ## How it works
6
+
7
+ 1. When a user selects an order from the order list, the `LabOrderDetails` component is rendered
8
+ 2. The component extracts the Health Gorilla Requisition ID from the ServiceRequest's `requisition` field
9
+ 3. The component searches for `DocumentReference` resources that:
10
+ - Have a category of "LabOrderRequisition"
11
+ - Have an identifier with system "https://www.healthgorilla.com" and value matching the Requisition ID
12
+ 4. If found, the PDF attachments are displayed using the `AttachmentDisplay` component
13
+ 5. Users can view the PDF directly in the browser or download it
14
+
15
+ ## Implementation Details
16
+
17
+ ### Files Modified/Created
18
+
19
+ - `src/utils/documentReference.ts` - Utility function to fetch Lab Order Requisition documents
20
+ - `src/components/labs/LabOrderDetails.tsx` - Updated to display PDF previews
21
+
22
+ ### Key Components
23
+
24
+ - `fetchLabOrderRequisitionDocuments()` - Searches for DocumentReference resources with LabOrderRequisition category using Health Gorilla Requisition ID
25
+ - `getHealthGorillaRequisitionId()` - Extracts the Health Gorilla Requisition ID from ServiceRequest
26
+ - `AttachmentDisplay` - Medplum React component that renders PDFs in an iframe
27
+ - Lab Order Requisition section in `LabOrderDetails` - Shows loading state, documents, or "no documents found" message
28
+
29
+ ### ServiceRequest Structure
30
+
31
+ The ServiceRequest should have a Health Gorilla Requisition ID:
32
+
33
+ ```json
34
+ {
35
+ "resourceType": "ServiceRequest",
36
+ "requisition": {
37
+ "system": "https://www.healthgorilla.com",
38
+ "value": "123456"
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### DocumentReference Structure
44
+
45
+ The DocumentReference resources should have:
46
+
47
+ ```json
48
+ {
49
+ "resourceType": "DocumentReference",
50
+ "category": [
51
+ {
52
+ "coding": [
53
+ {
54
+ "system": "http://terminology.hl7.org/CodeSystem/document-classcodes",
55
+ "code": "LabOrderRequisition",
56
+ "display": "Lab Order Requisition"
57
+ }
58
+ ]
59
+ }
60
+ ],
61
+ "identifier": [
62
+ {
63
+ "system": "https://www.healthgorilla.com",
64
+ "value": "123456"
65
+ }
66
+ ],
67
+ "content": [
68
+ {
69
+ "attachment": {
70
+ "contentType": "application/pdf",
71
+ "url": "https://example.com/document.pdf"
72
+ }
73
+ }
74
+ ]
75
+ }
76
+ ```
77
+
78
+ ## Testing
79
+
80
+ To test this functionality:
81
+
82
+ 1. Create a ServiceRequest resource with a `requisition` field containing a Health Gorilla ID:
83
+ ```json
84
+ {
85
+ "requisition": {
86
+ "system": "https://www.healthgorilla.com",
87
+ "value": "123456"
88
+ }
89
+ }
90
+ ```
91
+ 2. Create a DocumentReference resource with:
92
+ - `category` set to "LabOrderRequisition"
93
+ - `identifier` with system "https://www.healthgorilla.com" and the same value as the ServiceRequest
94
+ - `content.attachment` with a PDF URL
95
+ 3. Navigate to the Labs page and select the order
96
+ 4. The PDF should appear in the "LAB ORDER REQUISITION" section
97
+
98
+ ## Notes
99
+
100
+ - The search uses the FHIR `identifier` parameter to match DocumentReference resources by Health Gorilla Requisition ID
101
+ - The matching is done by extracting the Health Gorilla ID from the ServiceRequest's `requisition` field
102
+ - PDFs are displayed using an iframe with `#navpanes=0` to hide navigation
103
+ - The component handles loading states and error cases gracefully
104
+ - Multiple documents can be displayed if multiple DocumentReference resources are found with the same Health Gorilla ID
@@ -0,0 +1,318 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import type { LabOrderInputErrors, TestCoding } from '@medplum/health-gorilla-core';
5
+ import { MockClient } from '@medplum/mock';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import { act, render, screen, waitFor, fireEvent } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { MemoryRouter } from 'react-router';
10
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
11
+ import { TestMetadataCardInput } from './TestMetadataCardInput';
12
+ import { useHealthGorillaLabOrder, HealthGorillaLabOrderProvider } from '@medplum/health-gorilla-react';
13
+ import type { TestMetadata } from '@medplum/health-gorilla-react';
14
+ import type { Questionnaire } from '@medplum/fhirtypes';
15
+
16
+ vi.mock('@medplum/health-gorilla-react', async () => {
17
+ const actual = await vi.importActual('@medplum/health-gorilla-react');
18
+ return {
19
+ ...actual,
20
+ useHealthGorillaLabOrder: vi.fn(),
21
+ };
22
+ });
23
+
24
+ const mockTest: TestCoding = {
25
+ code: 'TEST001',
26
+ display: 'Complete Blood Count',
27
+ system: 'http://loinc.org',
28
+ };
29
+
30
+ const mockQuestionnaire: Questionnaire = {
31
+ resourceType: 'Questionnaire',
32
+ status: 'active',
33
+ title: 'AOE Questionnaire',
34
+ item: [
35
+ {
36
+ linkId: 'q1',
37
+ text: 'Question 1',
38
+ type: 'string',
39
+ },
40
+ ],
41
+ };
42
+
43
+ function createMockMetadata(overrides?: Partial<TestMetadata>): TestMetadata {
44
+ return {
45
+ priority: 'routine',
46
+ notes: undefined,
47
+ aoeStatus: 'none',
48
+ aoeQuestionnaire: undefined,
49
+ aoeResponses: undefined,
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ describe('TestMetadataCardInput', () => {
55
+ let medplum: MockClient;
56
+ let mockUpdateTestMetadata: ReturnType<typeof vi.fn>;
57
+ let mockLabOrderReturn: ReturnType<typeof useHealthGorillaLabOrder>;
58
+
59
+ beforeEach(() => {
60
+ vi.clearAllMocks();
61
+ medplum = new MockClient();
62
+
63
+ mockUpdateTestMetadata = vi.fn();
64
+
65
+ mockLabOrderReturn = {
66
+ state: {
67
+ performingLab: undefined,
68
+ performingLabAccountNumber: undefined,
69
+ selectedTests: [],
70
+ testMetadata: {},
71
+ diagnoses: [],
72
+ billingInformation: {
73
+ billTo: 'insurance',
74
+ },
75
+ specimenCollectedDateTime: undefined,
76
+ orderNotes: undefined,
77
+ },
78
+ removeDiagnosis: vi.fn(),
79
+ setDiagnoses: vi.fn(),
80
+ getActivePatientCoverages: vi.fn().mockResolvedValue([]),
81
+ updateBillingInformation: vi.fn(),
82
+ setSpecimenCollectedDateTime: vi.fn(),
83
+ setOrderNotes: vi.fn(),
84
+ validateOrder: vi.fn().mockReturnValue(undefined),
85
+ createOrderBundle: vi.fn(),
86
+ searchAvailableLabs: vi.fn().mockResolvedValue([]),
87
+ searchAvailableTests: vi.fn().mockResolvedValue([]),
88
+ setPerformingLab: vi.fn(),
89
+ setPerformingLabAccountNumber: vi.fn(),
90
+ addTest: vi.fn(),
91
+ removeTest: vi.fn(),
92
+ setTests: vi.fn(),
93
+ updateTestMetadata: mockUpdateTestMetadata,
94
+ addDiagnosis: vi.fn(),
95
+ } as any;
96
+
97
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
98
+ });
99
+
100
+ function setup(
101
+ props: {
102
+ test?: TestCoding;
103
+ metadata?: TestMetadata | undefined;
104
+ error?: NonNullable<LabOrderInputErrors['testMetadata']>[keyof NonNullable<LabOrderInputErrors['testMetadata']>];
105
+ } = {}
106
+ ): ReturnType<typeof render> {
107
+ return render(
108
+ <MemoryRouter>
109
+ <MedplumProvider medplum={medplum}>
110
+ <MantineProvider>
111
+ <HealthGorillaLabOrderProvider {...mockLabOrderReturn}>
112
+ <TestMetadataCardInput test={props.test || mockTest} metadata={props.metadata} error={props.error} />
113
+ </HealthGorillaLabOrderProvider>
114
+ </MantineProvider>
115
+ </MedplumProvider>
116
+ </MemoryRouter>
117
+ );
118
+ }
119
+
120
+ test('Renders "Missing metadata" when metadata is undefined', () => {
121
+ setup({ metadata: undefined });
122
+
123
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
124
+ expect(screen.getByText('Missing metadata')).toBeInTheDocument();
125
+ expect(screen.queryByLabelText('Priority')).not.toBeInTheDocument();
126
+ expect(screen.queryByLabelText('Notes')).not.toBeInTheDocument();
127
+ });
128
+
129
+ test('Renders form fields when metadata exists', () => {
130
+ const metadata = createMockMetadata();
131
+ setup({ metadata });
132
+
133
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
134
+ expect(screen.getByText('Priority')).toBeInTheDocument();
135
+ expect(screen.getByLabelText('Notes')).toBeInTheDocument();
136
+ expect(screen.queryByText('Missing metadata')).not.toBeInTheDocument();
137
+ });
138
+
139
+ test('Renders all priority radio options', () => {
140
+ const metadata = createMockMetadata({ priority: 'routine' });
141
+ setup({ metadata });
142
+
143
+ expect(screen.getByLabelText('Routine')).toBeInTheDocument();
144
+ expect(screen.getByLabelText('Urgent')).toBeInTheDocument();
145
+ expect(screen.getByLabelText('ASAP')).toBeInTheDocument();
146
+ expect(screen.getByLabelText('Stat')).toBeInTheDocument();
147
+ });
148
+
149
+ test('Selects correct priority radio option', () => {
150
+ const metadata = createMockMetadata({ priority: 'urgent' });
151
+ setup({ metadata });
152
+
153
+ const urgentRadio = screen.getByLabelText('Urgent');
154
+ expect(urgentRadio).toBeChecked();
155
+ });
156
+
157
+ test('Calls updateTestMetadata when priority changes', async () => {
158
+ const user = userEvent.setup();
159
+ const metadata = createMockMetadata({ priority: 'routine' });
160
+ setup({ metadata });
161
+
162
+ const urgentRadio = screen.getByLabelText('Urgent');
163
+ await act(async () => {
164
+ await user.click(urgentRadio);
165
+ });
166
+
167
+ expect(mockUpdateTestMetadata).toHaveBeenCalledWith(mockTest, { priority: 'urgent' });
168
+ });
169
+
170
+ test('Calls updateTestMetadata when priority changes to asap', async () => {
171
+ const user = userEvent.setup();
172
+ const metadata = createMockMetadata({ priority: 'routine' });
173
+ setup({ metadata });
174
+
175
+ const asapRadio = screen.getByLabelText('ASAP');
176
+ await act(async () => {
177
+ await user.click(asapRadio);
178
+ });
179
+
180
+ expect(mockUpdateTestMetadata).toHaveBeenCalledWith(mockTest, { priority: 'asap' });
181
+ });
182
+
183
+ test('Calls updateTestMetadata when priority changes to stat', async () => {
184
+ const user = userEvent.setup();
185
+ const metadata = createMockMetadata({ priority: 'routine' });
186
+ setup({ metadata });
187
+
188
+ const statRadio = screen.getByLabelText('Stat');
189
+ await act(async () => {
190
+ await user.click(statRadio);
191
+ });
192
+
193
+ expect(mockUpdateTestMetadata).toHaveBeenCalledWith(mockTest, { priority: 'stat' });
194
+ });
195
+
196
+ test('Displays notes value in TextInput', () => {
197
+ const metadata = createMockMetadata({ notes: 'Test notes here' });
198
+ setup({ metadata });
199
+
200
+ const notesInput = screen.getByLabelText<HTMLInputElement>('Notes');
201
+ expect(notesInput.value).toBe('Test notes here');
202
+ });
203
+
204
+ test('Displays empty string when notes is undefined', () => {
205
+ const metadata = createMockMetadata({ notes: undefined });
206
+ setup({ metadata });
207
+
208
+ const notesInput = screen.getByLabelText<HTMLInputElement>('Notes');
209
+ expect(notesInput.value).toBe('');
210
+ });
211
+
212
+ test('Calls updateTestMetadata when notes change', async () => {
213
+ const metadata = createMockMetadata({ notes: '' });
214
+ setup({ metadata });
215
+
216
+ const notesInput = screen.getByLabelText('Notes');
217
+ await act(async () => {
218
+ fireEvent.change(notesInput, { target: { value: 'Test note' } });
219
+ });
220
+
221
+ expect(mockUpdateTestMetadata).toHaveBeenCalledWith(mockTest, { notes: 'Test note' });
222
+ });
223
+
224
+ test('Shows "Loading AoE..." when aoeStatus is loading', () => {
225
+ const metadata = createMockMetadata({ aoeStatus: 'loading' });
226
+ setup({ metadata });
227
+
228
+ expect(screen.getByText('Loading AoE...')).toBeInTheDocument();
229
+ });
230
+
231
+ test('Renders QuestionnaireForm when aoeStatus is loaded and questionnaire exists', async () => {
232
+ const metadata = createMockMetadata({
233
+ aoeStatus: 'loaded',
234
+ aoeQuestionnaire: mockQuestionnaire,
235
+ });
236
+ setup({ metadata });
237
+
238
+ await waitFor(() => {
239
+ expect(screen.getByText('Question 1')).toBeInTheDocument();
240
+ });
241
+ });
242
+
243
+ test('Does not render QuestionnaireForm when aoeStatus is loaded but questionnaire is undefined', () => {
244
+ const metadata = createMockMetadata({
245
+ aoeStatus: 'loaded',
246
+ aoeQuestionnaire: undefined,
247
+ });
248
+ setup({ metadata });
249
+
250
+ expect(screen.queryByText('Question 1')).not.toBeInTheDocument();
251
+ });
252
+
253
+ test('Displays error message for priority', () => {
254
+ const error: NonNullable<LabOrderInputErrors['testMetadata']>[keyof NonNullable<
255
+ LabOrderInputErrors['testMetadata']
256
+ >] = {
257
+ priority: { message: 'Priority is required' },
258
+ };
259
+ const metadata = createMockMetadata();
260
+ setup({ metadata, error });
261
+
262
+ expect(screen.getByText('Priority is required')).toBeInTheDocument();
263
+ });
264
+
265
+ test('Displays error message for aoeResponses', () => {
266
+ const error: NonNullable<LabOrderInputErrors['testMetadata']>[keyof NonNullable<
267
+ LabOrderInputErrors['testMetadata']
268
+ >] = {
269
+ aoeResponses: { message: 'AOE responses are invalid' },
270
+ };
271
+ const metadata = createMockMetadata({
272
+ aoeStatus: 'loaded',
273
+ aoeQuestionnaire: mockQuestionnaire,
274
+ });
275
+ setup({ metadata, error });
276
+
277
+ expect(screen.getByText('AOE responses are invalid')).toBeInTheDocument();
278
+ });
279
+
280
+ test('Calls updateTestMetadata when questionnaire response changes', async () => {
281
+ const metadata = createMockMetadata({
282
+ aoeStatus: 'loaded',
283
+ aoeQuestionnaire: mockQuestionnaire,
284
+ });
285
+ setup({ metadata });
286
+
287
+ await waitFor(() => {
288
+ expect(screen.getByText('Question 1')).toBeInTheDocument();
289
+ });
290
+
291
+ const questionInput = screen.getByLabelText('Question 1');
292
+ await act(async () => {
293
+ await userEvent.type(questionInput, 'Answer');
294
+ });
295
+
296
+ await waitFor(() => {
297
+ expect(mockUpdateTestMetadata).toHaveBeenCalled();
298
+ });
299
+
300
+ const calls = mockUpdateTestMetadata.mock.calls;
301
+ const lastCall = calls[calls.length - 1];
302
+ expect(lastCall[0]).toBe(mockTest);
303
+ expect(lastCall[1]).toHaveProperty('aoeResponses');
304
+ expect(lastCall[1].aoeResponses).toHaveProperty('resourceType', 'QuestionnaireResponse');
305
+ });
306
+
307
+ test('Renders test display name correctly', () => {
308
+ const customTest: TestCoding = {
309
+ code: 'TEST002',
310
+ display: 'Lipid Panel',
311
+ system: 'http://loinc.org',
312
+ };
313
+ const metadata = createMockMetadata();
314
+ setup({ test: customTest, metadata });
315
+
316
+ expect(screen.getByText('Lipid Panel')).toBeInTheDocument();
317
+ });
318
+ });
@@ -0,0 +1,87 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Card, Radio, Stack, Text, TextInput } from '@mantine/core';
4
+ import type { LabOrderInputErrors, TestCoding } from '@medplum/health-gorilla-core';
5
+ import { useHealthGorillaLabOrderContext } from '@medplum/health-gorilla-react';
6
+ import type { TestMetadata } from '@medplum/health-gorilla-react';
7
+ import { QuestionnaireForm } from '@medplum/react';
8
+ import type { JSX } from 'react';
9
+
10
+ export type TestMetadataCardInputProps = {
11
+ test: TestCoding;
12
+ metadata: TestMetadata | undefined;
13
+ error?: NonNullable<LabOrderInputErrors['testMetadata']>[keyof NonNullable<LabOrderInputErrors['testMetadata']>];
14
+ };
15
+
16
+ export function TestMetadataCardInput({ test, metadata, error }: TestMetadataCardInputProps): JSX.Element {
17
+ const { updateTestMetadata } = useHealthGorillaLabOrderContext();
18
+
19
+ if (!metadata) {
20
+ return (
21
+ <Card key={test.code} withBorder shadow="none">
22
+ <Text fw={500}>{test.display}</Text>
23
+ <div>Missing metadata</div>
24
+ </Card>
25
+ );
26
+ }
27
+
28
+ return (
29
+ <Card key={test.code} withBorder shadow="none">
30
+ <Stack gap="xs">
31
+ <Text fw={500}>{test.display}</Text>
32
+ {!metadata ? (
33
+ <div>Missing metadata</div>
34
+ ) : (
35
+ <>
36
+ <Radio.Group
37
+ value={metadata.priority}
38
+ error={error?.priority?.message}
39
+ onChange={(newValue) => {
40
+ if (!newValue) {
41
+ console.warn('New value for priority unexpectedly falsey', newValue);
42
+ return;
43
+ }
44
+ const newPriority = newValue as TestMetadata['priority'];
45
+ updateTestMetadata(test, { priority: newPriority });
46
+ }}
47
+ label="Priority"
48
+ withAsterisk
49
+ >
50
+ <Stack gap={4}>
51
+ <Radio value="routine" label="Routine" />
52
+ <Radio value="urgent" label="Urgent" />
53
+ <Radio value="asap" label="ASAP" />
54
+ <Radio value="stat" label="Stat" />
55
+ </Stack>
56
+ </Radio.Group>
57
+
58
+ <TextInput
59
+ label="Notes"
60
+ value={metadata.notes ?? ''}
61
+ onChange={(event) => {
62
+ updateTestMetadata(test, { notes: event.currentTarget.value });
63
+ }}
64
+ placeholder="Test notes"
65
+ />
66
+
67
+ {metadata.aoeStatus === 'loading' && <Text>Loading AoE...</Text>}
68
+ {metadata.aoeStatus === 'error' && <Text>Error fetching AoE</Text>}
69
+ {metadata.aoeStatus === 'loaded' && metadata.aoeQuestionnaire && (
70
+ <>
71
+ {error?.aoeResponses?.message && <Text c="red">{error.aoeResponses.message}</Text>}
72
+ <QuestionnaireForm
73
+ questionnaire={metadata.aoeQuestionnaire}
74
+ disablePagination
75
+ excludeButtons
76
+ onChange={(qr) => {
77
+ updateTestMetadata(test, { aoeResponses: qr });
78
+ }}
79
+ />
80
+ </>
81
+ )}
82
+ </>
83
+ )}
84
+ </Stack>
85
+ </Card>
86
+ );
87
+ }
@@ -0,0 +1,126 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import type { Communication, Patient } from '@medplum/fhirtypes';
5
+ import { MockClient } from '@medplum/mock';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import { render, screen, waitFor } from '@testing-library/react';
8
+ import { MemoryRouter } from 'react-router';
9
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
10
+ import { ChatList } from './ChatList';
11
+
12
+ const mockPatient1: Patient = {
13
+ resourceType: 'Patient',
14
+ id: 'patient-1',
15
+ name: [{ given: ['John'], family: 'Doe' }],
16
+ };
17
+
18
+ const mockPatient2: Patient = {
19
+ resourceType: 'Patient',
20
+ id: 'patient-2',
21
+ name: [{ given: ['Jane'], family: 'Smith' }],
22
+ };
23
+
24
+ const mockCommunication1: Communication = {
25
+ resourceType: 'Communication',
26
+ id: 'comm-1',
27
+ status: 'in-progress',
28
+ topic: { text: 'Topic 1' },
29
+ subject: { reference: 'Patient/patient-1' },
30
+ };
31
+
32
+ const mockCommunication2: Communication = {
33
+ resourceType: 'Communication',
34
+ id: 'comm-2',
35
+ status: 'in-progress',
36
+ topic: { text: 'Topic 2' },
37
+ subject: { reference: 'Patient/patient-2' },
38
+ };
39
+
40
+ const mockLastCommunication1: Communication = {
41
+ resourceType: 'Communication',
42
+ id: 'last-comm-1',
43
+ status: 'in-progress',
44
+ payload: [{ contentString: 'Last message 1' }],
45
+ sent: '2024-01-01T12:00:00Z',
46
+ };
47
+
48
+ const mockGetThreadUri = vi.fn((topic: Communication) => `/Message/${topic.id}`);
49
+
50
+ describe('ChatList', () => {
51
+ let medplum: MockClient;
52
+
53
+ beforeEach(async () => {
54
+ medplum = new MockClient();
55
+ vi.clearAllMocks();
56
+
57
+ // Create patient resources
58
+ await medplum.createResource(mockPatient1);
59
+ await medplum.createResource(mockPatient2);
60
+ });
61
+
62
+ const setup = (
63
+ threads: [Communication, Communication | undefined][],
64
+ selectedCommunication?: Communication
65
+ ): void => {
66
+ render(
67
+ <MemoryRouter>
68
+ <MedplumProvider medplum={medplum}>
69
+ <MantineProvider>
70
+ <ChatList threads={threads} selectedCommunication={selectedCommunication} getThreadUri={mockGetThreadUri} />
71
+ </MantineProvider>
72
+ </MedplumProvider>
73
+ </MemoryRouter>
74
+ );
75
+ };
76
+
77
+ test('renders empty list when no threads', () => {
78
+ setup([]);
79
+ expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
80
+ });
81
+
82
+ test('renders single thread', async () => {
83
+ setup([[mockCommunication1, mockLastCommunication1]]);
84
+ await waitFor(() => {
85
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
86
+ });
87
+ expect(screen.getByText('Topic 1')).toBeInTheDocument();
88
+ });
89
+
90
+ test('renders multiple threads', async () => {
91
+ setup([
92
+ [mockCommunication1, mockLastCommunication1],
93
+ [mockCommunication2, undefined],
94
+ ]);
95
+ await waitFor(() => {
96
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
97
+ expect(screen.getByText('Jane Smith')).toBeInTheDocument();
98
+ });
99
+ });
100
+
101
+ test('renders thread with last communication message', async () => {
102
+ setup([[mockCommunication1, mockLastCommunication1]]);
103
+ await waitFor(() => {
104
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
105
+ });
106
+ // When topic has text, it shows the topic text instead of message content
107
+ expect(screen.getByText('Topic 1')).toBeInTheDocument();
108
+ });
109
+
110
+ test('handles thread without last communication', async () => {
111
+ setup([[mockCommunication1, undefined]]);
112
+ await waitFor(() => {
113
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
114
+ });
115
+ // When topic has text, it shows the topic text instead of "No messages available"
116
+ expect(screen.getByText('Topic 1')).toBeInTheDocument();
117
+ });
118
+
119
+ test('renders link with correct href from getThreadUri', async () => {
120
+ setup([[mockCommunication1, mockLastCommunication1]]);
121
+ await waitFor(() => {
122
+ const link = screen.getByText('John Doe').closest('a');
123
+ expect(link).toHaveAttribute('href', '/Message/comm-1');
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,38 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Divider, Stack } from '@mantine/core';
4
+ import type { Communication } from '@medplum/fhirtypes';
5
+ import { Fragment } from 'react';
6
+ import type { JSX } from 'react';
7
+ import { ChatListItem } from './ChatListItem';
8
+
9
+ interface ChatListProps {
10
+ threads: [Communication, Communication | undefined][];
11
+ selectedCommunication: Communication | undefined;
12
+ getThreadUri: (topic: Communication) => string;
13
+ }
14
+
15
+ export const ChatList = (props: ChatListProps): JSX.Element => {
16
+ const { threads, selectedCommunication, getThreadUri } = props;
17
+
18
+ return (
19
+ <Stack gap={0}>
20
+ {threads.map((thread: [Communication, Communication | undefined]) => {
21
+ const topicCommunication = thread[0];
22
+ const lastCommunication = thread[1];
23
+ const _isSelected = selectedCommunication?.id === topicCommunication.id;
24
+ return (
25
+ <Fragment key={topicCommunication.id}>
26
+ <ChatListItem
27
+ topic={topicCommunication}
28
+ lastCommunication={lastCommunication}
29
+ isSelected={_isSelected}
30
+ getThreadUri={getThreadUri}
31
+ />
32
+ <Divider />
33
+ </Fragment>
34
+ );
35
+ })}
36
+ </Stack>
37
+ );
38
+ };
@@ -0,0 +1,23 @@
1
+ .content {
2
+ word-wrap: break-word;
3
+ overflow-wrap: break-word;
4
+ white-space: normal;
5
+ word-break: break-word;
6
+ }
7
+
8
+ .contentContainer:hover {
9
+ background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-9));
10
+ }
11
+
12
+ .contentContainer {
13
+ transition: background 0.2s;
14
+ cursor: pointer;
15
+ transition: all 0.2s ease;
16
+ border: 1px solid transparent;
17
+ text-decoration: none;
18
+ color: light-dark(var(--mantine-color-dark-9), var(--mantine-color-gray-0));
19
+ }
20
+
21
+ .contentContainer.selected {
22
+ background: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-9));
23
+ }