diff --git a/src/components/DisplayBox/DisplayBox.js b/src/components/DisplayBox/DisplayBox.js index 7a510481..9e1554cc 100644 --- a/src/components/DisplayBox/DisplayBox.js +++ b/src/components/DisplayBox/DisplayBox.js @@ -1,81 +1,24 @@ -import React, { Component } from 'react'; -import FHIR from 'fhirclient'; +import React, { useEffect, useState } from 'react'; import './card-list.css'; import { Button, Card, CardActions, CardContent, Typography } from '@mui/material'; -import PropTypes from 'prop-types'; import axios from 'axios'; import ReactMarkdown from 'react-markdown'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import { retrieveLaunchContext } from '../../util/util'; import './displayBox.css'; -const propTypes = { - /** - * A boolean to determine if the context of this component is under the Demo Card feature of the Sandbox, or in the actual - * hook views that render cards themselves. This flag is necessary to make links and suggestions unactionable in the Card Demo view. - */ - isDemoCard: PropTypes.bool, - /** - * The FHIR access token retrieved from the authorization server. Used to retrieve a launch context for a SMART app - */ - fhirAccessToken: PropTypes.object, - /** - * Function callback to take a specific suggestion from a card - */ - takeSuggestion: PropTypes.func.isRequired, - /** - * Identifier of the Patient resource for the patient in context - */ - patientId: PropTypes.string, - /** - * The FHIR server URL in context - */ - fhirServerUrl: PropTypes.string, - /** - * The FHIR version in use - */ - fhirVersion: PropTypes.string, - /** - * JSON response from a CDS service containing potential cards to display - */ - cardResponses: PropTypes.object -}; +const DisplayBox = (props) => { -export default class DisplayBox extends Component { - constructor(props) { - super(props); - this.launchLink = this.launchLink.bind(this); - this.launchSource = this.launchSource.bind(this); - this.renderSource = this.renderSource.bind(this); - this.modifySmartLaunchUrls = this.modifySmartLaunchUrls.bind(this); - this.exitSmart = this.exitSmart.bind(this); - this.state = { - value: '', - smartLink: '', - response: {} - }; - } - - static getDerivedStateFromProps(nextProps, prevState) { - if (JSON.stringify(nextProps.response) !== JSON.stringify(prevState.response)) { - return { response: nextProps.response }; - } else { - return null; - } - } + const [state, setState] = useState({ smartLink: '', response: {}}); + const { isDemoCard, fhirAccessToken, ehrLaunch, patientId, client, response } = props; - shouldComponentUpdate(nextProps, prevState) { - if ( - JSON.stringify(nextProps.response) !== JSON.stringify(this.state.response) || - this.state.smartLink !== prevState.smartLink - ) { - return true; - } else { - return false; + useEffect(() => { + if (response !== state.response) { + setState(prevState => ({...prevState, response: response})); } - } + }, [response]); - supportedRequestType(resource) { + const supportedRequestType = (resource) => { let resourceType = resource.resourceType.toUpperCase(); if ( resourceType === 'DEVICEREQUEST' || @@ -85,15 +28,15 @@ export default class DisplayBox extends Component { ) { return true; } - } + }; /** * Take a suggestion from a CDS service based on action on from a card. Also pings the analytics endpoint (if any) of the * CDS service to notify that a suggestion was taken * @param {*} suggestion - CDS service-defined suggestion to take based on CDS Hooks specification * @param {*} url - CDS service endpoint URL */ - takeSuggestion(suggestion, url, buttonId, suggestionCount, cardNum, selectionBehavior) { - if (!this.props.isDemoCard) { + const takeSuggestion = (suggestion, url, buttonId, suggestionCount, cardNum, selectionBehavior) => { + if (!isDemoCard) { if (selectionBehavior === 'at-most-one') { // disable all suggestion buttons for this card for (var i = 0; i < suggestionCount; i++) { @@ -122,26 +65,26 @@ export default class DisplayBox extends Component { if (action.type.toUpperCase() === 'DELETE') { uri = action.resource.resourceType + '/' + action.resource.id; console.log('completing suggested action DELETE: ' + uri); - this.props.client.delete(uri).then(result => { + client.delete(uri).then(result => { console.log('suggested action DELETE result:'); console.log(result); }); } else if (action.type.toUpperCase() === 'CREATE') { uri = action.resource.resourceType; console.log('completing suggested action CREATE: ' + uri); - this.props.client.create(action.resource).then(result => { + client.create(action.resource).then(result => { console.log('suggested action CREATE result:'); console.log(result); - if (this.supportedRequestType(result)) { + if (supportedRequestType(result)) { // call into the request builder to resubmit the CRD request with the suggested request - this.props.takeSuggestion(result); + takeSuggestion(result); } }); } else if (action.type.toUpperCase() === 'UPDATE') { uri = action.resource.resourceType + '/' + action.resource.id; console.log('completing suggested action UPDATE: ' + uri); - this.props.client.update(action.resource).then(result => { + client.update(action.resource).then(result => { console.log('suggested action UPDATE result:'); console.log(result); }); @@ -153,28 +96,24 @@ export default class DisplayBox extends Component { console.error('There was no label on this suggestion', suggestion); } } - } + }; /** * Prevent the source link from opening in the same tab * @param {*} e - Event emitted when source link is clicked */ - launchSource(e, link) { + const launchSource = (e, link) => { e.preventDefault(); window.open(link.url, '_blank'); - } - - exitSmart(e) { - this.setState({ smartLink: '' }); - } + }; /** * Open the absolute or SMART link in a new tab and display an error if a SMART link does not have * appropriate launch context if used against a secured FHIR endpoint. * @param {*} e - Event emitted when link is clicked * @param {*} link - Link object that contains the URL and any error state to catch */ - launchLink(e, link) { - if (!this.props.isDemoCard) { + const launchLink = (e, link) => { + if (!isDemoCard) { e.preventDefault(); if (link.error) { // TODO: Create an error modal to display for SMART link that cannot be launched securely @@ -182,7 +121,7 @@ export default class DisplayBox extends Component { } window.open(link.url, '_blank'); } - } + }; /** * For SMART links, modify the link URLs as this component processes them according to two scenarios: @@ -190,17 +129,17 @@ export default class DisplayBox extends Component { * 2 - Open: Append a fhirServiceUrl and patientId parameter to the link for use against open endpoints * @param {*} card - Card object to process the links for */ - modifySmartLaunchUrls(card) { - if (!this.props.isDemoCard) { + const modifySmartLaunchUrls = (card) => { + if (!isDemoCard) { return card.links.map(link => { let linkCopy = Object.assign({}, link); if ( link.type === 'smart' && - (this.props.fhirAccessToken || this.props.ehrLaunch) && - !this.state.smartLink + (fhirAccessToken || ehrLaunch) && + !state.smartLink ) { - retrieveLaunchContext(linkCopy, this.props.patientId, this.props.client.state).then( + retrieveLaunchContext(linkCopy, patientId, client.state).then( result => { linkCopy = result; return linkCopy; @@ -219,13 +158,13 @@ export default class DisplayBox extends Component { }); } return undefined; - } + }; /** * Helper function to build out the UI for the source of the Card * @param {*} source - Object as part of the card to build the UI for */ - renderSource(source) { + const renderSource = (source) => { if (!source.label) { return null; } @@ -241,14 +180,14 @@ export default class DisplayBox extends Component { /> ); } - if (!this.props.isDemoCard) { + if (!isDemoCard) { return (
Source:{' '} this.launchSource(e, source)} + onClick={e => launchSource(e, source)} > {source.label} @@ -259,16 +198,16 @@ export default class DisplayBox extends Component { return (
Source: - this.launchSource(e, source)}> + launchSource(e, source)}> {source.label} {icon}
); - } + }; - render() { - this.buttonList = []; + const renderCards = () => { + let buttonList = []; const indicators = { info: 0, warning: 1, @@ -276,16 +215,11 @@ export default class DisplayBox extends Component { error: 3 }; - const summaryColors = { - info: '#0079be', - warning: '#ffae42', - 'hard-stop': '#c00', - error: '#333' - }; const renderedCards = []; + // Iterate over each card in the cards array - if (this.state.response != null && this.state.response.cards != null) { - this.state.response.cards + if (state.response != null && state.response.cards != null) { + state.response.cards .sort((b, a) => indicators[a.indicator] - indicators[b.indicator]) .forEach((c, cardInd) => { const card = JSON.parse(JSON.stringify(c)); @@ -295,7 +229,7 @@ export default class DisplayBox extends Component { // -- Source -- const sourceSection = - card.source && Object.keys(card.source).length ? this.renderSource(card.source) : ''; + card.source && Object.keys(card.source).length ? renderSource(card.source) : ''; // -- Detail (ReactMarkdown supports Github-flavored markdown) -- const detailSection = card.detail ? ( @@ -311,13 +245,13 @@ export default class DisplayBox extends Component { if (card.suggestions) { card.suggestions.forEach((item, ind) => { var buttonId = 'suggestion_button-' + cardInd + '-' + ind; - this.buttonList.push(buttonId); + buttonList.push(buttonId); suggestionsSection.push( ); } const pdfIcon = ; return ( - ); @@ -386,17 +320,20 @@ export default class DisplayBox extends Component { renderedCards.push(builtCard); }); - - if (renderedCards.length === 0) { - return
Notification Cards ({renderedCards.length})
; - } return (
+ { renderedCards.length === 0 ?
Notification Cards ({renderedCards.length})
: <>}
{renderedCards}
); - } else { - return
; } - } -} + }; + + return ( +
+ {renderCards()} +
+ ); +}; + +export default DisplayBox; diff --git a/src/components/Inputs/CheckBox.js b/src/components/Inputs/CheckBox.js deleted file mode 100644 index 16858b0c..00000000 --- a/src/components/Inputs/CheckBox.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { Component } from 'react'; - -export default class CheckBox extends Component { - constructor(props) { - super(props); - this.onInputChange = this.onInputChange.bind(this); - } - - onInputChange(_event) { - this.props.updateCB(this.props.elementName, !this.props.toggle); - } - - render() { - const toggleClass = this.props.toggle ? 'checkBoxClicked' : 'checkBox'; - const indicatorClass = this.props.toggle ? 'onOffActive' : 'onOff'; - return ( - - - - ); - } -} diff --git a/src/components/Inputs/InputBox.js b/src/components/Inputs/InputBox.js deleted file mode 100644 index 9f5bbe90..00000000 --- a/src/components/Inputs/InputBox.js +++ /dev/null @@ -1,25 +0,0 @@ -import React, { Component } from 'react'; - -export default class InputBox extends Component { - constructor(props) { - super(props); - this.onInputChange = this.onInputChange.bind(this); - } - - onInputChange(event) { - this.props.updateCB(this.props.elementName, event.target.value); - } - - render() { - return ( -
- -
- ); - } -} diff --git a/src/components/Inputs/Toggle.js b/src/components/Inputs/Toggle.js deleted file mode 100644 index 49936f43..00000000 --- a/src/components/Inputs/Toggle.js +++ /dev/null @@ -1,62 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -export default class Toggle extends Component { - constructor(props) { - super(props); - this.state = { - value: '', - option1class: 'genderBlockMaleUnselected', - option2class: 'genderBlockFemaleUnselected' - }; - - this.clickedOption1 = this.clickedOption1.bind(this); - this.clickedOption2 = this.clickedOption2.bind(this); - } - clickedOption1() { - if (this.props.value !== this.props.options.option1.value) { - this.props.updateCB(this.props.elementName, this.props.options.option1.value); - } else { - this.props.updateCB(this.props.elementName, ''); - } - } - clickedOption2() { - if (this.props.value !== this.props.options.option2.value) { - this.props.updateCB(this.props.elementName, this.props.options.option2.value); - } else { - this.props.updateCB(this.props.elementName, ''); - } - } - render() { - let option1class, - option2class = ''; - if (this.props.value == this.props.options.option1.value) { - option1class = 'genderBlockMaleSelected'; - option2class = 'genderBlockFemaleUnselected'; - } else if (this.props.value == this.props.options.option2.value) { - option1class = 'genderBlockMaleUnselected'; - option2class = 'genderBlockFemaleSelected'; - } else { - option1class = 'genderBlockMaleUnselected'; - option2class = 'genderBlockFemaleUnselected'; - } - return ( -
-
- - -
-
- ); - } -} diff --git a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js index aba49800..a330ca52 100644 --- a/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js +++ b/src/components/RequestBox/InProgressFormBox/InProgressFormBox.js @@ -2,7 +2,7 @@ import { Box, Button, Typography, ButtonGroup } from '@mui/material'; import React from 'react'; import './InProgressFormBoxStyle.css'; -export default function InProgressFormBox(props) { +const InProgressFormBox = (props) => { return props.qrResponse?.questionnaire ? ( @@ -34,4 +34,6 @@ export default function InProgressFormBox(props) { ) : ( '' ); -} +}; + +export default InProgressFormBox; diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js index 34aa1e43..40968dc3 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js @@ -7,7 +7,7 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import PatientBox from '../../SMARTBox/PatientBox'; import './PatientSearchBarStyle.css'; -export default function PatientSearchBar(props) { +const PatientSearchBar = (props) => { const [options] = useState(defaultValues); const [input, setInput] = useState(''); const [listOfPatients, setListOfPatients] = useState([]); @@ -87,7 +87,6 @@ export default function PatientSearchBar(props) { request={props.request} launchUrl={props.launchUrl} callback={props.callback} - callbackList={props.callbackList} callbackMap={props.callbackMap} updatePrefetchCallback={PrefetchTemplate.generateQueries} clearCallback={props.clearCallback} @@ -103,4 +102,6 @@ export default function PatientSearchBar(props) { } return {listOfPatients[0] ? patientSearchBar() : 'loading...'}; -} +}; + +export default PatientSearchBar; \ No newline at end of file diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 36ecab22..3744752b 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -1,6 +1,6 @@ import { Button, ButtonGroup, Grid } from '@mui/material'; import _ from 'lodash'; -import React, { Component } from 'react'; +import React, { useEffect, useState } from 'react'; import buildNewRxRequest from '../../util/buildScript.2017071.js'; import MuiAlert from '@mui/material/Alert'; import Snackbar from '@mui/material/Snackbar'; @@ -9,94 +9,74 @@ import { getAge, createMedicationDispenseFromMedicationRequest } from '../../uti import { retrieveLaunchContext } from '../../util/util'; import './request.css'; -export default class RequestBox extends Component { - constructor(props) { - super(props); - this.state = { +const RequestBox = (props) => { + + const [state, setState] = useState( + { gatherCount: 0, response: {}, submittedRx: false - }; + } + ); + + const { callback, prefetchedResources, submitInfo, patient, request, loading, + code, codeSystem, display, defaultUser, smartAppUrl, client, pimsUrl } = props; + const emptyField = (empty); - this.renderRequestResources = this.renderRequestResources.bind(this); - this.renderPatientInfo = this.renderPatientInfo.bind(this); - this.renderOtherInfo = this.renderOtherInfo.bind(this); - this.renderResource = this.renderResource.bind(this); - this.renderPrefetchedResources = this.renderPrefetchedResources.bind(this); - this.renderError = this.renderError.bind(this); - this.buildLaunchLink = this.buildLaunchLink.bind(this); - } - - // TODO - see how to submit response for alternative therapy - replaceRequestAndSubmit(request) { - this.props.callback(request, request); // Submit the cds hook request. - this.submitOrderSign(request); - } - - prepPrefetch() { + + const prepPrefetch = () => { const preppedResources = new Map(); - Object.keys(this.props.prefetchedResources).forEach(resourceKey => { + Object.keys(prefetchedResources).forEach(resourceKey => { let resourceList = []; - if (Array.isArray(this.props.prefetchedResources[resourceKey])) { - resourceList = this.props.prefetchedResources[resourceKey].map(resource => { + if (Array.isArray(prefetchedResources[resourceKey])) { + resourceList = prefetchedResources[resourceKey].map(resource => { return resource; }); } else { - resourceList = this.props.prefetchedResources[resourceKey]; + resourceList = prefetchedResources[resourceKey]; } preppedResources.set(resourceKey, resourceList); }); return preppedResources; - } + }; - submitPatientView = () => { - this.props.submitInfo(this.prepPrefetch(), null, this.props.patient, 'patient-view'); + const submitPatientView = () => { + submitInfo(prepPrefetch(), null, patient, 'patient-view'); }; - submitOrderSelect = () => { - if (!_.isEmpty(this.props.request)) { - this.props.submitInfo( - this.prepPrefetch(), - this.props.request, - this.props.patient, + const submitOrderSelect = () => { + if (!_.isEmpty(request)) { + submitInfo( + prepPrefetch(), + request, + patient, 'order-select' ); } }; - submitOrderSign = request => { - this.props.submitInfo(this.prepPrefetch(), request, this.props.patient, 'order-sign'); + const submitOrderSign = request => { + submitInfo(prepPrefetch(), request, patient, 'order-sign'); }; - submit = () => { - if (!_.isEmpty(this.props.request)) { - this.submitOrderSign(this.props.request); + const submit = () => { + if (!_.isEmpty(request)) { + submitOrderSign(request); } }; - componentDidUpdate(prevProps, prevState) { + useEffect(() => { // if prefetch completed - if ( - prevState.prefetchCompleted != this.state.prefetchCompleted && - this.state.prefetchCompleted - ) { + if (state.prefetchCompleted) { // if the prefetch contains a medicationRequests bundle - if (this.props.prefetchedResources.medicationRequests) { - this.submitPatientView(); + if (prefetchedResources.medicationRequests) { + submitPatientView(); } - // we could use this in the future to send order-select - //// if the prefetch contains a request - //if (this.props.prefetchedResources.request) { - // this.submitOrderSelect(); - //} } - } - - emptyField = (empty); + }, [state.prefetchCompleted]); - renderPatientInfo() { - const patient = this.props.patient; + const renderPatientInfo = () => { if (Object.keys(patient).length === 0) { return
; } @@ -104,7 +84,7 @@ export default class RequestBox extends Component { if (patient.name) { name = {`${patient.name[0].given[0]} ${patient.name[0].family}`} ; } else { - name = this.emptyField; + name = emptyField; } return (
@@ -113,58 +93,58 @@ export default class RequestBox extends Component {
Name: {name}
- Age: {patient.birthDate ? getAge(patient.birthDate) : this.emptyField} + Age: {patient.birthDate ? getAge(patient.birthDate) : emptyField}
- Gender: {patient.gender ? patient.gender : this.emptyField} + Gender: {patient.gender ? patient.gender : emptyField}
- State: {this.state.patientState ? this.state.patientState : this.emptyField} + State: {state.patientState ? state.patientState : emptyField}
- {this.renderOtherInfo()} + {renderOtherInfo()}
); - } + }; - renderOtherInfo() { + const renderOtherInfo = () => { return (
Coding
- Code: {this.props.code ? this.props.code : this.emptyField} + Code: {code ? code : emptyField}
- System: {this.props.codeSystem ? shortNameMap[this.props.codeSystem] : this.emptyField} + System: {codeSystem ? shortNameMap[codeSystem] : emptyField}
- Display: {this.props.display ? this.props.display : this.emptyField} + Display: {display ? display : emptyField}
); - } + }; - renderPrefetchedResources() { - const prefetchMap = new Map(Object.entries(this.props.prefetchedResources)); + const renderPrefetchedResources = () => { + const prefetchMap = new Map(Object.entries(prefetchedResources)); if (prefetchMap.size > 0) { - return this.renderRequestResources(prefetchMap); + return renderRequestResources(prefetchMap); } return
; - } + }; - renderRequestResources(requestResources) { - var renderedPrefetches = new Map(); + const renderRequestResources = (requestResources) => { + const renderedPrefetches = new Map(); requestResources.forEach((resourceList, resourceKey) => { const renderedList = []; if (Array.isArray(resourceList)) { resourceList.forEach(resource => { console.log('Request resources:' + JSON.stringify(requestResources)); console.log('Request key:' + resourceKey); - renderedList.push(this.renderResource(resource)); + renderedList.push(renderResource(resource)); }); } else { - renderedList.push(this.renderResource(resourceList)); + renderedList.push(renderResource(resourceList)); } renderedPrefetches.set(resourceKey, renderedList); @@ -185,9 +165,9 @@ export default class RequestBox extends Component { })}
); - } + }; - renderResource(resource) { + const renderResource = (resource) => { let value =
N/A
; if (!resource.id) { resource = resource.resource; @@ -210,35 +190,27 @@ export default class RequestBox extends Component { ); } return value; - } - - renderError() { - return ( - - Encountered Error: Try Refreshing The Client
{this.state.patientList.message}{' '} -
- ); - } + }; - launchSmartOnFhirApp = () => { + const launchSmartOnFhirApp = () => { console.log('Launch SMART on FHIR App'); - let userId = this.props.prefetchedResources?.practitioner?.id; + let userId = prefetchedResources?.practitioner?.id; if (!userId) { console.log( 'Practitioner not populated from prefetch, using default from config: ' + - this.props.defaultUser + defaultUser ); - userId = this.props.defaultUser; + userId = defaultUser; } let link = { - appContext: 'user=' + userId + '&patient=' + this.props.patient.id, + appContext: 'user=' + userId + '&patient=' + patient.id, type: 'smart', - url: this.props.smartAppUrl + url: smartAppUrl }; - retrieveLaunchContext(link, this.props.patient.id, this.props.client.state).then(result => { + retrieveLaunchContext(link, patient.id, client.state).then(result => { link = result; console.log(link); // launch the application in a new window @@ -246,76 +218,17 @@ export default class RequestBox extends Component { }); }; - /** - * Relaunch DTR using the available context - */ - relaunch = () => { - this.buildLaunchLink().then(link => { - window.open(link.url, '_blank'); - }); - }; - - buildLaunchLink() { - // build appContext and URL encode it - let appContext = ''; - let order = undefined, - coverage = undefined, - response = undefined; - - if (!this.isOrderNotSelected()) { - if (Object.keys(this.props.request).length > 0) { - order = `${this.props.request.resourceType}/${this.props.request.id}`; - if (this.props.request.insurance && this.props.request.insurance.length > 0) { - coverage = `${this.props.request.insurance[0].reference}`; - } - } - } - - if (order) { - appContext += `order=${order}`; - - if (coverage) { - appContext += `&coverage=${coverage}`; - } - } - - if (Object.keys(this.props.response).length > 0) { - response = `QuestionnaireResponse/${this.props.response.id}`; - } - - if (order && response) { - appContext += `&response=${response}`; - } else if (!order && response) { - appContext += `response=${response}`; - } - - const link = { - appContext: encodeURIComponent(appContext), - type: 'smart', - url: this.props.launchUrl - }; - - let linkCopy = Object.assign({}, link); - - return retrieveLaunchContext(linkCopy, this.props.patient.id, this.props.client.state).then( - result => { - linkCopy = result; - return linkCopy; - } - ); - } - /** * Send NewRx for new Medication to the Pharmacy Information System (PIMS) */ - sendRx = () => { - console.log('Sending NewRx to: ' + this.props.pimsUrl); + const sendRx = () => { + console.log('Sending NewRx to: ' + pimsUrl); // build the NewRx Message var newRx = buildNewRxRequest( - this.props.prefetchedResources.patient, - this.props.prefetchedResources.practitioner, - this.props.request + prefetchedResources.patient, + prefetchedResources.practitioner, + request ); console.log('Prepared NewRx:'); @@ -324,7 +237,7 @@ export default class RequestBox extends Component { const serializer = new XMLSerializer(); // Sending NewRx to the Pharmacy - fetch(this.props.pimsUrl, { + fetch(pimsUrl, { method: 'POST', //mode: 'no-cors', headers: { @@ -337,18 +250,18 @@ export default class RequestBox extends Component { console.log('Successfully sent NewRx to PIMS'); // create the MedicationDispense - var medicationDispense = createMedicationDispenseFromMedicationRequest(this.props.request); + var medicationDispense = createMedicationDispenseFromMedicationRequest(request); console.log('Create MedicationDispense:'); console.log(medicationDispense); // store the MedicationDispense in the EHR console.log(medicationDispense); - this.props.client.update(medicationDispense).then(result => { + client.update(medicationDispense).then(result => { console.log('Update MedicationDispense result:'); console.log(result); }); - this.handleRxResponse(); + handleRxResponse(); }) .catch(error => { console.log('sendRx Error - unable to send NewRx to PIMS: '); @@ -356,70 +269,70 @@ export default class RequestBox extends Component { }); }; - isOrderNotSelected() { - return Object.keys(this.props.request).length === 0; - } + const isOrderNotSelected = () => { + return Object.keys(request).length === 0; + }; - isPatientNotSelected() { - return Object.keys(this.props.patient).length === 0; - } + const isPatientNotSelected = () => { + return Object.keys(patient).length === 0; + }; // SnackBar - handleRxResponse = () => this.setState({ submittedRx: true }); + const handleRxResponse = () => setState(prevState => ({ ...prevState, submittedRx: true })); - handleClose = () => this.setState({ submittedRx: false }); + const handleClose = () => setState(prevState => ({ ...prevState, submittedRx: false })); - render() { - const disableSendToCRD = this.isOrderNotSelected() || this.props.loading; - const disableSendRx = this.isOrderNotSelected() || this.props.loading; - const disableLaunchSmartOnFhir = this.isPatientNotSelected(); + const disableSendToCRD = isOrderNotSelected() || loading; + const disableSendRx = isOrderNotSelected() || loading; + const disableLaunchSmartOnFhir = isPatientNotSelected(); - return ( - <> -
-
-
- Patient ID: {this.props.patient.id} -
-
- - - {this.renderPatientInfo()} - - - {this.renderPrefetchedResources()} - - -
+ return ( + <> +
+
+
+ Patient ID: {patient.id}
-
- - - - - +
+ + + {renderPatientInfo()} + + + {renderPrefetchedResources()} + +
- - - Success! NewRx Received By Pharmacy - - - - ); - } -} +
+ + + + + +
+
+ + + Success! NewRx Received By Pharmacy + + + + ); +}; + +export default RequestBox; diff --git a/src/components/SMARTBox/EHRLaunchBox.js b/src/components/SMARTBox/EHRLaunchBox.js index d7095727..d6080865 100644 --- a/src/components/SMARTBox/EHRLaunchBox.js +++ b/src/components/SMARTBox/EHRLaunchBox.js @@ -1,33 +1,27 @@ -import React, { Component } from 'react'; -import { fhir } from '../../util/fhir'; +import React from 'react'; import './smart.css'; import env from 'env-var'; -export default class EHRLaunchBox extends Component { - constructor(props) { - super(props); - this.state = {}; - } - - render() { - return ( +const EHRLaunchBox = () => { + return ( +
+
EHR Launch Settings
-
EHR Launch Settings
-
- - -
- Note: Only the local EHR is supported at this time for EHR launch -
- - - - - + + +
+ Note: Only the local EHR is supported at this time for EHR launch
+ + + + +
- ); - } -} +
+ ); +}; + +export default EHRLaunchBox; diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index 11a71519..8d8e4bf0 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState } from 'react'; import { getAge, getDrugCodeFromMedicationRequest } from '../../util/fhir'; import './smart.css'; import { Button } from '@mui/material'; @@ -13,10 +13,9 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import { retrieveLaunchContext } from '../../util/util'; -export default class PatientBox extends Component { - constructor(props) { - super(props); - this.state = { +const PatientBox = (props) => { + const [state, setState] = useState( + { request: '', deviceRequests: {}, medicationRequests: {}, @@ -29,30 +28,40 @@ export default class PatientBox extends Component { questionnaireTitles: {}, showMedications: false, showQuestionnaires: false, - numInProgressForms: 0 - }; + numInProgressForms: 0, + name: 'N/A', + fullName: 'N/A', + formatBirthdate: '', + options: [], + responseOptions: [] + } + ); + + const { patient, callback, clearCallback, defaultUser, client, callbackMap, + updatePrefetchCallback, responseExpirationDays, request, launchUrl } = props; + + const medicationColumns = [ + { id: 'name', label: 'Medication' }, + { id: 'code', label: 'Request #' } + ]; + const questionnaireColumns = [ + { id: 'name', label: 'Title' }, + { id: 'time', label: 'Created' } + ]; + const medicationTooltip = + state.options.length === 0 ? 'No medications found.' : `${state.options.length} medications available`; + const formTooltip = + state.numInProgressForms === 0 ? 'No In-Progress Forms' : 'Open In-Progress Forms'; + + useEffect(() => { - this.handleRequestChange = this.handleRequestChange.bind(this); - - this.updatePrefetchRequest = this.updatePrefetchRequest.bind(this); - this.getDeviceRequest = this.getDeviceRequest.bind(this); - this.getServiceRequest = this.getServiceRequest.bind(this); - this.getMedicationRequest = this.getMedicationRequest.bind(this); - this.getMedicationDispense = this.getMedicationDispense.bind(this); - this.getRequests = this.getRequests.bind(this); - this.getResponses = this.getResponses.bind(this); - this.getQuestionnaireTitles = this.getQuestionnaireTitles.bind(this); - this.makeQROption = this.makeQROption.bind(this); - this.handleResponseChange = this.handleResponseChange.bind(this); - } - - componentDidMount() { // get requests and responses on open of patients - this.getRequests(); - this.getResponses(); // TODO: PatientBox should not be rendering itself, needs to receive its state from parent - } + getRequests(); + getResponses(); // TODO: PatientBox should not be rendering itself, needs to receive its state from parent + getPatientInfo(); + }, []); - getCoding(resource) { + const getCoding =(resource) => { let code = null; if (resource.resourceType === 'DeviceRequest') { code = resource?.codeCodeableConcept.coding[0]; @@ -84,10 +93,11 @@ export default class PatientBox extends Component { }; } return code; - } + }; - makeOption(request, options) { - const code = this.getCoding(request); + const makeOption = (request, options) => { + const code = getCoding(request); + const tempOptions = options; let option = { key: request.id, @@ -96,61 +106,62 @@ export default class PatientBox extends Component { name: code.display, value: JSON.stringify(request) }; - options.push(option); - } - - updateValues(patient) { - this.props.callback('patient', patient); - this.props.callback('expanded', false); - this.props.clearCallback(); - if (this.state.request) { - const request = JSON.parse(this.state.request); + tempOptions.push(option); + setState(prevState => ({ ...prevState, options: tempOptions})); + }; + + const updateValues = (patient) => { + callback('patient', patient); + callback('expanded', false); + clearCallback(); + if (state.request) { + const request = JSON.parse(state.request); if ( request.resourceType === 'DeviceRequest' || request.resourceType === 'ServiceRequest' || request.resourceType === 'MedicationRequest' || request.resourceType === 'MedicationDispense' ) { - this.updatePrefetchRequest(request, patient, this.props.defaultUser); + updatePrefetchRequest(request, patient, defaultUser); } else { - this.props.clearCallback(); + clearCallback(); } } else { - this.updatePrefetchRequest(null, patient, this.props.defaultUser); - this.props.callback('request', {}); - this.props.callback('code', null); - this.props.callback('codeSystem', null); - this.props.callback('display', null); + updatePrefetchRequest(null, patient, defaultUser); + callback('request', {}); + callback('code', null); + callback('codeSystem', null); + callback('display', null); } - if (this.state.response) { - const response = JSON.parse(this.state.response); - this.updateQRResponse(response); + if (state.response) { + const response = JSON.parse(state.response); + updateQRResponse(response); } - } + }; - updatePatient(patient) { - this.props.callback('patient', patient); - } + const updatePatient = (patient) => { + callback('patient', patient); + }; - updateQRResponse(response) { - this.props.callback('response', response); - } + const updateQRResponse = (response) => { + callback('response', response); + }; - fetchResources(queries) { - var requests = []; - this.props.callback('prefetchCompleted', false); + const fetchResources = (queries) => { + let requests = []; + callback('prefetchCompleted', false); queries.forEach((query, queryKey) => { const urlQuery = '/' + query; requests.push( - this.props.client + client .request(urlQuery) .then(response => { console.log(response); return response; }) .then(resource => { - this.props.callbackMap('prefetchedResources', queryKey, resource); + callbackMap('prefetchedResources', queryKey, resource); }) ); }); @@ -158,20 +169,20 @@ export default class PatientBox extends Component { Promise.all(requests) .then(() => { console.log('fetchResourcesSync: finished'); - this.props.callback('prefetchCompleted', true); + callback('prefetchCompleted', true); }) .catch(function (err) { console.log('fetchResourcesSync: failed to wait for all the prefetch to populate'); console.log(err); }); - } + }; - updatePrefetchRequest(request, patient, user) { + const updatePrefetchRequest = (request, patient, user) => { const patientReference = 'Patient/' + patient?.id; const userReference = 'Practitioner/' + user; if (request) { - this.props.callback(request.resourceType, request); - const queries = this.props.updatePrefetchCallback( + callback(request.resourceType, request); + const queries = updatePrefetchCallback( request, patientReference, userReference, @@ -179,15 +190,15 @@ export default class PatientBox extends Component { 'patient', 'practitioner' ); - this.fetchResources(queries); + fetchResources(queries); - this.props.callback('request', request); - const coding = this.getCoding(request); - this.props.callback('code', coding.code); - this.props.callback('codeSystem', coding.system); - this.props.callback('display', coding.display); + callback('request', request); + const coding = getCoding(request); + callback('code', coding.code); + callback('codeSystem', coding.system); + callback('display', coding.display); } else { - const queries = this.props.updatePrefetchCallback( + const queries = updatePrefetchCallback( request, patientReference, userReference, @@ -195,36 +206,43 @@ export default class PatientBox extends Component { 'practitioner', 'medicationRequests' ); - this.fetchResources(queries); + fetchResources(queries); } - } + }; - getDeviceRequest(patientId) { - this.props.client + const getDeviceRequest = (patientId) => { + client .request(`DeviceRequest?subject=Patient/${patientId}`, { resolveReferences: ['subject', 'performer'], graph: false, flat: true }) .then(result => { - this.setState({ deviceRequests: result }); + setState(prevState => ({...prevState, deviceRequests: result})); + // now take results and make option + result.data.forEach(e => { + makeOption(e, state.options); + }); }); - } + }; - getServiceRequest(patientId) { - this.props.client + const getServiceRequest = (patientId) => { + client .request(`ServiceRequest?subject=Patient/${patientId}`, { resolveReferences: ['subject', 'performer'], graph: false, flat: true }) .then(result => { - this.setState({ serviceRequests: result }); + setState(prevState => ({...prevState, serviceRequests: result})); + result.data.forEach(e => { + makeOption(e, state.options); + }); }); - } + }; - getMedicationRequest(patientId) { - this.props.client + const getMedicationRequest = (patientId) => { + client .request(`MedicationRequest?subject=Patient/${patientId}`, { resolveReferences: ['subject', 'performer', 'medicationReference'], graph: false, @@ -262,34 +280,41 @@ export default class PatientBox extends Component { } } }); - - this.setState({ medicationRequests: result }); + setState(prevState => ({...prevState, medicationRequests: result})); + result.data.forEach(e => { + makeOption(e, state.options); + }); }); - } + }; - getMedicationDispense(patientId) { - this.props.client + const getMedicationDispense = (patientId) => { + client .request(`MedicationDispense?subject=Patient/${patientId}`, { resolveReferences: ['subject', 'performer'], graph: false, flat: true }) .then(result => { - this.setState({ medicationDispenses: result }); + setState(prevState => ({...prevState, medicationDispenses: result})); + result.data.forEach(e => { + makeOption(e, state.options); + }); }); - } + }; - handleRequestChange(data, patient) { + const handleRequestChange = (data, patient) => { if (data) { - let coding = this.getCoding(JSON.parse(data)); - this.setState({ + let coding = getCoding(JSON.parse(data)); + + setState(prevState => ({ + ...prevState, request: data, code: coding.code, system: coding.system, display: coding.display, response: '' - }); - this.props.callback('response', ''); + })); + callback('response', ''); // update prefetch request for the medication const request = JSON.parse(data); if ( @@ -298,128 +323,145 @@ export default class PatientBox extends Component { request.resourceType === 'MedicationRequest' || request.resourceType === 'MedicationDispense' ) { - this.updatePrefetchRequest(request, patient, this.props.defaultUser); + updatePrefetchRequest(request, patient, defaultUser); } else { - this.props.clearCallback(); + clearCallback(); } // close the accordian after selecting a medication, can change if we want to keep open - this.props.callback('expanded', false); + callback('expanded', false); } else { - this.setState({ + setState(prevState => ({ + ...prevState, request: '' - }); + })); } - } + }; - handleResponseChange(data) { + const handleResponseChange = (data) => { if (data) { - this.setState({ + setState(prevState => ({ + ...prevState, response: data - }); + })); const response = JSON.parse(data); - this.updateQRResponse(response); + updateQRResponse(response); } else { - this.setState({ + setState(prevState => ({ + ...prevState, response: '' - }); + })); } - } + }; - getRequests() { - const patientId = this.props.patient.id; - this.getDeviceRequest(patientId); - this.getServiceRequest(patientId); - this.getMedicationRequest(patientId); - this.getMedicationDispense(patientId); - } + const getRequests = () => { + const patientId = patient.id; + getDeviceRequest(patientId); + getServiceRequest(patientId); + getMedicationRequest(patientId); + getMedicationDispense(patientId); + }; /** * Retrieve QuestionnaireResponse */ - getResponses() { - const patientId = this.props.patient.id; + const getResponses = () => { + const patientId = patient.id; let updateDate = new Date(); - updateDate.setDate(updateDate.getDate() - this.props.responseExpirationDays); + updateDate.setDate(updateDate.getDate() - responseExpirationDays); const searchParameters = [ `_lastUpdated=gt${updateDate.toISOString().split('T')[0]}`, 'status=in-progress', `subject=Patient/${patientId}`, '_sort=-authored' ]; - this.props.client + client .request(`QuestionnaireResponse?${searchParameters.join('&')}`, { resolveReferences: ['subject'], graph: false, flat: true }) .then(result => { - this.setState({ questionnaireResponses: result }); - this.setState({ numInProgressForms: result.data.length }); - }) - .then(() => this.getQuestionnaireTitles()); - } + setState(prevState => ({ + ...prevState, + questionnaireResponses: result, + numInProgressForms: result.data.length + })); + getQuestionnaireTitles(result); + }); + }; - getQuestionnaireTitles() { + const getQuestionnaireTitles = (qResponse) => { const promises = []; - if (this.state.questionnaireResponses.data) { - if (this.state.questionnaireResponses.data.length > 0) { - for (const canonical of this.state.questionnaireResponses.data.map( + if (qResponse.data) { + if (qResponse.data.length > 0) { + for (const canonical of qResponse.data.map( questionnaireResponse => questionnaireResponse.questionnaire )) { promises.push( - this.props.client + client .request(canonical) .then(questionnaire => [canonical, questionnaire.title || canonical]) ); } Promise.all(promises).then(pairs => { - this.setState({ questionnaireTitles: Object.fromEntries(pairs) }); + setState(prevState => ({ + ...prevState, questionnaireTitles: Object.fromEntries(pairs) })); + // call get response options from here, to pass in the questionnaireResponses data and questionnaireTitles + // before state variables are set + getResponseOptions(qResponse.data, Object.fromEntries(pairs)); }); } } - } +}; + + const getResponseOptions = (data, title) => { + const temp = data.map(qr => makeQROption(qr, title)); + setState(prevState => ({ + ...prevState, + responseOptions: temp + })); + }; - makeQROption(qr) { - const questionnaireTitle = this.state.questionnaireTitles[qr.questionnaire]; - // const display = `${questionnaireTitle}: created at ${qr.authored}`; + const makeQROption = (qr, questionnaireTitles) => { + const questionnaireTitle = questionnaireTitles[qr.questionnaire]; return { key: qr.id, text: questionnaireTitle, time: qr.authored, value: JSON.stringify(qr) }; - } + }; - isOrderNotSelected() { - return Object.keys(this.props.request).length === 0; - } + const isOrderNotSelected = () => { + return Object.keys(request).length === 0; + }; /** * Launch In progress Form */ - relaunch = data => { - this.handleResponseChange(data); - this.props.callback('expanded', false); - this.buildLaunchLink(data).then(link => { + const relaunch = data => { + handleResponseChange(data); + callback('expanded', false); + buildLaunchLink(data).then(link => { //e.preventDefault(); window.open(link.url, '_blank'); }); }; - async buildLaunchLink(data) { + const buildLaunchLink = async (data) => { // build appContext and URL encode it let appContext = ''; let order = undefined, coverage = undefined, response = undefined; - if (!this.isOrderNotSelected()) { - if (Object.keys(this.props.request).length > 0) { - order = `${this.props.request.resourceType}/${this.props.request.id}`; - if (this.props.request.insurance && this.props.request.insurance.length > 0) { - coverage = `${this.props.request.insurance[0].reference}`; + if (!isOrderNotSelected()) { + if (Object.keys(request).length > 0) { + order = `${request.resourceType}/${request.id}`; + if (request.insurance && request.insurance.length > 0) { + coverage = `${request.insurance[0].reference}`; } } } @@ -447,21 +489,21 @@ export default class PatientBox extends Component { const link = { appContext: encodeURIComponent(appContext), type: 'smart', - url: this.props.launchUrl + url: launchUrl }; let linkCopy = Object.assign({}, link); const result = await retrieveLaunchContext( linkCopy, - this.props.patient.id, - this.props.client.state + patient.id, + client.state ); linkCopy = result; return linkCopy; - } + }; - makeResponseTable(columns, options, type, patient) { + const makeResponseTable = (columns, options, type, patient) => { return ( this.handleRequestChange(row.value, patient)} + onClick={() => handleRequestChange(row.value, patient)} > {row.name} @@ -497,9 +539,9 @@ export default class PatientBox extends Component { ); - } + }; - makeQuestionnaireTable(columns, options, type, patient) { + const makeQuestionnaireTable = (columns, options, type, patient) => { return ( {options.map(row => ( - + this.relaunch(row.value)} + onClick={() => relaunch(row.value)} className="hover-row" > @@ -536,172 +578,123 @@ export default class PatientBox extends Component { ); - } + }; - render() { - const patient = this.props.patient; - if (!patient) { - return <>; - } - let name = ''; - let fullName = ''; - let formatBirthdate = ''; + const getPatientInfo = () => { if (patient.name) { - name = {`${patient.name[0].given[0]} ${patient.name[0].family}`} ; - fullName = {`${patient.name[0].given.join(' ')} ${patient.name[0].family}`} ; + setState(prevState => ({ ...prevState, name: `${patient.name[0].given[0]} ${patient.name[0].family}`})); + setState(prevState => ({ ...prevState, fullName: `${patient.name[0].given.join(' ')} ${patient.name[0].family}`})); } if (patient.birthDate) { - formatBirthdate = new Date(patient.birthDate).toDateString(); - } - - // add all of the requests to the list of options - let options = []; - let responseOptions = []; - if (this.state.deviceRequests.data) { - this.state.deviceRequests.data.forEach(e => { - this.makeOption(e, options); - }); - } - if (this.state.serviceRequests.data) { - this.state.serviceRequests.data.forEach(e => { - this.makeOption(e, options); - }); - } - if (this.state.medicationRequests.data) { - this.state.medicationRequests.data.forEach(e => { - this.makeOption(e, options); - }); + setState(prevState => ({ ...prevState, formatBirthdate: new Date(patient.birthDate).toDateString()})); } + }; - if (this.state.medicationDispenses.data) { - this.state.medicationDispenses.data.forEach(e => { - this.makeOption(e, options); - }); - } - - if (this.state.questionnaireResponses.data) { - responseOptions = this.state.questionnaireResponses.data.map(qr => this.makeQROption(qr)); - } - - const medicationColumns = [ - { id: 'name', label: 'Medication' }, - { id: 'code', label: 'Request #' } - ]; - - const questionnaireColumns = [ - { id: 'name', label: 'Title' }, - { id: 'time', label: 'Created' } - ]; - - const medicationTooltip = - options.length === 0 ? 'No medications found.' : `${options.length} medications available`; - const formTooltip = - this.state.numInProgressForms === 0 ? 'No In-Progress Forms' : 'Open In-Progress Forms'; - - return ( -
-
- {name ? name : 'N/A'}{' '} - {`(ID: ${patient.id})`} -
-
-
-
- Full Name: {fullName} -
-
- Gender:{' '} - {patient.gender.charAt(0).toUpperCase() + patient.gender.slice(1)} -
-
- DoB/Age: {formatBirthdate} ( - {getAge(patient.birthDate)} years old) -
+ return ( +
+
+ {state.name}{' '} + {`(ID: ${patient.id})`} +
+
+
+
+ Full Name: {state.fullName}
-
- {this.state.showMedications ? ( - - ) : ( - - - - - - )} - {this.state.showQuestionnaires ? ( - - ) : ( - - - - - - )} +
+ Gender:{' '} + {patient.gender.charAt(0).toUpperCase() + patient.gender.slice(1)} +
+
+ DoB/Age: {state.formatBirthdate} ( + {getAge(patient.birthDate)} years old) +
+
+
+ {state.showMedications ? ( -
+ ) : ( + + + + + + )} + {state.showQuestionnaires ? ( + + ) : ( + + + + + + )} +
- {this.state.showMedications ? ( -
- {this.makeResponseTable(medicationColumns, options, 'medication', patient)} -
- ) : ( - - )} - {this.state.showQuestionnaires ? ( -
- {this.makeQuestionnaireTable( - questionnaireColumns, - responseOptions, - 'questionnaire', - patient - )} -
- ) : ( - - )}
- ); - } -} + {state.showMedications ? ( +
+ {makeResponseTable(medicationColumns, state.options, 'medication', patient)} +
+ ) : ( + + )} + {state.showQuestionnaires ? ( +
+ {makeQuestionnaireTable( + questionnaireColumns, + state.responseOptions, + 'questionnaire', + patient + )} +
+ ) : ( + + )} +
+ ); +}; + +export default PatientBox; \ No newline at end of file diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 7904d594..ce568aa7 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -1,67 +1,66 @@ -import React, { Component } from 'react'; -import { Button, Box, Grid, IconButton, Modal, DialogTitle } from '@mui/material'; +import React, { useState, useEffect } from 'react'; +import { Button, Box, Grid, IconButton } from '@mui/material'; import PersonIcon from '@mui/icons-material/Person'; import RefreshIcon from '@mui/icons-material/Refresh'; -import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import DisplayBox from '../components/DisplayBox/DisplayBox'; import '../index.css'; import RequestBox from '../components/RequestBox/RequestBox'; import buildRequest from '../util/buildRequest.js'; -import { types, defaultValues as codeValues, headerDefinitions } from '../util/data.js'; +import { types } from '../util/data.js'; import { createJwt } from '../util/auth'; -import env from 'env-var'; -import FHIR from 'fhirclient'; import Accordion from '@mui/material/Accordion'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import SettingsIcon from '@mui/icons-material/Settings'; import PatientSearchBar from '../components/RequestBox/PatientSearchBar/PatientSearchBar'; import { MedicationStatus } from '../components/MedicationStatus/MedicationStatus.jsx'; import { actionTypes } from './ContextProvider/reducer.js'; import axios from 'axios'; -export default class RequestBuilder extends Component { - constructor(props) { - super(props); - this.state = { - loading: false, - logs: [], - patient: {}, - expanded: true, - patientList: [], - response: {}, - code: null, - codeSystem: null, - display: null, - prefetchedResources: new Map(), - request: {}, - showSettings: false, - token: null, - client: this.props.client, - medicationDispense: null, - lastCheckedMedicationTime: null - }; +const RequestBuilder = (props) => { + const { globalState, dispatch, client } = props; + const [state, setState] = useState({ + loading: false, + patient: {}, + expanded: true, + patientList: [], + response: {}, + code: null, + codeSystem: null, + display: null, + prefetchedResources: new Map(), + request: {}, + showSettings: false, + token: null, + client: client, + medicationDispense: null, + lastCheckedMedicationTime: null + }); + const displayRequestBox = !!globalState.patient?.id; - this.updateStateElement = this.updateStateElement.bind(this); - this.submitInfo = this.submitInfo.bind(this); - this.consoleLog = this.consoleLog.bind(this); - this.takeSuggestion = this.takeSuggestion.bind(this); - this.requestBox = React.createRef(); - } + const isOrderNotSelected = () => { + return Object.keys(state.request).length === 0; + }; - getMedicationStatus = () => { - this.setState({ lastCheckedMedicationTime: Date.now() }); + const disableGetMedicationStatus = isOrderNotSelected() || state.loading; + const getMedicationStatus = () => { + setState(prevState => ({ + ...prevState, + lastCheckedMedicationTime: Date.now() + })); axios .get( - `${this.props.globalState.ehrUrl}/MedicationDispense?prescription=${this.state.request.id}` + `${globalState.ehrUrl}/MedicationDispense?prescription=${state.request.id}` ) .then( response => { const bundle = response.data; - this.setState({ medicationDispense: bundle.entry?.[0].resource }); + setState(prevState => ({ + ...prevState, + medicationDispense: bundle.entry?.[0].resource + })); }, error => { console.log('Was not able to get medication status', error); @@ -69,97 +68,78 @@ export default class RequestBuilder extends Component { ); }; - componentDidMount() { - if (!this.state.client) { - this.reconnectEhr(); - } else { + useEffect(() => { + if (state.client) { // Call patients on load of page - this.getPatients(); - this.props.dispatch({ + getPatients(); + dispatch({ type: actionTypes.updateSetting, settingId: 'baseUrl', - value: this.state.client.state.serverUrl + value: state.client.state.serverUrl }); - this.props.dispatch({ + dispatch({ type: actionTypes.updateSetting, settingId: 'ehrUrl', - value: this.state.client.state.serverUrl + value: state.client.state.serverUrl }); } - } - - consoleLog(content, type) { - console.log(content); - let jsonContent = { - content: content, - type: type - }; - this.setState(prevState => ({ - logs: [...prevState.logs, jsonContent] - })); - } + }, []); - updateStateElement = (elementName, text) => { + const updateStateElement = (elementName, text) => { if (elementName === 'patient') { - this.props.dispatch({ + dispatch({ type: actionTypes.updatePatient, value: text }); } else { - this.setState({ [elementName]: text }); + setState(prevState => ({ + ...prevState, + [elementName]: text + })); } - // if the patientFhirQuery is updated, make a call to get the patients - if (elementName === 'patientFhirQuery') { - setTimeout(() => { - this.getPatients(); - }, 1000); - } - }; - - timeout = time => { - let controller = new AbortController(); - setTimeout(() => controller.abort(), time * 1000); - return controller; }; - submitInfo(prefetch, request, patient, hook) { - this.setState({ loading: true }); - this.consoleLog('Initiating form submission', types.info); - this.setState({ patient }); + const submitInfo = (prefetch, request, patient, hook) => { + console.log('Initiating form submission ', types.info); + setState(prevState => ({ + ...prevState, + loading: true, + patient + })); const hookConfig = { - includeConfig: this.props.globalState.includeConfig, - alternativeTherapy: this.props.globalState.alternativeTherapy + includeConfig: globalState.includeConfig, + alternativeTherapy: globalState.alternativeTherapy }; - let user = this.props.globalState.defaultUser; + let user = globalState.defaultUser; let json_request = buildRequest( request, user, patient, - this.props.globalState.ehrUrlSentToRemsAdminForPreFetch, - this.state.client.state.tokenResponse, + globalState.ehrUrlSentToRemsAdminForPreFetch, + state.client.state.tokenResponse, prefetch, - this.props.globalState.sendPrefetch, + globalState.sendPrefetch, hook, hookConfig ); - let cdsUrl = this.props.globalState.cdsUrl; + let cdsUrl =globalState.cdsUrl; if (hook === 'order-sign') { - cdsUrl = cdsUrl + '/' + this.props.globalState.orderSign; + cdsUrl = cdsUrl + '/' + globalState.orderSign; } else if (hook === 'order-select') { - cdsUrl = cdsUrl + '/' + this.props.globalState.orderSelect; + cdsUrl = cdsUrl + '/' + globalState.orderSelect; } else if (hook === 'patient-view') { - cdsUrl = cdsUrl + '/' + this.props.globalState.patientView; + cdsUrl = cdsUrl + '/' + globalState.patientView; } else { - this.consoleLog("ERROR: unknown hook type: '", hook, "'"); + console.log("ERROR: unknown hook type: '", hook); return; } - let baseUrl = this.props.globalState.baseUrl; + let baseUrl = globalState.baseUrl; const headers = { 'Content-Type': 'application/json' }; - if (this.props.globalState.generateJsonToken) { + if (globalState.generateJsonToken) { const jwt = 'Bearer ' + createJwt(baseUrl, cdsUrl); headers.authorization = jwt; } @@ -168,198 +148,178 @@ export default class RequestBuilder extends Component { fetch(cdsUrl, { method: 'POST', headers: new Headers(headers), - body: JSON.stringify(json_request), - signal: this.timeout(10).signal //Timeout set to 10 seconds + body: JSON.stringify(json_request) }) .then(response => { - clearTimeout(this.timeout); response.json().then(fhirResponse => { console.log(fhirResponse); if (fhirResponse?.status) { - this.consoleLog( - 'Server returned status ' + fhirResponse.status + ': ' + fhirResponse.error, - types.error - ); - this.consoleLog(fhirResponse.message, types.error); + console.log('Server returned status ' + fhirResponse.status + ': ' + fhirResponse.error); + console.log(fhirResponse.message); } else { - this.setState({ response: fhirResponse }); + setState(prevState => ({ ...prevState, response: fhirResponse })); } - this.setState({ loading: false }); + setState(prevState => ({ ...prevState, loading: false })); }); }) .catch(() => { - this.consoleLog('No response received from the server', types.error); - this.setState({ response: {} }); - this.setState({ loading: false }); + console.log('No response received from the server', types.error); + setState(prevState => ({ ...prevState, response: {}, loading: false })); }); } catch (error) { - this.setState({ loading: false }); - this.consoleLog('Unexpected error occurred', types.error); + + setState(prevState => ({ ...prevState, loading: false })); + console.log('Unexpected error occurred', types.error); if (error instanceof TypeError) { - this.consoleLog(error.name + ': ' + error.message, types.error); + console.log(error.name + ': ' + error.message); } - this.setState({ loading: false }); } - } - - takeSuggestion(resource) { - // when a suggestion is taken, call into the requestBox to resubmit the CRD request with the new request - this.requestBox.current.replaceRequestAndSubmit(resource); - } + }; - getPatients = () => { - if (this.props.globalState.patientFhirQuery) { - this.props.client - .request(this.props.globalState.patientFhirQuery, { flat: true }) + const getPatients = () => { + setState(prevState => ({ ...prevState, expanded: false})); + if (globalState.patientFhirQuery) { + client + .request(globalState.patientFhirQuery, { flat: true }) .then(result => { - this.setState({ - patientList: result - }); + setState(prevState => ({ ...prevState, patientList: result, expanded: true })); }) .catch(e => { - this.setState({ - patientList: e - }); + setState(prevState => ({ ...prevState, patientList: e })); + console.log(e); }); } }; - updateStateList = (elementName, text) => { - this.setState(prevState => { - return { [elementName]: [...prevState[elementName], text] }; - }); - }; - - updateStateMap = (elementName, key, text) => { - this.setState(prevState => { - if (!prevState[elementName][key]) { - prevState[elementName][key] = []; - } - return { [elementName]: { ...prevState[elementName], [key]: text } }; - }); + const updateStateMap = (elementName, key, text) => { + setState(prevState => ({ + ...prevState, + [elementName]: { ...prevState[elementName], [key]: text } + })); }; - clearState = () => { - this.setState({ + const clearState = () => { + setState(prevState => ({ + ...prevState, prefetchedResources: new Map(), practitioner: {}, coverage: {}, response: {} - }); + })); }; - handleChange = () => (event, isExpanded) => { - this.setState({ expanded: isExpanded ? true : false }); + const handleChange = () => (event, isExpanded) => { + setState(prevState => ({ + ...prevState, + expanded: isExpanded ? true : false + })); }; - isOrderNotSelected() { - return Object.keys(this.state.request).length === 0; - } - - render() { - const displayRequestBox = !!this.props.globalState.patient?.id; - const disableGetMedicationStatus = this.isOrderNotSelected() || this.state.loading; - + const renderError = () => { return ( - <> - - - - } - aria-controls="panel1a-content" - id="panel1a-header" - style={{ marginLeft: '45%' }} - > - - - - {this.state.patientList.length > 0 && this.state.expanded && ( - - {this.state.patientList instanceof Error ? ( - this.renderError() - ) : ( - - )} - - )} - - - - - this.getPatients()} size="large"> - - - + + Encountered Error: Try Refreshing The Client
{state.patientList.message}{' '} +
+ ); + }; + + return ( + <> + + + + } + aria-controls="panel1a-content" + id="panel1a-header" + style={{ marginLeft: '45%' }} + > + + + + {state.patientList.length > 0 && state.expanded && ( + + {state.patientList instanceof Error ? ( + renderError() + ) : ( + + )} + + )} + + + + + getPatients()} size="large"> + + + - - {displayRequestBox && ( - - - - )} - {!disableGetMedicationStatus && ( - - - - )} - + + {displayRequestBox && ( + + + + )} + {!disableGetMedicationStatus && ( + + + + )} + - - - + + - - ); - } -} + + + ); +}; + +export default RequestBuilder;