From 71fc61bae4c9f40a6fc099db6448bb11e24cdcd2 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Fri, 28 Jun 2024 17:30:52 -0400 Subject: [PATCH 01/27] add modal to restrict research check in when clinical samples collected --- src/events.js | 155 ++++++++++++++++++++------------- src/fieldToConceptIdMapping.js | 1 + src/pages/checkIn.js | 9 +- src/shared.js | 5 +- static/css/style.css | 5 ++ 5 files changed, 111 insertions(+), 64 deletions(-) diff --git a/src/events.js b/src/events.js index 6efc8e38..03662734 100644 --- a/src/events.js +++ b/src/events.js @@ -539,7 +539,6 @@ export const addGoToCheckInEvent = () => { export const addGoToSpecimenLinkEvent = () => { const specimenLinkButtons = document.querySelectorAll('button[data-specimen-link-connect-id]'); - for (const btn of specimenLinkButtons) { btn.addEventListener('click', async () => { let query = `connectId=${parseInt(btn.dataset.specimenLinkConnectId)}`; @@ -563,6 +562,8 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { const response = await findParticipant(query); const data = response.data[0]; + console.log("๐Ÿš€ ~ addEventCheckInCompleteForm ~ data:", data) + if (isCheckedIn) { showAnimation(); await checkOutParticipant(data); @@ -577,12 +578,19 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }, 1500); } else { const visitConcept = document.getElementById('visit-select').value; + + const isClinicalUrineOrBloodCollected = checkClinicalBloodOrUrineCollected(data) + + if (isClinicalUrineOrBloodCollected) return; + + for (const visit of visitType) { if (data[conceptIds.collection.selectedVisit] && data[conceptIds.collection.selectedVisit][visit.concept]) { const visitTime = new Date(data[conceptIds.collection.selectedVisit][visit.concept][conceptIds.checkInDateTime]); - const now = new Date(); + const now = new Date(); if (now.getYear() == visitTime.getYear() && now.getMonth() == visitTime.getMonth() && now.getDate() == visitTime.getDate()) { const response = await getParticipantCollections(data.token); + console.log("๐Ÿš€ ~ addEventCheckInCompleteForm ~ response:", response) let collection = response.data.filter(res => res[conceptIds.collection.selectedVisit] == visit.concept); if (collection.length === 0) continue; const confirmContinueCheckIn = await handleCheckInWarning(visit, data, collection); @@ -591,7 +599,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { } } - await handleCheckInModal(data, visitConcept, query); + // await handleCheckInModal(data, visitConcept, query); } } catch (error) { const bodyMessage = isCheckedIn ? 'There was an error checking out the participant. Please try again.' : 'There was an error checking in the participant. Please try again.'; @@ -600,6 +608,23 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }); }; +/** + * Checks if the participant has a clinical blood or urine collected and any specimen collected at Regional. If participant has clinical blood or urine collected, show a notification and return true. + * @param {Object} data - participant data + * @returns {Boolean} - true if participant has any clinical blood or urine collected, false otherwise +*/ +const checkClinicalBloodOrUrineCollected = (data) => { + const isBloodOrUrineCollected = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]?.[conceptIds.clinicalBloodOrUrineCollected]; + const anySpecimenCollectedRRL = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]?.[conceptIds.anySpecimenCollected]; + + if (isBloodOrUrineCollected === conceptIds.yes && anySpecimenCollectedRRL === conceptIds.yes) { + const bodyMessage = 'Check In not allowed, participant already has clinical collection for this timepoint.' + showNotifications({ title: 'Check In Error', body: bodyMessage }); + return true; + } + return false; +} + const handleCheckInWarning = async (visit, data, collection) => { const message = { title: "Warning - Participant Previously Checked In", @@ -674,12 +699,14 @@ export const goToParticipantSearch = () => { export const addEventSpecimenLinkForm = (formData) => { const form = document.getElementById('researchSpecimenContinue'); const connectId = document.getElementById('researchSpecimenContinue').dataset.connectId; + // Note: Can use this connectId value to get related biospecimen documents if (document.getElementById('navBarParticipantCheckIn')) document.getElementById('navBarParticipantCheckIn').dataset.connectId = connectId; form.addEventListener('click', async (e) => { e.preventDefault(); const collections = await getCollectionsByVisit(formData); + console.log("๐Ÿš€ ~ form.addEventListener ~ collections:", collections, "--", "formData:", formData) if (collections.length) { existingCollectionAlert(collections, connectId, formData); } else { @@ -732,6 +759,7 @@ const existingCollectionAlert = async (collections, connectId, formData) => { * @param {*} formData */ const btnsClicked = async (connectId, formData) => { + console.log("๐Ÿš€ ~ btnsClicked ~ connectId, formData:", connectId, formData) removeAllErrors(); let scanSpecimenID = document.getElementById('scanSpecimenID')?.value && document.getElementById('scanSpecimenID')?.value.toUpperCase(); @@ -749,18 +777,17 @@ const btnsClicked = async (connectId, formData) => { errorMessage('scanSpecimenID', 'Please Scan Collection ID or Type in Manually', focus, true); focus = false; errorMessage('scanSpecimenID2', 'Please Scan Collection ID or Type in Manually', focus, true); - } - else if (scanSpecimenID !== scanSpecimenID2 && !formData?.collectionId) { + } else if (scanSpecimenID !== scanSpecimenID2 && !formData?.collectionId) { hasError = true; errorMessage('scanSpecimenID2', 'Entered Collection ID doesn\'t match.', focus, true); - } - else if (scanSpecimenID && scanSpecimenID2) { + } else if (scanSpecimenID && scanSpecimenID2) { if (!masterSpecimenIDRequirement.regExp.test(scanSpecimenID) || scanSpecimenID.length !== masterSpecimenIDRequirement.length) { hasError = true; errorMessage('scanSpecimenID', `Collection ID must be ${masterSpecimenIDRequirement.length} characters long and in CXA123456 format.`, focus, true); focus = false; } } + if (collectionLocation && collectionLocation.value === 'none') { hasError = true; errorMessage('collectionLocation', `Please Select Collection Location.`, focus, true); @@ -776,60 +803,10 @@ const btnsClicked = async (connectId, formData) => { const firstName = document.getElementById(firstNameCidString).innerText || "" -const showConfirmationModal = async (collectionID, firstName) => { - return new Promise((resolve) => { - const modalContainer = document.createElement('div'); - modalContainer.classList.add('modal', 'fade'); - modalContainer.id = 'confirmationModal'; - modalContainer.tabIndex = '-1'; - modalContainer.role = 'dialog'; - modalContainer.setAttribute('aria-labelledby', 'exampleModalCenterTitle'); - modalContainer.setAttribute('aria-hidden', 'true'); - const modalContent = document.createElement('div'); - modalContent.classList.add('modal-dialog', 'modal-dialog-centered'); - modalContent.setAttribute('role', 'document'); + const confirmVal = await showConfirmationModal(collectionID, firstName); - const modalBody = ` - - `; - - modalContent.innerHTML = modalBody; - modalContainer.appendChild(modalContent); - document.body.appendChild(modalContainer); + if (confirmVal === "cancel") return; - modalContainer.classList.add('show'); - modalContainer.style.display = 'block'; - modalContainer.addEventListener('click', (event) => { - const result = event.target.getAttribute('data-result'); - if (result) - { - document.body.removeChild(modalContainer); - resolve(result); - } - }); - }); -}; - -const confirmVal = await showConfirmationModal(collectionID, firstName); -if (confirmVal === "cancel") { - return; -} formData[conceptIds.collection.id] = collectionID; formData[conceptIds.collection.collectionSetting] = getWorkflow() === 'research' ? conceptIds.research : conceptIds.clinical; formData['Connect_ID'] = parseInt(document.getElementById('specimenLinkForm').dataset.connectId); @@ -844,9 +821,10 @@ if (confirmVal === "cancel") { if (!formData?.collectionId) { specimenData = (await searchSpecimen(formData[conceptIds.collection.id])).data; + } hideAnimation(); - + console.log("๐Ÿš€ ~ btnsClicked ~ specimenData:", specimenData) if (specimenData?.Connect_ID && parseInt(specimenData.Connect_ID) !== particpantData.Connect_ID) { showNotifications({ title: 'Collection ID Duplication', body: 'Entered Collection ID is already associated with a different Connect ID.' }) return; @@ -854,8 +832,13 @@ if (confirmVal === "cancel") { showAnimation(); formData[conceptIds.collection.selectedVisit] = formData?.[conceptIds.collection.selectedVisit] || parseInt(getCheckedInVisit(particpantData)); + console.log("๐Ÿš€ ~ btnsClicked ~ parseInt(getCheckedInVisit(particpantData)):", parseInt(getCheckedInVisit(particpantData))) + console.log("____") + console.log("๐Ÿš€ ~ btnsClicked ~ formData?.[conceptIds.collection.selectedVisit]:", formData?.[conceptIds.collection.selectedVisit]) + if (!formData?.collectionId) { + console.log("Form data to be added:", formData); const storeResponse = await storeSpecimen([formData]); if (storeResponse.code === 400) { hideAnimation(); @@ -880,6 +863,58 @@ if (confirmVal === "cancel") { searchTemplate(); } } + + +const showConfirmationModal = async (collectionID, firstName) => { + return new Promise((resolve) => { + const modalContainer = document.createElement('div'); + modalContainer.classList.add('modal', 'fade'); + modalContainer.id = 'confirmationModal'; + modalContainer.tabIndex = '-1'; + modalContainer.role = 'dialog'; + modalContainer.setAttribute('aria-labelledby', 'exampleModalCenterTitle'); + modalContainer.setAttribute('aria-hidden', 'true'); + const modalContent = document.createElement('div'); + modalContent.classList.add('modal-dialog', 'modal-dialog-centered'); + modalContent.setAttribute('role', 'document'); + + const modalBody = ` + + `; + + modalContent.innerHTML = modalBody; + modalContainer.appendChild(modalContent); + document.body.appendChild(modalContainer); + + modalContainer.classList.add('show'); + modalContainer.style.display = 'block'; + modalContainer.addEventListener('click', (event) => { + const result = event.target.getAttribute('data-result'); + if (result) + { + document.body.removeChild(modalContainer); + resolve(result); + } + }); + }); +}; + /** * Check accession number inputs after clicking 'Submit' button diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 7714d1e1..903edc8f 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -140,6 +140,7 @@ export const conceptIds = { checkOutDateTime: 343048998, checkInDateTime: 840048338, checkInComplete: 135591601, + clinicalBloodOrUrineCollected: 156605577, // not shipped specimen deviation id brokenSpecimenDeviation: 472864016, diff --git a/src/pages/checkIn.js b/src/pages/checkIn.js index 0c7d06b1..92d8a68b 100644 --- a/src/pages/checkIn.js +++ b/src/pages/checkIn.js @@ -12,8 +12,11 @@ export const checkInTemplate = async (data, checkOutFlag) => { } const isCheckedIn = checkedIn(data); + console.log("Check In template data", data) + console.log("๐Ÿš€ ~ checkInTemplate ~ isCheckedIn:", isCheckedIn) const canCheckIn = participantCanCheckIn(data); const visit = getCheckedInVisit(data); + console.log("๐Ÿš€ ~ checkInTemplate ~ visit:", visit) const response = await getParticipantCollections(data.token); let collections = []; @@ -75,7 +78,8 @@ export const checkInTemplate = async (data, checkOutFlag) => {
`; - + + console.log("collections", collections) template += await participantStatus(data, collections); @@ -131,8 +135,11 @@ const participantStatus = (data, collections) => { let siteTubesList = getSiteTubesLists({'951355211': conceptIds.research}); const bloodTubes = siteTubesList?.filter(tube => tube.tubeType === "Blood tube"); + console.log("๐Ÿš€ ~ participantStatus ~ bloodTubes:", bloodTubes) const urineTubes = siteTubesList?.filter(tube => tube.tubeType === "Urine"); + console.log("๐Ÿš€ ~ participantStatus ~ urineTubes:", urineTubes) const mouthwashTubes = siteTubesList?.filter(tube => tube.tubeType === "Mouthwash"); + console.log("๐Ÿš€ ~ participantStatus ~ mouthwashTubes:", mouthwashTubes) collections = collections.filter(collection => collection[conceptIds.collection.selectedVisit] == conceptIds.baseline.visitId); diff --git a/src/shared.js b/src/shared.js index d482cc3d..87282a03 100644 --- a/src/shared.js +++ b/src/shared.js @@ -2801,9 +2801,8 @@ export const checkInParticipant = async (data, visitConcept) => { const uid = data.state.uid; let shouldSendBioEmail = false; - if (data[conceptIds.selectedVisit]) { - visits = data[conceptIds.selectedVisit]; - + if (data[conceptIds.collection.selectedVisit]) { + visits = data[conceptIds.collection.selectedVisit]; if (!visits[visitConcept]) { if (visitConcept === conceptIds.baseline.visitId.toString()) shouldSendBioEmail = true; diff --git a/static/css/style.css b/static/css/style.css index 1b68e3c4..2179642d 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -467,4 +467,9 @@ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); height: auto; max-height: 200px; overflow-x: hidden; +} + +#checkInComplete:disabled { + cursor: not-allowed; + background-color: #e0e0e0; } \ No newline at end of file From df258f18dcc1e9146d229e83ea11b0d9688e6db9 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Wed, 3 Jul 2024 11:37:00 -0400 Subject: [PATCH 02/27] Remove logs, uncomment await handleCheckInModal, remove extra logic check anySpecimenCollectedRRL --- src/events.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/events.js b/src/events.js index 03662734..8b393610 100644 --- a/src/events.js +++ b/src/events.js @@ -539,6 +539,7 @@ export const addGoToCheckInEvent = () => { export const addGoToSpecimenLinkEvent = () => { const specimenLinkButtons = document.querySelectorAll('button[data-specimen-link-connect-id]'); + for (const btn of specimenLinkButtons) { btn.addEventListener('click', async () => { let query = `connectId=${parseInt(btn.dataset.specimenLinkConnectId)}`; @@ -562,7 +563,6 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { const response = await findParticipant(query); const data = response.data[0]; - console.log("๐Ÿš€ ~ addEventCheckInCompleteForm ~ data:", data) if (isCheckedIn) { showAnimation(); @@ -579,8 +579,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { } else { const visitConcept = document.getElementById('visit-select').value; - const isClinicalUrineOrBloodCollected = checkClinicalBloodOrUrineCollected(data) - + const isClinicalUrineOrBloodCollected = checkClinicalBloodOrUrineCollected(data); if (isClinicalUrineOrBloodCollected) return; @@ -590,7 +589,6 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { const now = new Date(); if (now.getYear() == visitTime.getYear() && now.getMonth() == visitTime.getMonth() && now.getDate() == visitTime.getDate()) { const response = await getParticipantCollections(data.token); - console.log("๐Ÿš€ ~ addEventCheckInCompleteForm ~ response:", response) let collection = response.data.filter(res => res[conceptIds.collection.selectedVisit] == visit.concept); if (collection.length === 0) continue; const confirmContinueCheckIn = await handleCheckInWarning(visit, data, collection); @@ -599,7 +597,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { } } - // await handleCheckInModal(data, visitConcept, query); + await handleCheckInModal(data, visitConcept, query); } } catch (error) { const bodyMessage = isCheckedIn ? 'There was an error checking out the participant. Please try again.' : 'There was an error checking in the participant. Please try again.'; @@ -609,15 +607,14 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }; /** - * Checks if the participant has a clinical blood or urine collected and any specimen collected at Regional. If participant has clinical blood or urine collected, show a notification and return true. + * Checks if the participant has a clinical blood or urine collected variable under baseline. If participant has clinical blood or urine collected, show a notification and return true. * @param {Object} data - participant data * @returns {Boolean} - true if participant has any clinical blood or urine collected, false otherwise */ const checkClinicalBloodOrUrineCollected = (data) => { const isBloodOrUrineCollected = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]?.[conceptIds.clinicalBloodOrUrineCollected]; - const anySpecimenCollectedRRL = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]?.[conceptIds.anySpecimenCollected]; - if (isBloodOrUrineCollected === conceptIds.yes && anySpecimenCollectedRRL === conceptIds.yes) { + if (isBloodOrUrineCollected === conceptIds.yes) { const bodyMessage = 'Check In not allowed, participant already has clinical collection for this timepoint.' showNotifications({ title: 'Check In Error', body: bodyMessage }); return true; @@ -706,7 +703,7 @@ export const addEventSpecimenLinkForm = (formData) => { form.addEventListener('click', async (e) => { e.preventDefault(); const collections = await getCollectionsByVisit(formData); - console.log("๐Ÿš€ ~ form.addEventListener ~ collections:", collections, "--", "formData:", formData) + if (collections.length) { existingCollectionAlert(collections, connectId, formData); } else { @@ -758,8 +755,7 @@ const existingCollectionAlert = async (collections, connectId, formData) => { * @param {string} connectId * @param {*} formData */ -const btnsClicked = async (connectId, formData) => { - console.log("๐Ÿš€ ~ btnsClicked ~ connectId, formData:", connectId, formData) +const btnsClicked = async (connectId, formData) => { removeAllErrors(); let scanSpecimenID = document.getElementById('scanSpecimenID')?.value && document.getElementById('scanSpecimenID')?.value.toUpperCase(); @@ -824,18 +820,15 @@ const btnsClicked = async (connectId, formData) => { } hideAnimation(); - console.log("๐Ÿš€ ~ btnsClicked ~ specimenData:", specimenData) + if (specimenData?.Connect_ID && parseInt(specimenData.Connect_ID) !== particpantData.Connect_ID) { showNotifications({ title: 'Collection ID Duplication', body: 'Entered Collection ID is already associated with a different Connect ID.' }) return; } showAnimation(); + formData[conceptIds.collection.selectedVisit] = formData?.[conceptIds.collection.selectedVisit] || parseInt(getCheckedInVisit(particpantData)); - console.log("๐Ÿš€ ~ btnsClicked ~ parseInt(getCheckedInVisit(particpantData)):", parseInt(getCheckedInVisit(particpantData))) - console.log("____") - console.log("๐Ÿš€ ~ btnsClicked ~ formData?.[conceptIds.collection.selectedVisit]:", formData?.[conceptIds.collection.selectedVisit]) - if (!formData?.collectionId) { console.log("Form data to be added:", formData); From e355e32f58818c4ab454ffb65ff60d56a90df39b Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Wed, 3 Jul 2024 11:39:17 -0400 Subject: [PATCH 03/27] remove extra logs --- src/events.js | 4 +--- src/pages/checkIn.js | 8 +------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/events.js b/src/events.js index 8b393610..3dfa1551 100644 --- a/src/events.js +++ b/src/events.js @@ -578,10 +578,9 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }, 1500); } else { const visitConcept = document.getElementById('visit-select').value; - const isClinicalUrineOrBloodCollected = checkClinicalBloodOrUrineCollected(data); - if (isClinicalUrineOrBloodCollected) return; + if (isClinicalUrineOrBloodCollected) return; for (const visit of visitType) { if (data[conceptIds.collection.selectedVisit] && data[conceptIds.collection.selectedVisit][visit.concept]) { @@ -696,7 +695,6 @@ export const goToParticipantSearch = () => { export const addEventSpecimenLinkForm = (formData) => { const form = document.getElementById('researchSpecimenContinue'); const connectId = document.getElementById('researchSpecimenContinue').dataset.connectId; - // Note: Can use this connectId value to get related biospecimen documents if (document.getElementById('navBarParticipantCheckIn')) document.getElementById('navBarParticipantCheckIn').dataset.connectId = connectId; diff --git a/src/pages/checkIn.js b/src/pages/checkIn.js index 92d8a68b..123f374e 100644 --- a/src/pages/checkIn.js +++ b/src/pages/checkIn.js @@ -12,11 +12,8 @@ export const checkInTemplate = async (data, checkOutFlag) => { } const isCheckedIn = checkedIn(data); - console.log("Check In template data", data) - console.log("๐Ÿš€ ~ checkInTemplate ~ isCheckedIn:", isCheckedIn) const canCheckIn = participantCanCheckIn(data); const visit = getCheckedInVisit(data); - console.log("๐Ÿš€ ~ checkInTemplate ~ visit:", visit) const response = await getParticipantCollections(data.token); let collections = []; @@ -79,7 +76,6 @@ export const checkInTemplate = async (data, checkOutFlag) => {
`; - console.log("collections", collections) template += await participantStatus(data, collections); @@ -135,11 +131,9 @@ const participantStatus = (data, collections) => { let siteTubesList = getSiteTubesLists({'951355211': conceptIds.research}); const bloodTubes = siteTubesList?.filter(tube => tube.tubeType === "Blood tube"); - console.log("๐Ÿš€ ~ participantStatus ~ bloodTubes:", bloodTubes) const urineTubes = siteTubesList?.filter(tube => tube.tubeType === "Urine"); - console.log("๐Ÿš€ ~ participantStatus ~ urineTubes:", urineTubes) const mouthwashTubes = siteTubesList?.filter(tube => tube.tubeType === "Mouthwash"); - console.log("๐Ÿš€ ~ participantStatus ~ mouthwashTubes:", mouthwashTubes) + collections = collections.filter(collection => collection[conceptIds.collection.selectedVisit] == conceptIds.baseline.visitId); From 40b9a1c3b6aec3d2bb965eadb6be976ebda6830a Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Fri, 5 Jul 2024 12:59:33 -0400 Subject: [PATCH 04/27] move showConfirmationModal function to share.js file --- src/events.js | 55 ++---------------------------------------------- src/shared.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/events.js b/src/events.js index 3dfa1551..070eebc3 100644 --- a/src/events.js +++ b/src/events.js @@ -6,7 +6,8 @@ import { convertConceptIdToPackageCondition, checkFedexShipDuplicate, shippingDuplicateMessage, checkInParticipant, checkOutParticipant, getCheckedInVisit, participantCanCheckIn, shippingPrintManifestReminder, checkNonAlphanumericStr, shippingNonAlphaNumericStrMessage, visitType, getParticipantCollections, updateBaselineData, siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, updateCollectionSettingData, convertToOldBox, translateNumToType, - getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, + getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, + showConfirmationModal, } from './shared.js'; import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js'; import { showReportsManifest } from './pages/reportsQuery.js'; @@ -818,7 +819,6 @@ const btnsClicked = async (connectId, formData) => { } hideAnimation(); - if (specimenData?.Connect_ID && parseInt(specimenData.Connect_ID) !== particpantData.Connect_ID) { showNotifications({ title: 'Collection ID Duplication', body: 'Entered Collection ID is already associated with a different Connect ID.' }) return; @@ -854,57 +854,6 @@ const btnsClicked = async (connectId, formData) => { searchTemplate(); } } - - -const showConfirmationModal = async (collectionID, firstName) => { - return new Promise((resolve) => { - const modalContainer = document.createElement('div'); - modalContainer.classList.add('modal', 'fade'); - modalContainer.id = 'confirmationModal'; - modalContainer.tabIndex = '-1'; - modalContainer.role = 'dialog'; - modalContainer.setAttribute('aria-labelledby', 'exampleModalCenterTitle'); - modalContainer.setAttribute('aria-hidden', 'true'); - const modalContent = document.createElement('div'); - modalContent.classList.add('modal-dialog', 'modal-dialog-centered'); - modalContent.setAttribute('role', 'document'); - - const modalBody = ` - - `; - - modalContent.innerHTML = modalBody; - modalContainer.appendChild(modalContent); - document.body.appendChild(modalContainer); - - modalContainer.classList.add('show'); - modalContainer.style.display = 'block'; - modalContainer.addEventListener('click', (event) => { - const result = event.target.getAttribute('data-result'); - if (result) - { - document.body.removeChild(modalContainer); - resolve(result); - } - }); - }); -}; /** diff --git a/src/shared.js b/src/shared.js index 87282a03..74c3ae32 100644 --- a/src/shared.js +++ b/src/shared.js @@ -489,10 +489,66 @@ export const showNotificationsSelectableList = (message, items, onCancel, onCont errorMessageDiv.style.display = 'block'; } }); - document.getElementById('root').removeChild(button); }; +/** + * Display confirmation modal to the user and returns a promise with the user's choice. + * + * @param {string} collectionID - the collection ID to display in the modal. + * @param {string} firstName - the participant's first name to display in the modal. + * @returns {Promise} - the user's choice on button click: 'cancel', 'back', or 'confirmed'. +*/ +export const showConfirmationModal = (collectionID, firstName) => { + return new Promise((resolve) => { + const modalContainer = document.createElement('div'); + modalContainer.classList.add('modal', 'fade'); + modalContainer.id = 'confirmationModal'; + modalContainer.tabIndex = '-1'; + modalContainer.role = 'dialog'; + modalContainer.setAttribute('aria-labelledby', 'exampleModalCenterTitle'); + modalContainer.setAttribute('aria-hidden', 'true'); + const modalContent = document.createElement('div'); + modalContent.classList.add('modal-dialog', 'modal-dialog-centered'); + modalContent.setAttribute('role', 'document'); + + const modalBody = ` + + `; + + modalContent.innerHTML = modalBody; + modalContainer.appendChild(modalContent); + document.body.appendChild(modalContainer); + + modalContainer.classList.add('show'); + modalContainer.style.display = 'block'; + modalContainer.addEventListener('click', (event) => { + const result = event.target.getAttribute('data-result'); + if (result) + { + document.body.removeChild(modalContainer); + resolve(result); + } + }); + }); +}; + export const showTimedNotifications = (data, zIndex, timeInMilliseconds = 2600) => { const button = document.createElement('button'); button.dataset.target = '#biospecimenModal'; From 11d281fbcdfa0d93962e873e635669f1944f06d8 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 8 Jul 2024 12:28:04 -0400 Subject: [PATCH 05/27] Kit validation now prevents duplicate tracking numbers --- src/pages/homeCollection/kitAssembly.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/pages/homeCollection/kitAssembly.js b/src/pages/homeCollection/kitAssembly.js index 30a602c6..e9df7cbf 100644 --- a/src/pages/homeCollection/kitAssembly.js +++ b/src/pages/homeCollection/kitAssembly.js @@ -204,7 +204,7 @@ const renderSidePane = () => { Return Kit ID = ${ kitObject[conceptIds.returnKitId] } | Cup Id = ${ kitObject[conceptIds.collectionCupId] } | Card Id = ${ kitObject[conceptIds.collectionCardId] } - + ` }) editAssembledKits(); @@ -217,7 +217,8 @@ const editAssembledKits = () => { if (detailedRow) { Array.from(detailedRow).forEach(function(editKitBtn) { editKitBtn.addEventListener('click', () => { - const editKitObj = JSON.parse(editKitBtn.getAttribute('data-kitObject')); + let data = decodeURIComponent(editKitBtn.getAttribute('data-kitObject')); + const editKitObj = JSON.parse(data); document.getElementById('scannedBarcode').value = editKitObj[conceptIds.returnKitTrackingNum] document.getElementById('supplyKitId').value = editKitObj[conceptIds.supplyKitId] document.getElementById('returnKitId').value = editKitObj[conceptIds.returnKitId] @@ -228,9 +229,9 @@ const editAssembledKits = () => { }); // state to indicate if its an edit & also pass the uniqueKitID }} -const checkUniqueness = async (supplyKitId, collectionId) => { +const checkUniqueness = async (supplyKitId, collectionId, returnKitTrackingNumber) => { const idToken = await getIdToken(); - const response = await fetch(`${baseAPI}api=collectionUniqueness&supplyKitId=${supplyKitId}&collectionId=${collectionId}`, { + const response = await fetch(`${baseAPI}api=collectionUniqueness&supplyKitId=${supplyKitId}&collectionId=${collectionId}&returnKitTrackingNumber=${returnKitTrackingNumber}`, { method: "GET", headers: { Authorization:"Bearer "+idToken @@ -244,7 +245,7 @@ const storeAssembledKit = async (kitData) => { showAnimation(); const collectionUnique = appState.getState().uniqueKitID !== '' ? { data: true } - : await checkUniqueness(kitData[conceptIds.supplyKitId], kitData?.[conceptIds.collectionCupId].replace(/\s/g, "\n")); + : await checkUniqueness(kitData[conceptIds.supplyKitId], kitData?.[conceptIds.collectionCupId].replace(/\s/g, "\n"), kitData[conceptIds.returnKitTrackingNum]); hideAnimation(); if (collectionUnique.data === true) { @@ -312,6 +313,14 @@ const storeAssembledKit = async (kitData) => { alertTemplate('The collection card and cup ID are already in use.'); return false } + else if (collectionUnique.data === 'duplicate return kit tracking number'){ + alertTemplate('This tracking number has already been used.'); + return false + } + else if (collectionUnique.data === 'return kit tracking number is for supply kit'){ + alertTemplate('This tracking number has already been used.'); + return false + } else { alertTemplate('Error'); return false From 0eccdc13f75a0f5d000ae6caa9fe0f8b422fcabb Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Tue, 9 Jul 2024 11:14:07 -0400 Subject: [PATCH 06/27] clear out fields date collection card and time collection card when clearPackageReceiptForm function is called --- src/pages/receipts/packageReceipt.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/receipts/packageReceipt.js b/src/pages/receipts/packageReceipt.js index 16da2f1d..a4739fbe 100644 --- a/src/pages/receipts/packageReceipt.js +++ b/src/pages/receipts/packageReceipt.js @@ -369,6 +369,12 @@ const clearPackageReceiptForm = (isSuccess) => { const dateReceived = document.getElementById("dateReceived"); if (dateReceived) dateReceived.value = getCurrentDate(); + + const dateCollectionCard = document.getElementById("dateCollectionCard"); + if (dateCollectionCard) dateCollectionCard.value = ''; + + const timeCollectionCard = document.getElementById("timeCollectionCard"); + if (timeCollectionCard) timeCollectionCard.value = ''; const collectionComments = document.getElementById("collectionComments"); if (collectionComments) collectionComments.value = ''; From 849ff19ce09b34cfe685db5b72a3267609ccd3b5 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Tue, 9 Jul 2024 12:10:10 -0400 Subject: [PATCH 07/27] Remove early code clearing of element with collectionId's value --- src/pages/homeCollection/kitsReceipt.js | 2 +- src/pages/receipts/packageReceipt.js | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/pages/homeCollection/kitsReceipt.js b/src/pages/homeCollection/kitsReceipt.js index c10fb183..3412a042 100644 --- a/src/pages/homeCollection/kitsReceipt.js +++ b/src/pages/homeCollection/kitsReceipt.js @@ -202,7 +202,7 @@ const storePackageReceipt = async (data) => { document.getElementById("receivePackageComments").value = ""; document.getElementById("dateReceived").value = getCurrentDate(); document.getElementById("collectionComments").value = ""; - document.getElementById("collectionId").value = ""; + enableCollectionCardFields(); enableCollectionCheckBox(); document.getElementById("packageCondition").setAttribute("data-selected", "[]"); diff --git a/src/pages/receipts/packageReceipt.js b/src/pages/receipts/packageReceipt.js index a4739fbe..16da2f1d 100644 --- a/src/pages/receipts/packageReceipt.js +++ b/src/pages/receipts/packageReceipt.js @@ -369,12 +369,6 @@ const clearPackageReceiptForm = (isSuccess) => { const dateReceived = document.getElementById("dateReceived"); if (dateReceived) dateReceived.value = getCurrentDate(); - - const dateCollectionCard = document.getElementById("dateCollectionCard"); - if (dateCollectionCard) dateCollectionCard.value = ''; - - const timeCollectionCard = document.getElementById("timeCollectionCard"); - if (timeCollectionCard) timeCollectionCard.value = ''; const collectionComments = document.getElementById("collectionComments"); if (collectionComments) collectionComments.value = ''; From 720aa797929926e747afbcbdead62706d93d288b Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Mon, 15 Jul 2024 10:06:28 -0400 Subject: [PATCH 08/27] add BCC-Fort Worth as placeholder waiting for cid assignment --- src/fieldToConceptIdMapping.js | 1 + src/shared.js | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 903edc8f..210ffb36 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -300,6 +300,7 @@ export const conceptIds = { 807835037: 'Other', 723351427: 'BCC- HWC', 807443231: 'FW All Saints', + "placeholder": 'BCC- Fort Worth', // add with concept it 475614532: 'BCC- Plano', 809370237: 'BCC- Worth St', 856158129: 'BCC- Irving', diff --git a/src/shared.js b/src/shared.js index 74c3ae32..bef0fee7 100644 --- a/src/shared.js +++ b/src/shared.js @@ -2066,6 +2066,7 @@ export const siteSpecificLocation = { "Orland Park": {"siteAcronym":"UCM", "siteCode": healthProviderAbbrToConceptIdObj.uOfChicagoMed, "loginSiteName": "University of Chicago Medicine"}, "BCC- HWC": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, "FW All Saints": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, + "BCC- Fort Worth": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, "BCC- Plano": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, "BCC- Worth St": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, "BCC- Irving": {"SiteAcronym":"BSWH", "siteCode": healthProviderAbbrToConceptIdObj.BSWH, "loginSiteName": "Baylor Scott & White Health"}, @@ -2325,6 +2326,14 @@ export const locationConceptIDToLocationMap = { loginSiteName: 'Baylor Scott & White Health', email: 'connectbiospecimen@BSWHealth.org', }, + 'placeholder': { + siteSpecificLocation: 'BCC- Fort Worth', + siteAcronym: 'BSWH', + siteCode: '472940358', + siteTeam: 'BSWH Connect Study Team', + loginSiteName: 'Baylor Scott & White Health', + email: 'connectbiospecimen@BSWHealth.org', + }, 475614532: { siteSpecificLocation: 'BCC- Plano', siteAcronym: 'BSWH', @@ -2409,6 +2418,7 @@ export const conceptIdToSiteSpecificLocation = { [conceptIds.nameToKeyObj.sfSC]: "Sioux Falls Sanford Center", 723351427: "BCC- HWC", 807443231: "FW All Saints", + 'placeholder': "BCC- Fort Worth", 475614532: "BCC- Plano", 809370237: "BCC- Worth St", 856158129: "BCC- Irving", @@ -2449,6 +2459,7 @@ export const siteSpecificLocationToConceptId = { "Sioux Falls Sanford Center": conceptIds.nameToKeyObj.sfSC, "BCC- HWC": 723351427, "FW All Saints": 807443231, + "BCC- Fort Worth": '', // placeholder "BCC- Plano": 475614532, "BCC- Worth St": 809370237, "BCC- Irving": 856158129, @@ -2529,6 +2540,7 @@ export const keyToLocationObj = [conceptIds.nameToKeyObj.sfSC] : "Sioux Falls Sanford Center", 723351427:'BCC- HWC', 807443231:'FW All Saints', + '':'BCC- Fort Worth', 475614532:'BCC- Plano', 809370237:'BCC- Worth St', 856158129:'BCC- Irving', @@ -2746,6 +2758,7 @@ export const siteLocations = { 'BSWH': [{location: 'BCC- HWC', concept: 723351427}, {location: 'FW All Saints', concept: 807443231}, + {location: 'BCC- Fort Worth', concept: 'placeholder'}, {location: 'BCC- Plano', concept: 475614532}, {location: 'BCC- Worth St', concept: 809370237}, {location: 'BCC- Irving', concept: 856158129}, From bad245bf4fe6efbd634f7dd549323655fad52209 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Mon, 15 Jul 2024 15:09:44 -0400 Subject: [PATCH 09/27] update research check-in block logic to include anoy of the 4 site EMR derived variables or any clinical specimen collected --- src/events.js | 19 ++++++++++++++----- src/fieldToConceptIdMapping.js | 6 ++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/events.js b/src/events.js index f15358e5..00689aff 100644 --- a/src/events.js +++ b/src/events.js @@ -607,14 +607,23 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }; /** - * Checks if the participant has a clinical blood or urine collected variable under baseline. If participant has clinical blood or urine collected, show a notification and return true. + * Checks if the participant has any specimen collected (clinical blood or a clinical urine) under baseline from the dashboard or collected clinical/blood derivations from the site EMR API. If participant has either a clinical blood or urine dashboard variable or other blood/urine derived variables from the site EMR API, show a notification and return true. * @param {Object} data - participant data - * @returns {Boolean} - true if participant has any clinical blood or urine collected, false otherwise + * @returns {Boolean} - true if participant has any clinical blood or urine collected derivations, false otherwise */ const checkClinicalBloodOrUrineCollected = (data) => { - const isBloodOrUrineCollected = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]?.[conceptIds.clinicalBloodOrUrineCollected]; - - if (isBloodOrUrineCollected === conceptIds.yes) { + const collectionDetailsBaseline = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]; + if (!collectionDetailsBaseline) return false; + + const collectedBaselineStatuses = [ + collectionDetailsBaseline?.[conceptIds.anySpecimenCollected], + collectionDetailsBaseline?.[conceptIds.clinicalSiteBloodCollected], + collectionDetailsBaseline?.[conceptIds.clinicalSiteUrineCollected], + collectionDetailsBaseline?.[conceptIds.clinicalSiteBloodRRLReceived], + collectionDetailsBaseline?.[conceptIds.clinicalSiteUrineRRLReceived] + ] + + if (collectedBaselineStatuses.includes(conceptIds.yes)) { const bodyMessage = 'Check In not allowed, participant already has clinical collection for this timepoint.' showNotifications({ title: 'Check In Error', body: bodyMessage }); return true; diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 903edc8f..a10e6e36 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -142,6 +142,12 @@ export const conceptIds = { checkInComplete: 135591601, clinicalBloodOrUrineCollected: 156605577, + // Site EMR API Clinical baseline derivations + clinicalSiteBloodCollected: 693370086, + clinicalSiteUrineCollected: 786930107, + clinicalSiteBloodRRLReceived: 728696253, + clinicalSiteUrineRRLReceived: 453452655, + // not shipped specimen deviation id brokenSpecimenDeviation: 472864016, discardSpecimenDeviation: 810960823, From c902b8dd874172a40a09bee993826cccad841a28 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Tue, 16 Jul 2024 08:57:24 -0400 Subject: [PATCH 10/27] add modal icon --- src/events.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/events.js b/src/events.js index 00689aff..17ae3a1c 100644 --- a/src/events.js +++ b/src/events.js @@ -624,8 +624,9 @@ const checkClinicalBloodOrUrineCollected = (data) => { ] if (collectedBaselineStatuses.includes(conceptIds.yes)) { - const bodyMessage = 'Check In not allowed, participant already has clinical collection for this timepoint.' - showNotifications({ title: 'Check In Error', body: bodyMessage }); + const modalIcon = `` + const bodyMessage = `Check In not allowed, participant already has clinical collection for this timepoint. If you have questions, contact the Connect Biospeicmen Team: connectbioteam@nih.gov.` + showNotifications({ title: `${modalIcon} WARNING`, body: bodyMessage }); return true; } return false; From 55aadfb9a83cb053d93795374d048ccf82a30138 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Tue, 16 Jul 2024 09:09:12 -0400 Subject: [PATCH 11/27] edit jsdocs typos --- src/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events.js b/src/events.js index 17ae3a1c..bf561de9 100644 --- a/src/events.js +++ b/src/events.js @@ -607,7 +607,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { }; /** - * Checks if the participant has any specimen collected (clinical blood or a clinical urine) under baseline from the dashboard or collected clinical/blood derivations from the site EMR API. If participant has either a clinical blood or urine dashboard variable or other blood/urine derived variables from the site EMR API, show a notification and return true. + * Checks if the participant has any specimen collected (clinical blood or clinical urine) under baseline from the dashboard or collected clinical blood or urine derivations from the site EMR API. If participant has either a clinical blood or urine dashboard variable or other blood/urine derived variables from the site EMR API, show a notification and return true. * @param {Object} data - participant data * @returns {Boolean} - true if participant has any clinical blood or urine collected derivations, false otherwise */ From 5a8d38d79f68e33d902c956b080cf5d33f90366f Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Tue, 16 Jul 2024 09:59:17 -0400 Subject: [PATCH 12/27] rename data to participantData --- src/events.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/events.js b/src/events.js index bf561de9..13709a99 100644 --- a/src/events.js +++ b/src/events.js @@ -608,11 +608,11 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { /** * Checks if the participant has any specimen collected (clinical blood or clinical urine) under baseline from the dashboard or collected clinical blood or urine derivations from the site EMR API. If participant has either a clinical blood or urine dashboard variable or other blood/urine derived variables from the site EMR API, show a notification and return true. - * @param {Object} data - participant data + * @param {Object} participantData - participant document data from find participant query * @returns {Boolean} - true if participant has any clinical blood or urine collected derivations, false otherwise */ -const checkClinicalBloodOrUrineCollected = (data) => { - const collectionDetailsBaseline = data?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]; +const checkClinicalBloodOrUrineCollected = (participantData) => { + const collectionDetailsBaseline = participantData?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]; if (!collectionDetailsBaseline) return false; const collectedBaselineStatuses = [ @@ -621,7 +621,7 @@ const checkClinicalBloodOrUrineCollected = (data) => { collectionDetailsBaseline?.[conceptIds.clinicalSiteUrineCollected], collectionDetailsBaseline?.[conceptIds.clinicalSiteBloodRRLReceived], collectionDetailsBaseline?.[conceptIds.clinicalSiteUrineRRLReceived] - ] + ]; if (collectedBaselineStatuses.includes(conceptIds.yes)) { const modalIcon = `` From c7127f671ffb898a1977bb9f17991c85e55f1676 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Wed, 17 Jul 2024 12:01:37 -0400 Subject: [PATCH 13/27] create dismissBiospecimenModal function --- src/events.js | 24 +++++++++++++----------- src/shared.js | 14 ++++++++++---- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/events.js b/src/events.js index 13709a99..250141c3 100644 --- a/src/events.js +++ b/src/events.js @@ -7,7 +7,7 @@ import { checkNonAlphanumericStr, shippingNonAlphaNumericStrMessage, visitType, getParticipantCollections, updateBaselineData, siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, updateCollectionSettingData, convertToOldBox, translateNumToType, getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, - showConfirmationModal, + showConfirmationModal, dismissBiospecimenModal } from './shared.js'; import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js'; import { showReportsManifest } from './pages/reportsQuery.js'; @@ -571,10 +571,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { hideAnimation(); showTimedNotifications({ title: 'Success', body: 'Participant is checked out.' }, 100000, 1500); setTimeout(() => { - const closeButton = document.querySelector('#biospecimenModal .btn[data-dismiss="modal"]'); - if (closeButton) { - closeButton.click(); - } + dismissBiospecimenModal(); checkOutFlag === true ? location.reload() : goToParticipantSearch(); }, 1500); } else { @@ -611,8 +608,9 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { * @param {Object} participantData - participant document data from find participant query * @returns {Boolean} - true if participant has any clinical blood or urine collected derivations, false otherwise */ -const checkClinicalBloodOrUrineCollected = (participantData) => { +const checkClinicalBloodOrUrineCollected = (participantData) => { const collectionDetailsBaseline = participantData?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]; + if (!collectionDetailsBaseline) return false; const collectedBaselineStatuses = [ @@ -623,10 +621,12 @@ const checkClinicalBloodOrUrineCollected = (participantData) => { collectionDetailsBaseline?.[conceptIds.clinicalSiteUrineRRLReceived] ]; - if (collectedBaselineStatuses.includes(conceptIds.yes)) { + if (collectedBaselineStatuses.includes(conceptIds.yes)) { const modalIcon = `` const bodyMessage = `Check In not allowed, participant already has clinical collection for this timepoint. If you have questions, contact the Connect Biospeicmen Team: connectbioteam@nih.gov.` + showNotifications({ title: `${modalIcon} WARNING`, body: bodyMessage }); + const biospecimenModal = document.getElementById('biospecimenModal'); return true; } return false; @@ -662,12 +662,15 @@ const handleCheckInModal = async (data, visitConcept, query) => { continueButtonText: "Continue to Specimen Link", }; - const checkInOnCancel = () => { }; + const checkInOnCancel = () => { + dismissBiospecimenModal(); + }; const checkInOnContinue = async () => { try { const updatedResponse = await findParticipant(query); const updatedData = updatedResponse.data[0]; - + + dismissBiospecimenModal(); specimenTemplate(updatedData); } catch (error) { showNotifications({ title: 'Error', body: 'There was an error checking in the participant. Please try again.' }); @@ -839,7 +842,6 @@ const btnsClicked = async (connectId, formData) => { formData[conceptIds.collection.selectedVisit] = formData?.[conceptIds.collection.selectedVisit] || parseInt(getCheckedInVisit(particpantData)); if (!formData?.collectionId) { - console.log("Form data to be added:", formData); const storeResponse = await storeSpecimen([formData]); if (storeResponse.code === 400) { hideAnimation(); @@ -2753,4 +2755,4 @@ const searchAvailableCollectionsForSpecimen = (specimenId) => { } } return false; -} +} \ No newline at end of file diff --git a/src/shared.js b/src/shared.js index 74c3ae32..403191a1 100644 --- a/src/shared.js +++ b/src/shared.js @@ -578,10 +578,7 @@ export const showTimedNotifications = (data, zIndex, timeInMilliseconds = 2600) // Programmatically close the modal on a timer. setTimeout(() => { - const closeButton = document.querySelector('#biospecimenModal .btn[data-dismiss="modal"]'); - if (closeButton) { - closeButton.click(); - } + dismissBiospecimenModal() }, timeInMilliseconds); }; @@ -603,6 +600,15 @@ const closeBiospecimenModal = () => { document.body.classList.remove('modal-open'); }; +/** + * Targets close button on biospecimen bootstrap modal and closes it. Can be used to close and dismiss modal for other buttons on the modal. + * */ + export const dismissBiospecimenModal = () => { + const closeButton = document.querySelector('#biospecimenModal .btn[data-dismiss="modal"]'); + + if (closeButton) closeButton.click(); + } + export const errorMessage = (id, msg, focus, offset, icon) => { const currentElement = document.getElementById(id); const parentElement = currentElement.parentNode; From cad851dd580b41ee1215c0afed71bbe306966d8f Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Wed, 17 Jul 2024 12:05:51 -0400 Subject: [PATCH 14/27] remove spaces --- src/events.js | 2 +- src/shared.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/events.js b/src/events.js index 250141c3..0934e1ae 100644 --- a/src/events.js +++ b/src/events.js @@ -608,7 +608,7 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { * @param {Object} participantData - participant document data from find participant query * @returns {Boolean} - true if participant has any clinical blood or urine collected derivations, false otherwise */ -const checkClinicalBloodOrUrineCollected = (participantData) => { +const checkClinicalBloodOrUrineCollected = (participantData) => { const collectionDetailsBaseline = participantData?.[conceptIds.collectionDetails]?.[conceptIds.baseline.visitId]; if (!collectionDetailsBaseline) return false; diff --git a/src/shared.js b/src/shared.js index 403191a1..c1c440aa 100644 --- a/src/shared.js +++ b/src/shared.js @@ -603,11 +603,11 @@ const closeBiospecimenModal = () => { /** * Targets close button on biospecimen bootstrap modal and closes it. Can be used to close and dismiss modal for other buttons on the modal. * */ - export const dismissBiospecimenModal = () => { - const closeButton = document.querySelector('#biospecimenModal .btn[data-dismiss="modal"]'); +export const dismissBiospecimenModal = () => { + const closeButton = document.querySelector('#biospecimenModal .btn[data-dismiss="modal"]'); - if (closeButton) closeButton.click(); - } + if (closeButton) closeButton.click(); +} export const errorMessage = (id, msg, focus, offset, icon) => { const currentElement = document.getElementById(id); From 1e233d946f77df839bdcadd44d6f2aac6a20e8aa Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Wed, 17 Jul 2024 17:27:20 -0400 Subject: [PATCH 15/27] remove already called dismiss function and unused variable --- src/events.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/events.js b/src/events.js index 0934e1ae..199ae1d5 100644 --- a/src/events.js +++ b/src/events.js @@ -571,7 +571,6 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { hideAnimation(); showTimedNotifications({ title: 'Success', body: 'Participant is checked out.' }, 100000, 1500); setTimeout(() => { - dismissBiospecimenModal(); checkOutFlag === true ? location.reload() : goToParticipantSearch(); }, 1500); } else { @@ -626,7 +625,6 @@ const checkClinicalBloodOrUrineCollected = (participantData) => { const bodyMessage = `Check In not allowed, participant already has clinical collection for this timepoint. If you have questions, contact the Connect Biospeicmen Team: connectbioteam@nih.gov.` showNotifications({ title: `${modalIcon} WARNING`, body: bodyMessage }); - const biospecimenModal = document.getElementById('biospecimenModal'); return true; } return false; From ea2c1a95a776eadb351415a27581e8bb6253a284 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 18 Jul 2024 10:59:30 -0400 Subject: [PATCH 16/27] add BCC- Forth Worth collection/shipping location --- src/fieldToConceptIdMapping.js | 3 ++- src/shared.js | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 210ffb36..04b18a83 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -300,7 +300,7 @@ export const conceptIds = { 807835037: 'Other', 723351427: 'BCC- HWC', 807443231: 'FW All Saints', - "placeholder": 'BCC- Fort Worth', // add with concept it + 288564244: 'BCC- Fort Worth', 475614532: 'BCC- Plano', 809370237: 'BCC- Worth St', 856158129: 'BCC- Irving', @@ -337,6 +337,7 @@ export const conceptIds = { "mfPopUp": 567969985, 'bccHwc': 723351427, 'bccAllSaints': 807443231, + 'bccFortWorth': 288564244, 'bccPlano': 475614532, 'bccWorthSt': 809370237, 'bccIrving': 856158129, diff --git a/src/shared.js b/src/shared.js index bef0fee7..b0cf0c5f 100644 --- a/src/shared.js +++ b/src/shared.js @@ -2326,7 +2326,7 @@ export const locationConceptIDToLocationMap = { loginSiteName: 'Baylor Scott & White Health', email: 'connectbiospecimen@BSWHealth.org', }, - 'placeholder': { + 288564244: { siteSpecificLocation: 'BCC- Fort Worth', siteAcronym: 'BSWH', siteCode: '472940358', @@ -2418,7 +2418,7 @@ export const conceptIdToSiteSpecificLocation = { [conceptIds.nameToKeyObj.sfSC]: "Sioux Falls Sanford Center", 723351427: "BCC- HWC", 807443231: "FW All Saints", - 'placeholder': "BCC- Fort Worth", + 288564244: "BCC- Fort Worth", 475614532: "BCC- Plano", 809370237: "BCC- Worth St", 856158129: "BCC- Irving", @@ -2459,7 +2459,7 @@ export const siteSpecificLocationToConceptId = { "Sioux Falls Sanford Center": conceptIds.nameToKeyObj.sfSC, "BCC- HWC": 723351427, "FW All Saints": 807443231, - "BCC- Fort Worth": '', // placeholder + "BCC- Fort Worth": 288564244, "BCC- Plano": 475614532, "BCC- Worth St": 809370237, "BCC- Irving": 856158129, @@ -2538,15 +2538,15 @@ export const keyToLocationObj = 589224449: "Sioux Falls Imagenetics", [conceptIds.nameToKeyObj.sfBM] : "Bismarck Medical Center", [conceptIds.nameToKeyObj.sfSC] : "Sioux Falls Sanford Center", - 723351427:'BCC- HWC', - 807443231:'FW All Saints', - '':'BCC- Fort Worth', - 475614532:'BCC- Plano', - 809370237:'BCC- Worth St', - 856158129:'BCC- Irving', - 436956777:'NTX Biorepository', + 723351427: 'BCC- HWC', + 807443231: 'FW All Saints', + 288564244: 'BCC- Fort Worth', + 475614532: 'BCC- Plano', + 809370237: 'BCC- Worth St', + 856158129: 'BCC- Irving', + 436956777: 'NTX Biorepository', 111111111: "NIH", - 13:"NCI" + 13: "NCI" } @@ -2758,7 +2758,7 @@ export const siteLocations = { 'BSWH': [{location: 'BCC- HWC', concept: 723351427}, {location: 'FW All Saints', concept: 807443231}, - {location: 'BCC- Fort Worth', concept: 'placeholder'}, + {location: 'BCC- Fort Worth', concept: 288564244}, {location: 'BCC- Plano', concept: 475614532}, {location: 'BCC- Worth St', concept: 809370237}, {location: 'BCC- Irving', concept: 856158129}, From 4aedcbea56e746033cd31e44af4e56df9d986d21 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 18 Jul 2024 15:23:41 -0400 Subject: [PATCH 17/27] change disabled timing of check-in button --- src/events.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/events.js b/src/events.js index 13709a99..2ad1ad6c 100644 --- a/src/events.js +++ b/src/events.js @@ -558,7 +558,6 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { e.preventDefault(); try { const btnCheckIn = document.getElementById('checkInComplete'); - btnCheckIn.disabled = true; let query = `connectId=${parseInt(form.dataset.connectId)}`; @@ -599,6 +598,8 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { await handleCheckInModal(data, visitConcept, query); } + btnCheckIn.disabled = true; + } catch (error) { const bodyMessage = isCheckedIn ? 'There was an error checking out the participant. Please try again.' : 'There was an error checking in the participant. Please try again.'; showNotifications({ title: 'Error', body: bodyMessage }); From 27a628b756047a6972d65dfebe08253da4ded082 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 18 Jul 2024 15:28:39 -0400 Subject: [PATCH 18/27] move disable check-in btn in the else block --- src/events.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/events.js b/src/events.js index 2ad1ad6c..86a7d8b1 100644 --- a/src/events.js +++ b/src/events.js @@ -597,9 +597,8 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => { } await handleCheckInModal(data, visitConcept, query); + btnCheckIn.disabled = true; } - btnCheckIn.disabled = true; - } catch (error) { const bodyMessage = isCheckedIn ? 'There was an error checking out the participant. Please try again.' : 'There was an error checking in the participant. Please try again.'; showNotifications({ title: 'Error', body: bodyMessage }); From 60e22528b3d2fa0efa45ccdba552b94249114a40 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Sat, 20 Jul 2024 11:37:39 -0400 Subject: [PATCH 19/27] display app version to footer --- appVersion.js | 5 +++++ index.js | 11 +++++++++++ static/css/style.css | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 appVersion.js diff --git a/appVersion.js b/appVersion.js new file mode 100644 index 00000000..aa2c96be --- /dev/null +++ b/appVersion.js @@ -0,0 +1,5 @@ +const appVersion = { + "versionNumber": "v1.4.0" +} + +export default appVersion; \ No newline at end of file diff --git a/index.js b/index.js index bd8292b3..b9345262 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ import { collectionIdSearchScreen } from "./src/pages/reports/collectionIdSearch import { bptlShipReportsScreen } from "./src/pages/reports/shippingReport.js"; import { checkOutReportTemplate } from "./src/pages/checkOutReport.js"; import { dailyReportTemplate } from "./src/pages/dailyReport.js"; +import appVersion from "./appVersion.js"; //test @@ -136,3 +137,13 @@ const userLoggedIn = () => { }); }); }; + +(function displayAppVersion() { + document.addEventListener('DOMContentLoaded', function() { + const contentWrapper = document.querySelector('.footer-content .content-wrapper'); + + const newElement = document.createElement('div'); + newElement.innerHTML = `

${appVersion.versionNumber}

`; + contentWrapper.appendChild(newElement); + }); +})(); \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 2179642d..5bccda21 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -472,4 +472,8 @@ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); #checkInComplete:disabled { cursor: not-allowed; background-color: #e0e0e0; +} + +#appVersion { + font-size: 0.8rem; } \ No newline at end of file From 5c879b862c2f959805e0f293c60028b1c4db0846 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 25 Jul 2024 13:50:28 -0400 Subject: [PATCH 20/27] cache app version via storage cache --- appVersion.js | 6 ++-- index.js | 87 +++++++++++++++++++++++++++++++++++++++--------- serviceWorker.js | 29 +++++++++++++++- 3 files changed, 102 insertions(+), 20 deletions(-) diff --git a/appVersion.js b/appVersion.js index aa2c96be..5f24e492 100644 --- a/appVersion.js +++ b/appVersion.js @@ -1,5 +1,3 @@ const appVersion = { - "versionNumber": "v1.4.0" -} - -export default appVersion; \ No newline at end of file + "versionNumber": "v24.7.0" +} \ No newline at end of file diff --git a/index.js b/index.js index b9345262..19a9f94d 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,6 @@ import { collectionIdSearchScreen } from "./src/pages/reports/collectionIdSearch import { bptlShipReportsScreen } from "./src/pages/reports/shippingReport.js"; import { checkOutReportTemplate } from "./src/pages/checkOutReport.js"; import { dailyReportTemplate } from "./src/pages/dailyReport.js"; -import appVersion from "./appVersion.js"; //test @@ -50,15 +49,16 @@ const datadogConfig = { const isLocalDev = location.hostname === 'localhost' || location.hostname === '127.0.0.1'; -window.onload = () => { - if ("serviceWorker" in navigator) { - try { - navigator.serviceWorker.register("./serviceWorker.js"); - } catch (error) { - console.log(error); - } + +window.onload = async () => { + try { + await registerServiceWorker(); + await updateVersionDisplay(); + } catch (error) { + console.log(error); } + if(location.host === urls.prod) { !firebase.apps.length ? firebase.initializeApp(prodFirebaseConfig()) : firebase.app(); window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'prod' }); @@ -138,12 +138,69 @@ const userLoggedIn = () => { }); }; -(function displayAppVersion() { - document.addEventListener('DOMContentLoaded', function() { + +const fetchAppVersionFromCache = async () => { + try { + const cache = await caches.open('app-version-cache'); + const response = await cache.match('./appVersion.js'); + + if (!response) return; + + const appVersionText = await response.text(); + const versionMatch = appVersionText.match(/"versionNumber":\s*"(.+?)"/); + + if (!versionMatch) return; + + return versionMatch[1]; + } catch (error) { + console.error('Error fetching app version:', error); + return 'Error fetching version'; + } + } + +/** + * This function is an async function that checks if the service worker is supported by the browser + * If it is supported, it registers the service worker + * If the service worker is already installed and there is a new service worker available, it refreshes the page +*/ +const registerServiceWorker = async () => { + if ("serviceWorker" in navigator) { + try { + const registration = await navigator.serviceWorker.register("./serviceWorker.js"); + // console.log('Service Worker registered'); + + registration.addEventListener('updatefound', () => { // This event fires when a new service worker is found + const newWorker = registration.installing; + newWorker.addEventListener('statechange', () => { // This event fires when the state of the service worker changes + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + // New content is available, refresh the page + console.log("Refreshing page"); + window.location.reload(); + } + }); + }); + } catch (error) { + console.log('Service Worker registration failed:', error); + } + } +}; + +/** + * Fetches the app version from the cache storage and updates the version display in the footer +*/ +const updateVersionDisplay = async () => { + const versionNumber = await fetchAppVersionFromCache(); + if (!versionNumber) return; + + let versionElement = document.getElementById('appVersion'); + + if (!versionElement) { const contentWrapper = document.querySelector('.footer-content .content-wrapper'); + if (!contentWrapper || !versionNumber) return; - const newElement = document.createElement('div'); - newElement.innerHTML = `

${appVersion.versionNumber}

`; - contentWrapper.appendChild(newElement); - }); -})(); \ No newline at end of file + versionElement = document.createElement('p'); + versionElement.id = 'appVersion'; + contentWrapper.appendChild(versionElement); + versionElement.textContent = versionNumber; + } +}; diff --git a/serviceWorker.js b/serviceWorker.js index 21c7856e..de20ed54 100644 --- a/serviceWorker.js +++ b/serviceWorker.js @@ -1,4 +1,6 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); +importScripts('./appVersion.js'); + workbox.setConfig({debug: false}); const { registerRoute } = workbox.routing; const { CacheFirst, NetworkFirst, StaleWhileRevalidate, NetworkOnly } = workbox.strategies; @@ -58,4 +60,29 @@ registerRoute( }), ], }) -); \ No newline at end of file +); + +const cacheVersionName = `app-version-cache`; +const precacheVersionAssets = ['/appVersion.js']; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(cacheVersionName) + .then((cache) => { + return cache.keys() + .then((keys) => { + const deletionPromises = keys + .filter(key => key.url.includes('app-version-cache')) // + .map(key => cache.delete(key)); + return Promise.all(deletionPromises); + }) + .then(() => cache.addAll(precacheVersionAssets)); + }) + .then(() => { + self.skipWaiting(); // Forces the waiting service worker to become the active service worker + }) + .catch(error => { + console.error('Cache update failed:', error); + }) + ); +}); \ No newline at end of file From 5dfb5269939133fc5ca463dd0b4ff747bd4671ab Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 25 Jul 2024 14:31:56 -0400 Subject: [PATCH 21/27] change match and move function --- index.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 19a9f94d..b06279fe 100644 --- a/index.js +++ b/index.js @@ -138,26 +138,6 @@ const userLoggedIn = () => { }); }; - -const fetchAppVersionFromCache = async () => { - try { - const cache = await caches.open('app-version-cache'); - const response = await cache.match('./appVersion.js'); - - if (!response) return; - - const appVersionText = await response.text(); - const versionMatch = appVersionText.match(/"versionNumber":\s*"(.+?)"/); - - if (!versionMatch) return; - - return versionMatch[1]; - } catch (error) { - console.error('Error fetching app version:', error); - return 'Error fetching version'; - } - } - /** * This function is an async function that checks if the service worker is supported by the browser * If it is supported, it registers the service worker @@ -204,3 +184,22 @@ const updateVersionDisplay = async () => { versionElement.textContent = versionNumber; } }; + +const fetchAppVersionFromCache = async () => { + try { + const cache = await caches.open('app-version-cache'); + const response = await cache.match('./appVersion.js'); + + if (!response) return; + + const appVersionText = await response.text(); + const versionMatch = appVersionText.match(/"versionNumber"\s*:\s*"(v\d+\.\d+\.\d+)"/); + + if (!versionMatch) return; + + return versionMatch[1]; + } catch (error) { + console.error('Error fetching app version:', error); + return 'Error fetching version'; + } +} From fc4d0a5b19dabeb1ff3c38d29f4f5367dfc52f7b Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 25 Jul 2024 15:16:29 -0400 Subject: [PATCH 22/27] add log to check if service worker is registered --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b06279fe..380018c0 100644 --- a/index.js +++ b/index.js @@ -147,7 +147,7 @@ const registerServiceWorker = async () => { if ("serviceWorker" in navigator) { try { const registration = await navigator.serviceWorker.register("./serviceWorker.js"); - // console.log('Service Worker registered'); + console.log('Service Worker registered with scope:', registration.scope); registration.addEventListener('updatefound', () => { // This event fires when a new service worker is found const newWorker = registration.installing; From f074653b8e5e5d895ed57514cf96beaecebb1803 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Thu, 25 Jul 2024 15:33:58 -0400 Subject: [PATCH 23/27] remove comments --- serviceWorker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serviceWorker.js b/serviceWorker.js index de20ed54..3f6614d7 100644 --- a/serviceWorker.js +++ b/serviceWorker.js @@ -72,7 +72,7 @@ self.addEventListener('install', (event) => { return cache.keys() .then((keys) => { const deletionPromises = keys - .filter(key => key.url.includes('app-version-cache')) // + .filter(key => key.url.includes('app-version-cache')) .map(key => cache.delete(key)); return Promise.all(deletionPromises); }) From 4b1fd2e4010a799f90d1210c179c599a41c7725e Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Fri, 26 Jul 2024 09:23:57 -0400 Subject: [PATCH 24/27] add appVersion p element to template literal --- index.html | 1 + index.js | 17 ++++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 232a9e61..48a5531c 100644 --- a/index.html +++ b/index.html @@ -95,6 +95,7 @@ +

diff --git a/index.js b/index.js index 380018c0..c20d428f 100644 --- a/index.js +++ b/index.js @@ -170,20 +170,11 @@ const registerServiceWorker = async () => { */ const updateVersionDisplay = async () => { const versionNumber = await fetchAppVersionFromCache(); - if (!versionNumber) return; + const versionElement = document.getElementById('appVersion'); - let versionElement = document.getElementById('appVersion'); - - if (!versionElement) { - const contentWrapper = document.querySelector('.footer-content .content-wrapper'); - if (!contentWrapper || !versionNumber) return; - - versionElement = document.createElement('p'); - versionElement.id = 'appVersion'; - contentWrapper.appendChild(versionElement); - versionElement.textContent = versionNumber; - } -}; + if (!versionNumber || !versionElement) return; + versionElement.textContent = `${versionNumber}`; + }; const fetchAppVersionFromCache = async () => { try { From 5ef27c3175a0c44d0ab129e0c44a1a47b58d5034 Mon Sep 17 00:00:00 2001 From: Warren Lu Date: Fri, 26 Jul 2024 14:44:16 -0400 Subject: [PATCH 25/27] move version to service worker; cleanups --- appVersion.js | 3 -- index.html | 12 ------ index.js | 87 ++++++++++---------------------------------- serviceWorker.js | 95 +++++++++++++++--------------------------------- 4 files changed, 48 insertions(+), 149 deletions(-) delete mode 100644 appVersion.js diff --git a/appVersion.js b/appVersion.js deleted file mode 100644 index 5f24e492..00000000 --- a/appVersion.js +++ /dev/null @@ -1,3 +0,0 @@ -const appVersion = { - "versionNumber": "v24.7.0" -} \ No newline at end of file diff --git a/index.html b/index.html index 48a5531c..f330edcf 100644 --- a/index.html +++ b/index.html @@ -100,18 +100,6 @@ - diff --git a/index.js b/index.js index c20d428f..f196ee22 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,24 @@ import { bptlShipReportsScreen } from "./src/pages/reports/shippingReport.js"; import { checkOutReportTemplate } from "./src/pages/checkOutReport.js"; import { dailyReportTemplate } from "./src/pages/dailyReport.js"; -//test +if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("./serviceWorker.js").catch((error) => { + console.error("Service worker registration failed.", error); + return; + }); + + navigator.serviceWorker.ready.then(() => { + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ action: "getAppVersion" }); + } + }); + + navigator.serviceWorker.addEventListener("message", (event) => { + if (event.data.action === "sendAppVersion") { + document.getElementById("appVersion").textContent = event.data.payload; + } + }); +} let auth = ''; @@ -49,16 +66,7 @@ const datadogConfig = { const isLocalDev = location.hostname === 'localhost' || location.hostname === '127.0.0.1'; - window.onload = async () => { - try { - await registerServiceWorker(); - await updateVersionDisplay(); - } catch (error) { - console.log(error); - } - - if(location.host === urls.prod) { !firebase.apps.length ? firebase.initializeApp(prodFirebaseConfig()) : firebase.app(); window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'prod' }); @@ -104,7 +112,7 @@ const manageRoutes = async () => { else if (route === "#allParticipants") allParticipantsScreen(auth, route); else if (route === "#addressPrinted") addressesPrintedScreen(auth, route); else if (route === "#assigned") assignedScreen(auth, route); - else if (route === "#status_shipped") kitStatusReportsShipped(auth, route); + // else if (route === "#status_shipped") kitStatusReportsShipped(auth, route); else if (route === "#received") receivedKitsScreen(auth,route); else if (route === "#kitshipment") kitShipmentScreen(auth, route); else if (route === "#packagesintransit") packagesInTransitScreen(auth, route); @@ -137,60 +145,3 @@ const userLoggedIn = () => { }); }); }; - -/** - * This function is an async function that checks if the service worker is supported by the browser - * If it is supported, it registers the service worker - * If the service worker is already installed and there is a new service worker available, it refreshes the page -*/ -const registerServiceWorker = async () => { - if ("serviceWorker" in navigator) { - try { - const registration = await navigator.serviceWorker.register("./serviceWorker.js"); - console.log('Service Worker registered with scope:', registration.scope); - - registration.addEventListener('updatefound', () => { // This event fires when a new service worker is found - const newWorker = registration.installing; - newWorker.addEventListener('statechange', () => { // This event fires when the state of the service worker changes - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - // New content is available, refresh the page - console.log("Refreshing page"); - window.location.reload(); - } - }); - }); - } catch (error) { - console.log('Service Worker registration failed:', error); - } - } -}; - -/** - * Fetches the app version from the cache storage and updates the version display in the footer -*/ -const updateVersionDisplay = async () => { - const versionNumber = await fetchAppVersionFromCache(); - const versionElement = document.getElementById('appVersion'); - - if (!versionNumber || !versionElement) return; - versionElement.textContent = `${versionNumber}`; - }; - -const fetchAppVersionFromCache = async () => { - try { - const cache = await caches.open('app-version-cache'); - const response = await cache.match('./appVersion.js'); - - if (!response) return; - - const appVersionText = await response.text(); - const versionMatch = appVersionText.match(/"versionNumber"\s*:\s*"(v\d+\.\d+\.\d+)"/); - - if (!versionMatch) return; - - return versionMatch[1]; - } catch (error) { - console.error('Error fetching app version:', error); - return 'Error fetching version'; - } -} diff --git a/serviceWorker.js b/serviceWorker.js index 3f6614d7..24166c83 100644 --- a/serviceWorker.js +++ b/serviceWorker.js @@ -1,19 +1,22 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); -importScripts('./appVersion.js'); +const appVersion = "v24.7.0"; workbox.setConfig({debug: false}); const { registerRoute } = workbox.routing; -const { CacheFirst, NetworkFirst, StaleWhileRevalidate, NetworkOnly } = workbox.strategies; -const { CacheableResponse, CacheableResponsePlugin } = workbox.cacheableResponse; +const { CacheFirst, NetworkFirst } = workbox.strategies; const { ExpirationPlugin } = workbox.expiration; -const { BackgroundSyncPlugin } = workbox.backgroundSync const googleAnalytics = workbox.googleAnalytics; +const cacheNameMapper = { + "static-cache": `static-cache-${appVersion}`, + "images-cache": `images-cache-${appVersion}`, +}; +const currCacheNameArray = Object.values(cacheNameMapper); googleAnalytics.initialize(); -registerRoute(/\.(?:js|css)$/, new NetworkFirst({cacheName: 'static-cache'})); +registerRoute(/\.(?:js|css|html)$/, new NetworkFirst({ cacheName: cacheNameMapper["static-cache"] })); registerRoute(/\.(?:png|jpg|jpeg|svg|gif|ico)$/, new CacheFirst({ - cacheName: 'images-cache', + cacheName: cacheNameMapper["images-cache"], plugins: [ new ExpirationPlugin({ maxEntries: 30, @@ -23,66 +26,26 @@ registerRoute(/\.(?:png|jpg|jpeg|svg|gif|ico)$/, }) ); -registerRoute( - new RegExp('https://us-central1-nih-nci-dceg-connect-dev.cloudfunctions.net/.+'), - new NetworkFirst({ - cacheName: 'api-cache', - plugins: [ - new CacheableResponsePlugin({ - statuses: [200], - }) - ] - }), - 'GET' -); - - -const bgSyncPlugin = new BackgroundSyncPlugin('connectBiospecimen', { - maxRetentionTime: 24 * 60 // Retry for max of 24 Hours (specified in minutes) +self.addEventListener("install", () => { + self.skipWaiting(); }); -registerRoute( - new RegExp('https://us-central1-nih-nci-dceg-connect-dev.cloudfunctions.net/.+'), - new NetworkOnly({ - plugins: [bgSyncPlugin] - }), - 'POST' -); - -registerRoute( - /index\.html$/, - new NetworkFirst({ - cacheName: 'index-html-cache', - networkTimeoutSeconds: 3, - plugins: [ - new CacheableResponsePlugin({ - statuses: [0, 200], - }), - ], - }) -); - -const cacheVersionName = `app-version-cache`; -const precacheVersionAssets = ['/appVersion.js']; +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (!currCacheNameArray.includes(cacheName)) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(cacheVersionName) - .then((cache) => { - return cache.keys() - .then((keys) => { - const deletionPromises = keys - .filter(key => key.url.includes('app-version-cache')) - .map(key => cache.delete(key)); - return Promise.all(deletionPromises); - }) - .then(() => cache.addAll(precacheVersionAssets)); - }) - .then(() => { - self.skipWaiting(); // Forces the waiting service worker to become the active service worker - }) - .catch(error => { - console.error('Cache update failed:', error); - }) - ); -}); \ No newline at end of file +self.addEventListener("message", (event) => { + if (event.data.action === "getAppVersion") { + event.source.postMessage({ action: "sendAppVersion", payload: appVersion }); + } +}); From f85f8e0a53268bbdb5e5c66f34cd996312315d4b Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Fri, 26 Jul 2024 16:27:12 -0400 Subject: [PATCH 26/27] remove async --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index f196ee22..496a9e2e 100644 --- a/index.js +++ b/index.js @@ -66,7 +66,7 @@ const datadogConfig = { const isLocalDev = location.hostname === 'localhost' || location.hostname === '127.0.0.1'; -window.onload = async () => { +window.onload = () => { if(location.host === urls.prod) { !firebase.apps.length ? firebase.initializeApp(prodFirebaseConfig()) : firebase.app(); window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'prod' }); From d3514557cdf9e1eba37ec0ca9aa3878e40cfd682 Mon Sep 17 00:00:00 2001 From: Gene Barra Date: Fri, 26 Jul 2024 18:00:25 -0400 Subject: [PATCH 27/27] remove unused route --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 496a9e2e..ee9d8a4c 100644 --- a/index.js +++ b/index.js @@ -112,7 +112,6 @@ const manageRoutes = async () => { else if (route === "#allParticipants") allParticipantsScreen(auth, route); else if (route === "#addressPrinted") addressesPrintedScreen(auth, route); else if (route === "#assigned") assignedScreen(auth, route); - // else if (route === "#status_shipped") kitStatusReportsShipped(auth, route); else if (route === "#received") receivedKitsScreen(auth,route); else if (route === "#kitshipment") kitShipmentScreen(auth, route); else if (route === "#packagesintransit") packagesInTransitScreen(auth, route);