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,282 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Box, Card, Stack, Textarea, Title } from '@mantine/core';
4
+ import { createReference, getReferenceString } from '@medplum/core';
5
+ import type {
6
+ ClinicalImpression,
7
+ Encounter,
8
+ Patient,
9
+ Practitioner,
10
+ Provenance,
11
+ Reference,
12
+ Task,
13
+ } from '@medplum/fhirtypes';
14
+ import { Loading, useMedplum, useResource } from '@medplum/react';
15
+ import { useCallback, useEffect, useState } from 'react';
16
+ import type { JSX } from 'react';
17
+ import { SAVE_TIMEOUT_MS } from '../../config/constants';
18
+ import { useEncounterChart } from '../../hooks/useEncounterChart';
19
+ import { useDebouncedUpdateResource } from '../../hooks/useDebouncedUpdateResource';
20
+ import { ChartNoteStatus } from '../../types/encounter';
21
+ import { updateEncounterStatus } from '../../utils/encounter';
22
+ import { showErrorNotification } from '../../utils/notifications';
23
+ import { EncounterHeader } from './EncounterHeader';
24
+ import { SignAddendum } from './SignAddendum';
25
+ import { TaskPanel } from '../tasks/encounter/TaskPanel';
26
+ import { BillingTab } from './BillingTab';
27
+
28
+ const FHIR_ACT_REASON_SYSTEM = 'http://terminology.hl7.org/CodeSystem/v3-ActReason';
29
+ const FHIR_PROVENANCE_PARTICIPANT_TYPE_SYSTEM = 'http://terminology.hl7.org/CodeSystem/provenance-participant-type';
30
+ const FHIR_DOCUMENT_COMPLETION_SYSTEM = 'http://terminology.hl7.org/CodeSystem/v3-DocumentCompletion';
31
+
32
+ const TASK_COMPLETED_STATUSES = new Set<Task['status']>([
33
+ 'completed',
34
+ 'cancelled',
35
+ 'failed',
36
+ 'rejected',
37
+ 'entered-in-error',
38
+ ]);
39
+
40
+ export interface EncounterChartProps {
41
+ encounter: Encounter | Reference<Encounter>;
42
+ }
43
+
44
+ export const EncounterChart = (props: EncounterChartProps): JSX.Element => {
45
+ const { encounter: encounterProp } = props;
46
+ const medplum = useMedplum();
47
+ const encounterResource = useResource(encounterProp);
48
+ const patientReference = encounterResource?.subject as Reference<Patient> | undefined;
49
+ const patientResource = useResource(patientReference);
50
+
51
+ const [activeTab, setActiveTab] = useState<string>('notes');
52
+ const {
53
+ encounter,
54
+ claim,
55
+ practitioner,
56
+ tasks,
57
+ clinicalImpression,
58
+ chargeItems,
59
+ appointment,
60
+ setEncounter,
61
+ setClaim,
62
+ setPractitioner,
63
+ setTasks,
64
+ setChargeItems,
65
+ } = useEncounterChart(encounterProp, patientReference);
66
+
67
+ const [chartNote, setChartNote] = useState<string | undefined>(clinicalImpression?.note?.[0]?.text);
68
+ const debouncedUpdateResource = useDebouncedUpdateResource(medplum, SAVE_TIMEOUT_MS);
69
+ const [provenances, setProvenances] = useState<Provenance[]>([]);
70
+ const [chartNoteStatus, setChartNoteStatus] = useState<ChartNoteStatus>(ChartNoteStatus.Unsigned);
71
+
72
+ useEffect(() => {
73
+ if (!encounter) {
74
+ return;
75
+ }
76
+
77
+ const fetchProvenance = async (): Promise<void> => {
78
+ const provenance = await medplum.searchResources('Provenance', `target=${getReferenceString(encounter)}`);
79
+ setProvenances(provenance);
80
+ if (provenance.length > 0 && clinicalImpression?.status === 'completed') {
81
+ setChartNoteStatus(ChartNoteStatus.SignedAndLocked);
82
+ } else if (provenance.length > 0) {
83
+ setChartNoteStatus(ChartNoteStatus.Signed);
84
+ } else {
85
+ setChartNoteStatus(ChartNoteStatus.Unsigned);
86
+ }
87
+ };
88
+
89
+ fetchProvenance().catch((err) => showErrorNotification(err));
90
+ }, [clinicalImpression, encounter, medplum]);
91
+
92
+ const updateTaskList = useCallback(
93
+ (updatedTask: Task): void => {
94
+ setTasks((prevTasks) => prevTasks.map((task) => (task.id === updatedTask.id ? updatedTask : task)));
95
+ },
96
+ [setTasks]
97
+ );
98
+
99
+ const handleEncounterStatusChange = useCallback(
100
+ async (newStatus: Encounter['status']): Promise<void> => {
101
+ if (!encounter) {
102
+ return;
103
+ }
104
+
105
+ try {
106
+ const updatedEncounter = await updateEncounterStatus(medplum, encounter, appointment, newStatus);
107
+ setEncounter(updatedEncounter);
108
+ } catch (err) {
109
+ showErrorNotification(err);
110
+ }
111
+ },
112
+ [encounter, medplum, setEncounter, appointment]
113
+ );
114
+
115
+ const handleTabChange = (tab: string): void => {
116
+ setActiveTab(tab);
117
+ };
118
+
119
+ const handleChartNoteChange = async (e: React.ChangeEvent<HTMLTextAreaElement>): Promise<void> => {
120
+ setChartNote(e.target.value);
121
+
122
+ if (!clinicalImpression) {
123
+ return;
124
+ }
125
+
126
+ try {
127
+ if (!e.target.value || e.target.value === '') {
128
+ const { note: _, ...restOfClinicalImpression } = clinicalImpression;
129
+ const updatedClinicalImpression: ClinicalImpression = restOfClinicalImpression;
130
+ await debouncedUpdateResource(updatedClinicalImpression);
131
+ } else {
132
+ const updatedClinicalImpression: ClinicalImpression = {
133
+ ...clinicalImpression,
134
+ note: [{ text: e.target.value }],
135
+ };
136
+ await debouncedUpdateResource(updatedClinicalImpression);
137
+ }
138
+ } catch (err) {
139
+ showErrorNotification(err);
140
+ }
141
+ };
142
+
143
+ const handleSign = async (practitioner: Reference<Practitioner>, lock: boolean): Promise<void> => {
144
+ if (!encounter) {
145
+ return;
146
+ }
147
+
148
+ if (lock) {
149
+ // Complete all incomplete tasks
150
+ const tasksToUpdate = tasks.filter((task) => !TASK_COMPLETED_STATUSES.has(task.status));
151
+ const updatedTasks = await Promise.all(
152
+ tasksToUpdate.map((task) =>
153
+ medplum.updateResource({
154
+ ...task,
155
+ status: 'completed',
156
+ })
157
+ )
158
+ );
159
+
160
+ setTasks(
161
+ tasks.map((task) => {
162
+ const updated = updatedTasks.find((t) => t.id === task.id);
163
+ return updated || task;
164
+ })
165
+ );
166
+ }
167
+
168
+ // Create provenance record with signature
169
+ const newProvenance = await medplum.createResource<Provenance>({
170
+ resourceType: 'Provenance',
171
+ target: [createReference(encounter)],
172
+ recorded: new Date().toISOString(),
173
+ reason: [
174
+ {
175
+ coding: [
176
+ {
177
+ system: FHIR_ACT_REASON_SYSTEM,
178
+ code: 'SIGN',
179
+ display: 'Signed',
180
+ },
181
+ ],
182
+ },
183
+ ],
184
+ agent: [
185
+ {
186
+ type: {
187
+ coding: [
188
+ {
189
+ system: FHIR_PROVENANCE_PARTICIPANT_TYPE_SYSTEM,
190
+ code: 'author',
191
+ },
192
+ ],
193
+ },
194
+ who: practitioner,
195
+ },
196
+ ],
197
+ signature: [
198
+ {
199
+ type: [
200
+ {
201
+ system: FHIR_DOCUMENT_COMPLETION_SYSTEM,
202
+ code: 'LA',
203
+ display: 'legally authenticated',
204
+ },
205
+ ],
206
+ when: new Date().toISOString(),
207
+ who: practitioner,
208
+ },
209
+ ],
210
+ });
211
+
212
+ setProvenances([...provenances, newProvenance]);
213
+
214
+ if (lock) {
215
+ setChartNoteStatus(ChartNoteStatus.SignedAndLocked);
216
+ } else {
217
+ setChartNoteStatus(ChartNoteStatus.Signed);
218
+ }
219
+ };
220
+
221
+ if (!patientResource || !encounter) {
222
+ return <Loading />;
223
+ }
224
+
225
+ return (
226
+ <>
227
+ <Stack justify="space-between" gap={0}>
228
+ <EncounterHeader
229
+ encounter={encounter}
230
+ chartNoteStatus={chartNoteStatus}
231
+ practitioner={practitioner}
232
+ onStatusChange={handleEncounterStatusChange}
233
+ onTabChange={handleTabChange}
234
+ onSign={handleSign}
235
+ />
236
+ <Box p="md">
237
+ {activeTab === 'notes' && (
238
+ <Stack gap="md">
239
+ <SignAddendum encounter={encounter} provenances={provenances} chartNoteStatus={chartNoteStatus} />
240
+
241
+ {clinicalImpression && (
242
+ <Card withBorder shadow="sm" mt="md">
243
+ <Title>Fill chart note</Title>
244
+ <Textarea
245
+ defaultValue={clinicalImpression.note?.[0]?.text}
246
+ value={chartNote}
247
+ onChange={handleChartNoteChange}
248
+ autosize
249
+ minRows={4}
250
+ maxRows={8}
251
+ disabled={chartNoteStatus === ChartNoteStatus.SignedAndLocked}
252
+ />
253
+ </Card>
254
+ )}
255
+ {tasks.map((task: Task) => (
256
+ <TaskPanel
257
+ key={task.id}
258
+ task={task}
259
+ onUpdateTask={updateTaskList}
260
+ enabled={chartNoteStatus !== ChartNoteStatus.SignedAndLocked}
261
+ />
262
+ ))}
263
+ </Stack>
264
+ )}
265
+ {activeTab === 'details' && (
266
+ <BillingTab
267
+ encounter={encounter}
268
+ setEncounter={setEncounter}
269
+ claim={claim}
270
+ patient={patientResource}
271
+ practitioner={practitioner}
272
+ setPractitioner={setPractitioner}
273
+ chargeItems={chargeItems}
274
+ setChargeItems={setChargeItems}
275
+ setClaim={setClaim}
276
+ />
277
+ )}
278
+ </Box>
279
+ </Stack>
280
+ </>
281
+ );
282
+ };
@@ -0,0 +1,268 @@
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, waitFor } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import type { Encounter, Practitioner } from '@medplum/fhirtypes';
8
+ import { MockClient } from '@medplum/mock';
9
+ import { MemoryRouter } from 'react-router';
10
+ import { describe, expect, test, beforeEach, vi } from 'vitest';
11
+ import { EncounterHeader } from './EncounterHeader';
12
+ import { ChartNoteStatus } from '../../types/encounter';
13
+
14
+ const mockEncounter: Encounter = {
15
+ resourceType: 'Encounter',
16
+ id: 'encounter-123',
17
+ status: 'in-progress',
18
+ class: { code: 'AMB', system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode' },
19
+ period: {
20
+ start: '2024-01-01T10:00:00Z',
21
+ },
22
+ subject: { reference: 'Patient/patient-123' },
23
+ };
24
+
25
+ const mockPractitioner: Practitioner = {
26
+ resourceType: 'Practitioner',
27
+ id: 'practitioner-123',
28
+ name: [{ given: ['Dr.'], family: 'Test' }],
29
+ };
30
+
31
+ describe('EncounterHeader', () => {
32
+ let medplum: MockClient;
33
+
34
+ beforeEach(async () => {
35
+ medplum = new MockClient();
36
+ });
37
+
38
+ const setup = (props: Partial<Parameters<typeof EncounterHeader>[0]> = {}): ReturnType<typeof render> => {
39
+ return render(
40
+ <MemoryRouter>
41
+ <MedplumProvider medplum={medplum}>
42
+ <MantineProvider>
43
+ <EncounterHeader encounter={mockEncounter} {...props} />
44
+ </MantineProvider>
45
+ </MedplumProvider>
46
+ </MemoryRouter>
47
+ );
48
+ };
49
+
50
+ test('renders encounter title and details', () => {
51
+ setup({ practitioner: mockPractitioner });
52
+
53
+ expect(screen.getByText('Visit')).toBeInTheDocument();
54
+ expect(screen.getByText(/Dr\. Test/)).toBeInTheDocument();
55
+ });
56
+
57
+ test('displays status button', () => {
58
+ setup();
59
+
60
+ expect(screen.getByText('In Progress')).toBeInTheDocument();
61
+ });
62
+
63
+ test('shows status menu for active encounters', async () => {
64
+ const user = userEvent.setup();
65
+ setup();
66
+
67
+ const statusButton = screen.getByText('In Progress');
68
+ await user.click(statusButton);
69
+
70
+ await waitFor(() => {
71
+ expect(screen.getByText('Finished')).toBeInTheDocument();
72
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
73
+ });
74
+ });
75
+
76
+ test('calls onStatusChange when status is changed', async () => {
77
+ const user = userEvent.setup();
78
+ const onStatusChange = vi.fn();
79
+ setup({ onStatusChange });
80
+
81
+ const statusButton = screen.getByText('In Progress');
82
+ await user.click(statusButton);
83
+
84
+ await waitFor(() => {
85
+ expect(screen.getByText('Finished')).toBeInTheDocument();
86
+ });
87
+
88
+ await user.click(screen.getByText('Finished'));
89
+
90
+ await waitFor(() => {
91
+ expect(onStatusChange).toHaveBeenCalledWith('finished');
92
+ });
93
+ });
94
+
95
+ test('shows confirmation modal when cancelling encounter', async () => {
96
+ const user = userEvent.setup();
97
+ const onStatusChange = vi.fn();
98
+ setup({ onStatusChange });
99
+
100
+ const statusButton = screen.getByText('In Progress');
101
+ await user.click(statusButton);
102
+
103
+ await waitFor(() => {
104
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
105
+ });
106
+
107
+ await user.click(screen.getByText('Cancelled'));
108
+
109
+ await waitFor(() => {
110
+ expect(screen.getByText('Are you sure you want to cancel this encounter?')).toBeInTheDocument();
111
+ });
112
+ });
113
+
114
+ test('confirms cancellation when user clicks yes', async () => {
115
+ const user = userEvent.setup();
116
+ const onStatusChange = vi.fn();
117
+ setup({ onStatusChange });
118
+
119
+ const statusButton = screen.getByText('In Progress');
120
+ await user.click(statusButton);
121
+
122
+ await waitFor(() => {
123
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
124
+ });
125
+
126
+ await user.click(screen.getByText('Cancelled'));
127
+
128
+ await waitFor(() => {
129
+ expect(screen.getByText('Yes, cancel it')).toBeInTheDocument();
130
+ });
131
+
132
+ await user.click(screen.getByText('Yes, cancel it'));
133
+
134
+ await waitFor(() => {
135
+ expect(onStatusChange).toHaveBeenCalledWith('cancelled');
136
+ });
137
+ });
138
+
139
+ test('cancels cancellation when user clicks no', async () => {
140
+ const user = userEvent.setup();
141
+ const onStatusChange = vi.fn();
142
+ setup({ onStatusChange });
143
+
144
+ const statusButton = screen.getByText('In Progress');
145
+ await user.click(statusButton);
146
+
147
+ await waitFor(() => {
148
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
149
+ });
150
+
151
+ await user.click(screen.getByText('Cancelled'));
152
+
153
+ await waitFor(() => {
154
+ expect(screen.getByText('No, keep it')).toBeInTheDocument();
155
+ });
156
+
157
+ await user.click(screen.getByText('No, keep it'));
158
+
159
+ await waitFor(() => {
160
+ expect(screen.queryByText('Are you sure you want to cancel this encounter?')).not.toBeInTheDocument();
161
+ });
162
+
163
+ expect(onStatusChange).not.toHaveBeenCalled();
164
+ });
165
+
166
+ test('calls onTabChange when tab is changed', async () => {
167
+ const user = userEvent.setup();
168
+ const onTabChange = vi.fn();
169
+ setup({ onTabChange });
170
+
171
+ const detailsTab = screen.getByText('Details & Billing');
172
+ await user.click(detailsTab);
173
+
174
+ await waitFor(() => {
175
+ expect(onTabChange).toHaveBeenCalledWith('details');
176
+ });
177
+ });
178
+
179
+ test('shows sign button for finished unsigned encounters', () => {
180
+ const finishedEncounter: Encounter = {
181
+ ...mockEncounter,
182
+ status: 'finished',
183
+ };
184
+ const { container } = setup({ encounter: finishedEncounter, chartNoteStatus: ChartNoteStatus.Unsigned });
185
+
186
+ const signButtons = container.querySelectorAll('button');
187
+ expect(signButtons.length).toBeGreaterThan(0);
188
+ });
189
+
190
+ test('opens sign dialog when sign button is clicked', async () => {
191
+ const user = userEvent.setup();
192
+ const finishedEncounter: Encounter = {
193
+ ...mockEncounter,
194
+ status: 'finished',
195
+ };
196
+ setup({ encounter: finishedEncounter, chartNoteStatus: ChartNoteStatus.Unsigned });
197
+
198
+ const signButtons = screen.getAllByRole('button');
199
+ const signButton = signButtons.find((btn) => btn.querySelector('svg'));
200
+ if (signButton) {
201
+ await user.click(signButton);
202
+ }
203
+
204
+ await waitFor(() => {
205
+ expect(screen.getByText('Signing As')).toBeInTheDocument();
206
+ });
207
+ });
208
+
209
+ test('calls onSign when signing', async () => {
210
+ const user = userEvent.setup();
211
+ const onSign = vi.fn();
212
+ const finishedEncounter: Encounter = {
213
+ ...mockEncounter,
214
+ status: 'finished',
215
+ };
216
+ setup({ encounter: finishedEncounter, chartNoteStatus: ChartNoteStatus.Unsigned, onSign });
217
+
218
+ const signButtons = screen.getAllByRole('button');
219
+ const signButton = signButtons.find((btn) => btn.querySelector('svg'));
220
+ if (signButton) {
221
+ await user.click(signButton);
222
+ }
223
+
224
+ await waitFor(() => {
225
+ expect(screen.getByText('Just Sign')).toBeInTheDocument();
226
+ });
227
+
228
+ await user.click(screen.getByText('Just Sign'));
229
+
230
+ await waitFor(() => {
231
+ expect(onSign).toHaveBeenCalled();
232
+ });
233
+ });
234
+
235
+ test('does not show sign button for cancelled encounters', () => {
236
+ const cancelledEncounter: Encounter = {
237
+ ...mockEncounter,
238
+ status: 'cancelled',
239
+ };
240
+ setup({ encounter: cancelledEncounter });
241
+
242
+ const signButtons = screen.queryAllByRole('button');
243
+ const hasSignIcon = signButtons.some((btn) => btn.querySelector('svg[data-icon="lock"]'));
244
+ expect(hasSignIcon).toBe(false);
245
+ });
246
+
247
+ test('displays correct status for finished encounters', () => {
248
+ setup({ encounter: { ...mockEncounter, status: 'finished' } });
249
+ expect(screen.getByText('Finished')).toBeInTheDocument();
250
+ });
251
+
252
+ test('displays correct status for cancelled encounters', () => {
253
+ setup({ encounter: { ...mockEncounter, status: 'cancelled' } });
254
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
255
+ });
256
+
257
+ test('renders with practitioner name', () => {
258
+ setup({ practitioner: mockPractitioner });
259
+
260
+ expect(screen.getByText(/Dr\. Test/)).toBeInTheDocument();
261
+ });
262
+
263
+ test('renders with unknown provider when practitioner is not provided', () => {
264
+ setup({ practitioner: undefined });
265
+
266
+ expect(screen.getByText(/Unknown Provider/)).toBeInTheDocument();
267
+ });
268
+ });