diff --git a/src/applications/mhv-medical-records/actions/downloads.js b/src/applications/mhv-medical-records/actions/downloads.js new file mode 100644 index 000000000000..69c791bbb1af --- /dev/null +++ b/src/applications/mhv-medical-records/actions/downloads.js @@ -0,0 +1,15 @@ +import { Actions } from '../util/actionTypes'; + +export const updateReportDateRange = (fromDate, toDate) => async dispatch => { + dispatch({ + type: Actions.Downloads.SET_DATE_FILTER, + response: `${fromDate}<->${toDate}`, + }); +}; + +export const updateReportRecordType = selectedTypes => async dispatch => { + dispatch({ + type: Actions.Downloads.SET_RECORD_FILTER, + response: selectedTypes, + }); +}; diff --git a/src/applications/mhv-medical-records/components/DownloadRecords/DowloadDateRange.jsx b/src/applications/mhv-medical-records/components/DownloadRecords/DowloadDateRange.jsx index 27233f70c9a6..ea44d3691e3a 100644 --- a/src/applications/mhv-medical-records/components/DownloadRecords/DowloadDateRange.jsx +++ b/src/applications/mhv-medical-records/components/DownloadRecords/DowloadDateRange.jsx @@ -3,13 +3,22 @@ import { VaDate, VaSelect, } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { subMonths, format } from 'date-fns'; import NeedHelpSection from './NeedHelpSection'; +import { updateReportDateRange } from '../../actions/downloads'; const DownloadDateRange = () => { const history = useHistory(); const [selectedDate, setSelectedDate] = useState(''); + const [selectionError, setSelectionError] = useState(null); + const [customFromDate, setCustomFromDate] = useState(''); + const [customToDate, setCustomToDate] = useState(''); + const [customToError, setCustomToError] = useState(null); + const [customFromError, setCustomFromError] = useState(null); + const dispatch = useDispatch(); const handleDateSelect = useCallback( e => { @@ -18,11 +27,30 @@ const DownloadDateRange = () => { return; } + setSelectionError(null); setSelectedDate(e.detail.value); + if (e.detail.value !== 'custom') { + const currentDate = new Date(); + dispatch( + updateReportDateRange( + format(subMonths(currentDate, e.detail.value), 'yyyy-MM-dd'), + format(currentDate, 'yyyy-MM-dd'), + ), + ); + } }, [setSelectedDate], ); + useEffect( + () => { + if (customFromDate !== '' && customToDate !== '') { + dispatch(updateReportDateRange(customFromDate, customToDate)); + } + }, + [customFromDate, customToDate], + ); + return (

Select records and download report

@@ -39,17 +67,44 @@ const DownloadDateRange = () => {

Select date range

- - - - + + + +
{selectedDate === 'custom' && (
- - + { + const [year, month, day] = e.target.value.split('-'); + if (parseInt(year, 10) >= 1900 && month && day) { + setCustomFromError(null); + setCustomFromDate(e.target.value); + } + }} + /> + { + const [year, month, day] = e.target.value.split('-'); + if (parseInt(year, 10) >= 1900 && month && day) { + setCustomToError(null); + setCustomToDate(e.target.value); + } + }} + />
)} { history.push('/download'); }} onPrimaryClick={() => { + if (selectedDate === '') { + setSelectionError('Please select a valid date range.'); + return; + } + if (selectedDate === 'custom') { + if (customFromDate === '') { + setCustomFromError('Please enter a valid start date.'); + return; + } + if (customToDate === '') { + setCustomToError('Please enter a valid end date.'); + return; + } + } history.push('/download/record-type'); }} /> diff --git a/src/applications/mhv-medical-records/components/DownloadRecords/DownloadFileType.jsx b/src/applications/mhv-medical-records/components/DownloadRecords/DownloadFileType.jsx index 8a1ed4e7acd1..58a47c85e5fd 100644 --- a/src/applications/mhv-medical-records/components/DownloadRecords/DownloadFileType.jsx +++ b/src/applications/mhv-medical-records/components/DownloadRecords/DownloadFileType.jsx @@ -1,93 +1,298 @@ -import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import { formatDateLong } from '@department-of-veterans-affairs/platform-utilities/exports'; import { generatePdfScaffold, - crisisLineHeader, - reportGeneratedBy, - txtLine, + formatName, } from '@department-of-veterans-affairs/mhv/exports'; -import { formatDateLong } from '@department-of-veterans-affairs/platform-utilities/exports'; -import PropTypes from 'prop-types'; +import { VaRadio } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import { isBefore, isAfter } from 'date-fns'; import NeedHelpSection from './NeedHelpSection'; import DownloadingRecordsInfo from '../shared/DownloadingRecordsInfo'; +import DownloadSuccessAlert from '../shared/DownloadSuccessAlert'; import { getNameDateAndTime, makePdf, - processList, - getLastUpdatedText, generateTextFile, - formatNameFirstLast, } from '../../util/helpers'; -import DownloadSuccessAlert from '../shared/DownloadSuccessAlert'; -import { refreshExtractTypes } from '../../util/constants'; -import { - generateDownloadIntro, - generateDownloadContent, -} from '../../util/pdfHelpers/download'; +import { getTxtContent } from '../../util/txtHelpers/blueButton'; +import { getBlueButtonReportData } from '../../actions/blueButtonReport'; +import { generateBlueButtonData } from '../../util/pdfHelpers/blueButton'; +import { clearAlerts } from '../../actions/alerts'; const DownloadFileType = props => { + const { runningUnitTest = false } = props; const history = useHistory(); - const { runningUnitTest } = props; + const [fileType, setFileType] = useState(''); + + const dispatch = useDispatch(); const user = useSelector(state => state.user.profile); - const download = useSelector(state => state.mr.download.downloadList); - const refresh = useSelector(state => state.mr.refresh); - const [downloadStarted, setDownloadStarted] = useState(false); - const [fileType, setFileType] = useState(null); + const name = formatName(user.userFullName); + const dob = formatDateLong(user.dob); + const [blueButtonRequested, setBlueButtonRequested] = useState(false); + const [downloadType, setDownloadType] = useState(''); - const lastUpdatedText = getLastUpdatedText( - refresh.status, - refreshExtractTypes.VPR, + const labsAndTests = useSelector( + state => state.mr.labsAndTests.labsAndTestsList, + ); + const notes = useSelector( + state => state.mr.careSummariesAndNotes.careSummariesAndNotesList, + ); + const vaccines = useSelector(state => state.mr.vaccines.vaccinesList); + const allergies = useSelector(state => state.mr.allergies.allergiesList); + const conditions = useSelector(state => state.mr.conditions.conditionsList); + const vitals = useSelector(state => state.mr.vitals.vitalsList); + const medications = useSelector(state => state.mr.blueButton.medicationsList); + const appointments = useSelector( + state => state.mr.blueButton.appointmentsList, + ); + const demographics = useSelector(state => state.mr.blueButton.demographics); + const militaryService = useSelector( + state => state.mr.blueButton.militaryService, ); + const accountSummary = useSelector( + state => state.mr.blueButton.accountSummary, + ); + + const recordFilter = useSelector(state => state.mr.downloads?.recordFilter); + const dateFilter = useSelector(state => state.mr.downloads?.dateFilter); + + const [downloadStarted, setDownloadStarted] = useState(false); - const generateDownloadPdf = async () => { - setDownloadStarted(true); - const { title, subject, preface } = generateDownloadIntro( - download, - lastUpdatedText, + const allAreDefined = arrayOfArrays => { + return arrayOfArrays.every( + data => + (typeof data === 'object' && Object.keys(data)?.length) || + (typeof data === 'string' && data?.length) || + (Array.isArray(data) && !!data?.length), ); - const scaffold = generatePdfScaffold(user, title, subject, preface); - const pdfData = { ...scaffold, ...generateDownloadContent(download) }; - const pdfName = `VA-Download-list-${getNameDateAndTime(user)}`; - makePdf(pdfName, pdfData, 'Download', runningUnitTest); }; - const generateDownloadListItemTxt = item => { - setDownloadStarted(true); - return ` -${txtLine}\n\n -${item.name}\n -Date received: ${item.date}\n -Location: ${item.location}\n -Reaction: ${processList(item.reactions)}\n`; - }; + useEffect(() => { + if (!dateFilter) { + history.push('/download/date-range'); + } else if (!recordFilter) { + history.push('/download/record-type'); + } + }); + + const [filterFromDate, filterToDate] = useMemo( + () => { + return dateFilter ? dateFilter.split('<->') : [null, null]; + }, + [dateFilter], + ); - const generateDownloadTxt = async () => { - const content = ` -${crisisLineHeader}\n\n -Download\n -${formatNameFirstLast(user.userFullName)}\n -Date of birth: ${formatDateLong(user.dob)}\n -${reportGeneratedBy}\n -This list includes download you got at VA health facilities and from providers or pharmacies in our community care network. It may not include vaccines you got outside our network.\n -For complete records of your allergies and reactions to vaccines, review your allergy records.\n -Showing ${download.length} records from newest to oldest -${download.map(entry => generateDownloadListItemTxt(entry)).join('')}`; - - const fileName = `VA-download-list-${getNameDateAndTime(user)}`; - - generateTextFile(content, fileName); + const filterByDate = recDate => { + return ( + isBefore(new Date(filterFromDate), new Date(recDate)) && + isAfter(new Date(filterToDate), new Date(recDate)) + ); }; - const handleDownload = () => { - if (fileType === 'pdf') { - // Create and generate the PDF - generateDownloadPdf(); - } else if (fileType === 'txt') { - // Create and download a text file - generateDownloadTxt(); + const recordData = useMemo(() => { + if ( + !allAreDefined([ + labsAndTests, + notes, + vaccines, + allergies, + conditions, + vitals, + ]) + ) { + dispatch(getBlueButtonReportData()); + } else { + return { + labsAndTests: recordFilter?.includes('labTests') + ? labsAndTests.filter(rec => filterByDate(rec.sortDate)) + : null, + notes: recordFilter?.includes('careSummaries') + ? notes.filter(rec => filterByDate(rec.sortByDate)) + : null, + vaccines: recordFilter?.includes('vaccines') + ? vaccines.filter(rec => filterByDate(rec.date)) + : null, + allergies: + recordFilter?.includes('allergies') || + recordFilter?.includes('medications') + ? allergies + : null, + conditions: recordFilter?.includes('conditions') ? conditions : null, + vitals: recordFilter?.includes('vitals') + ? vitals.filter(rec => filterByDate(rec.date)) + : null, + medications: recordFilter?.includes('medications') + ? medications.filter(rec => filterByDate(rec.lastFilledOn)) + : null, + appointments: + recordFilter?.includes('upcomingAppts') || + recordFilter?.includes('pastAppts') + ? appointments.filter( + rec => + filterByDate(rec.date) && + ((recordFilter.includes('upcomingAppts') && rec.isUpcoming) || + (recordFilter.includes('pastAppts') && !rec.isUpcoming)), + ) + : null, + demographics: recordFilter?.includes('demographics') + ? demographics + : null, + militaryService: recordFilter?.includes('militaryService') + ? militaryService + : null, + accountSummary, + }; } - }; + return {}; + }); + + const recordCount = useMemo(() => { + let count = 0; + count += recordData?.labsAndTests ? recordData?.labsAndTests?.length : 0; + count += recordData?.notes ? recordData?.notes?.length : 0; + count += recordData?.vaccines ? recordData?.vaccines?.length : 0; + count += recordData?.allergies ? recordData?.allergies?.length : 0; + count += recordData?.conditions ? recordData?.conditions?.length : 0; + count += recordData?.vitals ? recordData?.vitals?.length : 0; + count += recordData?.medications ? recordData?.medications?.length : 0; + count += recordData?.appointments ? recordData?.appointments?.length : 0; + count += recordData?.demographics ? 1 : 0; + count += recordData?.militaryService ? 1 : 0; + + return count; + }); + + const generatePdf = useCallback( + async () => { + setDownloadStarted(true); + setDownloadType('pdf'); + setBlueButtonRequested(true); + dispatch(clearAlerts()); + const allDefd = allAreDefined([ + labsAndTests, + notes, + vaccines, + allergies, + conditions, + vitals, + medications, + appointments, + demographics, + militaryService, + accountSummary, + ]); + if (!allDefd) { + dispatch(getBlueButtonReportData()); + } else { + setBlueButtonRequested(false); + const title = 'Blue Button report'; + const subject = 'VA Medical Record'; + const scaffold = generatePdfScaffold(user, title, subject); + const pdfName = `VA-Blue-Button-report-${getNameDateAndTime(user)}`; + const pdfData = { + recordSets: generateBlueButtonData(recordData), + ...scaffold, + name, + dob, + }; + makePdf(pdfName, pdfData, title, runningUnitTest, 'blueButtonReport'); + } + }, + [ + accountSummary, + allergies, + appointments, + conditions, + demographics, + dispatch, + dob, + labsAndTests, + medications, + militaryService, + name, + notes, + user, + vaccines, + vitals, + ], + ); + + /** + * Generate text function + */ + const generateTxt = useCallback( + async () => { + setDownloadStarted(true); + setDownloadType('txt'); + setBlueButtonRequested(true); + dispatch(clearAlerts()); + if ( + !allAreDefined([ + labsAndTests, + notes, + vaccines, + allergies, + conditions, + vitals, + ]) + ) { + dispatch(getBlueButtonReportData()); + } else { + setBlueButtonRequested(false); + const pdfName = `VA-Blue-Button-report-${getNameDateAndTime(user)}`; + const content = getTxtContent(recordData, user); + + generateTextFile(content, pdfName, user); + } + }, + [ + allergies, + conditions, + dispatch, + labsAndTests, + notes, + user, + vaccines, + vitals, + ], + ); + + useEffect( + () => { + if ( + allAreDefined([ + labsAndTests, + notes, + vaccines, + allergies, + conditions, + vitals, + ]) && + blueButtonRequested + ) { + if (downloadType === 'pdf') { + generatePdf(); + } else { + generateTxt(); + } + } + }, + [ + labsAndTests, + notes, + vaccines, + allergies, + conditions, + vitals, + generatePdf, + generateTxt, + downloadType, + blueButtonRequested, + ], + ); return (
@@ -102,16 +307,16 @@ ${download.map(entry => generateDownloadListItemTxt(entry)).join('')}`;

Select file type

- You’re downloading 2,782 total records + You’re downloading {recordCount} total records

- setFileType(event.detail.value)} + onVaValueChange={e => setFileType(e.detail.value)} > - + {downloadStarted && }
@@ -119,7 +324,7 @@ ${download.map(entry => generateDownloadListItemTxt(entry)).join('')}`;
- + { { + setLabTestCheck(e.detail.checked); + handleSingleCheck('labTests', e.detail.checked); + }} />
{ + setCareSummariesCheck(e.detail.checked); + handleSingleCheck('careSummaries', e.detail.checked); + }} />
{ + setVaccineCheck(e.detail.checked); + handleSingleCheck('vaccines', e.detail.checked); + }} />
{ + setAllergiesCheck(e.detail.checked); + handleSingleCheck('allergies', e.detail.checked); + }} />
{ + setConditionsCheck(e.detail.checked); + handleSingleCheck('conditions', e.detail.checked); + }} />
{ + setVitalsCheck(e.detail.checked); + handleSingleCheck('vitals', e.detail.checked); + }} />
{ + setMedicationsCheck(e.detail.checked); + handleSingleCheck('medications', e.detail.checked); + }} />
{ + setUpcomingAppCheck(e.detail.checked); + handleSingleCheck('upcomingAppts', e.detail.checked); + }} />
{ + setPastAppCheck(e.detail.checked); + handleSingleCheck('pastAppts', e.detail.checked); + }} />
{ + setDemoCheck(e.detail.checked); + handleSingleCheck('demographics', e.detail.checked); + }} />
{ + setMilServCheck(e.detail.checked); + handleSingleCheck('militaryService', e.detail.checked); + }} />
@@ -147,7 +215,15 @@ const DownloadRecordType = () => { history.push('/download/date-range'); }} onPrimaryClick={() => { - history.push('/download/file-type'); + if (selectedRecords.length === 0) { + setSelectionError( + 'Please select at least one record type to download.', + ); + return; + } + dispatch(updateReportRecordType(selectedRecords)).then( + history.push('/download/file-type'), + ); }} /> diff --git a/src/applications/mhv-medical-records/reducers/downloads.js b/src/applications/mhv-medical-records/reducers/downloads.js new file mode 100644 index 000000000000..de8b1b173f09 --- /dev/null +++ b/src/applications/mhv-medical-records/reducers/downloads.js @@ -0,0 +1,23 @@ +import { Actions } from '../util/actionTypes'; + +const initialState = {}; + +export const downloadsReducer = (state = initialState, action) => { + switch (action.type) { + case Actions.Downloads.SET_DATE_FILTER: { + return { + ...state, + dateFilter: action.response, + }; + } + case Actions.Downloads.SET_RECORD_FILTER: { + return { + ...state, + recordFilter: action.response, + }; + } + default: { + return { ...state }; + } + } +}; diff --git a/src/applications/mhv-medical-records/reducers/index.js b/src/applications/mhv-medical-records/reducers/index.js index 75d7a4821505..ad94c8ddbf8f 100644 --- a/src/applications/mhv-medical-records/reducers/index.js +++ b/src/applications/mhv-medical-records/reducers/index.js @@ -11,6 +11,7 @@ import { sharingReducer } from './sharing'; import { alertsReducer } from './alerts'; import { refreshReducer } from './refresh'; import { pageTrackerReducer } from './pageTracker'; +import { downloadsReducer } from './downloads'; import { blueButtonReducer } from './blueButton'; import { selfEnteredReducer } from './selfEnteredData'; @@ -18,6 +19,7 @@ const rootReducer = { mr: combineReducers({ allergies: allergyReducer, breadcrumbs: breadcrumbsReducer, + downloads: downloadsReducer, labsAndTests: labsAndTestsReducer, careSummariesAndNotes: careSummariesAndNotesReducer, vaccines: vaccineReducer, diff --git a/src/applications/mhv-medical-records/util/actionTypes.js b/src/applications/mhv-medical-records/util/actionTypes.js index 4e37a537bbfc..2a793a2b46ba 100644 --- a/src/applications/mhv-medical-records/util/actionTypes.js +++ b/src/applications/mhv-medical-records/util/actionTypes.js @@ -22,6 +22,10 @@ export const Actions = { UPDATE_LIST_STATE: 'MR_CARE_SUMMARIES_AND_NOTES_UPDATE_LIST_STATE', COPY_UPDATED_LIST: 'MR_CARE_SUMMARIES_AND_NOTES_COPY_UPDATED_LIST', }, + Downloads: { + SET_DATE_FILTER: 'MR_DOWNLOADS_DATE_FILTER', + SET_RECORD_FILTER: 'MR_DOWNLOADS_RECORDS_FILTER', + }, Vaccines: { GET: 'MR_VACCINES_GET', GET_LIST: 'MR_VACCINES_GET_LIST', diff --git a/src/applications/mhv-medical-records/util/pdfHelpers/blueButton.js b/src/applications/mhv-medical-records/util/pdfHelpers/blueButton.js index dff577742fd3..cf0dce51fcd5 100644 --- a/src/applications/mhv-medical-records/util/pdfHelpers/blueButton.js +++ b/src/applications/mhv-medical-records/util/pdfHelpers/blueButton.js @@ -76,7 +76,7 @@ export const generateBlueButtonData = ({ }); } - if (notes.length) { + if (notes?.length) { data.push({ type: recordType.CARE_SUMMARIES_AND_NOTES, title: 'Care summaries and notes', @@ -104,7 +104,7 @@ export const generateBlueButtonData = ({ }); } - if (vaccines.length) { + if (vaccines?.length) { data.push({ type: recordType.VACCINES, title: 'Vaccines', @@ -121,7 +121,7 @@ export const generateBlueButtonData = ({ }); } - if (allergies.length) { + if (allergies?.length) { data.push({ type: recordType.ALLERGIES, title: 'Allergies', @@ -137,7 +137,7 @@ export const generateBlueButtonData = ({ }); } - if (conditions.length) { + if (conditions?.length) { data.push({ title: 'Health conditions', subtitles: [ @@ -156,7 +156,7 @@ export const generateBlueButtonData = ({ }); } - if (vitals.length) { + if (vitals?.length) { data.push({ type: recordType.VITALS, title: 'Vitals', @@ -172,7 +172,7 @@ export const generateBlueButtonData = ({ }); } - if (medications.length) { + if (medications?.length) { data.push({ type: blueButtonRecordTypes.MEDICATIONS, title: 'Medications', @@ -189,7 +189,7 @@ export const generateBlueButtonData = ({ }); } - if (appointments.length) { + if (appointments?.length) { const upcoming = appointments.filter(appt => appt.isUpcoming); const past = appointments.filter(appt => !appt.isUpcoming); @@ -212,7 +212,7 @@ export const generateBlueButtonData = ({ }); } - if (demographics.length) { + if (demographics?.length) { data.push({ type: blueButtonRecordTypes.DEMOGRAPHICS, title: 'Demographics', @@ -227,7 +227,7 @@ export const generateBlueButtonData = ({ }); } - if (militaryService.length) { + if (militaryService?.length) { data.push({ type: blueButtonRecordTypes.MILITARY_SERVICE, title: 'DOD military service information',