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.
Files changed (283) hide show
  1. package/README.md +1 -0
  2. package/dist/examples/vintasend-medplum-example/.env.example +11 -0
  3. package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
  4. package/dist/examples/vintasend-medplum-example/README.md +190 -0
  5. package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
  6. package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
  7. package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
  8. package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
  9. package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
  10. package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
  11. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
  12. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
  13. package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
  14. package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
  15. package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
  16. package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
  17. package/dist/examples/vintasend-medplum-example/index.html +14 -0
  18. package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
  19. package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
  20. package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
  21. package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
  22. package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
  23. package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
  24. package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
  25. package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
  26. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
  27. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
  28. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
  29. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
  30. package/dist/examples/vintasend-medplum-example/package.json +75 -0
  31. package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
  32. package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
  33. package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
  34. package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
  35. package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
  36. package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
  37. package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
  38. package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
  39. package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
  40. package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
  41. package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
  42. package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
  43. package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
  44. package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
  45. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
  46. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
  47. package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
  48. package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
  49. package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
  50. package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
  51. package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
  52. package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
  53. package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
  54. package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
  55. package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
  56. package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
  57. package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
  58. package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
  59. package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
  60. package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
  61. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
  62. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
  63. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
  64. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
  65. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
  66. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
  67. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
  68. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
  69. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
  70. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
  71. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
  72. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
  73. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
  74. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
  75. package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
  76. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
  77. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
  78. package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
  79. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
  80. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
  81. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
  82. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
  83. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
  84. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
  85. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
  86. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
  87. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
  88. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
  89. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
  90. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
  91. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
  92. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
  93. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
  94. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
  95. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
  96. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
  97. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
  98. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
  99. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
  100. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
  101. package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
  102. package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
  103. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
  104. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
  105. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
  106. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
  107. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
  108. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
  109. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
  110. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
  111. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
  112. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
  113. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
  114. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
  115. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
  116. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
  117. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
  118. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
  119. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
  120. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
  121. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
  122. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
  123. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
  124. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
  125. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
  126. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
  127. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
  128. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
  129. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
  130. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
  131. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
  132. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
  133. package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
  134. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
  135. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
  136. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
  137. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
  138. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
  139. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
  140. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
  141. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
  142. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
  143. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
  144. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
  145. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
  146. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
  147. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
  148. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
  149. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
  150. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
  151. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
  152. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
  153. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
  154. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
  155. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
  156. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
  157. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
  158. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
  159. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
  160. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
  161. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
  162. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
  163. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
  164. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
  165. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
  166. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
  167. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
  168. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
  169. package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
  170. package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
  171. package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
  172. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
  173. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
  174. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
  175. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
  176. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
  177. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
  178. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
  179. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
  180. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
  181. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
  182. package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
  183. package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
  184. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
  185. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
  186. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
  187. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
  188. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
  189. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
  190. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
  191. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
  192. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
  193. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
  194. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
  195. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
  196. package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
  197. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
  198. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
  199. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
  200. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
  201. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
  202. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
  203. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
  204. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
  205. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
  206. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
  207. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
  208. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
  209. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
  210. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
  211. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
  212. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
  213. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
  214. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
  215. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
  216. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
  217. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
  218. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
  219. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
  220. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
  221. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
  222. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
  223. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
  224. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
  225. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
  226. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
  227. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
  228. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
  229. package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
  230. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
  231. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
  232. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
  233. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
  234. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
  235. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
  236. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
  237. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
  238. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
  239. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
  240. package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
  241. package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
  242. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
  243. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
  244. package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
  245. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
  246. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
  247. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
  248. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
  249. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
  250. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
  251. package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
  252. package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
  253. package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
  254. package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
  255. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
  256. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
  257. package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
  258. package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
  259. package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
  260. package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
  261. package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
  262. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
  263. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
  264. package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
  265. package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
  266. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
  267. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
  268. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
  269. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
  270. package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
  271. package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
  272. package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
  273. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
  274. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
  275. package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
  276. package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
  277. package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
  278. package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
  279. package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
  280. package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
  281. package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
  282. package/dist/services/notification-service.js +5 -1
  283. 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.