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 += `
+ Success! Participant Reset.
+
+
`
+ hideAnimation();
+ alertList.innerHTML = template;
+}
+
+export const participantRefreshError = async (errorMsg) => {
+ showAnimation();
+ let alertList = document.getElementById('alert_placeholder');
+ let template = '';
+ template += `
+ Error resetting participant: ${errorMsg}
+
+
`
+ 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)
+
+
+
+
+
+