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,170 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Divider, Flex, Paper, Stack, Text } from '@mantine/core';
4
+ import type { PaperProps } from '@mantine/core';
5
+ import { getReferenceString } from '@medplum/core';
6
+ import type { Media, Organization, Patient, Practitioner, Reference, ResourceType, Task } from '@medplum/fhirtypes';
7
+ import { CodeInput, DateTimeInput, ReferenceInput, ResourceInput, useMedplum } from '@medplum/react';
8
+ import React, { useEffect, useState } from 'react';
9
+ import { getTaskAttachments } from '../../../lib/file-upload';
10
+ import { TaskAttachmentList } from './TaskAttachmentList';
11
+
12
+ interface TaskPropertiesProps extends PaperProps {
13
+ task: Task;
14
+ onTaskChange: (task: Task) => void;
15
+ }
16
+
17
+ export function TaskProperties(props: TaskPropertiesProps): React.JSX.Element {
18
+ const { task: initialTask, onTaskChange, ...paperProps } = props;
19
+ const medplum = useMedplum();
20
+ const [task, setTask] = useState<Task | undefined>(initialTask);
21
+ const [dueDate, setDueDate] = useState<string | undefined>(task?.restriction?.period?.end);
22
+ const [attachments, setAttachments] = useState<Media[]>([]);
23
+
24
+ useEffect(() => {
25
+ setTask(initialTask);
26
+
27
+ // Load attachments when task changes
28
+ const loadAttachments = async (): Promise<void> => {
29
+ if (initialTask) {
30
+ const taskAttachments = await getTaskAttachments(medplum, initialTask);
31
+ setAttachments(taskAttachments);
32
+ }
33
+ };
34
+
35
+ void loadAttachments();
36
+ }, [initialTask, medplum]);
37
+
38
+ const handleDueDateChange = async (value: string | undefined): Promise<void> => {
39
+ setDueDate(value);
40
+ await handleTaskUpdate({ ...task, restriction: { ...task?.restriction, period: { end: value } } } as Task);
41
+ };
42
+
43
+ const handlePatientChange = async (value: Reference<Patient> | undefined): Promise<void> => {
44
+ await handleTaskUpdate({ ...task, for: value } as Task);
45
+ };
46
+
47
+ const handlePriorityChange = async (value: string): Promise<void> => {
48
+ await handleTaskUpdate({ ...task, priority: value as Task['priority'] } as Task);
49
+ };
50
+
51
+ const handleOwnerChange = async (value: Reference<Practitioner | Organization> | undefined): Promise<void> => {
52
+ await handleTaskUpdate({ ...task, owner: value } as Task);
53
+ };
54
+
55
+ const handleStatusChange = async (value: string | undefined): Promise<void> => {
56
+ if (value) {
57
+ await handleTaskUpdate({ ...task, status: value as Task['status'] } as Task);
58
+ }
59
+ };
60
+
61
+ const handleTaskUpdate = async (value: Task): Promise<void> => {
62
+ onTaskChange(value);
63
+ };
64
+
65
+ return (
66
+ <Paper {...paperProps}>
67
+ <Flex direction="column" gap="lg">
68
+ <Stack gap="xs">
69
+ <CodeInput
70
+ key={`${task?.status}-${task?.id}`}
71
+ name="status"
72
+ label="Status"
73
+ binding="http://hl7.org/fhir/ValueSet/task-status"
74
+ maxValues={1}
75
+ defaultValue={task?.status}
76
+ onChange={handleStatusChange}
77
+ />
78
+
79
+ <DateTimeInput
80
+ name="Due Date"
81
+ placeholder="End"
82
+ label="Due Date"
83
+ defaultValue={dueDate}
84
+ onChange={handleDueDateChange}
85
+ />
86
+
87
+ <Stack gap={0}>
88
+ <Text size="sm" fw={500}>
89
+ Assignee
90
+ </Text>
91
+ <ReferenceInput
92
+ name="owner"
93
+ targetTypes={['Practitioner', 'Organization']}
94
+ defaultValue={task?.owner ? { reference: task.owner.reference } : undefined}
95
+ onChange={async (value: Reference<Practitioner | Organization> | undefined) => {
96
+ await handleOwnerChange(value ? { reference: value.reference } : undefined);
97
+ }}
98
+ />
99
+ </Stack>
100
+
101
+ <CodeInput
102
+ name="priority"
103
+ label="Priority"
104
+ binding="http://hl7.org/fhir/ValueSet/request-priority"
105
+ maxValues={1}
106
+ defaultValue={task?.priority?.toString()}
107
+ onChange={(value: string | undefined) => handlePriorityChange(value ?? '')}
108
+ />
109
+
110
+ {task?.basedOn && task.basedOn.length > 0 ? (
111
+ <ResourceInput
112
+ label="Based On"
113
+ resourceType={task.basedOn[0].reference?.split('/').pop() as ResourceType}
114
+ name="basedOn-0"
115
+ defaultValue={task.basedOn[0]}
116
+ disabled
117
+ />
118
+ ) : (
119
+ <Stack gap={0}>
120
+ <Text size="sm" fw={500}>
121
+ Based On
122
+ </Text>
123
+ <ReferenceInput
124
+ name="basedOn"
125
+ placeholder="Select any resource..."
126
+ onChange={async (value: Reference | undefined) => {
127
+ if (value?.reference) {
128
+ const newBasedOn = [...(task?.basedOn || []), value];
129
+ await handleTaskUpdate({ ...task, basedOn: newBasedOn } as Task);
130
+ }
131
+ }}
132
+ />
133
+ </Stack>
134
+ )}
135
+ </Stack>
136
+
137
+ <Divider />
138
+
139
+ <Stack gap="xs" pt="md">
140
+ <ResourceInput
141
+ label="Patient"
142
+ resourceType="Patient"
143
+ name="patient"
144
+ placeholder="Search for patient"
145
+ defaultValue={task?.for as Reference<Patient>}
146
+ onChange={async (patient: Patient | undefined) => {
147
+ await handlePatientChange(patient ? { reference: getReferenceString(patient) } : undefined);
148
+ }}
149
+ />
150
+
151
+ {task?.encounter && (
152
+ <ResourceInput label="Encounter" resourceType="Encounter" name="encounter" defaultValue={task.encounter} />
153
+ )}
154
+ </Stack>
155
+
156
+ {attachments.length > 0 && (
157
+ <>
158
+ <Divider />
159
+ <Stack gap="xs" pt="md">
160
+ <Text size="sm" fw={500}>
161
+ Attachments
162
+ </Text>
163
+ <TaskAttachmentList attachments={attachments} readOnly />
164
+ </Stack>
165
+ </>
166
+ )}
167
+ </Flex>
168
+ </Paper>
169
+ );
170
+ }
@@ -0,0 +1,32 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { describe, expect, test } from 'vitest';
6
+ import { TaskSelectEmpty } from './TaskSelectEmpty';
7
+
8
+ describe('TaskSelectEmpty', () => {
9
+ const setup = (props: { notFound?: boolean } = {}): ReturnType<typeof render> => {
10
+ return render(
11
+ <MantineProvider>
12
+ <TaskSelectEmpty {...props} />
13
+ </MantineProvider>
14
+ );
15
+ };
16
+
17
+ test('renders "No task selected" message by default', () => {
18
+ setup();
19
+ expect(screen.getByText('No task selected')).toBeInTheDocument();
20
+ expect(
21
+ screen.getByText('Select a task from the list to view details, add notes, and manage properties')
22
+ ).toBeInTheDocument();
23
+ });
24
+
25
+ test('renders "Task not found" message when notFound is true', () => {
26
+ setup({ notFound: true });
27
+ expect(screen.getByText('Task not found')).toBeInTheDocument();
28
+ expect(
29
+ screen.queryByText('Select a task from the list to view details, add notes, and manage properties')
30
+ ).not.toBeInTheDocument();
31
+ });
32
+ });
@@ -0,0 +1,34 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Center, Flex, Stack, Text, ThemeIcon } from '@mantine/core';
4
+ import { IconClipboardList } from '@tabler/icons-react';
5
+ import type { JSX } from 'react';
6
+
7
+ interface TaskSelectEmptyProps {
8
+ notFound?: boolean;
9
+ }
10
+
11
+ export function TaskSelectEmpty(props: TaskSelectEmptyProps): JSX.Element {
12
+ const { notFound = false } = props;
13
+ return (
14
+ <Flex direction="row" h="100%" w="100%">
15
+ <Center h="100%" w="100%">
16
+ <Stack align="center" gap="md">
17
+ <ThemeIcon size={64} variant="light" color="gray">
18
+ <IconClipboardList size={32} />
19
+ </ThemeIcon>
20
+ <Stack align="center" gap="xs">
21
+ <Text size="lg" fw={500} c="dimmed">
22
+ {notFound ? 'Task not found' : 'No task selected'}
23
+ </Text>
24
+ {!notFound && (
25
+ <Text size="sm" c="dimmed" ta="center">
26
+ Select a task from the list to view details, add notes, and manage properties
27
+ </Text>
28
+ )}
29
+ </Stack>
30
+ </Stack>
31
+ </Center>
32
+ </Flex>
33
+ );
34
+ }
@@ -0,0 +1,47 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { render, screen } from '@testing-library/react';
4
+ import { describe, expect, test } from 'vitest';
5
+ import { SimpleTask } from './SimpleTask';
6
+ import type { Task } from '@medplum/fhirtypes';
7
+ import { MantineProvider } from '@mantine/core';
8
+
9
+ describe('SimpleTask', () => {
10
+ const mockTask: Task = {
11
+ resourceType: 'Task',
12
+ id: 'task-123',
13
+ status: 'in-progress',
14
+ intent: 'order',
15
+ code: { text: 'Test Task Code' },
16
+ description: 'Test Task Description',
17
+ };
18
+
19
+ const setup = (task: Task): void => {
20
+ render(
21
+ <MantineProvider>
22
+ <SimpleTask task={task} />
23
+ </MantineProvider>
24
+ );
25
+ };
26
+
27
+ test('renders task details correctly', () => {
28
+ setup(mockTask);
29
+ expect(screen.getByText('Test Task Code')).toBeInTheDocument();
30
+ expect(screen.getByText('Test Task Description')).toBeInTheDocument();
31
+ });
32
+
33
+ test('renders View Service Request button when focus is ServiceRequest', () => {
34
+ const taskWithFocus: Task = {
35
+ ...mockTask,
36
+ focus: { reference: 'ServiceRequest/123' },
37
+ };
38
+ setup(taskWithFocus);
39
+ expect(screen.getByText('View Service Request')).toBeInTheDocument();
40
+ expect(screen.getByText('View Service Request').closest('a')).toHaveAttribute('href', '/ServiceRequest/123');
41
+ });
42
+
43
+ test('does not render View Service Request button when focus is not ServiceRequest', () => {
44
+ setup(mockTask);
45
+ expect(screen.queryByText('View Service Request')).not.toBeInTheDocument();
46
+ });
47
+ });
@@ -0,0 +1,29 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Box, Button, Stack, Text } from '@mantine/core';
4
+ import type { Task } from '@medplum/fhirtypes';
5
+ import type { JSX } from 'react';
6
+
7
+ interface SimpleTaskProps {
8
+ task: Task;
9
+ }
10
+
11
+ export const SimpleTask = ({ task }: SimpleTaskProps): JSX.Element => {
12
+ return (
13
+ <Box p="md">
14
+ <Stack gap="xs">
15
+ {task.code?.text && (
16
+ <Text fw={500} size="lg">
17
+ {task.code.text}
18
+ </Text>
19
+ )}
20
+ <Text>{task.description}</Text>
21
+ {task.focus?.reference?.startsWith('ServiceRequest/') && (
22
+ <Button component="a" href={`/${task.focus.reference}`} target="_blank">
23
+ View Service Request
24
+ </Button>
25
+ )}
26
+ </Stack>
27
+ </Box>
28
+ );
29
+ };
@@ -0,0 +1,285 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import { Notifications } from '@mantine/notifications';
5
+ import { render, screen, waitFor, act } from '@testing-library/react';
6
+ import userEvent from '@testing-library/user-event';
7
+ import { MedplumProvider } from '@medplum/react';
8
+ import { MockClient } from '@medplum/mock';
9
+ import { MemoryRouter, Routes, Route } from 'react-router';
10
+ import * as reactRouter from 'react-router';
11
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
12
+ import { TaskPanel } from './TaskPanel';
13
+ import type { Questionnaire, QuestionnaireResponse, ServiceRequest, Task } from '@medplum/fhirtypes';
14
+
15
+ describe('TaskPanel', () => {
16
+ let medplum: MockClient;
17
+ let navigateSpy: ReturnType<typeof vi.fn>;
18
+
19
+ beforeEach(() => {
20
+ medplum = new MockClient();
21
+ vi.clearAllMocks();
22
+ navigateSpy = vi.fn().mockReturnValue(Promise.resolve());
23
+ vi.spyOn(reactRouter, 'useNavigate').mockReturnValue(navigateSpy as any);
24
+ });
25
+
26
+ const setup = async (task: Task, onUpdateTask: (task: Task) => void, enabled = true): Promise<void> => {
27
+ await act(async () => {
28
+ render(
29
+ <MemoryRouter initialEntries={['/Patient/123/Encounter/456']}>
30
+ <MedplumProvider medplum={medplum}>
31
+ <MantineProvider>
32
+ <Notifications />
33
+ <Routes>
34
+ <Route
35
+ path="/Patient/:patientId/Encounter/:encounterId"
36
+ element={<TaskPanel task={task} onUpdateTask={onUpdateTask} enabled={enabled} />}
37
+ />
38
+ <Route
39
+ path="/Patient/:patientId/Encounter/:encounterId/Task/:taskId"
40
+ element={<div>Task Detail Page</div>}
41
+ />
42
+ </Routes>
43
+ </MantineProvider>
44
+ </MedplumProvider>
45
+ </MemoryRouter>
46
+ );
47
+ });
48
+ };
49
+
50
+ const mockTask: Task = {
51
+ resourceType: 'Task',
52
+ id: 'task-123',
53
+ status: 'in-progress',
54
+ intent: 'order',
55
+ code: { text: 'Test Task Code' },
56
+ description: 'Test Task Description',
57
+ };
58
+
59
+ test('renders TaskQuestionnaireForm when focus is Questionnaire', async () => {
60
+ const questionnaireId = `q-${Date.now()}`;
61
+ const questionnaire: Questionnaire = {
62
+ resourceType: 'Questionnaire',
63
+ id: questionnaireId,
64
+ status: 'active',
65
+ item: [{ linkId: 'q1', type: 'string', text: 'Test Question' }],
66
+ };
67
+ await medplum.createResource(questionnaire);
68
+
69
+ const task: Task = {
70
+ ...mockTask,
71
+ focus: { reference: `Questionnaire/${questionnaireId}` },
72
+ input: [{ type: { text: 'Questionnaire' }, valueReference: { reference: `Questionnaire/${questionnaireId}` } }],
73
+ };
74
+ const onUpdateTask = vi.fn();
75
+ await setup(task, onUpdateTask);
76
+
77
+ // Wait for the task status panel to render
78
+ await waitFor(() => {
79
+ expect(screen.getByText('Task Status:')).toBeInTheDocument();
80
+ });
81
+ });
82
+
83
+ test('renders TaskServiceRequest when focus is ServiceRequest', async () => {
84
+ const task: Task = {
85
+ ...mockTask,
86
+ focus: { reference: 'ServiceRequest/1234' },
87
+ for: { reference: 'Patient/123' },
88
+ input: [{ type: { text: 'ServiceRequest' }, valueReference: { reference: 'ServiceRequest/123' } }],
89
+ };
90
+ const onUpdateTask = vi.fn();
91
+ const serviceRequest: ServiceRequest = {
92
+ resourceType: 'ServiceRequest',
93
+ id: '1234',
94
+ status: 'active',
95
+ code: { text: 'Test Service Request' },
96
+ intent: 'order',
97
+ subject: { reference: 'Patient/123' },
98
+ requisition: { value: 'REQ-123' },
99
+ };
100
+ await medplum.createResource(serviceRequest);
101
+ medplum.readReference = vi.fn().mockResolvedValue(serviceRequest);
102
+ await setup(task, onUpdateTask);
103
+
104
+ await waitFor(() => {
105
+ expect(screen.getByText('Test Task Code')).toBeInTheDocument();
106
+ expect(screen.getByText(/✅ Order Sent/)).toBeInTheDocument();
107
+ });
108
+ });
109
+
110
+ test('renders SimpleTask when focus is neither Questionnaire nor ServiceRequest', async () => {
111
+ const onUpdateTask = vi.fn();
112
+ await setup(mockTask, onUpdateTask);
113
+ expect(screen.getByText('Test Task Code')).toBeInTheDocument();
114
+ expect(screen.getByText('Test Task Description')).toBeInTheDocument();
115
+ });
116
+
117
+ test('renders TaskStatusPanel', async () => {
118
+ const onUpdateTask = vi.fn();
119
+ await setup(mockTask, onUpdateTask);
120
+ expect(screen.getByText('Task Status:')).toBeInTheDocument();
121
+ });
122
+
123
+ test('updates task status when changed in TaskStatusPanel', async () => {
124
+ const user = userEvent.setup();
125
+ const onUpdateTask = vi.fn();
126
+ medplum.updateResource = vi.fn().mockResolvedValue({ ...mockTask, status: 'completed' });
127
+ await setup(mockTask, onUpdateTask);
128
+
129
+ const statusBadge = screen.getByText('In Progress');
130
+ await user.click(statusBadge);
131
+
132
+ await waitFor(() => {
133
+ const completedOption = screen.getByText('Completed');
134
+ expect(completedOption).toBeInTheDocument();
135
+ });
136
+
137
+ await user.click(screen.getByText('Completed'));
138
+
139
+ await waitFor(() => {
140
+ expect(medplum.updateResource).toHaveBeenCalledWith(
141
+ expect.objectContaining({
142
+ id: 'task-123',
143
+ status: 'completed',
144
+ })
145
+ );
146
+ expect(onUpdateTask).toHaveBeenCalled();
147
+ });
148
+ });
149
+
150
+ test('renders with edit action button', async () => {
151
+ const onUpdateTask = vi.fn();
152
+ await setup(mockTask, onUpdateTask);
153
+
154
+ // Wait for the component to render
155
+ await waitFor(() => {
156
+ expect(screen.getByText('Test Task Code')).toBeInTheDocument();
157
+ });
158
+
159
+ // The panel should have action buttons
160
+ const buttons = screen.getAllByRole('button');
161
+ expect(buttons.length).toBeGreaterThan(0);
162
+ });
163
+
164
+ test('saves questionnaire response when response changes - update existing', async () => {
165
+ const questionnaire: Questionnaire = {
166
+ resourceType: 'Questionnaire',
167
+ id: 'q-123',
168
+ status: 'active',
169
+ item: [{ linkId: 'q1', type: 'string', text: 'Test Question' }],
170
+ };
171
+ await medplum.createResource(questionnaire);
172
+
173
+ const existingResponse: QuestionnaireResponse = {
174
+ resourceType: 'QuestionnaireResponse',
175
+ id: 'qr-456',
176
+ status: 'in-progress',
177
+ questionnaire: 'Questionnaire/q-123',
178
+ };
179
+ await medplum.createResource(existingResponse);
180
+
181
+ const task: Task = {
182
+ ...mockTask,
183
+ focus: { reference: 'Questionnaire/q-123' },
184
+ output: [
185
+ { type: { text: 'QuestionnaireResponse' }, valueReference: { reference: 'QuestionnaireResponse/qr-456' } },
186
+ ],
187
+ };
188
+
189
+ const onUpdateTask = vi.fn();
190
+ await setup(task, onUpdateTask);
191
+
192
+ // Wait for the component to render with task status
193
+ await waitFor(() => {
194
+ expect(screen.getByText('Task Status:')).toBeInTheDocument();
195
+ });
196
+ });
197
+
198
+ test('creates new questionnaire response when none exists', async () => {
199
+ const questionnaire: Questionnaire = {
200
+ resourceType: 'Questionnaire',
201
+ id: 'q-new',
202
+ status: 'active',
203
+ item: [{ linkId: 'q1', type: 'string', text: 'Test Question' }],
204
+ };
205
+ await medplum.createResource(questionnaire);
206
+
207
+ const task: Task = {
208
+ ...mockTask,
209
+ id: 'task-new-qr',
210
+ focus: { reference: 'Questionnaire/q-new' },
211
+ };
212
+
213
+ const onUpdateTask = vi.fn();
214
+ await setup(task, onUpdateTask);
215
+
216
+ // Wait for the component to render with task status
217
+ await waitFor(() => {
218
+ expect(screen.getByText('Task Status:')).toBeInTheDocument();
219
+ });
220
+ });
221
+
222
+ test('shows error notification when status update fails', async () => {
223
+ const user = userEvent.setup();
224
+ const onUpdateTask = vi.fn();
225
+ medplum.updateResource = vi.fn().mockRejectedValue(new Error('Update failed'));
226
+ await setup(mockTask, onUpdateTask);
227
+
228
+ const statusBadge = screen.getByText('In Progress');
229
+ await user.click(statusBadge);
230
+
231
+ await waitFor(() => {
232
+ const completedOption = screen.getByText('Completed');
233
+ expect(completedOption).toBeInTheDocument();
234
+ });
235
+
236
+ await user.click(screen.getByText('Completed'));
237
+
238
+ await waitFor(() => {
239
+ expect(medplum.updateResource).toHaveBeenCalled();
240
+ });
241
+
242
+ await waitFor(() => {
243
+ expect(medplum.updateResource).toHaveBeenCalled();
244
+ });
245
+ });
246
+
247
+ test('renders with enabled=false disables status changes', async () => {
248
+ const onUpdateTask = vi.fn();
249
+ await setup(mockTask, onUpdateTask, false);
250
+
251
+ expect(screen.getByText('Test Task Code')).toBeInTheDocument();
252
+ expect(screen.getByText('Task Status:')).toBeInTheDocument();
253
+ });
254
+
255
+ test('handles task without focus reference', async () => {
256
+ const taskWithoutFocus: Task = {
257
+ resourceType: 'Task',
258
+ id: 'task-no-focus',
259
+ status: 'draft',
260
+ intent: 'order',
261
+ code: { text: 'Simple Task' },
262
+ };
263
+
264
+ const onUpdateTask = vi.fn();
265
+ await setup(taskWithoutFocus, onUpdateTask);
266
+
267
+ expect(screen.getByText('Simple Task')).toBeInTheDocument();
268
+ });
269
+
270
+ test('handles task with undefined focus reference', async () => {
271
+ const taskWithUndefinedFocus: Task = {
272
+ resourceType: 'Task',
273
+ id: 'task-undefined-focus',
274
+ status: 'draft',
275
+ intent: 'order',
276
+ code: { text: 'Task with undefined focus' },
277
+ focus: { display: 'Some Display' }, // focus without reference
278
+ };
279
+
280
+ const onUpdateTask = vi.fn();
281
+ await setup(taskWithUndefinedFocus, onUpdateTask);
282
+
283
+ expect(screen.getByText('Task with undefined focus')).toBeInTheDocument();
284
+ });
285
+ });