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,431 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import type { Coverage, Patient } from '@medplum/fhirtypes';
5
+ import { MockClient } from '@medplum/mock';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import { act, render, screen, waitFor } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { MemoryRouter } from 'react-router';
10
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
11
+ import { CoverageInput } from './CoverageInput';
12
+ import { useHealthGorillaLabOrder, HealthGorillaLabOrderProvider } from '@medplum/health-gorilla-react';
13
+
14
+ vi.mock('@medplum/health-gorilla-react', async () => {
15
+ const actual = await vi.importActual('@medplum/health-gorilla-react');
16
+ return {
17
+ ...actual,
18
+ useHealthGorillaLabOrder: vi.fn(),
19
+ };
20
+ });
21
+
22
+ const mockPatient: Patient = {
23
+ resourceType: 'Patient',
24
+ id: 'patient-123',
25
+ name: [{ given: ['John'], family: 'Doe' }],
26
+ };
27
+
28
+ function createMockCoverage(id: string, display: string): Coverage {
29
+ return {
30
+ resourceType: 'Coverage',
31
+ id,
32
+ status: 'active',
33
+ beneficiary: { reference: 'Patient/patient-123' },
34
+ payor: [{ display }],
35
+ };
36
+ }
37
+
38
+ describe('CoverageInput', () => {
39
+ let medplum: MockClient;
40
+ let mockGetActivePatientCoverages: ReturnType<typeof vi.fn>;
41
+ let mockUpdateBillingInformation: ReturnType<typeof vi.fn>;
42
+ let mockLabOrderReturn: ReturnType<typeof useHealthGorillaLabOrder>;
43
+
44
+ beforeEach(() => {
45
+ vi.clearAllMocks();
46
+ medplum = new MockClient();
47
+
48
+ mockGetActivePatientCoverages = vi.fn();
49
+ mockUpdateBillingInformation = vi.fn();
50
+
51
+ mockLabOrderReturn = {
52
+ state: {
53
+ performingLab: undefined,
54
+ performingLabAccountNumber: undefined,
55
+ selectedTests: [],
56
+ testMetadata: {},
57
+ diagnoses: [],
58
+ billingInformation: {
59
+ billTo: 'insurance',
60
+ },
61
+ specimenCollectedDateTime: undefined,
62
+ orderNotes: undefined,
63
+ },
64
+ removeDiagnosis: vi.fn(),
65
+ setDiagnoses: vi.fn(),
66
+ getActivePatientCoverages: mockGetActivePatientCoverages,
67
+ updateBillingInformation: mockUpdateBillingInformation,
68
+ setSpecimenCollectedDateTime: vi.fn(),
69
+ setOrderNotes: vi.fn(),
70
+ validateOrder: vi.fn().mockReturnValue(undefined),
71
+ createOrderBundle: vi.fn(),
72
+ searchAvailableLabs: vi.fn().mockResolvedValue([]),
73
+ searchAvailableTests: vi.fn().mockResolvedValue([]),
74
+ setPerformingLab: vi.fn(),
75
+ setPerformingLabAccountNumber: vi.fn(),
76
+ addTest: vi.fn(),
77
+ removeTest: vi.fn(),
78
+ setTests: vi.fn(),
79
+ updateTestMetadata: vi.fn(),
80
+ addDiagnosis: vi.fn(),
81
+ } as any;
82
+
83
+ vi.mocked(useHealthGorillaLabOrder).mockReturnValue(mockLabOrderReturn);
84
+ });
85
+
86
+ function setup(props: { patient?: Patient; error?: { message: string } } = {}): void {
87
+ const patient = props.patient || mockPatient;
88
+ render(
89
+ <MemoryRouter>
90
+ <MedplumProvider medplum={medplum}>
91
+ <MantineProvider>
92
+ <HealthGorillaLabOrderProvider {...mockLabOrderReturn}>
93
+ <CoverageInput patient={patient} error={props.error} />
94
+ </HealthGorillaLabOrderProvider>
95
+ </MantineProvider>
96
+ </MedplumProvider>
97
+ </MemoryRouter>
98
+ );
99
+ }
100
+
101
+ test('Renders "No coverages found" when no coverages exist', async () => {
102
+ mockGetActivePatientCoverages.mockResolvedValue([]);
103
+ setup();
104
+
105
+ await waitFor(() => {
106
+ expect(screen.getByText('No coverages found')).toBeInTheDocument();
107
+ });
108
+
109
+ expect(mockGetActivePatientCoverages).toHaveBeenCalled();
110
+ });
111
+
112
+ test('Renders coverages list when coverages exist', async () => {
113
+ const coverages = [
114
+ createMockCoverage('coverage-1', 'Blue Cross Blue Shield'),
115
+ createMockCoverage('coverage-2', 'Aetna'),
116
+ createMockCoverage('coverage-3', 'United Healthcare'),
117
+ ];
118
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
119
+ setup();
120
+
121
+ await waitFor(() => {
122
+ expect(screen.getByText('Blue Cross Blue Shield')).toBeInTheDocument();
123
+ });
124
+
125
+ expect(screen.getByText('Blue Cross Blue Shield')).toBeInTheDocument();
126
+ expect(screen.getByText('Aetna')).toBeInTheDocument();
127
+ expect(screen.getByText('United Healthcare')).toBeInTheDocument();
128
+ });
129
+
130
+ test('Displays coverage numbers correctly (1-3)', async () => {
131
+ const coverages = [
132
+ createMockCoverage('coverage-1', 'Insurance 1'),
133
+ createMockCoverage('coverage-2', 'Insurance 2'),
134
+ createMockCoverage('coverage-3', 'Insurance 3'),
135
+ ];
136
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
137
+ setup();
138
+
139
+ await waitFor(() => {
140
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
141
+ });
142
+
143
+ expect(screen.getByText('1.')).toBeInTheDocument();
144
+ expect(screen.getByText('2.')).toBeInTheDocument();
145
+ expect(screen.getByText('3.')).toBeInTheDocument();
146
+ });
147
+
148
+ test('Shows divider after third coverage when more than 3 coverages exist', async () => {
149
+ const coverages = [
150
+ createMockCoverage('coverage-1', 'Insurance 1'),
151
+ createMockCoverage('coverage-2', 'Insurance 2'),
152
+ createMockCoverage('coverage-3', 'Insurance 3'),
153
+ createMockCoverage('coverage-4', 'Insurance 4'),
154
+ ];
155
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
156
+ setup();
157
+
158
+ await waitFor(() => {
159
+ expect(screen.getByText('Insurance 4')).toBeInTheDocument();
160
+ });
161
+
162
+ expect(screen.getByText('Additional coverages that will not be submitted')).toBeInTheDocument();
163
+
164
+ const numberTexts = screen.queryAllByText('4.');
165
+ expect(numberTexts).toHaveLength(0);
166
+ });
167
+
168
+ test('Displays "Unknown" when coverage has no payor display', async () => {
169
+ const coverage: Coverage = {
170
+ resourceType: 'Coverage',
171
+ id: 'coverage-1',
172
+ status: 'active',
173
+ beneficiary: { reference: 'Patient/patient-123' },
174
+ payor: [{ reference: 'Organization/org-1' }],
175
+ };
176
+ mockGetActivePatientCoverages.mockResolvedValue([coverage]);
177
+ setup();
178
+
179
+ await waitFor(() => {
180
+ expect(screen.getByText('Unknown')).toBeInTheDocument();
181
+ });
182
+ });
183
+
184
+ test('Calls updateBillingInformation with selected coverages (first 3)', async () => {
185
+ const coverages = [
186
+ createMockCoverage('coverage-1', 'Insurance 1'),
187
+ createMockCoverage('coverage-2', 'Insurance 2'),
188
+ createMockCoverage('coverage-3', 'Insurance 3'),
189
+ ];
190
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
191
+ setup();
192
+
193
+ await waitFor(() => {
194
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
195
+ });
196
+
197
+ await waitFor(() => {
198
+ const calls = mockUpdateBillingInformation.mock.calls;
199
+ const lastCall = calls[calls.length - 1];
200
+ expect(lastCall[0].patientCoverage).toHaveLength(3);
201
+ });
202
+
203
+ const lastCall = mockUpdateBillingInformation.mock.calls[mockUpdateBillingInformation.mock.calls.length - 1];
204
+ expect(lastCall[0].patientCoverage).toEqual([coverages[0], coverages[1], coverages[2]]);
205
+ });
206
+
207
+ test('Calls updateBillingInformation with empty array when no coverages', async () => {
208
+ mockGetActivePatientCoverages.mockResolvedValue([]);
209
+ setup();
210
+
211
+ await waitFor(() => {
212
+ expect(mockUpdateBillingInformation).toHaveBeenCalled();
213
+ });
214
+
215
+ expect(mockUpdateBillingInformation).toHaveBeenCalledWith({
216
+ patientCoverage: [],
217
+ });
218
+ });
219
+
220
+ test('Moves coverage up when "Move up" button is clicked', async () => {
221
+ const user = userEvent.setup();
222
+ const coverages = [
223
+ createMockCoverage('coverage-1', 'Insurance 1'),
224
+ createMockCoverage('coverage-2', 'Insurance 2'),
225
+ ];
226
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
227
+ setup();
228
+
229
+ await waitFor(() => {
230
+ expect(screen.getByText('Insurance 2')).toBeInTheDocument();
231
+ });
232
+
233
+ const moveUpButtons = screen.getAllByLabelText('Move up');
234
+ const secondItemMoveUp = moveUpButtons[1];
235
+ expect(secondItemMoveUp).not.toBeDisabled();
236
+
237
+ await act(async () => {
238
+ await user.click(secondItemMoveUp);
239
+ });
240
+
241
+ await waitFor(() => {
242
+ const texts = screen.getAllByText(/Insurance/);
243
+ expect(texts[0]).toHaveTextContent('Insurance 2');
244
+ });
245
+ });
246
+
247
+ test('Moves coverage down when "Move down" button is clicked', async () => {
248
+ const user = userEvent.setup();
249
+ const coverages = [
250
+ createMockCoverage('coverage-1', 'Insurance 1'),
251
+ createMockCoverage('coverage-2', 'Insurance 2'),
252
+ ];
253
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
254
+ setup();
255
+
256
+ await waitFor(() => {
257
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
258
+ });
259
+
260
+ const moveDownButtons = screen.getAllByLabelText('Move down');
261
+ const firstItemMoveDown = moveDownButtons[0];
262
+ expect(firstItemMoveDown).not.toBeDisabled();
263
+
264
+ await act(async () => {
265
+ await user.click(firstItemMoveDown);
266
+ });
267
+
268
+ await waitFor(() => {
269
+ const texts = screen.getAllByText(/Insurance/);
270
+ expect(texts[0]).toHaveTextContent('Insurance 2');
271
+ });
272
+ });
273
+
274
+ test('Disables "Move up" button for first item', async () => {
275
+ const coverages = [
276
+ createMockCoverage('coverage-1', 'Insurance 1'),
277
+ createMockCoverage('coverage-2', 'Insurance 2'),
278
+ ];
279
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
280
+ setup();
281
+
282
+ await waitFor(() => {
283
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
284
+ });
285
+
286
+ const moveUpButtons = screen.getAllByLabelText('Move up');
287
+ expect(moveUpButtons[0]).toBeDisabled();
288
+ expect(moveUpButtons[1]).not.toBeDisabled();
289
+ });
290
+
291
+ test('Disables "Move down" button for last item', async () => {
292
+ const coverages = [
293
+ createMockCoverage('coverage-1', 'Insurance 1'),
294
+ createMockCoverage('coverage-2', 'Insurance 2'),
295
+ ];
296
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
297
+ setup();
298
+
299
+ await waitFor(() => {
300
+ expect(screen.getByText('Insurance 2')).toBeInTheDocument();
301
+ });
302
+
303
+ const moveDownButtons = screen.getAllByLabelText('Move down');
304
+ expect(moveDownButtons[1]).toBeDisabled();
305
+ expect(moveDownButtons[0]).not.toBeDisabled();
306
+ });
307
+
308
+ test('Disables all controls when billTo is not "insurance"', async () => {
309
+ mockLabOrderReturn.state.billingInformation.billTo = 'patient';
310
+ const coverages = [
311
+ createMockCoverage('coverage-1', 'Insurance 1'),
312
+ createMockCoverage('coverage-2', 'Insurance 2'),
313
+ ];
314
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
315
+ setup();
316
+
317
+ await waitFor(() => {
318
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
319
+ });
320
+
321
+ const moveUpButtons = screen.getAllByLabelText('Move up');
322
+ const moveDownButtons = screen.getAllByLabelText('Move down');
323
+
324
+ moveUpButtons.forEach((button) => {
325
+ expect(button).toBeDisabled();
326
+ });
327
+ moveDownButtons.forEach((button) => {
328
+ expect(button).toBeDisabled();
329
+ });
330
+ });
331
+
332
+ test('Displays error message when error prop is provided', async () => {
333
+ const error = { message: 'Invalid coverage selection' };
334
+ mockGetActivePatientCoverages.mockResolvedValue([]);
335
+ setup({ error });
336
+
337
+ await waitFor(() => {
338
+ expect(screen.getByText('Invalid coverage selection')).toBeInTheDocument();
339
+ });
340
+ });
341
+
342
+ test('Re-fetches coverages when patient changes', async () => {
343
+ const coverages1 = [createMockCoverage('coverage-1', 'Insurance 1')];
344
+ mockGetActivePatientCoverages.mockResolvedValue(coverages1);
345
+ const { rerender } = render(
346
+ <MemoryRouter>
347
+ <MedplumProvider medplum={medplum}>
348
+ <MantineProvider>
349
+ <HealthGorillaLabOrderProvider {...mockLabOrderReturn}>
350
+ <CoverageInput patient={mockPatient} />
351
+ </HealthGorillaLabOrderProvider>
352
+ </MantineProvider>
353
+ </MedplumProvider>
354
+ </MemoryRouter>
355
+ );
356
+
357
+ await waitFor(() => {
358
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
359
+ });
360
+
361
+ const newPatient: Patient = {
362
+ resourceType: 'Patient',
363
+ id: 'patient-456',
364
+ name: [{ given: ['Jane'], family: 'Smith' }],
365
+ };
366
+
367
+ const coverages2 = [createMockCoverage('coverage-2', 'Insurance 2')];
368
+ mockGetActivePatientCoverages.mockResolvedValue(coverages2);
369
+
370
+ rerender(
371
+ <MemoryRouter>
372
+ <MedplumProvider medplum={medplum}>
373
+ <MantineProvider>
374
+ <HealthGorillaLabOrderProvider {...mockLabOrderReturn}>
375
+ <CoverageInput patient={newPatient} />
376
+ </HealthGorillaLabOrderProvider>
377
+ </MantineProvider>
378
+ </MedplumProvider>
379
+ </MemoryRouter>
380
+ );
381
+
382
+ await waitFor(() => {
383
+ expect(screen.getByText('Insurance 2')).toBeInTheDocument();
384
+ });
385
+
386
+ expect(mockGetActivePatientCoverages).toHaveBeenCalledTimes(2);
387
+ });
388
+
389
+ test('Handles error when getActivePatientCoverages fails', async () => {
390
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
391
+ mockGetActivePatientCoverages.mockRejectedValue(new Error('Failed to fetch coverages'));
392
+ setup();
393
+
394
+ await waitFor(() => {
395
+ expect(screen.getByText('No coverages found')).toBeInTheDocument();
396
+ });
397
+
398
+ expect(consoleErrorSpy).toHaveBeenCalled();
399
+ consoleErrorSpy.mockRestore();
400
+ });
401
+
402
+ test('Updates billing information when coverages are reordered', async () => {
403
+ const user = userEvent.setup();
404
+ const coverages = [
405
+ createMockCoverage('coverage-1', 'Insurance 1'),
406
+ createMockCoverage('coverage-2', 'Insurance 2'),
407
+ createMockCoverage('coverage-3', 'Insurance 3'),
408
+ ];
409
+ mockGetActivePatientCoverages.mockResolvedValue(coverages);
410
+ setup();
411
+
412
+ await waitFor(() => {
413
+ expect(screen.getByText('Insurance 1')).toBeInTheDocument();
414
+ });
415
+
416
+ mockUpdateBillingInformation.mockClear();
417
+
418
+ const moveDownButtons = screen.getAllByLabelText('Move down');
419
+ await act(async () => {
420
+ await user.click(moveDownButtons[0]);
421
+ });
422
+
423
+ await waitFor(() => {
424
+ expect(mockUpdateBillingInformation).toHaveBeenCalled();
425
+ });
426
+
427
+ const lastCall = mockUpdateBillingInformation.mock.calls[mockUpdateBillingInformation.mock.calls.length - 1];
428
+ expect(lastCall[0].patientCoverage).toHaveLength(3);
429
+ expect(lastCall[0].patientCoverage[0].id).toBe('coverage-2');
430
+ });
431
+ });
@@ -0,0 +1,130 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { ActionIcon, Divider, Group, Input, List, ListItem, rem, Text } from '@mantine/core';
4
+ import type { Coverage, Patient } from '@medplum/fhirtypes';
5
+ import type { BillingInformation, LabOrderInputErrors } from '@medplum/health-gorilla-core';
6
+ import { useHealthGorillaLabOrderContext } from '@medplum/health-gorilla-react';
7
+ import { IconArrowDown, IconArrowUp } from '@tabler/icons-react';
8
+ import { useEffect, useState } from 'react';
9
+ import type { JSX } from 'react';
10
+
11
+ const INITIAL_COVERAGES: { coverage: Coverage; selected: boolean }[] = [];
12
+ const MAX_COVERAGES = 3;
13
+
14
+ export type PatientCoverages = { coverage: Coverage; selected: boolean }[];
15
+
16
+ export type CoverageInputProps = {
17
+ patient: Patient;
18
+ error?: NonNullable<LabOrderInputErrors['billingInformation']>['patientCoverage'];
19
+ };
20
+ export function CoverageInput({ patient, error }: CoverageInputProps): JSX.Element {
21
+ const { getActivePatientCoverages, updateBillingInformation, state } = useHealthGorillaLabOrderContext();
22
+ const [coverages, setCoverages] = useState<PatientCoverages>(INITIAL_COVERAGES);
23
+ const disabled = state.billingInformation.billTo !== 'insurance';
24
+
25
+ useEffect(() => {
26
+ getActivePatientCoverages()
27
+ .then((coverages) => {
28
+ setCoverages(coverages.map((coverage, i) => ({ coverage, selected: i <= 2 })));
29
+ })
30
+ .catch(console.error);
31
+ }, [getActivePatientCoverages, patient]);
32
+
33
+ useEffect(() => {
34
+ const selectedCoverages = coverages
35
+ ?.filter((item) => item.selected)
36
+ .map((item) => item.coverage)
37
+ .slice(0, 3);
38
+ updateBillingInformation({ patientCoverage: validatePatientCoverages(selectedCoverages) });
39
+ }, [coverages, updateBillingInformation]);
40
+
41
+ return (
42
+ <Input.Wrapper c={disabled ? 'dimmed' : undefined} label="Patient coverages (max 3)" error={error?.message}>
43
+ {coverages?.length ? (
44
+ <List type="ordered" listStyleType="none" spacing={4}>
45
+ {coverages.map(({ coverage }, i) => {
46
+ const display = coverage.payor?.find((p) => p.display)?.display || 'Unknown';
47
+ const listItem = (
48
+ <ListItem key={coverage.id} c={disabled || i >= MAX_COVERAGES ? 'dimmed' : undefined}>
49
+ <Group align="center" gap={4}>
50
+ {getCoverageListNumber(i)}
51
+ <ActionIcon.Group>
52
+ <ActionIcon
53
+ disabled={disabled || i === 0}
54
+ variant="default"
55
+ size="sm"
56
+ aria-label="Move up"
57
+ onClick={() => {
58
+ const newCoverages = [...coverages];
59
+ const temp = newCoverages[i];
60
+ newCoverages[i] = newCoverages[i - 1];
61
+ newCoverages[i - 1] = temp;
62
+ setCoverages(newCoverages);
63
+ }}
64
+ >
65
+ <IconArrowUp style={{ width: rem(16) }} stroke={1.5} />
66
+ </ActionIcon>
67
+
68
+ <ActionIcon
69
+ disabled={disabled || i === coverages.length - 1}
70
+ variant="default"
71
+ size="sm"
72
+ aria-label="Move down"
73
+ onClick={() => {
74
+ const newCoverages = [...coverages];
75
+ const temp = newCoverages[i];
76
+ newCoverages[i] = newCoverages[i + 1];
77
+ newCoverages[i + 1] = temp;
78
+ setCoverages(newCoverages);
79
+ }}
80
+ >
81
+ <IconArrowDown style={{ width: rem(16) }} stroke={1.5} />
82
+ </ActionIcon>
83
+ </ActionIcon.Group>
84
+ <Text>{display}</Text>
85
+ </Group>
86
+ </ListItem>
87
+ );
88
+ if (i === 3) {
89
+ return [
90
+ <Divider
91
+ key="more-divider"
92
+ label="Additional coverages that will not be submitted"
93
+ labelPosition="center"
94
+ my="xs"
95
+ />,
96
+ listItem,
97
+ ];
98
+ }
99
+
100
+ return listItem;
101
+ })}
102
+ </List>
103
+ ) : (
104
+ <Text>No coverages found</Text>
105
+ )}
106
+ </Input.Wrapper>
107
+ );
108
+ }
109
+
110
+ function getCoverageListNumber(index: number): React.ReactNode {
111
+ return (
112
+ <Text fw={500} display="inline-block" w={16} h={16} lh={1} ta="center">
113
+ {index < 3 ? `${index + 1}.` : ''}
114
+ </Text>
115
+ );
116
+ }
117
+
118
+ function validatePatientCoverages(coverages: Coverage[]): BillingInformation['patientCoverage'] {
119
+ if (coverages.length === 0) {
120
+ return coverages as [];
121
+ } else if (coverages.length === 1) {
122
+ return coverages as [Coverage];
123
+ } else if (coverages.length === 2) {
124
+ return coverages as [Coverage, Coverage];
125
+ } else if (coverages.length === 3) {
126
+ return coverages as [Coverage, Coverage, Coverage];
127
+ }
128
+
129
+ throw new Error('Invalid number of coverages');
130
+ }
@@ -0,0 +1,31 @@
1
+ .content {
2
+ word-wrap: break-word;
3
+ overflow-wrap: break-word;
4
+ white-space: normal;
5
+ word-break: break-word;
6
+ }
7
+
8
+ .title {
9
+ white-space: nowrap !important;
10
+ overflow: hidden !important;
11
+ text-overflow: ellipsis !important;
12
+ width: 100% !important;
13
+ max-width: 100% !important;
14
+ }
15
+
16
+ .contentContainer {
17
+ transition: background 0.2s;
18
+ cursor: pointer;
19
+ padding: 0.5rem;
20
+ border-radius: 0.5rem;
21
+ color: light-dark(var(--mantine-color-black), var(--mantine-color-gray-2));
22
+ }
23
+
24
+ .contentContainer:hover {
25
+ background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8));
26
+ }
27
+
28
+
29
+ .contentContainer.selected {
30
+ background: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8));
31
+ }