Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react-dom": "^17.0.0",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.17.0",
"uuid": "^9.0.1",
"vite": "^5.1.6",
"vite-tsconfig-paths": "^4.3.2"
},
Expand Down
66 changes: 56 additions & 10 deletions src/components/Dashboard/ListSelections/NotificationsSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,77 @@ import useStyles from '../styles';
import { SettingsContext } from '../../../containers/ContextProvider/SettingsProvider';
import { EtasuStatusComponent } from '../../EtasuStatus/EtasuStatusComponent';
import axios from 'axios';
import { createMedicationFromMedicationRequest } from '../../../util/fhir';
import { standardsBasedGetEtasu } from '../../../util/util';

const NotificationsSection = () => {
const [globalState, _] = useContext(SettingsContext);
const classes = useStyles();
const [etasu, setEtasu] = useState([]);
const [medications, setMedications] = useState([]);
useEffect(() => {
const patientFirstName = globalState.patient?.name?.at(0)?.given?.at(0);
const patientLastName = globalState.patient?.name?.at(0)?.family;
const patientDOB = globalState.patient?.birthDate;
setEtasu([]);
getMedicationRequest();
}, []);

useEffect(() => {
getAllEtasu();
}, [medications]);

const etasuUrl = `${globalState.remsAdminServer}/etasu/met/patient/${patientFirstName}/${patientLastName}/${patientDOB}`;
const getMedicationRequest = () => {
const patientsMedications = [];
axios({
method: 'get',
url: etasuUrl
}).then((response) => {
setEtasu(response.data);
url: `${globalState.baseUrl}/MedicationRequest?subject=Patient/${globalState.patient.id}`
}).then((result) => {
result?.data.entry.forEach((m) => {
const medication = createMedicationFromMedicationRequest(m.resource);
patientsMedications.push(medication);
})
setMedications(patientsMedications);
}, (error) =>{
console.error(error);
})
}, []);
});
};

const compileResponses = (newRequest, body) => {
if (newRequest.contained) {
newRequest.body = body;
setEtasu(prevState => [ ...prevState, newRequest]);
}
}

const getAllEtasu = () => {
medications.forEach((medication) => {
const body = makeBody(medication);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
standardsBasedGetEtasu(standardEtasuUrl, body, compileResponses);
});

}

const makeBody = (medication) => {
return {
resourceType: "Parameters",
parameter: [
{
name: 'patient',
resource: globalState.patient
},
{
name: 'medication',
resource: medication
}
]
}
}

return (
<div className={classes.dashboardArea}>
<h2 className={classes.elementHeader}>Notifications</h2>
{etasu.map((remsCase) => {
return <EtasuStatusComponent key = {remsCase.case_number} remsAdminResponseInit={remsCase} />
const display = remsCase.body.parameter[1]?.resource.code.coding[0].display;
return <EtasuStatusComponent key={display} display={display} remsAdminResponseInit={remsCase} data={remsCase.body} />
})}
</div>
);
Expand Down
64 changes: 38 additions & 26 deletions src/components/EtasuStatus/EtasuStatus.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
import { EtasuStatusButton } from './EtasuStatusButton.jsx';
import { EtasuStatusModal } from './EtasuStatusModal.jsx';
import { useState, useEffect, useContext } from 'react';
import { Card, Typography } from '@mui/material';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider.jsx';
import axios from 'axios';
import { EtasuStatusComponent } from './EtasuStatusComponent.jsx';
import { getEtasu } from '../../util/util.js';
import { standardsBasedGetEtasu } from '../../util/util.js';
import { createMedicationFromMedicationRequest } from '../../util/fhir.js';

// converts code into etasu for the component to render
// simplifies usage for applications that only know the code, not the case they want to display
export const EtasuStatus = props => {
const [globalState, _] = useContext(SettingsContext);

const { code } =
props;
const { code, request } = props;
const [remsAdminResponse, setRemsAdminResponse] = useState({});
useEffect(() => getEtasuStatus(), [code]);
const getEtasuStatus = () => {
const patientFirstName = globalState.patient?.name?.at(0)?.given?.at(0);
const patientLastName = globalState.patient?.name?.at(0)?.family;
const patientDOB = globalState.patient?.birthDate;

console.log(
'get Etastu Status: ' +
patientFirstName +
' ' +
patientLastName +
' - ' +
patientDOB +
' - ' +
code
);
const etasuUrl = `${globalState.remsAdminServer}/etasu/met/patient/${patientFirstName}/${patientLastName}/${patientDOB}/drugCode/${code}`;
getEtasu(etasuUrl, setRemsAdminResponse);
const [etasuData, setEtasuData] = useState({});
const [display, setDisplay] = useState('');

useEffect(() => {
const medication = createMedicationFromMedicationRequest(request);
getEtasuStatus(medication);
}, [code]);

const getEtasuStatus = (medication) => {
const body = makeBody(medication);
setEtasuData(body);
const display = body.parameter[1]?.resource.code.coding[0].display;
setDisplay(display);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
standardsBasedGetEtasu(standardEtasuUrl, body, setRemsAdminResponse);

};


const makeBody = (medication) => {
console.log('patient -- > ', globalState.patient);
return {
resourceType: "Parameters",
parameter: [
{
name: 'patient',
resource: globalState.patient
},
{
name: 'medication',
resource: medication
}
]
}
}

return (
<>
{remsAdminResponse.case_number ? <EtasuStatusComponent remsAdminResponseInit={remsAdminResponse} /> : ""}
{remsAdminResponse.contained ? <EtasuStatusComponent remsAdminResponseInit={remsAdminResponse} data={etasuData} display={display} /> : ""}
</>
);
};
Expand Down
12 changes: 11 additions & 1 deletion src/components/EtasuStatus/EtasuStatusButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const EtasuStatusButton = props => {
<Typography className="etasuButtonText" component="p">
ETASU:
</Typography>
<Typography component="p">{remsAdminResponse?.status || 'Not Started'}</Typography>
<Typography component="p">{convertStatus(remsAdminResponse?.status)}</Typography>
</Button>
{renderTimestamp(lastCheckedEtasuTime)}
</Grid>
Expand Down Expand Up @@ -62,3 +62,13 @@ const convertTimeDifference = start => {
}
return `Last checked ${prefix} ago`;
};

export const convertStatus = status => {
if (status === 'success') {
return 'Approved';
} else if (status === 'data-required') {
return 'Pending';
} else {
return 'Not Started';
}
}
15 changes: 7 additions & 8 deletions src/components/EtasuStatus/EtasuStatusComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { EtasuStatusModal } from './EtasuStatusModal.jsx';
import { useState, useEffect, useContext } from 'react';
import { Card, Typography } from '@mui/material';
import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider.jsx';
import axios from 'axios';
import { getEtasu } from '../../util/util.js';
import { standardsBasedGetEtasu } from '../../util/util.js';

export const EtasuStatusComponent = props => {
const [globalState, _] = useContext(SettingsContext);

const { remsAdminResponseInit } =
const { remsAdminResponseInit, data, display } =
props;

const [remsAdminResponse, setRemsAdminResponse] = useState(remsAdminResponseInit);
Expand All @@ -30,15 +29,15 @@ export const EtasuStatusComponent = props => {

const refreshEtasu = () => {
if(remsAdminResponse) {
const etasuUrl = `${globalState.remsAdminServer}/etasu/met/patient/${remsAdminResponse.patientFirstName}/${remsAdminResponse.patientLastName}/${remsAdminResponse.patientDOB}/drugCode/${remsAdminResponse.drugCode}`;
getEtasu(etasuUrl, setRemsAdminResponse);
const standardEtasuUrl = `${globalState.remsAdminServer}/4_0_0/GuidanceResponse/$rems-etasu`;
standardsBasedGetEtasu(standardEtasuUrl, data, setRemsAdminResponse);
setLastCheckedEtasuTime(Date.now());
}
}
return (
<Card variant="outlined" sx={{ padding: 2 }}>
<Typography variant="h6" align="center" mb={2}>
{remsAdminResponse?.drugName}
{display}
</Typography>
<EtasuStatusButton
baseColor={getStatusColor(remsAdminResponse?.status)}
Expand All @@ -58,9 +57,9 @@ export const EtasuStatusComponent = props => {

export const getStatusColor = status => {
switch (status) {
case 'Approved':
case 'success':
return 'green';
case 'Pending':
case 'data-required':
return '#f0ad4e';
default:
return '#0c0c0c';
Expand Down
22 changes: 9 additions & 13 deletions src/components/EtasuStatus/EtasuStatusModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { getStatusColor } from './EtasuStatusComponent';
import './EtasuStatusModal.css';
import CheckCircle from '@mui/icons-material/CheckCircle';
import Close from '@mui/icons-material/Close';

const getIdText = remsAdminResponse => remsAdminResponse?.case_number || 'N/A';
import { convertStatus } from './EtasuStatusButton';

export const EtasuStatusModal = props => {
const { callback, onClose, remsAdminResponse, update } = props;
Expand All @@ -28,10 +27,7 @@ export const EtasuStatusModal = props => {
<div className="status-icon" style={{ backgroundColor: color }}></div>
<Grid container columns={12}>
<Grid item xs={10}>
<div className="bundle-entry">
Case Number: {getIdText(remsAdminResponse)}
</div>
<div className="bundle-entry">Status: {remsAdminResponse.status || 'N/A'}</div>
<div className="bundle-entry">Status: {convertStatus(remsAdminResponse.status)}</div>
</Grid>
<Grid item xs={2}>
<div className="bundle-entry">
Expand All @@ -51,26 +47,26 @@ export const EtasuStatusModal = props => {
<br></br>
<h3>ETASU</h3>
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
{remsAdminResponse ? (
{remsAdminResponse && remsAdminResponse.contained ? (
<List>
{remsAdminResponse?.metRequirements?.map((metRequirements) => (
{remsAdminResponse?.contained[0]?.parameter.map((metRequirements) => (
<ListItem
disablePadding
key={metRequirements.metRequirementId}
key={metRequirements.name}
data-testid="etasu-item"
>
<ListItemIcon>
{metRequirements.completed ? (
{metRequirements.resource.status === 'success' ? (
<CheckCircle color="success" />
) : (
<Close color="warning" />
)}
</ListItemIcon>
{metRequirements.completed ? (
<ListItemText primary={metRequirements.requirementName} />
{metRequirements.resource.status === 'success' ? (
<ListItemText primary={metRequirements.name} />
) : (
<ListItemText
primary={metRequirements.requirementName}
primary={metRequirements.name}
secondary={metRequirements.requirementDescription}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions src/containers/RequestBuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ const RequestBuilder = props => {
<Grid item>
<EtasuStatus
code={state.code}
request={state.request}
/>
</Grid>
)}
Expand Down
13 changes: 12 additions & 1 deletion src/util/fhir.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,21 @@ function createMedicationDispenseFromMedicationRequest(medicationRequest) {
return medicationDispense;
}

function createMedicationFromMedicationRequest(medicationRequest) {
let medication = {};
medication.resourceType = 'Medication';
medication.id = medicationRequest?.id + '-med';
if (medicationRequest.medicationCodeableConcept) {
medication.code = medicationRequest.medicationCodeableConcept;
}
return medication;
}

export {
fhir,
getAge,
getDrugCodeableConceptFromMedicationRequest,
getDrugCodeFromMedicationRequest,
createMedicationDispenseFromMedicationRequest
createMedicationDispenseFromMedicationRequest,
createMedicationFromMedicationRequest
};
29 changes: 14 additions & 15 deletions src/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,30 +66,29 @@ function retrieveLaunchContext(link, patientId, clientState) {
});
}

function getEtasu(etasuUrl, responseCallback) {
function standardsBasedGetEtasu(etasuUrl, body, responseCallback) {
axios({
method: 'get',
url: etasuUrl
}).then(
response => {
method: 'post',
url: etasuUrl,
data: body
}).then(response => {
// Sorting an array mutates the data in place.
const remsMetRes = response.data;
if (remsMetRes.metRequirements) {
remsMetRes.metRequirements.sort((first, second) => {
if (remsMetRes?.parameter[0]?.resource?.contained) {
remsMetRes.parameter[0].resource.contained[0].parameter.sort((first, second) => {
// Keep the other forms unsorted.
if (second.requirementName.includes('Patient Status Update')) {
if (second.name.includes('Patient Status Update')) {
// Sort the Patient Status Update forms in descending order of timestamp.
return second.requirementName.localeCompare(first.requirementName);
return second.name.localeCompare(first.name);
}
return 0;
});
}
responseCallback(response.data);
},
error => {
console.log(error);
responseCallback(response.data.parameter[0].resource, body);
}, error => {
console.log('error -- > ', error);
}
);
)
}

const getMedicationSpecificRemsAdminUrl = (request, globalState, hook) => {
Expand Down Expand Up @@ -118,4 +117,4 @@ const getMedicationSpecificRemsAdminUrl = (request, globalState, hook) => {
return cdsUrl;
};

export { retrieveLaunchContext, getEtasu, getMedicationSpecificRemsAdminUrl };
export { retrieveLaunchContext, standardsBasedGetEtasu, getMedicationSpecificRemsAdminUrl };