From 23dbc3cb358223f7ab30c7e3e7fedc0a5f9827d8 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Tue, 26 Mar 2024 08:59:35 -0400 Subject: [PATCH 1/2] Add in etasu status to request generator --- .env | 1 + src/components/EtasuStatus/EtasuStatus.jsx | 51 +++++++++ .../EtasuStatus/EtasuStatusButton.css | 17 +++ .../EtasuStatus/EtasuStatusButton.jsx | 64 +++++++++++ .../EtasuStatus/EtasuStatusModal.css | 33 ++++++ .../EtasuStatus/EtasuStatusModal.jsx | 101 ++++++++++++++++++ src/containers/RequestBuilder.js | 92 +++++++++++++--- src/util/data.js | 5 + 8 files changed, 352 insertions(+), 12 deletions(-) create mode 100644 src/components/EtasuStatus/EtasuStatus.jsx create mode 100644 src/components/EtasuStatus/EtasuStatusButton.css create mode 100644 src/components/EtasuStatus/EtasuStatusButton.jsx create mode 100644 src/components/EtasuStatus/EtasuStatusModal.css create mode 100644 src/components/EtasuStatus/EtasuStatusModal.jsx diff --git a/.env b/.env index a2d1e8c8..ae7f1797 100644 --- a/.env +++ b/.env @@ -29,3 +29,4 @@ REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ REACT_APP_URL = http://localhost:3000 REACT_APP_URL_FILTER = http://localhost:3000/* REACT_APP_USER = alice +REACT_APP_REMS_ADMIN_SERVER_BASE = http://localhost:8090 diff --git a/src/components/EtasuStatus/EtasuStatus.jsx b/src/components/EtasuStatus/EtasuStatus.jsx new file mode 100644 index 00000000..bc57861b --- /dev/null +++ b/src/components/EtasuStatus/EtasuStatus.jsx @@ -0,0 +1,51 @@ +import { EtasuStatusButton } from './EtasuStatusButton.jsx'; +import { EtasuStatusModal } from './EtasuStatusModal.jsx'; +import { useState, useEffect } from 'react'; +import { Card, Typography } from '@mui/material'; + +export const EtasuStatus = props => { + const { etasuUrl, request, remsAdminResponse, getEtasuStatus, lastCheckedEtasuTime } = + props; + const [showEtasuStatus, setShowEtasuStatus] = useState(false); + + useEffect(() => getEtasuStatus(), [request.id, etasuUrl]); + + const handleCloseEtasuStatus = () => { + setShowEtasuStatus(false); + }; + + const handleOpenEtasuStatus = () => { + setShowEtasuStatus(true); + }; + + return ( + + + {remsAdminResponse?.drugName} + + + + + ); +}; + +export const getStatusColor = status => { + switch (status) { + case 'Approved': + return 'green'; + case 'Pending': + return '#f0ad4e'; + default: + return '#0c0c0c'; + } +}; diff --git a/src/components/EtasuStatus/EtasuStatusButton.css b/src/components/EtasuStatus/EtasuStatusButton.css new file mode 100644 index 00000000..006c066d --- /dev/null +++ b/src/components/EtasuStatus/EtasuStatusButton.css @@ -0,0 +1,17 @@ +.etasuButtonText { + margin-bottom: 0px; + font-weight: bold; + font-size: 14px; + } + + .etasuButtonTimestamp { + margin: 0 auto; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 0.8rem; + } + + .timestampString { + font-size: 0.7rem; + opacity: 0.8; + } + \ No newline at end of file diff --git a/src/components/EtasuStatus/EtasuStatusButton.jsx b/src/components/EtasuStatus/EtasuStatusButton.jsx new file mode 100644 index 00000000..06aa1895 --- /dev/null +++ b/src/components/EtasuStatus/EtasuStatusButton.jsx @@ -0,0 +1,64 @@ +import { Button, Grid, Typography } from '@mui/material'; +import ListIcon from '@mui/icons-material/List'; +import './EtasuStatusButton.css'; + +export const EtasuStatusButton = props => { + const { baseColor, remsAdminResponse, handleOpenEtasuStatus, lastCheckedEtasuTime } = + props; + return ( + + + {renderTimestamp(lastCheckedEtasuTime)} + + ); +}; + +const buttonSx = baseColor => ({ + backgroundColor: baseColor, + ':hover': { filter: 'brightness(110%)', backgroundColor: baseColor }, + flexDirection: 'column' +}); + +const renderTimestamp = checkedTime => { + return checkedTime ? ( + <> + + {convertTimeDifference(checkedTime)} + + + {new Date(checkedTime).toLocaleString()} + + + ) : ( + No medication selected + ); +}; + +const convertTimeDifference = start => { + const end = Date.now(); + const difference = end - start; + const diffMin = difference / 60000; + let prefix = 'a long time'; + if (diffMin < 1) { + prefix = 'a few seconds'; + } else if (diffMin > 1 && diffMin < 2) { + prefix = 'a minute'; + } else if (diffMin > 2 && diffMin < 60) { + prefix = `${Math.round(diffMin)} minutes`; + } else if (diffMin > 60 && diffMin < 120) { + prefix = 'an hour'; + } else if (diffMin > 120 && diffMin < 1440) { + prefix = `${Math.round(diffMin / 60)} hours`; + } else if (diffMin > 1440 && diffMin < 2880) { + prefix = 'a day'; + } else if (diffMin > 2880) { + prefix = `${Math.round(diffMin / 1440)} days`; + } + return `Last checked ${prefix} ago`; +}; diff --git a/src/components/EtasuStatus/EtasuStatusModal.css b/src/components/EtasuStatus/EtasuStatusModal.css new file mode 100644 index 00000000..a5a47e27 --- /dev/null +++ b/src/components/EtasuStatus/EtasuStatusModal.css @@ -0,0 +1,33 @@ +.renew-icon{ + cursor: pointer; + margin-left: 15px; + margin-right: 15px; + } + +.refresh{ + cursor: pointer; + margin-left: 15px; + margin-right: 15px; + animation-name: spin; + animation-duration: 500ms; + animation-iteration-count: 2; + animation-timing-function: ease-in-out; + } + +.status-icon{ + width: 100%; + height:5px; + } + +.bundle-entry{ + margin: 5px; + } + +@keyframes spin { + from { + transform:rotate(0deg); + } + to { + transform:rotate(360deg); + } + } \ No newline at end of file diff --git a/src/components/EtasuStatus/EtasuStatusModal.jsx b/src/components/EtasuStatus/EtasuStatusModal.jsx new file mode 100644 index 00000000..8b235056 --- /dev/null +++ b/src/components/EtasuStatus/EtasuStatusModal.jsx @@ -0,0 +1,101 @@ +import { Box, Grid, IconButton, Modal, Tooltip, List, ListItem, ListItemIcon, ListItemText } from '@mui/material'; +import AutorenewIcon from '@mui/icons-material/Autorenew'; +import { useState, useEffect } from 'react'; +import { getStatusColor } from './EtasuStatus'; +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'; + +export const EtasuStatusModal = props => { + const { callback, onClose, remsAdminResponse, update } = props; + const [spin, setSpin] = useState(false); + const color = getStatusColor(remsAdminResponse?.status); + + useEffect(() => { + if (update) { + setSpin(true); + callback(); + } + }, [update]); + + return ( + + +
+

REMS Status

+
+ + +
+ Case Number: {getIdText(remsAdminResponse)} +
+
Status: {remsAdminResponse.status || 'N/A'}
+
+ +
+ + + setSpin(false)} + /> + + +
+
+
+
+

+

ETASU

+ + {remsAdminResponse ? ( + + {remsAdminResponse?.metRequirements?.map((metRequirements) => ( + + + {metRequirements.completed ? ( + + ) : ( + + )} + + {metRequirements.completed ? ( + + ) : ( + + )} + + ))} + + ) : ( + 'Not Available' + )} + +
+
+
+
+ ); +}; + +const modalStyle = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: 'background.paper', + border: '1px solid #000', + boxShadow: 24, + p: 4 +}; diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index ce568aa7..2b88f54e 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -16,7 +16,9 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import PatientSearchBar from '../components/RequestBox/PatientSearchBar/PatientSearchBar'; import { MedicationStatus } from '../components/MedicationStatus/MedicationStatus.jsx'; import { actionTypes } from './ContextProvider/reducer.js'; + import axios from 'axios'; +import { EtasuStatus } from '../components/EtasuStatus/EtasuStatus'; const RequestBuilder = (props) => { const { globalState, dispatch, client } = props; @@ -35,7 +37,9 @@ const RequestBuilder = (props) => { token: null, client: client, medicationDispense: null, - lastCheckedMedicationTime: null + lastCheckedMedicationTime: null, + remsAdminResponse: {}, + lastCheckedEtasuTime: null }); const displayRequestBox = !!globalState.patient?.id; @@ -43,7 +47,9 @@ const RequestBuilder = (props) => { return Object.keys(state.request).length === 0; }; + const disableGetMedicationStatus = isOrderNotSelected() || state.loading; + const disableGetEtasu = isOrderNotSelected() || state.loading; const getMedicationStatus = () => { setState(prevState => ({ ...prevState, @@ -68,6 +74,55 @@ const RequestBuilder = (props) => { ); }; + const getEtasu = () => { + setState(prevState => ({ + ...prevState, + lastCheckedEtasuTime: Date.now() + })); + + 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 + + ' - ' + + state.code + ); + const etasuUrl = `${globalState.remsAdminBaseUrl}/etasu/met/patient/${patientFirstName}/${patientLastName}/${patientDOB}/drugCode/${state.code}`; + axios({ + method: 'get', + url: etasuUrl + }).then( + response => { + // Sorting an array mutates the data in place. + const remsMetRes = response.data; + if (remsMetRes.metRequirements) { + remsMetRes.metRequirements.sort((first, second) => { + // Keep the other forms unsorted. + if (second.requirementName.includes('Patient Status Update')) { + // Sort the Patient Status Update forms in descending order of timestamp. + return second.requirementName.localeCompare(first.requirementName); + } + return 0; + }); + } + setState(prevState => ({ + ...prevState, + remsAdminResponse: response.data + })); + }, + error => { + console.log(error); + } + ); + }; + useEffect(() => { if (state.client) { // Call patients on load of page @@ -296,17 +351,30 @@ const RequestBuilder = (props) => { /> )} - {!disableGetMedicationStatus && ( - - - - )} + + {!disableGetEtasu && ( + + + + )} + {!disableGetMedicationStatus && ( + + + + )} + diff --git a/src/util/data.js b/src/util/data.js index b1f0b2e2..e15defc6 100644 --- a/src/util/data.js +++ b/src/util/data.js @@ -85,6 +85,11 @@ const headerDefinitions = { display: 'SMART App', type: 'input', default: env.get('REACT_APP_SMART_LAUNCH_URL').asString() + }, + remsAdminBaseUrl: { + display: 'REMS Admin Base', + type: 'input', + default: env.get('REACT_APP_REMS_ADMIN_SERVER_BASE').asString() } }; From dd3ceb00e0c2d5cffdf863834a93d0260fc91e7a Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Tue, 26 Mar 2024 09:24:54 -0400 Subject: [PATCH 2/2] Clean up duplicate env variables --- .env | 1 - src/containers/RequestBuilder.js | 2 +- src/util/data.js | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.env b/.env index ae7f1797..a2d1e8c8 100644 --- a/.env +++ b/.env @@ -29,4 +29,3 @@ REACT_APP_SMART_LAUNCH_URL = http://localhost:4040/ REACT_APP_URL = http://localhost:3000 REACT_APP_URL_FILTER = http://localhost:3000/* REACT_APP_USER = alice -REACT_APP_REMS_ADMIN_SERVER_BASE = http://localhost:8090 diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 2b88f54e..c371526a 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -94,7 +94,7 @@ const RequestBuilder = (props) => { ' - ' + state.code ); - const etasuUrl = `${globalState.remsAdminBaseUrl}/etasu/met/patient/${patientFirstName}/${patientLastName}/${patientDOB}/drugCode/${state.code}`; + const etasuUrl = `${globalState.remsAdminServer}/etasu/met/patient/${patientFirstName}/${patientLastName}/${patientDOB}/drugCode/${state.code}`; axios({ method: 'get', url: etasuUrl diff --git a/src/util/data.js b/src/util/data.js index e15defc6..f4f3bee4 100644 --- a/src/util/data.js +++ b/src/util/data.js @@ -86,10 +86,10 @@ const headerDefinitions = { type: 'input', default: env.get('REACT_APP_SMART_LAUNCH_URL').asString() }, - remsAdminBaseUrl: { - display: 'REMS Admin Base', + remsAdminServer: { + display: 'REMS Admin Server', type: 'input', - default: env.get('REACT_APP_REMS_ADMIN_SERVER_BASE').asString() + default: env.get('REACT_APP_SERVER').asString() } };