diff --git a/.gitignore b/.gitignore index edae712..f83ede4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,72 +1,8 @@ -# remove hidden mac files .DS_Store - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file +.hintrc .env - -# next.js build output -.next - .vscode - -# ignore scratch & scratch.js -scratch -scratch.js .eslintrc.json -.hintrc \ No newline at end of file +node_modules/ +temp +config/local-dev/ \ No newline at end of file diff --git a/index.js b/index.js index 8f4d732..56d2eb1 100644 --- a/index.js +++ b/index.js @@ -68,6 +68,23 @@ window.onload = async () => { !firebase.apps.length ? firebase.initializeApp(stageFirebaseConfig) : firebase.app(); window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'stage' }); } + else if (isLocalDev) { + let localDevFirebaseConfig = null; + let hasError = false; + try { + const localDevConfig = await import("./config/local-dev/config.js"); + localDevFirebaseConfig = localDevConfig.firebaseConfig; + if (!localDevFirebaseConfig) hasError = true; + } catch (error) { + hasError = true; + } + + if (hasError) { + console.error("Local development requires firebaseConfig defined in src/local-dev/config.js."); + return; + } + !firebase.apps.length ? firebase.initializeApp(localDevFirebaseConfig) : firebase.app(); + } else { !firebase.apps.length ? firebase.initializeApp(devFirebaseConfig) : firebase.app(); !isLocalDev && window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'dev' }); diff --git a/serviceWorker.js b/serviceWorker.js index a13136f..b95180a 100644 --- a/serviceWorker.js +++ b/serviceWorker.js @@ -1,6 +1,6 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); -const appVersion = "v24.7.0"; +const appVersion = "v24.9.0"; workbox.setConfig({ debug: false }); const { registerRoute } = workbox.routing; const { CacheFirst, NetworkFirst } = workbox.strategies; diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 3a509f9..d5b437c 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -188,6 +188,10 @@ export default 'combinedBoodUrineMouthwashSurveyStartDate': 822499427, 'combinedBoodUrineMouthwashSurveyCompleteDate': 222161762, + 'experienceSurvey': 956490759, + 'experienceSurveyStartDate': 263355177, + 'experienceCompleteDate': 199471989, + "baselineEMR": 209454331, "baselineEMRflag": 101170406, "baselineEMRpushDate": 143523420, @@ -329,6 +333,8 @@ export default 'refusedFutureSamples': 352996056, 'refusedQualityOfLifeSurvey': 936015433, 'refusedAllFutureQualityOfLifeSurveys': 688142378, + 'refusedExperienceSurvey': 101763809, + 'refusedAllFutureExperienceSurveys': 525277409, 'refusedAllFutureActivities': 906417725, 'revokeHIPAA': 773707518, @@ -487,6 +493,8 @@ export default 'refAllFutureActivitesTimeStamp': 614264509, 'refQualityOfLifeSurveyTimeStamp': 667960135, 'refAllFutureQualityOfLifeSurveysTimeStamp': 953225775, + 'refExperienceSurveyTimeStamp': 688172931, + 'refAllFutureExperienceSurveysTimeStamp': 182718292, // remaining withdrawal 'contactSuspended': 726389747, diff --git a/src/participantCommons.js b/src/participantCommons.js index ad6e320..e2dac2b 100644 --- a/src/participantCommons.js +++ b/src/participantCommons.js @@ -25,7 +25,7 @@ export const renderTable = (data, source) => { fieldMapping.email2, fieldMapping.cellPhone, fieldMapping.homePhone, fieldMapping.otherPhone, fieldMapping.previousCancer, fieldMapping.allBaselineSurveysCompleted, fieldMapping.preferredLanguage, fieldMapping.participationStatus, fieldMapping.bohStatusFlag1, fieldMapping.mreStatusFlag1, fieldMapping.sasStatusFlag1, fieldMapping.lawStausFlag1, fieldMapping.ssnFullflag, fieldMapping.ssnPartialFlag , fieldMapping.refusedSurvey, fieldMapping.refusedBlood, fieldMapping.refusedUrine, fieldMapping.refusedMouthwash, fieldMapping.refusedSpecimenSurveys, fieldMapping.refusedFutureSamples, fieldMapping.refusedQualityOfLifeSurvey, fieldMapping.refusedAllFutureQualityOfLifeSurveys, - fieldMapping.refusedFutureSurveys, fieldMapping.refusedAllFutureActivities, fieldMapping.revokeHIPAA, fieldMapping.dateHipaaRevokeRequested, fieldMapping.dateHIPAARevoc, fieldMapping.withdrawConsent, fieldMapping.dateWithdrewConsentRequested, + fieldMapping.refusedExperienceSurvey, fieldMapping.refusedAllFutureExperienceSurveys, fieldMapping.refusedFutureSurveys, fieldMapping.refusedAllFutureActivities, fieldMapping.revokeHIPAA, fieldMapping.dateHipaaRevokeRequested, fieldMapping.dateHIPAARevoc, fieldMapping.withdrawConsent, fieldMapping.dateWithdrewConsentRequested, fieldMapping.participantDeceased, fieldMapping.dateOfDeath, fieldMapping.destroyData, fieldMapping.dateDataDestroyRequested, fieldMapping.dateDataDestroy, fieldMapping.suspendContact ]; localStorage.removeItem("participant"); @@ -579,7 +579,8 @@ const tableTemplate = (data, showButtons) => { ) : (x === (fieldMapping.refusedSurvey).toString() || x === (fieldMapping.refusedBlood).toString() || x === (fieldMapping.refusedUrine).toString() || x === (fieldMapping.refusedMouthwash).toString() || x === (fieldMapping.refusedSpecimenSurveys).toString() || x === (fieldMapping.refusedFutureSamples).toString() || - x === (fieldMapping.refusedFutureSurveys).toString() || x === (fieldMapping.refusedQualityOfLifeSurvey).toString() || x === (fieldMapping.refusedAllFutureQualityOfLifeSurveys).toString()) ? + x === (fieldMapping.refusedFutureSurveys).toString() || x === (fieldMapping.refusedQualityOfLifeSurvey).toString() || x === (fieldMapping.refusedAllFutureQualityOfLifeSurveys).toString() || + x === (fieldMapping.refusedExperienceSurvey).toString() || x === (fieldMapping.refusedAllFutureExperienceSurveys).toString()) ? ( (participant[fieldMapping.refusalOptions]?.[x] === fieldMapping.yes ? ( template += `${participant[fieldMapping.refusalOptions]?.[x] ? 'Yes' : ''}` ) diff --git a/src/participantDetails.js b/src/participantDetails.js index be1de91..95a7025 100644 --- a/src/participantDetails.js +++ b/src/participantDetails.js @@ -1,8 +1,8 @@ import { dashboardNavBarLinks, removeActiveClass } from './navigationBar.js'; -import { attachUpdateLoginMethodListeners, allStates, closeModal, getFieldValues, getImportantRows, getModalLabel, hideUneditableButtons, renderReturnSearchResults, resetChanges, saveResponses, showSaveNoteInModal, submitClickHandler, suffixList, languageList, viewParticipantSummary, } from './participantDetailsHelpers.js'; +import { attachUpdateLoginMethodListeners, allStates, closeModal, getFieldValues, getImportantRows, getModalLabel, hideUneditableButtons, renderReturnSearchResults, resetChanges, saveResponses, showSaveNoteInModal, submitClickHandler, resetClickHandlers, suffixList, languageList, viewParticipantSummary, } from './participantDetailsHelpers.js'; import fieldMapping from './fieldToConceptIdMapping.js'; import { renderParticipantHeader } from './participantHeader.js'; -import { getDataAttributes } from './utils.js'; +import { getDataAttributes, urls } from './utils.js'; import { appState } from './stateManager.js'; appState.setState({unsavedChangesTrack:{saveFlag: false, counter: 0}}); @@ -38,6 +38,7 @@ export const renderParticipantDetails = (participant, changedOption) => { hideUneditableButtons(participant, changedOption); localStorage.setItem("participant", JSON.stringify(participant)); changeParticipantDetail(participant, changedOption, originalHTML); + resetParticipantConfirm(); editAltContact(participant); viewParticipantSummary(participant); renderReturnSearchResults(); @@ -63,6 +64,7 @@ export const render = (participant, changedOption) => { ${renderParticipantHeader(participant)} ${renderBackToSearchDivAndButton()} ${renderCancelChangesAndSaveChangesButtons()} + ${renderResetUserButton(participant?.state?.uid)} ${renderDetailsTableHeader()} `; @@ -107,6 +109,30 @@ export const render = (participant, changedOption) => { return template; } +const resetParticipantConfirm = () => { + const openResetDialogBtn = document.getElementById('openResetDialog'); + if(openResetDialogBtn) { + let data = getDataAttributes(openResetDialogBtn); + openResetDialogBtn.addEventListener('click', () => { + const header = document.getElementById('modalHeader'); + const body = document.getElementById('modalBody'); + const uid = data.participantuid; + header.innerHTML = ` +
Confirm Participant Reset
+ ` + body.innerHTML = `
+ Are you sure you want to reset this participant to a just-verified state? This cannot be undone. +
+ +   + +
+
` + resetClickHandlers(uid); + }); + } +} + const changeParticipantDetail = (participant, changedOption, originalHTML) => { const detailedRow = Array.from(document.getElementsByClassName('detailedRow')); if (detailedRow) { @@ -282,6 +308,24 @@ const renderBackToSearchDivAndButton = () => { `; }; +const renderResetUserButton = (participantUid) => { + if(location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.host === urls.dev) { + return ` + + + + ` + } else { + return ''; + } +} + const renderDetailsTableHeader = () => { return `

Participant Details

diff --git a/src/participantDetailsHelpers.js b/src/participantDetailsHelpers.js index cd64675..1e8b38b 100644 --- a/src/participantDetailsHelpers.js +++ b/src/participantDetailsHelpers.js @@ -760,6 +760,37 @@ const showAuthUpdateAPIError = (bodyId, message) => { return false; } +export const refreshParticipantAfterReset = async (participant) => { + showAnimation(); + localStorage.setItem('participant', JSON.stringify(participant)); + renderParticipantDetails(participant, {}); + appState.setState({unsavedChangesTrack:{saveFlag: false, counter: 0}}) + let alertList = document.getElementById('alert_placeholder'); + let template = ''; + template += `` + hideAnimation(); + alertList.innerHTML = template; +} + +export const participantRefreshError = async (errorMsg) => { + showAnimation(); + let alertList = document.getElementById('alert_placeholder'); + let template = ''; + template += `` + hideAnimation(); + alertList.innerHTML = template; +} + export const refreshParticipantAfterUpdate = async (participant) => { showAnimation(); localStorage.setItem('participant', JSON.stringify(participant)); @@ -1206,6 +1237,29 @@ export const submitClickHandler = async (participant, changedOption) => { } }; +export const resetClickHandlers = async (participantUid) => { + const resetButton = document.getElementById('resetUserBtn'); + if(!resetButton) { + return; + } + resetButton.addEventListener('click', async () => { + try { + const json = await postResetUserData(participantUid); + closeModal(); + if(json.code === 200) { + refreshParticipantAfterReset(json.data.data); + } else if (json.code === 404) { + participantRefreshError('Unable to find participant.'); + } else { + participantRefreshError(json.data); + } + } catch(error) { + console.error('error', error); + participantRefreshError('Unknown error.'); + } + }); +} + /** * Handle the query.frstName and query.lastName fields. * Check changedUserDataForProfile the participant profile for all name types. If a name is in changedUserDataForProfile, Add it to the queryNameArray. @@ -1490,3 +1544,27 @@ export const postUserDataUpdate = async (changedUserData) => { throw error; } } + +export const postResetUserData = async (uid) => { + try { + const idToken = await getIdToken(); + const response = await fetch(`${baseAPI}/dashboard?api=resetUser`, { + method: "POST", + headers:{ + Authorization: "Bearer " + idToken, + "Content-Type": "application/json" + }, + body: JSON.stringify({uid, saveToDb: 'true'}) + }); + + if (!response.ok) { + const error = (response.status + ": " + (await response.json()).message); + throw new Error(error); + } + + return await response.json(); + } catch (error) { + console.error('Error in postResetUserData:', error); + throw error; + } +} \ No newline at end of file diff --git a/src/participantSummary.js b/src/participantSummary.js index 67410b2..643c6de 100644 --- a/src/participantSummary.js +++ b/src/participantSummary.js @@ -3,7 +3,8 @@ import { renderParticipantHeader } from './participantHeader.js'; import fieldMapping from './fieldToConceptIdMapping.js'; import { userProfile, verificationStatus, baselineBOHSurvey, baselineMRESurvey,baselineSASSurvey, baselineLAWSurvey, baselineSSN, baselineCOVIDSurvey, baselineBloodSample, baselineUrineSample, baselineBiospecSurvey, baselineMenstrualSurvey, - baselineMouthwashSample, baselineBloodUrineSurvey, baselineMouthwashSurvey, baselinePromisSurvey, baselineEMR, baselinePayment } from './participantSummaryRow.js'; + baselineMouthwashSample, baselineBloodUrineSurvey, baselineMouthwashSurvey, baselinePromisSurvey, baselineEMR, baselinePayment, + baselineExperienceSurvey} from './participantSummaryRow.js'; import { humanReadableMDY, conceptToSiteMapping, pdfCoordinatesMap } from './utils.js'; const { PDFDocument, StandardFonts } = PDFLib @@ -98,6 +99,9 @@ export const render = (participant) => { ${baselinePromisSurvey(participant)} + + ${baselineExperienceSurvey(participant)} + ${baselineBloodSample(participant)} diff --git a/src/participantSummaryRow.js b/src/participantSummaryRow.js index 70cbaa4..fd232fd 100644 --- a/src/participantSummaryRow.js +++ b/src/participantSummaryRow.js @@ -362,6 +362,24 @@ export const baselinePromisSurvey = (participant) => { return getTemplateRow(icon, color, timeline, category, item, itemStatus, date, setting, refused, extra); }; +export const baselineExperienceSurvey = (participant) => { + + const refusedAllFutureSurveys = participant[fieldMapping.refusalOptions]?.[fieldMapping.refusedFutureSurveys]; + const refusedAllFutureActivities = participant[fieldMapping.refusedAllFutureActivities]; + const refusedExperienceSurvey = participant[fieldMapping.refusalOptions]?.[fieldMapping.refusedExperienceSurvey]; + + const { icon, color, itemStatus, date } = getSurveyStatus(participant, fieldMapping.experienceSurvey, fieldMapping.experienceSurveyStartDate, fieldMapping.experienceCompleteDate); + + const timeline = "Follow-Up 3-mo"; + const category = "Survey"; + const item = "2024 Connect Experience"; + const setting = "N/A"; + const refused = refusedAllFutureSurveys === fieldMapping.yes || refusedAllFutureActivities === fieldMapping.yes || refusedExperienceSurvey === fieldMapping.yes ? "Y" : "N"; + const extra = "N/A"; + + return getTemplateRow(icon, color, timeline, category, item, itemStatus, date, setting, refused, extra); +}; + export const baselineMenstrualSurvey = (participant) => { let template = ``; diff --git a/src/participantWithdrawal.js b/src/participantWithdrawal.js index 1aabf9a..db6bbe1 100644 --- a/src/participantWithdrawal.js +++ b/src/participantWithdrawal.js @@ -85,6 +85,10 @@ const getParticipantSelectedRefusals = (participant) => { strArray.push("Refused QOL survey 3-mo (but willing to do other future surveys)"); if (refusalOptions[fieldMapping.refusedAllFutureQualityOfLifeSurveys] === fieldMapping.yes) strArray.push("Refused all future QOL surveys (but willing to do other future surveys)"); + if (refusalOptions[fieldMapping.refusedExperienceSurvey] === fieldMapping.yes) + strArray.push("Refused 2024 Connect Experience Survey (but willing to do other future surveys)"); + if (refusalOptions[fieldMapping.refusedAllFutureExperienceSurveys] === fieldMapping.yes) + strArray.push("Refused all future Connect Experience surveys (but willing to do other future surveys)"); if (refusalOptions[fieldMapping.refusedFutureSurveys] === fieldMapping.yes) strArray.push("All future surveys (willing to do specimens)"); } diff --git a/src/participantWithdrawalForm.js b/src/participantWithdrawalForm.js index 15c3b67..cea796b 100644 --- a/src/participantWithdrawalForm.js +++ b/src/participantWithdrawalForm.js @@ -76,6 +76,18 @@ export const renderParticipantWithdrawalLandingPage = () => { All future QOL Surveys (but willing to do other future surveys)
+ + +
+ + +