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