Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev -> Stage Sync (July 2024 Release) #731

Merged
merged 36 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
71fc61b
add modal to restrict research check in when clinical samples collected
Gbarra9 Jun 28, 2024
df258f1
Remove logs, uncomment await handleCheckInModal, remove extra logic …
Gbarra9 Jul 3, 2024
e355e32
remove extra logs
Gbarra9 Jul 3, 2024
40b9a1c
move showConfirmationModal function to share.js file
Gbarra9 Jul 5, 2024
e08abe0
Merge pull request #723 from episphere/issue#1004-restrict-research-c…
Gbarra9 Jul 5, 2024
11d281f
Kit validation now prevents duplicate tracking numbers
amber-emmes Jul 8, 2024
0eccdc1
clear out fields date collection card and time collection card when c…
Gbarra9 Jul 9, 2024
849ff19
Remove early code clearing of element with collectionId's value
Gbarra9 Jul 9, 2024
f6e8d2d
Merge pull request #725 from episphere/issue#1043-clear-collection-da…
Gbarra9 Jul 9, 2024
720aa79
add BCC-Fort Worth as placeholder waiting for cid assignment
Gbarra9 Jul 15, 2024
bad245b
update research check-in block logic to include anoy of the 4 site EM…
Gbarra9 Jul 15, 2024
c902b8d
add modal icon
Gbarra9 Jul 16, 2024
55aadfb
edit jsdocs typos
Gbarra9 Jul 16, 2024
5a8d38d
rename data to participantData
Gbarra9 Jul 16, 2024
ee36eb0
Merge pull request #726 from episphere/update-block-research-check-in
Gbarra9 Jul 16, 2024
c7127f6
create dismissBiospecimenModal function
Gbarra9 Jul 17, 2024
cad851d
remove spaces
Gbarra9 Jul 17, 2024
1e233d9
remove already called dismiss function and unused variable
Gbarra9 Jul 17, 2024
7682ef9
Merge pull request #727 from episphere/issue#1004-fix-create-dismiss-…
Gbarra9 Jul 18, 2024
2c2e5fb
Merge pull request #724 from episphere/1038-fix
amber-emmes Jul 18, 2024
ea2c1a9
add BCC- Forth Worth collection/shipping location
Gbarra9 Jul 18, 2024
515c1ad
Merge pull request #728 from episphere/issue#1032-add-BCC-Fort-Worth
Gbarra9 Jul 18, 2024
4aedcbe
change disabled timing of check-in button
Gbarra9 Jul 18, 2024
27a628b
move disable check-in btn in the else block
Gbarra9 Jul 18, 2024
4b75193
Merge pull request #729 from episphere/adjust-disabled-timing-of-chec…
Gbarra9 Jul 19, 2024
60e2252
display app version to footer
Gbarra9 Jul 20, 2024
5c879b8
cache app version via storage cache
Gbarra9 Jul 25, 2024
5dfb526
change match and move function
Gbarra9 Jul 25, 2024
fc4d0a5
add log to check if service worker is registered
Gbarra9 Jul 25, 2024
f074653
remove comments
Gbarra9 Jul 25, 2024
4b1fd2e
add appVersion p element to template literal
Gbarra9 Jul 26, 2024
5ef27c3
move version to service worker; cleanups
we-ai Jul 26, 2024
f85f8e0
remove async
Gbarra9 Jul 26, 2024
d351455
remove unused route
Gbarra9 Jul 26, 2024
aded1ae
Merge pull request #730 from episphere/issue#621-display-app-version
Gbarra9 Jul 29, 2024
6891c56
Merge branch 'stage' into dev
we-ai Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 53 additions & 68 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, dismissBiospecimenModal
} from './shared.js';
import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js';
import { showReportsManifest } from './pages/reportsQuery.js';
Expand Down Expand Up @@ -557,30 +558,30 @@ export const addEventCheckInCompleteForm = (isCheckedIn, checkOutFlag) => {
e.preventDefault();
try {
const btnCheckIn = document.getElementById('checkInComplete');
btnCheckIn.disabled = true;

let query = `connectId=${parseInt(form.dataset.connectId)}`;

const response = await findParticipant(query);
const data = response.data[0];

if (isCheckedIn) {
showAnimation();
await checkOutParticipant(data);
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();
}
checkOutFlag === true ? location.reload() : goToParticipantSearch();
}, 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);
let collection = response.data.filter(res => res[conceptIds.collection.selectedVisit] == visit.concept);
Expand All @@ -592,6 +593,7 @@ 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.';
Expand All @@ -600,6 +602,34 @@ 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} 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 collectionDetailsBaseline = participantData?.[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 modalIcon = `<i class="fas fa-exclamation-circle" style="color: red; font-size: 1.4rem;"></i>`
const bodyMessage = `Check In not allowed, participant already has clinical collection for this timepoint. If you have questions, contact the Connect Biospeicmen Team: <a href="mailto:[email protected]">[email protected]</a>.`

showNotifications({ title: `${modalIcon} WARNING`, body: bodyMessage });
return true;
}
return false;
}

const handleCheckInWarning = async (visit, data, collection) => {
const message = {
title: "Warning - Participant Previously Checked In",
Expand Down Expand Up @@ -630,12 +660,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.' });
Expand Down Expand Up @@ -680,6 +713,7 @@ export const addEventSpecimenLinkForm = (formData) => {
form.addEventListener('click', async (e) => {
e.preventDefault();
const collections = await getCollectionsByVisit(formData);

if (collections.length) {
existingCollectionAlert(collections, connectId, formData);
} else {
Expand Down Expand Up @@ -731,7 +765,7 @@ const existingCollectionAlert = async (collections, connectId, formData) => {
* @param {string} connectId
* @param {*} formData
*/
const btnsClicked = async (connectId, formData) => {
const btnsClicked = async (connectId, formData) => {
removeAllErrors();

let scanSpecimenID = document.getElementById('scanSpecimenID')?.value && document.getElementById('scanSpecimenID')?.value.toUpperCase();
Expand All @@ -749,18 +783,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);
Expand All @@ -776,60 +809,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 modalBody = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Confirm Collection ID</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Collection ID: ${collectionID}</p>
<p>Confirm ID is correct for participant: ${firstName}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" data-result="cancel">Cancel</button>
<button type="button" class="btn btn-info" data-result="back" data-dismiss="modal">Confirm and Exit</button>
<button type="button" class="btn btn-success" data-result="confirmed" data-dismiss="modal">Confirm and Continue</button>
</div>
</div>
`;
const confirmVal = await showConfirmationModal(collectionID, firstName);

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);
}
});
});
};
if (confirmVal === "cancel") return;

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);
Expand All @@ -844,15 +827,16 @@ if (confirmVal === "cancel") {

if (!formData?.collectionId) {
specimenData = (await searchSpecimen(formData[conceptIds.collection.id])).data;

}
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;
}

showAnimation();

formData[conceptIds.collection.selectedVisit] = formData?.[conceptIds.collection.selectedVisit] || parseInt(getCheckedInVisit(particpantData));

if (!formData?.collectionId) {
Expand Down Expand Up @@ -881,6 +865,7 @@ if (confirmVal === "cancel") {
}
}


/**
* Check accession number inputs after clicking 'Submit' button
* @param {*} formData
Expand Down Expand Up @@ -2768,4 +2753,4 @@ const searchAvailableCollectionsForSpecimen = (specimenId) => {
}
}
return false;
}
}
9 changes: 9 additions & 0 deletions src/fieldToConceptIdMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ export const conceptIds = {
checkOutDateTime: 343048998,
checkInDateTime: 840048338,
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,
Expand Down Expand Up @@ -299,6 +306,7 @@ export const conceptIds = {
807835037: 'Other',
723351427: 'BCC- HWC',
807443231: 'FW All Saints',
288564244: 'BCC- Fort Worth',
475614532: 'BCC- Plano',
809370237: 'BCC- Worth St',
856158129: 'BCC- Irving',
Expand Down Expand Up @@ -335,6 +343,7 @@ export const conceptIds = {
"mfPopUp": 567969985,
'bccHwc': 723351427,
'bccAllSaints': 807443231,
'bccFortWorth': 288564244,
'bccPlano': 475614532,
'bccWorthSt': 809370237,
'bccIrving': 856158129,
Expand Down
3 changes: 2 additions & 1 deletion src/pages/checkIn.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const checkInTemplate = async (data, checkOutFlag) => {

<hr/>
`;

template += await participantStatus(data, collections);


Expand Down Expand Up @@ -134,6 +134,7 @@ const participantStatus = (data, collections) => {
const urineTubes = siteTubesList?.filter(tube => tube.tubeType === "Urine");
const mouthwashTubes = siteTubesList?.filter(tube => tube.tubeType === "Mouthwash");


collections = collections.filter(collection => collection[conceptIds.collection.selectedVisit] == conceptIds.baseline.visitId);

collections.forEach(collection => {
Expand Down
19 changes: 14 additions & 5 deletions src/pages/homeCollection/kitAssembly.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ const renderSidePane = () => {
Return Kit ID = ${ kitObject[conceptIds.returnKitId] } |
Cup Id = ${ kitObject[conceptIds.collectionCupId] } |
Card Id = ${ kitObject[conceptIds.collectionCardId] }
<button type="button" class="btn btn-outline-primary detailedRow" data-kitObject=${JSON.stringify(kitObject)} id="editAssembledKits">Edit</button>
<button type="button" class="btn btn-outline-primary detailedRow" data-kitObject=${encodeURIComponent(JSON.stringify(kitObject))} id="editAssembledKits">Edit</button>
</ul>`
})
editAssembledKits();
Expand All @@ -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]
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/pages/homeCollection/kitsReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", "[]");
Expand Down
Loading