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.
- package/README.md +5 -0
- package/dist/examples/vintasend-medplum-example/.env.example +11 -0
- package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
- package/dist/examples/vintasend-medplum-example/README.md +190 -0
- package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
- package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
- package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
- package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
- package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
- package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
- package/dist/examples/vintasend-medplum-example/index.html +14 -0
- package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
- package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
- package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
- package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
- package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
- package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
- package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
- package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
- package/dist/examples/vintasend-medplum-example/package.json +75 -0
- package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
- package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
- package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
- package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
- package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
- package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
- package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
- package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
- package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
- package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
- package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
- package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
- package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
- package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
- package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
- package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
- package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
- package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
- package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
- package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
- package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
- package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
- package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
- package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
- package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
- package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
- package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
- package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
- package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
- package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
- package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
- package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
- package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
- package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
- package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
- package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
- package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
- package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
- package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
- package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
- package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
- package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
- package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
- package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
- package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
- package/dist/services/notification-service.js +11 -1
- package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +5 -0
- package/package.json +1 -1
package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { MantineProvider } from '@mantine/core';
|
|
4
|
+
import { Notifications } from '@mantine/notifications';
|
|
5
|
+
import { MockClient } from '@medplum/mock';
|
|
6
|
+
import { MedplumProvider } from '@medplum/react';
|
|
7
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
8
|
+
import userEvent from '@testing-library/user-event';
|
|
9
|
+
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { DoseSpotAdvancedOptions } from './DoseSpotAdvancedOptions';
|
|
11
|
+
|
|
12
|
+
describe('DoseSpotAdvancedOptions', () => {
|
|
13
|
+
let medplum: MockClient;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
medplum = new MockClient();
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const setup = (patientId: string): ReturnType<typeof render> => {
|
|
21
|
+
return render(
|
|
22
|
+
<MedplumProvider medplum={medplum}>
|
|
23
|
+
<MantineProvider>
|
|
24
|
+
<Notifications />
|
|
25
|
+
<DoseSpotAdvancedOptions patientId={patientId} />
|
|
26
|
+
</MantineProvider>
|
|
27
|
+
</MedplumProvider>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
test('Renders Advanced Options button', async () => {
|
|
32
|
+
setup('patient-123');
|
|
33
|
+
|
|
34
|
+
await waitFor(() => {
|
|
35
|
+
expect(screen.getByText('Advanced Options')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('Opens modal when Advanced Options button is clicked', async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
setup('patient-123');
|
|
42
|
+
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
expect(screen.getByText('Advanced Options')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const button = screen.getByText('Advanced Options');
|
|
48
|
+
await user.click(button);
|
|
49
|
+
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(screen.getByText('Advanced DoseSpot Options')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('Renders all sync sections in modal', async () => {
|
|
56
|
+
const user = userEvent.setup();
|
|
57
|
+
setup('patient-123');
|
|
58
|
+
|
|
59
|
+
const button = screen.getByText('Advanced Options');
|
|
60
|
+
await user.click(button);
|
|
61
|
+
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(screen.getByText('Prescriptions Sync')).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByText('Medication History Sync')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Patient Information Sync')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('Sync Prescriptions button calls executeBot', async () => {
|
|
70
|
+
const user = userEvent.setup();
|
|
71
|
+
setup('patient-123');
|
|
72
|
+
|
|
73
|
+
vi.spyOn(medplum, 'executeBot').mockResolvedValue({} as any);
|
|
74
|
+
|
|
75
|
+
const button = screen.getByText('Advanced Options');
|
|
76
|
+
await user.click(button);
|
|
77
|
+
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(screen.getByText('Sync Prescriptions')).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const syncButton = screen.getByText('Sync Prescriptions');
|
|
83
|
+
await user.click(syncButton);
|
|
84
|
+
|
|
85
|
+
await waitFor(() => {
|
|
86
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
87
|
+
expect(screen.getByText('Prescriptions synced successfully')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Sync History button calls executeBot', async () => {
|
|
92
|
+
const user = userEvent.setup();
|
|
93
|
+
setup('patient-123');
|
|
94
|
+
|
|
95
|
+
vi.spyOn(medplum, 'executeBot').mockResolvedValue({} as any);
|
|
96
|
+
|
|
97
|
+
const button = screen.getByText('Advanced Options');
|
|
98
|
+
await user.click(button);
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(screen.getByText('Sync History')).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const syncButton = screen.getByText('Sync History');
|
|
105
|
+
await user.click(syncButton);
|
|
106
|
+
|
|
107
|
+
await waitFor(() => {
|
|
108
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
109
|
+
expect(screen.getByText('History synced successfully')).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Sync Patient button calls executeBot', async () => {
|
|
114
|
+
const user = userEvent.setup();
|
|
115
|
+
setup('patient-123');
|
|
116
|
+
|
|
117
|
+
vi.spyOn(medplum, 'executeBot').mockResolvedValue({} as any);
|
|
118
|
+
|
|
119
|
+
const button = screen.getByText('Advanced Options');
|
|
120
|
+
await user.click(button);
|
|
121
|
+
|
|
122
|
+
await waitFor(() => {
|
|
123
|
+
expect(screen.getByText('Sync Patient')).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const syncButton = screen.getByText('Sync Patient');
|
|
127
|
+
await user.click(syncButton);
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
131
|
+
expect(screen.getByText('Patient sync success')).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('Handles sync error', async () => {
|
|
136
|
+
const user = userEvent.setup();
|
|
137
|
+
setup('patient-123');
|
|
138
|
+
|
|
139
|
+
const error = new Error('Sync failed');
|
|
140
|
+
vi.spyOn(medplum, 'executeBot').mockRejectedValue(error);
|
|
141
|
+
|
|
142
|
+
const button = screen.getByText('Advanced Options');
|
|
143
|
+
await user.click(button);
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
expect(screen.getByText('Sync Prescriptions')).toBeInTheDocument();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const syncButton = screen.getByText('Sync Prescriptions');
|
|
150
|
+
await user.click(syncButton);
|
|
151
|
+
|
|
152
|
+
await waitFor(() => {
|
|
153
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
154
|
+
expect(screen.getByText(/sync failed/i)).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('Handles history sync error', async () => {
|
|
159
|
+
const user = userEvent.setup();
|
|
160
|
+
setup('patient-123');
|
|
161
|
+
|
|
162
|
+
const error = new Error('History sync failed');
|
|
163
|
+
vi.spyOn(medplum, 'executeBot').mockRejectedValue(error);
|
|
164
|
+
|
|
165
|
+
const button = screen.getByText('Advanced Options');
|
|
166
|
+
await user.click(button);
|
|
167
|
+
|
|
168
|
+
await waitFor(() => {
|
|
169
|
+
expect(screen.getByText('Sync History')).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const syncButton = screen.getByText('Sync History');
|
|
173
|
+
await user.click(syncButton);
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
177
|
+
expect(screen.getByText(/History sync failed/i)).toBeInTheDocument();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('Handles patient sync error', async () => {
|
|
182
|
+
const user = userEvent.setup();
|
|
183
|
+
setup('patient-123');
|
|
184
|
+
|
|
185
|
+
const error = new Error('Patient sync failed');
|
|
186
|
+
vi.spyOn(medplum, 'executeBot').mockRejectedValue(error);
|
|
187
|
+
|
|
188
|
+
const button = screen.getByText('Advanced Options');
|
|
189
|
+
await user.click(button);
|
|
190
|
+
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
expect(screen.getByText('Sync Patient')).toBeInTheDocument();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const syncButton = screen.getByText('Sync Patient');
|
|
196
|
+
await user.click(syncButton);
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(medplum.executeBot).toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Error notification should appear - use getAllByText for multiple matches
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
const errorTexts = screen.getAllByText(/error/i);
|
|
205
|
+
expect(errorTexts.length).toBeGreaterThan(0);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('Can change prescription start date', async () => {
|
|
210
|
+
const user = userEvent.setup();
|
|
211
|
+
setup('patient-123');
|
|
212
|
+
|
|
213
|
+
const button = screen.getByText('Advanced Options');
|
|
214
|
+
await user.click(button);
|
|
215
|
+
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
expect(screen.getByText('Prescriptions Sync')).toBeInTheDocument();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const startDateInputs = screen.getAllByLabelText('Start Date');
|
|
221
|
+
const prescriptionStartInput = startDateInputs[0];
|
|
222
|
+
|
|
223
|
+
await user.clear(prescriptionStartInput);
|
|
224
|
+
await user.type(prescriptionStartInput, '2024-01-01');
|
|
225
|
+
|
|
226
|
+
expect(prescriptionStartInput).toHaveValue('2024-01-01');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('Can change prescription end date', async () => {
|
|
230
|
+
const user = userEvent.setup();
|
|
231
|
+
setup('patient-123');
|
|
232
|
+
|
|
233
|
+
const button = screen.getByText('Advanced Options');
|
|
234
|
+
await user.click(button);
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(screen.getByText('Prescriptions Sync')).toBeInTheDocument();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const endDateInputs = screen.getAllByLabelText('End Date');
|
|
241
|
+
const prescriptionEndInput = endDateInputs[0];
|
|
242
|
+
|
|
243
|
+
await user.clear(prescriptionEndInput);
|
|
244
|
+
await user.type(prescriptionEndInput, '2024-12-31');
|
|
245
|
+
|
|
246
|
+
expect(prescriptionEndInput).toHaveValue('2024-12-31');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('Can change history start date', async () => {
|
|
250
|
+
const user = userEvent.setup();
|
|
251
|
+
setup('patient-123');
|
|
252
|
+
|
|
253
|
+
const button = screen.getByText('Advanced Options');
|
|
254
|
+
await user.click(button);
|
|
255
|
+
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(screen.getByText('Medication History Sync')).toBeInTheDocument();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const startDateInputs = screen.getAllByLabelText('Start Date');
|
|
261
|
+
const historyStartInput = startDateInputs[1];
|
|
262
|
+
|
|
263
|
+
await user.clear(historyStartInput);
|
|
264
|
+
await user.type(historyStartInput, '2023-06-01');
|
|
265
|
+
|
|
266
|
+
expect(historyStartInput).toHaveValue('2023-06-01');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('Can change history end date', async () => {
|
|
270
|
+
const user = userEvent.setup();
|
|
271
|
+
setup('patient-123');
|
|
272
|
+
|
|
273
|
+
const button = screen.getByText('Advanced Options');
|
|
274
|
+
await user.click(button);
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(screen.getByText('Medication History Sync')).toBeInTheDocument();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const endDateInputs = screen.getAllByLabelText('End Date');
|
|
281
|
+
const historyEndInput = endDateInputs[1];
|
|
282
|
+
|
|
283
|
+
await user.clear(historyEndInput);
|
|
284
|
+
await user.type(historyEndInput, '2023-12-31');
|
|
285
|
+
|
|
286
|
+
expect(historyEndInput).toHaveValue('2023-12-31');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('Modal can be opened and viewed', async () => {
|
|
290
|
+
const user = userEvent.setup();
|
|
291
|
+
setup('patient-123');
|
|
292
|
+
|
|
293
|
+
const button = screen.getByText('Advanced Options');
|
|
294
|
+
await user.click(button);
|
|
295
|
+
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
expect(screen.getByText('Advanced DoseSpot Options')).toBeInTheDocument();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Verify modal content
|
|
301
|
+
expect(screen.getByText('Prescriptions Sync')).toBeInTheDocument();
|
|
302
|
+
expect(screen.getByText('Medication History Sync')).toBeInTheDocument();
|
|
303
|
+
expect(screen.getByText('Patient Information Sync')).toBeInTheDocument();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('Prescription sync has date inputs', async () => {
|
|
307
|
+
const user = userEvent.setup();
|
|
308
|
+
setup('patient-123');
|
|
309
|
+
|
|
310
|
+
const button = screen.getByText('Advanced Options');
|
|
311
|
+
await user.click(button);
|
|
312
|
+
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(screen.getByText('Prescriptions Sync')).toBeInTheDocument();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Verify date inputs exist for prescriptions sync
|
|
318
|
+
const startDateInputs = screen.getAllByLabelText('Start Date');
|
|
319
|
+
const endDateInputs = screen.getAllByLabelText('End Date');
|
|
320
|
+
|
|
321
|
+
expect(startDateInputs.length).toBeGreaterThan(0);
|
|
322
|
+
expect(endDateInputs.length).toBeGreaterThan(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('Renders description texts for each sync section', async () => {
|
|
326
|
+
const user = userEvent.setup();
|
|
327
|
+
setup('patient-123');
|
|
328
|
+
|
|
329
|
+
const button = screen.getByText('Advanced Options');
|
|
330
|
+
await user.click(button);
|
|
331
|
+
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(screen.getByText(/Fetches recently completed and active prescriptions/i)).toBeInTheDocument();
|
|
334
|
+
expect(screen.getByText(/Retrieves medication history from DoseSpot/i)).toBeInTheDocument();
|
|
335
|
+
expect(screen.getByText(/Syncs patient between Medplum and DoseSpot/i)).toBeInTheDocument();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('Sync buttons call executeBot with correct parameters', async () => {
|
|
340
|
+
const user = userEvent.setup();
|
|
341
|
+
setup('test-patient-id');
|
|
342
|
+
|
|
343
|
+
vi.spyOn(medplum, 'executeBot').mockResolvedValue({} as any);
|
|
344
|
+
|
|
345
|
+
const button = screen.getByText('Advanced Options');
|
|
346
|
+
await user.click(button);
|
|
347
|
+
|
|
348
|
+
await waitFor(() => {
|
|
349
|
+
expect(screen.getByText('Sync Patient')).toBeInTheDocument();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const syncButton = screen.getByText('Sync Patient');
|
|
353
|
+
await user.click(syncButton);
|
|
354
|
+
|
|
355
|
+
await waitFor(() => {
|
|
356
|
+
expect(medplum.executeBot).toHaveBeenCalledWith(
|
|
357
|
+
expect.anything(),
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
patientId: 'test-patient-id',
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { Box, Button, Group, Modal, Stack, Text, TextInput } from '@mantine/core';
|
|
4
|
+
import { showNotification } from '@mantine/notifications';
|
|
5
|
+
import { normalizeErrorString } from '@medplum/core';
|
|
6
|
+
import {
|
|
7
|
+
DOSESPOT_MEDICATION_HISTORY_BOT,
|
|
8
|
+
DOSESPOT_PATIENT_SYNC_BOT,
|
|
9
|
+
DOSESPOT_PRESCRIPTIONS_SYNC_BOT,
|
|
10
|
+
} from '@medplum/dosespot-react';
|
|
11
|
+
import { useMedplum } from '@medplum/react-hooks';
|
|
12
|
+
import { IconSettings } from '@tabler/icons-react';
|
|
13
|
+
import { useCallback, useState } from 'react';
|
|
14
|
+
import type { JSX } from 'react';
|
|
15
|
+
|
|
16
|
+
export function DoseSpotAdvancedOptions({ patientId }: { patientId: string }): JSX.Element {
|
|
17
|
+
const medplum = useMedplum();
|
|
18
|
+
const [prescriptionStartDate, setPrescriptionStartDate] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
19
|
+
const [prescriptionEndDate, setPrescriptionEndDate] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
20
|
+
const [historyStartDate, setHistoryStartDate] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
21
|
+
const [historyEndDate, setHistoryEndDate] = useState<string>(new Date().toISOString().split('T')[0]);
|
|
22
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
23
|
+
|
|
24
|
+
const syncPrescriptions = useCallback(async () => {
|
|
25
|
+
if (!prescriptionStartDate || !prescriptionEndDate) {
|
|
26
|
+
showNotification({ color: 'red', title: 'Error', message: 'Please select both start and end dates' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await medplum.executeBot(DOSESPOT_PRESCRIPTIONS_SYNC_BOT, {
|
|
32
|
+
patientId,
|
|
33
|
+
start: prescriptionStartDate,
|
|
34
|
+
end: prescriptionEndDate,
|
|
35
|
+
});
|
|
36
|
+
showNotification({ color: 'green', title: 'Success', message: 'Prescriptions synced successfully' });
|
|
37
|
+
} catch (err) {
|
|
38
|
+
showNotification({ color: 'red', title: 'Error', message: normalizeErrorString(err) });
|
|
39
|
+
}
|
|
40
|
+
}, [medplum, patientId, prescriptionStartDate, prescriptionEndDate]);
|
|
41
|
+
|
|
42
|
+
const syncHistory = useCallback(async () => {
|
|
43
|
+
try {
|
|
44
|
+
await medplum.executeBot(DOSESPOT_MEDICATION_HISTORY_BOT, {
|
|
45
|
+
patientId,
|
|
46
|
+
start: historyStartDate,
|
|
47
|
+
end: historyEndDate,
|
|
48
|
+
});
|
|
49
|
+
showNotification({ color: 'green', title: 'Success', message: 'History synced successfully' });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
showNotification({ color: 'red', title: 'Error', message: normalizeErrorString(err) });
|
|
52
|
+
}
|
|
53
|
+
}, [medplum, patientId, historyStartDate, historyEndDate]);
|
|
54
|
+
|
|
55
|
+
const syncPatient = useCallback(async () => {
|
|
56
|
+
try {
|
|
57
|
+
await medplum.executeBot(DOSESPOT_PATIENT_SYNC_BOT, {
|
|
58
|
+
patientId,
|
|
59
|
+
});
|
|
60
|
+
showNotification({ color: 'green', title: 'Success', message: 'Patient sync success' });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
showNotification({ color: 'red', title: 'Error', message: normalizeErrorString(err) });
|
|
63
|
+
}
|
|
64
|
+
}, [medplum, patientId]);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Group style={{ position: 'absolute', top: 8, right: 8, zIndex: 100 }}>
|
|
69
|
+
<Button variant="subtle" size="sm" onClick={() => setShowAdvanced(true)}>
|
|
70
|
+
<IconSettings size={16} style={{ marginRight: 8 }} />
|
|
71
|
+
Advanced Options
|
|
72
|
+
</Button>
|
|
73
|
+
</Group>
|
|
74
|
+
|
|
75
|
+
<Modal
|
|
76
|
+
opened={showAdvanced}
|
|
77
|
+
onClose={() => setShowAdvanced(false)}
|
|
78
|
+
size="lg"
|
|
79
|
+
title="Advanced DoseSpot Options"
|
|
80
|
+
styles={{
|
|
81
|
+
title: {
|
|
82
|
+
fontSize: '1.5rem',
|
|
83
|
+
fontWeight: 600,
|
|
84
|
+
},
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<Box>
|
|
88
|
+
<Stack>
|
|
89
|
+
<Box>
|
|
90
|
+
<Text mb="sm">Prescriptions Sync</Text>
|
|
91
|
+
<Text c="dimmed" mb="md">
|
|
92
|
+
Fetches recently completed and active prescriptions from DoseSpot for the specified date range and
|
|
93
|
+
patient. This will create or update MedicationRequest resources.
|
|
94
|
+
</Text>
|
|
95
|
+
<Group align="flex-end">
|
|
96
|
+
<TextInput
|
|
97
|
+
label="Start Date"
|
|
98
|
+
type="date"
|
|
99
|
+
value={prescriptionStartDate}
|
|
100
|
+
onChange={(e) => setPrescriptionStartDate(e.target.value)}
|
|
101
|
+
/>
|
|
102
|
+
<TextInput
|
|
103
|
+
label="End Date"
|
|
104
|
+
type="date"
|
|
105
|
+
value={prescriptionEndDate}
|
|
106
|
+
onChange={(e) => setPrescriptionEndDate(e.target.value)}
|
|
107
|
+
/>
|
|
108
|
+
<Button onClick={syncPrescriptions}>Sync Prescriptions</Button>
|
|
109
|
+
</Group>
|
|
110
|
+
</Box>
|
|
111
|
+
|
|
112
|
+
<Box mt="md">
|
|
113
|
+
<Text mb="sm">Medication History Sync</Text>
|
|
114
|
+
<Text c="dimmed" mb="md">
|
|
115
|
+
Retrieves medication history from DoseSpot for this patient and adds MedicationRequest resources to
|
|
116
|
+
Medplum.
|
|
117
|
+
</Text>
|
|
118
|
+
<Group align="flex-end">
|
|
119
|
+
<TextInput
|
|
120
|
+
label="Start Date"
|
|
121
|
+
type="date"
|
|
122
|
+
value={historyStartDate}
|
|
123
|
+
onChange={(e) => setHistoryStartDate(e.target.value)}
|
|
124
|
+
/>
|
|
125
|
+
<TextInput
|
|
126
|
+
label="End Date"
|
|
127
|
+
type="date"
|
|
128
|
+
value={historyEndDate}
|
|
129
|
+
onChange={(e) => setHistoryEndDate(e.target.value)}
|
|
130
|
+
/>
|
|
131
|
+
<Button onClick={syncHistory}>Sync History</Button>
|
|
132
|
+
</Group>
|
|
133
|
+
</Box>
|
|
134
|
+
|
|
135
|
+
<Box mt="md">
|
|
136
|
+
<Text mb="sm">Patient Information Sync</Text>
|
|
137
|
+
<Text c="dimmed" mb="md">
|
|
138
|
+
Syncs patient between Medplum and DoseSpot. It creates an identifier for the patient in Medplum to link
|
|
139
|
+
it to the patient's record in DoseSpot. It also adds MedicationRequest and AllergyIntolerance resources
|
|
140
|
+
from Medplum to that patient's record in DoseSpot.
|
|
141
|
+
</Text>
|
|
142
|
+
<Button onClick={syncPatient}>Sync Patient</Button>
|
|
143
|
+
</Box>
|
|
144
|
+
</Stack>
|
|
145
|
+
</Box>
|
|
146
|
+
</Modal>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { MantineProvider } from '@mantine/core';
|
|
4
|
+
import { Notifications } from '@mantine/notifications';
|
|
5
|
+
import { HomerSimpson, MockClient } from '@medplum/mock';
|
|
6
|
+
import { MedplumProvider } from '@medplum/react';
|
|
7
|
+
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
8
|
+
import { MemoryRouter, Routes, Route } from 'react-router';
|
|
9
|
+
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { DoseSpotTab } from './DoseSpotTab';
|
|
11
|
+
import { useDoseSpotIFrame } from '@medplum/dosespot-react';
|
|
12
|
+
|
|
13
|
+
// Mock useDoseSpotIFrame
|
|
14
|
+
vi.mock('@medplum/dosespot-react', () => ({
|
|
15
|
+
useDoseSpotIFrame: vi.fn(() => 'https://dosespot.example.com/iframe'),
|
|
16
|
+
DOSESPOT_MEDICATION_HISTORY_BOT: { reference: 'Bot/med-history' },
|
|
17
|
+
DOSESPOT_PATIENT_SYNC_BOT: { reference: 'Bot/patient-sync' },
|
|
18
|
+
DOSESPOT_PRESCRIPTIONS_SYNC_BOT: { reference: 'Bot/prescriptions-sync' },
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('DoseSpotTab', () => {
|
|
22
|
+
let medplum: MockClient;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
medplum = new MockClient();
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const setup = (url: string): ReturnType<typeof render> => {
|
|
30
|
+
return render(
|
|
31
|
+
<MemoryRouter initialEntries={[url]}>
|
|
32
|
+
<MedplumProvider medplum={medplum}>
|
|
33
|
+
<MantineProvider>
|
|
34
|
+
<Notifications />
|
|
35
|
+
<Routes>
|
|
36
|
+
<Route path="/Patient/:patientId/dosespot" element={<DoseSpotTab />} />
|
|
37
|
+
</Routes>
|
|
38
|
+
</MantineProvider>
|
|
39
|
+
</MedplumProvider>
|
|
40
|
+
</MemoryRouter>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
test('Renders DoseSpotAdvancedOptions when patientId is present', async () => {
|
|
45
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
46
|
+
|
|
47
|
+
await waitFor(() => {
|
|
48
|
+
expect(screen.getByText('Advanced Options')).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('Renders iframe when iframeUrl is available', async () => {
|
|
53
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
54
|
+
|
|
55
|
+
await waitFor(() => {
|
|
56
|
+
const iframe = document.querySelector('iframe#dosespot-iframe');
|
|
57
|
+
expect(iframe).toBeInTheDocument();
|
|
58
|
+
expect(iframe).toHaveAttribute('src', 'https://dosespot.example.com/iframe');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Does not render iframe when iframeUrl is undefined', async () => {
|
|
63
|
+
vi.mocked(useDoseSpotIFrame).mockReturnValue(undefined);
|
|
64
|
+
|
|
65
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
66
|
+
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
const iframe = document.querySelector('iframe#dosespot-iframe');
|
|
69
|
+
expect(iframe).not.toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('Calls onPatientSyncSuccess callback and shows notification', async () => {
|
|
74
|
+
let capturedOptions: any = {};
|
|
75
|
+
|
|
76
|
+
vi.mocked(useDoseSpotIFrame).mockImplementation((options) => {
|
|
77
|
+
capturedOptions = options;
|
|
78
|
+
return 'https://dosespot.example.com/iframe';
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
82
|
+
|
|
83
|
+
// Simulate the onPatientSyncSuccess callback being called
|
|
84
|
+
await act(async () => {
|
|
85
|
+
capturedOptions.onPatientSyncSuccess?.();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(screen.getByText('Patient sync success')).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Calls onIframeSuccess callback and shows notification', async () => {
|
|
94
|
+
let capturedOptions: any = {};
|
|
95
|
+
|
|
96
|
+
vi.mocked(useDoseSpotIFrame).mockImplementation((options) => {
|
|
97
|
+
capturedOptions = options;
|
|
98
|
+
return 'https://dosespot.example.com/iframe';
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
102
|
+
|
|
103
|
+
// Simulate the onIframeSuccess callback being called
|
|
104
|
+
await act(async () => {
|
|
105
|
+
capturedOptions.onIframeSuccess?.();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(screen.getByText('DoseSpot iframe success')).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Calls onError callback and shows error notification', async () => {
|
|
114
|
+
let capturedOptions: any = {};
|
|
115
|
+
|
|
116
|
+
vi.mocked(useDoseSpotIFrame).mockImplementation((options) => {
|
|
117
|
+
capturedOptions = options;
|
|
118
|
+
return 'https://dosespot.example.com/iframe';
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
122
|
+
|
|
123
|
+
// Simulate the onError callback being called
|
|
124
|
+
await act(async () => {
|
|
125
|
+
capturedOptions.onError?.(new Error('DoseSpot connection failed'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(screen.getByText(/DoseSpot connection failed/i)).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('Passes correct patientId to useDoseSpotIFrame', async () => {
|
|
134
|
+
vi.mocked(useDoseSpotIFrame).mockReturnValue('https://dosespot.example.com/iframe');
|
|
135
|
+
|
|
136
|
+
setup(`/Patient/custom-patient-id/dosespot`);
|
|
137
|
+
|
|
138
|
+
expect(useDoseSpotIFrame).toHaveBeenCalledWith(
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
patientId: 'custom-patient-id',
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('Iframe has correct attributes', async () => {
|
|
146
|
+
vi.mocked(useDoseSpotIFrame).mockReturnValue('https://dosespot.example.com/iframe');
|
|
147
|
+
|
|
148
|
+
setup(`/Patient/${HomerSimpson.id}/dosespot`);
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
const iframe = document.querySelector('iframe#dosespot-iframe') as HTMLIFrameElement;
|
|
152
|
+
expect(iframe).toBeInTheDocument();
|
|
153
|
+
expect(iframe.name).toBe('dosespot-iframe');
|
|
154
|
+
expect(iframe.id).toBe('dosespot-iframe');
|
|
155
|
+
// Check basic styling is applied
|
|
156
|
+
expect(iframe.style.minHeight).toBe('calc(100vh)');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|