vintasend 0.4.3 → 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 +1 -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 +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -345,6 +345,7 @@ VintaSend has many backend, adapter, and template renderer implementations. If y
|
|
|
345
345
|
##### Adapters
|
|
346
346
|
|
|
347
347
|
* **[vintasend-nodemailer](https://github.com/vintasoftware/vintasend-nodemailer/)**: Uses nodemailer to send transactional emails to users.
|
|
348
|
+
* **[vintasend-sendgrid](https://github.com/vintasoftware/vintasend-ts-sendgrid/)**: Uses SendGrid's API to send transactional emails with attachment support.
|
|
348
349
|
* **[vintasend-medplum](https://github.com/vintasoftware/vintasend-medplum/)**: Uses Medplum's email API to send notifications in healthcare applications.
|
|
349
350
|
|
|
350
351
|
##### Attachment Managers
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Medplum Configuration
|
|
2
|
+
MEDPLUM_BASE_URL=https://api.medplum.com
|
|
3
|
+
MEDPLUM_CLIENT_ID=your-client-id-here
|
|
4
|
+
MEDPLUM_CLIENT_SECRET=your-client-secret-here
|
|
5
|
+
|
|
6
|
+
# Application Configuration
|
|
7
|
+
APP_BASE_URL=https://your-app-url.com
|
|
8
|
+
|
|
9
|
+
SENDGRID_API_KEY=
|
|
10
|
+
SENDGRID_FROM_EMAIL=vintasend-medplum@vinta.com.br
|
|
11
|
+
SENDGRID_FROM_NAME=VintaSend Medplum Example
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# Implementation Plan: Task File Attachments with Email Notifications
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This plan outlines the implementation of file upload functionality for tasks, with automatic inclusion of those files as email attachments in task assignment notifications. This builds on the existing VintaSend notification system and leverages VintaSend's attachment management capabilities.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Phase 1: Foundation - Medplum File Storage Setup
|
|
9
|
+
|
|
10
|
+
### Objectives
|
|
11
|
+
- Understand FHIR Binary and Media resources
|
|
12
|
+
- Set up file upload utilities
|
|
13
|
+
- Create helper functions for file management
|
|
14
|
+
|
|
15
|
+
### Tasks
|
|
16
|
+
|
|
17
|
+
#### 1.1: Create File Upload Utilities
|
|
18
|
+
**File**: `lib/file-upload.ts`
|
|
19
|
+
|
|
20
|
+
**Implementation**:
|
|
21
|
+
```typescript
|
|
22
|
+
import { MedplumClient } from '@medplum/core';
|
|
23
|
+
import { Binary, Media, Reference, Task } from '@medplum/fhirtypes';
|
|
24
|
+
|
|
25
|
+
export interface FileUploadResult {
|
|
26
|
+
binary: Binary;
|
|
27
|
+
media: Media;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function uploadFileToMedplum(
|
|
31
|
+
medplum: MedplumClient,
|
|
32
|
+
file: File | Buffer,
|
|
33
|
+
filename: string,
|
|
34
|
+
contentType: string
|
|
35
|
+
): Promise<FileUploadResult> {
|
|
36
|
+
// Create Binary resource for file storage
|
|
37
|
+
// Create Media resource as metadata wrapper
|
|
38
|
+
// Return both references
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function attachFileToTask(
|
|
42
|
+
medplum: MedplumClient,
|
|
43
|
+
task: Task,
|
|
44
|
+
mediaReference: Reference<Media>
|
|
45
|
+
): Promise<Task> {
|
|
46
|
+
// Add media reference to task.input array
|
|
47
|
+
// Update and return task
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getTaskAttachments(
|
|
51
|
+
medplum: MedplumClient,
|
|
52
|
+
task: Task
|
|
53
|
+
): Promise<Media[]> {
|
|
54
|
+
// Retrieve all Media resources referenced in task.input
|
|
55
|
+
// Return array of Media resources
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Documentation in Tutorial**:
|
|
60
|
+
- Add new section: "Advanced: File Attachments for Task Notifications"
|
|
61
|
+
- Subsection: "Step 1: Understanding FHIR File Storage"
|
|
62
|
+
- Explain Binary vs Media resources
|
|
63
|
+
- Document helper functions
|
|
64
|
+
|
|
65
|
+
#### 1.2: Update Constants
|
|
66
|
+
**File**: `lib/constants.ts`
|
|
67
|
+
|
|
68
|
+
**Implementation**:
|
|
69
|
+
```typescript
|
|
70
|
+
export const TASK_ATTACHMENT_INPUT_TYPE = {
|
|
71
|
+
system: 'http://your-app-url.com/task-input-types',
|
|
72
|
+
code: 'attachment',
|
|
73
|
+
display: 'File Attachment'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024; // 10MB
|
|
77
|
+
export const ALLOWED_FILE_TYPES = [
|
|
78
|
+
'application/pdf',
|
|
79
|
+
'image/jpeg',
|
|
80
|
+
'image/png',
|
|
81
|
+
'application/msword',
|
|
82
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
83
|
+
];
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Documentation in Tutorial**:
|
|
87
|
+
- Subsection: "Step 2: Configure File Upload Constraints"
|
|
88
|
+
- Document allowed file types
|
|
89
|
+
- Explain size limits and FHIR standards
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Phase 2: Frontend - File Upload UI Component
|
|
94
|
+
|
|
95
|
+
### Objectives
|
|
96
|
+
- Create React component for file uploads
|
|
97
|
+
- Integrate with task creation/editing forms
|
|
98
|
+
- Handle file validation and preview
|
|
99
|
+
|
|
100
|
+
### Tasks
|
|
101
|
+
|
|
102
|
+
#### 2.1: Create File Upload Component
|
|
103
|
+
**File**: `src/components/tasks/TaskFileUpload.tsx`
|
|
104
|
+
|
|
105
|
+
**Implementation**:
|
|
106
|
+
```typescript
|
|
107
|
+
import { useState, useCallback } from 'react';
|
|
108
|
+
import { useMedplum } from '@medplum/react';
|
|
109
|
+
import { Button, FileInput, Group, Text } from '@mantine/core';
|
|
110
|
+
import { uploadFileToMedplum } from '../../../lib/file-upload';
|
|
111
|
+
|
|
112
|
+
interface TaskFileUploadProps {
|
|
113
|
+
onFileUploaded: (mediaId: string) => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function TaskFileUpload({ onFileUploaded }: TaskFileUploadProps) {
|
|
117
|
+
const medplum = useMedplum();
|
|
118
|
+
const [uploading, setUploading] = useState(false);
|
|
119
|
+
const [error, setError] = useState<string | null>(null);
|
|
120
|
+
|
|
121
|
+
const handleFileChange = useCallback(async (file: File) => {
|
|
122
|
+
// Validate file type and size
|
|
123
|
+
// Upload to Medplum
|
|
124
|
+
// Call onFileUploaded with Media ID
|
|
125
|
+
}, [medplum, onFileUploaded]);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div>
|
|
129
|
+
{/* FileInput with validation */}
|
|
130
|
+
{/* Upload progress indicator */}
|
|
131
|
+
{/* Error display */}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### 2.2: Create Attachment List Component
|
|
138
|
+
**File**: `src/components/tasks/TaskAttachmentList.tsx`
|
|
139
|
+
|
|
140
|
+
**Implementation**:
|
|
141
|
+
```typescript
|
|
142
|
+
import { Media } from '@medplum/fhirtypes';
|
|
143
|
+
import { Badge, Group, ActionIcon } from '@mantine/core';
|
|
144
|
+
import { IconDownload, IconX } from '@tabler/icons-react';
|
|
145
|
+
|
|
146
|
+
interface TaskAttachmentListProps {
|
|
147
|
+
attachments: Media[];
|
|
148
|
+
onRemove?: (mediaId: string) => void;
|
|
149
|
+
readOnly?: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function TaskAttachmentList({
|
|
153
|
+
attachments,
|
|
154
|
+
onRemove,
|
|
155
|
+
readOnly = false
|
|
156
|
+
}: TaskAttachmentListProps) {
|
|
157
|
+
// Display list of attachments with download/remove options
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### 2.3: Integrate with Task Form
|
|
162
|
+
**File**: `src/components/tasks/TaskFormWithAttachments.tsx`
|
|
163
|
+
|
|
164
|
+
**Implementation**:
|
|
165
|
+
- Extend existing task form component
|
|
166
|
+
- Add file upload section
|
|
167
|
+
- Handle attachment state management
|
|
168
|
+
- Submit attachments with task creation/update
|
|
169
|
+
|
|
170
|
+
**Documentation in Tutorial**:
|
|
171
|
+
- Subsection: "Step 3: Build File Upload UI"
|
|
172
|
+
- Show component implementation
|
|
173
|
+
- Explain integration with task forms
|
|
174
|
+
- Include screenshots/examples
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Phase 3: Backend - Email Attachment Integration
|
|
179
|
+
|
|
180
|
+
### Objectives
|
|
181
|
+
- Retrieve task attachments when sending notifications
|
|
182
|
+
- Convert Media/Binary resources to VintaSend attachments
|
|
183
|
+
- Update email templates to mention attachments
|
|
184
|
+
|
|
185
|
+
### Tasks
|
|
186
|
+
|
|
187
|
+
#### 3.1: Update Notification Service with Attachment Mapping
|
|
188
|
+
**File**: `lib/notification-service.ts`
|
|
189
|
+
|
|
190
|
+
**Implementation**:
|
|
191
|
+
```typescript
|
|
192
|
+
import { Media } from '@medplum/fhirtypes';
|
|
193
|
+
|
|
194
|
+
async function convertMediaToAttachment(
|
|
195
|
+
medplum: MedplumClient,
|
|
196
|
+
media: Media
|
|
197
|
+
): Promise<{
|
|
198
|
+
filename: string;
|
|
199
|
+
content: Buffer;
|
|
200
|
+
contentType: string;
|
|
201
|
+
}> {
|
|
202
|
+
// Fetch Binary resource from media.content.url
|
|
203
|
+
// Extract file data
|
|
204
|
+
// Return in VintaSend attachment format
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update TaskAssignmentContextGenerator if needed
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 3.2: Update Task Assignment Email Service
|
|
211
|
+
**File**: `bots/services/emails/send-task-assignment-email.ts`
|
|
212
|
+
|
|
213
|
+
**Implementation**:
|
|
214
|
+
```typescript
|
|
215
|
+
import { getTaskAttachments } from '../../../lib/file-upload';
|
|
216
|
+
|
|
217
|
+
export async function sendTaskAssignmentEmail(
|
|
218
|
+
medplum: MedplumClient,
|
|
219
|
+
task: Task,
|
|
220
|
+
taskLinkBaseUrl: string,
|
|
221
|
+
sendgridConfig: SendGridConfig
|
|
222
|
+
) {
|
|
223
|
+
// ... existing code ...
|
|
224
|
+
|
|
225
|
+
// NEW: Retrieve task attachments
|
|
226
|
+
const taskAttachments = await getTaskAttachments(medplum, task);
|
|
227
|
+
|
|
228
|
+
// NEW: Convert to VintaSend attachment format
|
|
229
|
+
const attachments = await Promise.all(
|
|
230
|
+
taskAttachments.map(media => convertMediaToAttachment(medplum, media))
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
await vintasend.createNotification({
|
|
234
|
+
userId: referenceString,
|
|
235
|
+
notificationTypeId: 'taskAssignment',
|
|
236
|
+
// ... existing params ...
|
|
237
|
+
attachments, // NEW: Add attachments
|
|
238
|
+
extraParams: {
|
|
239
|
+
attachmentCount: attachments.length // For template
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### 3.3: Update Email Templates
|
|
246
|
+
**File**: `notification-templates/emails/task-assignment/body.html.pug`
|
|
247
|
+
|
|
248
|
+
**Implementation**:
|
|
249
|
+
```pug
|
|
250
|
+
doctype html
|
|
251
|
+
html
|
|
252
|
+
head
|
|
253
|
+
meta(charset='utf-8')
|
|
254
|
+
style.
|
|
255
|
+
body { white-space: pre-line; }
|
|
256
|
+
body
|
|
257
|
+
h1 You've been assigned a task
|
|
258
|
+
|
|
259
|
+
p Hello #{firstName},
|
|
260
|
+
|
|
261
|
+
p You have been assigned a new task:
|
|
262
|
+
|
|
263
|
+
p
|
|
264
|
+
strong Task:
|
|
265
|
+
| #{taskTitle}
|
|
266
|
+
if taskDescription
|
|
267
|
+
p
|
|
268
|
+
strong Description:
|
|
269
|
+
| #{taskDescription}
|
|
270
|
+
|
|
271
|
+
// NEW: Show attachment info
|
|
272
|
+
if attachmentCount > 0
|
|
273
|
+
p
|
|
274
|
+
strong Attachments:
|
|
275
|
+
| #{attachmentCount} file(s) attached
|
|
276
|
+
p Files are attached to this email for your reference.
|
|
277
|
+
|
|
278
|
+
p
|
|
279
|
+
a(href=taskLink) View Task Details
|
|
280
|
+
|
|
281
|
+
if taskIsUrgent
|
|
282
|
+
p
|
|
283
|
+
strong URGENT - This task requires immediate attention
|
|
284
|
+
|
|
285
|
+
if requesterName
|
|
286
|
+
p
|
|
287
|
+
em Task created by: #{requesterName}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Documentation in Tutorial**:
|
|
291
|
+
- Subsection: "Step 4: Add Attachment Support to Notifications"
|
|
292
|
+
- Explain attachment retrieval
|
|
293
|
+
- Show VintaSend attachment API usage
|
|
294
|
+
- Document template updates
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Phase 4: Testing & Validation
|
|
299
|
+
|
|
300
|
+
### Objectives
|
|
301
|
+
- Test file upload flow end-to-end
|
|
302
|
+
- Validate email attachments
|
|
303
|
+
- Test error scenarios
|
|
304
|
+
|
|
305
|
+
### Tasks
|
|
306
|
+
|
|
307
|
+
#### 4.1: Create Test Utilities
|
|
308
|
+
**File**: `lib/file-upload.test.ts`
|
|
309
|
+
|
|
310
|
+
**Implementation**:
|
|
311
|
+
```typescript
|
|
312
|
+
import { describe, it, expect } from 'vitest';
|
|
313
|
+
import { uploadFileToMedplum, attachFileToTask } from './file-upload';
|
|
314
|
+
|
|
315
|
+
describe('File Upload Utilities', () => {
|
|
316
|
+
it('should upload file and create Binary/Media resources', async () => {
|
|
317
|
+
// Test implementation
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should attach media to task', async () => {
|
|
321
|
+
// Test implementation
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should reject files over size limit', async () => {
|
|
325
|
+
// Test implementation
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should reject invalid file types', async () => {
|
|
329
|
+
// Test implementation
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### 4.2: Update Bot Tests
|
|
335
|
+
**File**: `bots/services/emails/send-task-assignment-email.test.ts`
|
|
336
|
+
|
|
337
|
+
**Implementation**:
|
|
338
|
+
- Add test cases for tasks with attachments
|
|
339
|
+
- Mock Media and Binary resources
|
|
340
|
+
- Verify attachment conversion logic
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Phase 5: Tutorial Documentation
|
|
345
|
+
|
|
346
|
+
### Objectives
|
|
347
|
+
- Write comprehensive tutorial section
|
|
348
|
+
- Add code examples
|
|
349
|
+
- Include troubleshooting guide
|
|
350
|
+
|
|
351
|
+
### Tasks
|
|
352
|
+
|
|
353
|
+
#### 5.1: Tutorial Structure
|
|
354
|
+
Add to `TUTORIAL_EMAIL_NOTIFICATIONS.md`:
|
|
355
|
+
|
|
356
|
+
```markdown
|
|
357
|
+
## Advanced: File Attachments for Task Notifications
|
|
358
|
+
|
|
359
|
+
One of VintaSend's powerful features is built-in attachment management.
|
|
360
|
+
Files are stored efficiently in Medplum as FHIR Binary resources with
|
|
361
|
+
automatic deduplication, and can be easily attached to email notifications.
|
|
362
|
+
|
|
363
|
+
In this section, we'll implement:
|
|
364
|
+
- ✅ File upload UI for tasks
|
|
365
|
+
- ✅ FHIR-compliant file storage (Binary/Media resources)
|
|
366
|
+
- ✅ Automatic email attachment inclusion
|
|
367
|
+
- ✅ File deduplication via checksums
|
|
368
|
+
- ✅ Support for multiple file types
|
|
369
|
+
|
|
370
|
+
### Why Use VintaSend for Attachments?
|
|
371
|
+
|
|
372
|
+
**📎 Automatic Deduplication**
|
|
373
|
+
- Files with identical content stored once via checksum
|
|
374
|
+
- Multiple notifications can reference the same file
|
|
375
|
+
- Reduces storage costs
|
|
376
|
+
|
|
377
|
+
**🔒 FHIR-Native Storage**
|
|
378
|
+
- Files stored as Binary resources
|
|
379
|
+
- Metadata stored as Media resources
|
|
380
|
+
- Full FHIR compliance and access control
|
|
381
|
+
|
|
382
|
+
**📧 Email Provider Agnostic**
|
|
383
|
+
- Works with SendGrid, AWS SES, or any adapter
|
|
384
|
+
- Attachment handling abstracted from email provider
|
|
385
|
+
- Easy to switch providers without code changes
|
|
386
|
+
|
|
387
|
+
### Prerequisites
|
|
388
|
+
|
|
389
|
+
- Completed the basic task assignment tutorial
|
|
390
|
+
- VintaSend and VintaSend-Medplum installed
|
|
391
|
+
- Understanding of FHIR Binary and Media resources
|
|
392
|
+
|
|
393
|
+
### Step 1: Understanding FHIR File Storage
|
|
394
|
+
[Documentation of Binary/Media resources]
|
|
395
|
+
|
|
396
|
+
### Step 2: Configure File Upload Constraints
|
|
397
|
+
[Constants and validation rules]
|
|
398
|
+
|
|
399
|
+
### Step 3: Build File Upload UI
|
|
400
|
+
[React components implementation]
|
|
401
|
+
|
|
402
|
+
### Step 4: Add Attachment Support to Notifications
|
|
403
|
+
[Backend integration]
|
|
404
|
+
|
|
405
|
+
### Step 5: Testing File Attachments
|
|
406
|
+
[Test cases and validation]
|
|
407
|
+
|
|
408
|
+
### Step 6: Advanced Features
|
|
409
|
+
[Preview, deduplication, security]
|
|
410
|
+
|
|
411
|
+
### How It Works: The Full Flow
|
|
412
|
+
[End-to-end explanation]
|
|
413
|
+
|
|
414
|
+
### Benefits of This Approach
|
|
415
|
+
✅ **FHIR-Compliant**: All files stored as proper FHIR resources
|
|
416
|
+
✅ **Efficient**: Automatic deduplication saves storage
|
|
417
|
+
✅ **Type-Safe**: TypeScript ensures correct file handling
|
|
418
|
+
✅ **Flexible**: Support multiple file types and sizes
|
|
419
|
+
✅ **Auditable**: Complete file access history via FHIR
|
|
420
|
+
✅ **Provider-Agnostic**: Works with any email service
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### 6.2: Add Troubleshooting Section
|
|
424
|
+
```markdown
|
|
425
|
+
### Troubleshooting File Attachments
|
|
426
|
+
|
|
427
|
+
**Files not uploading?**
|
|
428
|
+
- Check file size against MAX_ATTACHMENT_SIZE
|
|
429
|
+
- Verify file type is in ALLOWED_FILE_TYPES
|
|
430
|
+
- Ensure user has permission to create Binary resources
|
|
431
|
+
|
|
432
|
+
**Attachments not appearing in emails?**
|
|
433
|
+
- Verify Media resources are properly linked to task
|
|
434
|
+
- Check that Binary content is accessible
|
|
435
|
+
- Review SendGrid attachment size limits (max 30MB total)
|
|
436
|
+
|
|
437
|
+
**Deduplication not working?**
|
|
438
|
+
- Ensure MedplumAttachmentManager is configured
|
|
439
|
+
- Verify checksums are being calculated correctly
|
|
440
|
+
- Check Binary resource metadata
|
|
441
|
+
|
|
442
|
+
**Performance issues?**
|
|
443
|
+
- Consider image optimization before upload
|
|
444
|
+
- Implement lazy loading for attachment lists
|
|
445
|
+
- Use pagination for tasks with many attachments
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Timeline Estimation
|
|
451
|
+
|
|
452
|
+
| Phase | Estimated Time | Priority |
|
|
453
|
+
|-------|---------------|----------|
|
|
454
|
+
| Phase 1: Foundation | 4-6 hours | High |
|
|
455
|
+
| Phase 2: Frontend UI | 6-8 hours | High |
|
|
456
|
+
| Phase 3: Email Integration | 4-6 hours | High |
|
|
457
|
+
| Phase 4: Testing | 4-6 hours | Medium |
|
|
458
|
+
| Phase 5: Documentation | 3-4 hours | High |
|
|
459
|
+
| **Total** | **27-38 hours** | - |
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Dependencies & Requirements
|
|
464
|
+
|
|
465
|
+
### NPM Packages
|
|
466
|
+
- Existing: `vintasend`, `vintasend-medplum`, `vintasend-sendgrid`
|
|
467
|
+
- No new packages required (all functionality built-in)
|
|
468
|
+
|
|
469
|
+
### Environment Variables
|
|
470
|
+
```bash
|
|
471
|
+
# Existing variables
|
|
472
|
+
SENDGRID_API_KEY=your-key
|
|
473
|
+
SENDGRID_FROM_EMAIL=noreply@example.com
|
|
474
|
+
APP_BASE_URL=https://your-app.com
|
|
475
|
+
|
|
476
|
+
# Optional: Add file upload limits
|
|
477
|
+
MAX_FILE_SIZE_MB=10
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Medplum Configuration
|
|
481
|
+
- Ensure Binary resource creation permissions
|
|
482
|
+
- Verify Media resource read/write access
|
|
483
|
+
- Configure storage backend if needed (S3, Azure, etc.)
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Success Criteria
|
|
488
|
+
|
|
489
|
+
### Must Have (MVP)
|
|
490
|
+
- ✅ Users can upload files when creating tasks
|
|
491
|
+
- ✅ Files stored as FHIR Binary/Media resources
|
|
492
|
+
- ✅ Email notifications include uploaded files as attachments
|
|
493
|
+
- ✅ Basic file type validation (PDF, images, docs)
|
|
494
|
+
- ✅ File size limit enforcement
|
|
495
|
+
|
|
496
|
+
### Should Have
|
|
497
|
+
- ✅ File preview for images
|
|
498
|
+
- ✅ Multiple file upload support
|
|
499
|
+
- ✅ Attachment list display in task UI
|
|
500
|
+
- ✅ Remove attachment functionality
|
|
501
|
+
- ✅ Comprehensive error handling
|
|
502
|
+
|
|
503
|
+
### Nice to Have
|
|
504
|
+
- ✅ Drag-and-drop file upload
|
|
505
|
+
- ✅ PDF preview in browser
|
|
506
|
+
- ✅ File compression before upload
|
|
507
|
+
- ✅ Bulk attachment download
|
|
508
|
+
- ✅ Attachment access audit trail
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Risk Assessment & Mitigation
|
|
513
|
+
|
|
514
|
+
| Risk | Impact | Probability | Mitigation |
|
|
515
|
+
|------|--------|-------------|------------|
|
|
516
|
+
| SendGrid attachment size limit | High | Medium | Validate total size before sending; link large files instead |
|
|
517
|
+
| File storage costs | Medium | High | Implement deduplication; add cleanup policies |
|
|
518
|
+
| Security vulnerabilities | High | Low | Validate file types; scan for malware; enforce access control |
|
|
519
|
+
| Performance degradation | Medium | Medium | Lazy loading; pagination; optimize file sizes |
|
|
520
|
+
| Browser compatibility | Low | Low | Test across browsers; use standard File API |
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Future Enhancements
|
|
525
|
+
|
|
526
|
+
### V2 Features
|
|
527
|
+
1. **Virus scanning integration** - Scan uploaded files for malware
|
|
528
|
+
2. **Image optimization** - Automatically compress/resize images
|
|
529
|
+
3. **Attachment versioning** - Track file changes over time
|
|
530
|
+
4. **Bulk operations** - Upload/download multiple files at once
|
|
531
|
+
5. **Advanced preview** - Preview more file types (Word, Excel, etc.)
|
|
532
|
+
|
|
533
|
+
### V3 Features
|
|
534
|
+
1. **Cloud storage integration** - Direct upload to S3/Azure/GCS
|
|
535
|
+
2. **OCR for documents** - Extract text from scanned files
|
|
536
|
+
3. **E-signature support** - Sign documents within the app
|
|
537
|
+
4. **Attachment templates** - Pre-populate common file types
|
|
538
|
+
5. **Collaborative editing** - Real-time document collaboration
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Rollback Plan
|
|
543
|
+
|
|
544
|
+
If issues arise during implementation:
|
|
545
|
+
|
|
546
|
+
1. **Phase 1-2 Issues**: Revert UI changes, keep backend utilities
|
|
547
|
+
2. **Phase 3 Issues**: Disable attachment feature in email service
|
|
548
|
+
3. **Production Issues**:
|
|
549
|
+
- Feature flag to disable file uploads
|
|
550
|
+
- Graceful degradation (email without attachments)
|
|
551
|
+
- Roll back bot to previous version
|
|
552
|
+
|
|
553
|
+
**Rollback Commands**:
|
|
554
|
+
```bash
|
|
555
|
+
# Revert bot deployment
|
|
556
|
+
npm run bots:deploy -- --version previous
|
|
557
|
+
|
|
558
|
+
# Disable file upload UI
|
|
559
|
+
# Set feature flag: ENABLE_TASK_ATTACHMENTS=false
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Monitoring & Metrics
|
|
565
|
+
|
|
566
|
+
### Key Metrics to Track
|
|
567
|
+
- File upload success/failure rate
|
|
568
|
+
- Average file size
|
|
569
|
+
- Email with attachments delivery rate
|
|
570
|
+
- Storage usage over time
|
|
571
|
+
- Attachment download frequency
|
|
572
|
+
|
|
573
|
+
### Logging Points
|
|
574
|
+
```typescript
|
|
575
|
+
// Log file uploads
|
|
576
|
+
console.log('[FileUpload] File uploaded', {
|
|
577
|
+
filename,
|
|
578
|
+
size,
|
|
579
|
+
contentType,
|
|
580
|
+
userId
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Log email attachments
|
|
584
|
+
console.log('[EmailAttachment] Sending email with attachments', {
|
|
585
|
+
taskId,
|
|
586
|
+
attachmentCount,
|
|
587
|
+
totalSize
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Conclusion
|
|
594
|
+
|
|
595
|
+
This implementation plan provides a comprehensive roadmap for adding file attachment functionality to task assignments with email notification integration. By leveraging VintaSend's built-in attachment management and FHIR's Binary/Media resources, we can build a robust, scalable, and compliant solution.
|
|
596
|
+
|
|
597
|
+
The phased approach allows for incremental development and testing, with clear documentation at each step to help future developers understand and maintain the system.
|