From 9e30f153cf54230ce44ea8370819d69a9b2931b0 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Tue, 17 Jun 2025 10:54:30 -0400 Subject: [PATCH 1/6] update vite config for deployment --- vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 986eeba..75de307 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ base: '', plugins: [react(), viteTsconfigPaths(), nodePolyfills()], preview: { - allowedHosts: ['.mitre.org', '.us-east-1.elb.amazonaws.com'] + allowedHosts: ['.mitre.org', '.elb.us-east-1.amazonaws.com'] }, define: { 'process.env': process.env @@ -23,7 +23,7 @@ export default defineConfig({ port: 3000, open: false, host: true, - allowedHosts: ['.mitre.org', '.us-east-1.elb.amazonaws.com'] + allowedHosts: ['.mitre.org', '.elb.us-east-1.amazonaws.com'] }, build: { From 6010650d4ae2a68664d528e612d358afe9315ec5 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque <41444457+plarocque4@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:38:50 -0400 Subject: [PATCH 2/6] Get the drug description for the NewRx from the first drug code with a display specified (#166) --- src/util/buildScript.2017071.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/util/buildScript.2017071.js b/src/util/buildScript.2017071.js index f8766e4..1ff52a9 100644 --- a/src/util/buildScript.2017071.js +++ b/src/util/buildScript.2017071.js @@ -227,13 +227,19 @@ function buildNewRxMedication(doc, medicationRequestResource) { // loop through the coding values and find the ndc code and the rxnorm code let medicationCodingList = getDrugCodeableConceptFromMedicationRequest(medicationRequestResource)?.coding; + + var drugDisplay = 'undefined'; for (let i = 0; i < medicationCodingList.length; i++) { const coding = medicationCodingList[i]; const system = coding.system.toLowerCase(); + // get the display from first drug coding that contains a display value + if (coding.display && drugDisplay == 'undefined') { + drugDisplay = coding.display; + } + if (system.endsWith('ndc')) { // Medication Drug Code - xmlAddTextNode(doc, medicationPrescribed, 'DrugDescription', coding.display); var productCode = doc.createElement('ProductCode'); xmlAddTextNode(doc, productCode, 'Code', coding.code); xmlAddTextNode(doc, productCode, 'Qualifier', 'ND'); // National Drug Code (NDC) @@ -241,6 +247,9 @@ function buildNewRxMedication(doc, medicationRequestResource) { } } + // set the drug description + xmlAddTextNode(doc, medicationPrescribed, 'DrugDescription', drugDisplay); + medicationPrescribed.appendChild(drugCoded); // Medication Quantity From 3d0b28ee07a2a5323324a8ca640d057560f07e46 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque <41444457+plarocque4@users.noreply.github.com> Date: Wed, 2 Jul 2025 01:15:19 -0400 Subject: [PATCH 3/6] show the details even if there are no links (#167) --- src/components/DisplayBox/DisplayBox.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/DisplayBox/DisplayBox.jsx b/src/components/DisplayBox/DisplayBox.jsx index c2a3da8..1df6bcb 100644 --- a/src/components/DisplayBox/DisplayBox.jsx +++ b/src/components/DisplayBox/DisplayBox.jsx @@ -355,10 +355,13 @@ const DisplayBox = props => { {summarySection} +
+ {detailSection} +
+ {/* Forms */} {linksSection.length !== 0 ? (
- {detailSection} Required Forms {linksSection}
From 79f859f86aae89d8a75d8eeab1caafafe8a3ddf6 Mon Sep 17 00:00:00 2001 From: lmd59 Date: Fri, 8 Aug 2025 10:37:13 -0400 Subject: [PATCH 4/6] Update README.md Add data rights --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d76231..12842bd 100644 --- a/README.md +++ b/README.md @@ -134,4 +134,35 @@ Following are a list of modifiable paths: | VITE_URL | `http://localhost:3000` | The base url of this app. Should be modified if the port or domain change. | | VITE_USER | `alice` | The default user to login as when opening the app. | | VITE_USE_INTERMEDIARY | false | When true, the app will send all CDS Hooks and REMS ETASU check calls to the intermediary defined in VITE_INTERMEDIARY. | -| VITE_INTERMEDIARY | `http://localhost:3030` | The base url of the intermediary. | \ No newline at end of file +| VITE_INTERMEDIARY | `http://localhost:3030` | The base url of the intermediary. | + +# Data Rights + +
+NOTICE +
+ +This (software/technical data) was produced for the U. S. Government under Contract Number 75FCMC18D0047/75FCMC23D0004, and is subject to Federal Acquisition Regulation Clause 52.227-14, Rights in Data-General. + + +No other use other than that granted to the U. S. Government, or to those acting on behalf of the U. S. Government under that Clause is authorized without the express written permission of The MITRE Corporation. + + +For further information, please contact The MITRE Corporation, Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539, (703) 983-6000. + +
+©2025 The MITRE Corporation. +
+ +
+ +Licensed under the Apache License, Version 2.0 (the "License"); use of this repository is permitted in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From 7f879b182c9472e56f0ef237183c9db93e445b90 Mon Sep 17 00:00:00 2001 From: lmd59 Date: Fri, 22 Aug 2025 09:36:20 -0400 Subject: [PATCH 5/6] Add fork information --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 12842bd..7604fe7 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ Following are a list of modifiable paths: | VITE_INTERMEDIARY | `http://localhost:3030` | The base url of the intermediary. | # Data Rights +This repository has been forked from the [HL7-DaVinci/crd-request-generator](https://github.com/HL7-DaVinci/crd-request-generator) repository. As such, the following data rights apply to all changes made on this fork of the repository, starting with release 0.1 and onward.
NOTICE From ed6f92d7e2a2f912a58ce2ac412f763bec3e66d2 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra <88040167+smalho01@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:31:39 -0400 Subject: [PATCH 6/6] update prefetch with pharmacy ref (#169) * update prefetch with pharmacy ref * fix the prefetch bug * update request generator to use healthcareservice for pharmacy id * fix the context issue * remove pharmacy from context in request builder * run prettier/lint * remove pharmacyid from queries * have the prefetch pharmacy setting work * add settings config for pharmacy id --- .env | 2 + README.md | 4 +- src/PrefetchTemplate.js | 74 ++++++---- src/components/App.jsx | 18 ++- .../PatientSearchBar/PatientSearchBar.jsx | 43 +++--- src/components/RequestBox/RequestBox.jsx | 4 +- src/components/RequestDashboard/Home.jsx | 61 ++++----- .../RequestDashboard/PatientSection.jsx | 7 +- .../RequestDashboard/SettingsSection.jsx | 35 ++--- .../RequestDashboard/TasksSection.jsx | 55 ++++---- src/components/RequestDashboard/styles.jsx | 2 +- src/components/SMARTBox/PatientBox.jsx | 24 +++- src/containers/BackOffice/BackOffice.jsx | 54 ++++---- src/containers/BackOffice/Dashboard.jsx | 127 +++++++++--------- src/containers/BackOffice/TaskTab.jsx | 59 ++++---- src/containers/BackOffice/styles.jsx | 99 +++++++------- .../ContextProvider/SettingsProvider.jsx | 9 +- src/containers/Gateway/Gateway.jsx | 14 +- src/containers/Index.jsx | 35 +++-- src/containers/PatientPortal.jsx | 16 ++- src/containers/RequestBuilder.jsx | 30 ++++- src/util/auth.js | 8 +- src/util/data.js | 89 +++++++++--- src/util/fhir.js | 2 +- 24 files changed, 524 insertions(+), 347 deletions(-) diff --git a/.env b/.env index c6757a9..d4b9e1e 100644 --- a/.env +++ b/.env @@ -26,5 +26,7 @@ VITE_USER = alice VITE_HOOK_TO_SEND = patient-view VITE_URL_FILTER = http://localhost:3000/* VITE_USE_INTERMEDIARY = false +VITE_USE_PHARMACY_IN_PREFETCH = true VITE_INTERMEDIARY = http://localhost:3003 VITE_DISABLE_MEDICATION_STATUS = false +VITE_PHARMACY_ID = pharm0111 diff --git a/README.md b/README.md index 7604fe7..3608494 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,9 @@ Following are a list of modifiable paths: | VITE_URL | `http://localhost:3000` | The base url of this app. Should be modified if the port or domain change. | | VITE_USER | `alice` | The default user to login as when opening the app. | | VITE_USE_INTERMEDIARY | false | When true, the app will send all CDS Hooks and REMS ETASU check calls to the intermediary defined in VITE_INTERMEDIARY. | -| VITE_INTERMEDIARY | `http://localhost:3030` | The base url of the intermediary. | +| VITE_INTERMEDIARY | `http:/localhost:3030` | The base url of the intermediary. | +| VITE_USE_PHARMACY_IN_PREFETCH | true | When true, the app will send pharmacy information to the rems admin in the CDS Hooks prefetch | +| VITE_PHARMACY_ID | `pharm0111` | The pharmacy ID to use in the CDS Hooks Prefetch | # Data Rights This repository has been forked from the [HL7-DaVinci/crd-request-generator](https://github.com/HL7-DaVinci/crd-request-generator) repository. As such, the following data rights apply to all changes made on this fork of the repository, starting with release 0.1 and onward. diff --git a/src/PrefetchTemplate.js b/src/PrefetchTemplate.js index 50b8e9e..573a880 100644 --- a/src/PrefetchTemplate.js +++ b/src/PrefetchTemplate.js @@ -1,27 +1,35 @@ // Prefetch Template Source: // https://build.fhir.org/ig/HL7/davinci-crd/hooks.html#prefetch export class PrefetchTemplate { - static generatePrefetchMap() { + static generatePrefetchMap(settings = null) { + // If no settings provided, use defaults from data.js + const includePharmacy = settings?.includePharmacyInPreFetch ?? + headerDefinitions.includePharmacyInPreFetch.default; + const pharmacyId = settings?.pharmacyId ?? + headerDefinitions.pharmacyId.default; + const prefetchMap = new Map(); const PRACTITIONER_PREFETCH = new PrefetchTemplate('{{context.userId}}'); - const REQUEST_PREFETCH = new PrefetchTemplate( 'MedicationRequest/{{context.medications.MedicationRequest.id}}' ); const PATIENT_PREFETCH = new PrefetchTemplate('{{context.patientId}}'); - const ALL_REQUESTS_PREFETCH = new PrefetchTemplate( 'MedicationRequest?subject={{context.patientId}}&_include=MedicationRequest:medication' ); - // prefetchMap.set("Coverage", COVERAGE_PREFETCH_QUERY); + // Core prefetch items (always included) prefetchMap.set('request', REQUEST_PREFETCH); prefetchMap.set('practitioner', PRACTITIONER_PREFETCH); prefetchMap.set('patient', PATIENT_PREFETCH); prefetchMap.set('medicationRequests', ALL_REQUESTS_PREFETCH); - // prefetchMap.set("ServiceRequest", SERVICE_REQUEST_BUNDLE); - // prefetchMap.set("Encounter", ENCOUNTER_BUNDLE); + + // Optional pharmacy prefetch based on settings + if (includePharmacy && pharmacyId) { + const PHARMACY_PREFETCH = new PrefetchTemplate(`HealthcareService/${pharmacyId}`); + prefetchMap.set('pharmacy', PHARMACY_PREFETCH); + } return prefetchMap; } @@ -47,27 +55,49 @@ export class PrefetchTemplate { return paramElementMap; } - static generateQueries(requestBundle, patientReference, userReference, ...prefetchKeys) { + static generateQueries( + requestBundle, + patientReference, + userReference, + settings = null, + ...prefetchKeys + ) { + const prefetchMap = PrefetchTemplate.generatePrefetchMap(settings); + const paramElementMap = PrefetchTemplate.generateParamElementMap(); + var resolvedQueries = new Map(); for (var i = 0; i < prefetchKeys.length; i++) { var prefetchKey = prefetchKeys[i]; + if (!prefetchKey || !prefetchMap.has(prefetchKey)) continue; var query = prefetchMap.get(prefetchKey).getQuery(); // Regex source: https://regexland.com/all-between-specified-characters/ var parametersToFill = query.match(/(?<={{).*?(?=}})/gs); var resolvedQuery = query.slice(); - for (var j = 0; j < parametersToFill.length; j++) { - var unresolvedParameter = parametersToFill[j]; - var resolvedParameter; - if (requestBundle) { - resolvedParameter = PrefetchTemplate.resolveParameter(unresolvedParameter, requestBundle); - } else { - if (unresolvedParameter === 'context.patientId') { - resolvedParameter = patientReference; - } else if (unresolvedParameter === 'context.userId') { - resolvedParameter = userReference; + + if (parametersToFill) { + for (var j = 0; j < parametersToFill.length; j++) { + var unresolvedParameter = parametersToFill[j]; + var resolvedParameter; + if (requestBundle) { + resolvedParameter = PrefetchTemplate.resolveParameter( + unresolvedParameter, + requestBundle, + paramElementMap + ); + } else { + if (unresolvedParameter === 'context.patientId') { + resolvedParameter = patientReference; + } else if (unresolvedParameter === 'context.userId') { + resolvedParameter = userReference; + } + } + if (resolvedParameter) { + resolvedQuery = resolvedQuery.replace( + '{{' + unresolvedParameter + '}}', + resolvedParameter + ); } } - resolvedQuery = resolvedQuery.replace('{{' + unresolvedParameter + '}}', resolvedParameter); } resolvedQueries.set(prefetchKey, resolvedQuery); } @@ -89,8 +119,9 @@ export class PrefetchTemplate { } } - static resolveParameter(unresolvedParameter, requestBundle) { + static resolveParameter(unresolvedParameter, requestBundle, paramElementMap) { const paramField = paramElementMap.get(unresolvedParameter); + if (!paramField) return null; const resolvedParameter = PrefetchTemplate.getProp(requestBundle, paramField); return resolvedParameter; } @@ -104,7 +135,4 @@ export class PrefetchTemplate { getQuery() { return this.query; } -} - -const prefetchMap = PrefetchTemplate.generatePrefetchMap(); -const paramElementMap = PrefetchTemplate.generateParamElementMap(); +} \ No newline at end of file diff --git a/src/components/App.jsx b/src/components/App.jsx index de99d55..f24fc4b 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -28,10 +28,24 @@ const App = () => { } /> - } /> + + + + } + /> } /> {/* forcibly enter backoffice workflow */} - } /> + + + + } + /> { - -

Filter patient list

- { - setInput(newInputValue.toLowerCase()); - }} - options={listOfPatients[0].map(item => item.name)} - renderInput={params => } - /> -

- Showing {getFilteredLength(input, listOfPatients)} of {props.searchablePatients.length}{' '} - records -

-
+ +

Filter patient list

+ { + setInput(newInputValue.toLowerCase()); + }} + options={listOfPatients[0].map(item => item.name)} + renderInput={params => } + /> +

+ Showing {getFilteredLength(input, listOfPatients)} of{' '} + {props.searchablePatients.length} records +

+
- diff --git a/src/components/RequestBox/RequestBox.jsx b/src/components/RequestBox/RequestBox.jsx index f982324..c402427 100644 --- a/src/components/RequestBox/RequestBox.jsx +++ b/src/components/RequestBox/RequestBox.jsx @@ -182,9 +182,7 @@ const RequestBox = props => { let userId = prefetchedResources?.practitioner?.id; if (!userId) { - console.log( - 'Practitioner not populated from prefetch, using user: ' + user - ); + console.log('Practitioner not populated from prefetch, using user: ' + user); userId = user; } diff --git a/src/components/RequestDashboard/Home.jsx b/src/components/RequestDashboard/Home.jsx index 9ce8648..da8a9b0 100644 --- a/src/components/RequestDashboard/Home.jsx +++ b/src/components/RequestDashboard/Home.jsx @@ -64,36 +64,37 @@ const Home = props => { } return (
- - {section ? '' : } {/* spacer */} - {renderMainButton(patientButton, )} - {renderMainButton(taskButton, )} - {renderMainButton(settingsButton, )} - {section ? ( - - -   EHR Request Generator - - - ) : ( - - )} - {/* spacer */} - {/** */} - {section ? ( - - - {token.name} - - - - ) : ( - - )} - {/**/} - + + {section ? '' : } {/* spacer */} + {renderMainButton(patientButton, )} + {renderMainButton(taskButton, )} + {renderMainButton(settingsButton, )} + {section ? ( + + + +   EHR Request Generator + + + ) : ( + + )} + {/* spacer */} + {/** */} + {section ? ( + + + {token.name} + + + + ) : ( + + )} + {/**/} +
); }; diff --git a/src/components/RequestDashboard/PatientSection.jsx b/src/components/RequestDashboard/PatientSection.jsx index 0439e99..5eaeedf 100644 --- a/src/components/RequestDashboard/PatientSection.jsx +++ b/src/components/RequestDashboard/PatientSection.jsx @@ -8,7 +8,12 @@ const PatientSection = props => { return (
{state.startup ? ( - + ) : ( <>Loading... )} diff --git a/src/components/RequestDashboard/SettingsSection.jsx b/src/components/RequestDashboard/SettingsSection.jsx index 4bd6efd..e1a031e 100644 --- a/src/components/RequestDashboard/SettingsSection.jsx +++ b/src/components/RequestDashboard/SettingsSection.jsx @@ -39,7 +39,8 @@ import { SettingsContext } from '../../containers/ContextProvider/SettingsProvid const ENDPOINT = [ORDER_SIGN, ORDER_SELECT, PATIENT_VIEW, ENCOUNTER_START, REMS_ETASU]; const SettingsSection = props => { - const [state, dispatch, updateSetting, readSettings, saveSettings] = React.useContext(SettingsContext); + const [state, dispatch, updateSetting, readSettings, saveSettings] = + React.useContext(SettingsContext); const fieldHeaders = Object.keys(headerDefinitions) .map(key => ({ ...headerDefinitions[key], key })) @@ -94,7 +95,7 @@ const SettingsSection = props => { }); }; - const clearResource = + const clearResource = ({ ehrUrl, access_token }, type) => () => { console.log('Clear ' + type + 's from the EHR: ' + ehrUrl); @@ -151,19 +152,19 @@ const SettingsSection = props => { display: 'Clear EHR In-Progress Forms', key: 'clearQuestionnaireResponses', reset: clearResource, - parameter: 'QuestionnaireResponse' + parameter: 'QuestionnaireResponse' }, { display: 'Clear EHR Dispense Statuses', key: 'clearMedicationDispenses', reset: clearResource, - parameter: 'MedicationDispense' + parameter: 'MedicationDispense' }, { display: 'Clear EHR Tasks', key: 'clearTasks', reset: clearResource, - parameter: 'Task' + parameter: 'Task' }, { display: 'Reconnect EHR', @@ -184,17 +185,19 @@ const SettingsSection = props => { case 'input': return ( - { ( (state['useDefaultUser'] && key === 'defaultUser') || key != 'defaultUser' ) ? ( -
- updateSetting(key, event.target.value)} - sx={{ width: '100%' }} - /> -
- ) : ('') } + {(state['useDefaultUser'] && key === 'defaultUser') || key != 'defaultUser' ? ( +
+ updateSetting(key, event.target.value)} + sx={{ width: '100%' }} + /> +
+ ) : ( + '' + )}
); case 'check': diff --git a/src/components/RequestDashboard/TasksSection.jsx b/src/components/RequestDashboard/TasksSection.jsx index a342bf6..7bf0315 100644 --- a/src/components/RequestDashboard/TasksSection.jsx +++ b/src/components/RequestDashboard/TasksSection.jsx @@ -28,7 +28,7 @@ const taskStatus = Object.freeze({ }); const TasksSection = props => { const classes = useStyles(); - const {client, userName, userId} = props; + const { client, userName, userId } = props; const [tasks, setTasks] = useState([]); const [state] = React.useContext(SettingsContext); const [value, setValue] = useState(0); @@ -64,7 +64,8 @@ const TasksSection = props => { assignTaskToDefaultPractitioner(taskClone); } else if (val === 'patient') { assignTaskToPatient(taskClone); - } else { //'unassign' + } else { + //'unassign' unassignTask(taskClone); } handleAssignMenuClose(); @@ -120,7 +121,7 @@ const TasksSection = props => { fetchTasks(); }); } - } + }; const assignTaskToRequester = task => { if (task) { task = washTask(task); @@ -129,7 +130,7 @@ const TasksSection = props => { // assign to requester if available if (task?.requester) { - user = task.requester?.reference + user = task.requester?.reference; } task.owner = { reference: user @@ -139,7 +140,7 @@ const TasksSection = props => { fetchTasks(); }); } - };; + }; const assignTaskToDefaultPractitioner = task => { if (task) { task = washTask(task); @@ -190,14 +191,16 @@ const TasksSection = props => { if (state.patient && state.patient.id) { identifier = `Task?patient=${state.patient.id}`; } - client.request(identifier, { resolveReferences: ['for', 'owner', 'requester'] }).then(request => { - console.log(request); - if (request && request.entry) { - setTasks(request.entry.map(e => e.resource)); - } else { - setTasks([]); - } - }); + client + .request(identifier, { resolveReferences: ['for', 'owner', 'requester'] }) + .then(request => { + console.log(request); + if (request && request.entry) { + setTasks(request.entry.map(e => e.resource)); + } else { + setTasks([]); + } + }); }; useEffect(() => { fetchTasks(); @@ -291,18 +294,20 @@ const TasksSection = props => { {assignOptions.map(({ id, display }) => { // only give the 'Assign to requester if the requester is available' - if (((id === 'me') && userId && userName) - || ((id === 'requester') && (anchorAssign?.task?.requester)) - || ((id === 'defaultPractitioner') && (!anchorAssign?.task?.requester)) - || (id != 'me') && (id != 'requester') && (id != 'defaultPractitioner')) { - return ( - { - handleChangeAssign(anchorAssign?.task, id); - }} - >{`${display}`} - ); + if ( + (id === 'me' && userId && userName) || + (id === 'requester' && anchorAssign?.task?.requester) || + (id === 'defaultPractitioner' && !anchorAssign?.task?.requester) || + (id != 'me' && id != 'requester' && id != 'defaultPractitioner') + ) { + return ( + { + handleChangeAssign(anchorAssign?.task, id); + }} + >{`${display}`} + ); } })} diff --git a/src/components/RequestDashboard/styles.jsx b/src/components/RequestDashboard/styles.jsx index 2eab437..cc74b42 100644 --- a/src/components/RequestDashboard/styles.jsx +++ b/src/components/RequestDashboard/styles.jsx @@ -157,7 +157,7 @@ export default makeStyles( color: 'white !important', borderColor: 'white !important', marginRight: '5px !important', - marginLeft: '20px !important', + marginLeft: '20px !important' } }), diff --git a/src/components/SMARTBox/PatientBox.jsx b/src/components/SMARTBox/PatientBox.jsx index 9b4e4ed..a0d2358 100644 --- a/src/components/SMARTBox/PatientBox.jsx +++ b/src/components/SMARTBox/PatientBox.jsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useContext } from 'react'; import { getAge, getDrugCodeFromMedicationRequest } from '../../util/fhir'; +import { SettingsContext } from '../../containers/ContextProvider/SettingsProvider'; import './smart.css'; import { Button } from '@mui/material'; import Tooltip from '@mui/material/Tooltip'; @@ -18,6 +19,8 @@ import { } from '../../util/util'; const PatientBox = props => { + const [globalState] = useContext(SettingsContext); + const [state, setState] = useState({ request: '', deviceRequests: {}, @@ -50,7 +53,7 @@ const PatientBox = props => { responseExpirationDays, request, launchUrl, - showButtons, + showButtons } = props; const medicationColumns = [ @@ -173,7 +176,6 @@ const PatientBox = props => { client .request(urlQuery) .then(response => { - console.log(response); return response; }) .then(resource => { @@ -202,9 +204,11 @@ const PatientBox = props => { request, patientReference, userReference, + globalState, 'request', 'patient', - 'practitioner' + 'practitioner', + globalState.includePharmacyInPreFetch ? 'pharmacy' : undefined ); fetchResources(queries); @@ -218,9 +222,11 @@ const PatientBox = props => { request, patientReference, userReference, + globalState, 'patient', 'practitioner', - 'medicationRequests' + 'medicationRequests', + globalState.includePharmacyInPreFetch ? 'pharmacy' : undefined ); fetchResources(queries); } @@ -613,7 +619,9 @@ const PatientBox = props => { ) - ) : ""} + ) : ( + '' + )} {props.showButtons ? ( state.showQuestionnaires ? ( diff --git a/src/containers/BackOffice/BackOffice.jsx b/src/containers/BackOffice/BackOffice.jsx index facd8b3..0d3da7b 100644 --- a/src/containers/BackOffice/BackOffice.jsx +++ b/src/containers/BackOffice/BackOffice.jsx @@ -10,7 +10,7 @@ import useStyles from './styles'; import { logout } from '../../util/auth'; -const BackOffice = (props) => { +const BackOffice = props => { const classes = useStyles(); const { client, token } = props; @@ -19,39 +19,35 @@ const BackOffice = (props) => { }, []); return ( - - { token && client ? ( - - -
- -
-
- -

EHR Back Office

+ {token && client ? ( + +
+ +
+
+ +

+ EHR Back Office{' '} +

+
+ + {token.name} + +
- - {token.name} - - -
- -
- - - + +
+ + ) : ( - ) } - + )} - - ); }; diff --git a/src/containers/BackOffice/Dashboard.jsx b/src/containers/BackOffice/Dashboard.jsx index 86cb9da..a479250 100644 --- a/src/containers/BackOffice/Dashboard.jsx +++ b/src/containers/BackOffice/Dashboard.jsx @@ -7,75 +7,74 @@ import { SettingsContext } from '../ContextProvider/SettingsProvider'; import TaskTab from './TaskTab'; function a11yProps(index) { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}` - }; - } + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}` + }; +} export default function Dashboard(props) { - const { client, token } = props; - const [headerStyle, setHeaderStyle] = useState(undefined); - const [globalState, dispatch, updateSetting, readSettings] = React.useContext(SettingsContext); - console.log('global state patient -- > ', globalState.patient); + const { client, token } = props; + const [headerStyle, setHeaderStyle] = useState(undefined); + const [globalState, dispatch, updateSetting, readSettings] = React.useContext(SettingsContext); + console.log('global state patient -- > ', globalState.patient); + useEffect(() => { + readSettings(); + const updateScrollState = () => { + var threshold = 10; + if (window.scrollY > threshold) { + setHeaderStyle('true'); + } else { + setHeaderStyle(undefined); + } + }; + document.addEventListener('scroll', updateScrollState); + return () => document.removeEventListener('scroll', updateScrollState); + }, []); - useEffect(() => { - readSettings(); - const updateScrollState = () => { - var threshold = 10; - if (window.scrollY > threshold) { - setHeaderStyle("true"); - } else { - setHeaderStyle(undefined); - } - } - document.addEventListener("scroll", updateScrollState); - return () => document.removeEventListener("scroll", updateScrollState); - }, []); + const [tabIndex, setValue] = useState(0); - const [tabIndex, setValue] = useState(0); + const handleChange = (event, newValue) => { + setValue(newValue); + }; - const handleChange = (event, newValue) => { - setValue(newValue); - }; + return ( +
+ + + + + + + + - return ( -
- - - - - - - - - - - - {tabIndex === 0 && ( - - - - )} - {tabIndex === 1 && ( - - - - )} - - + + + {tabIndex === 0 && ( + + + + )} + {tabIndex === 1 && ( + + - -
- ); -} \ No newline at end of file + )} +
+ + +
+
+ ); +} diff --git a/src/containers/BackOffice/TaskTab.jsx b/src/containers/BackOffice/TaskTab.jsx index 5b8ef19..4db81f2 100644 --- a/src/containers/BackOffice/TaskTab.jsx +++ b/src/containers/BackOffice/TaskTab.jsx @@ -60,10 +60,14 @@ const TaskTab = props => { getPatients(); } // if use default user is set, use default user otherwise use logged in user if set - let currentUser = globalState.useDefaultUser ? globalState.defaultUser : (token.userId ? token.userId : globalState.defaultUser); - setState(prevState => ({...prevState, user: currentUser})); + let currentUser = globalState.useDefaultUser + ? globalState.defaultUser + : token.userId + ? token.userId + : globalState.defaultUser; + setState(prevState => ({ ...prevState, user: currentUser })); }, []); - + const updateStateElement = (elementName, text) => { if (elementName === 'patient') { setState(prevState => ({ ...prevState, patient: text })); @@ -78,47 +82,57 @@ const TaskTab = props => { })); } }; - + const updateStateMap = (elementName, key, text) => { setState(prevState => ({ ...prevState, [elementName]: { ...prevState[elementName], [key]: text } })); }; - + const clearState = () => { setState(prevState => ({ ...prevState, response: {} })); }; - + const handleChange = () => (event, isExpanded) => { setState(prevState => ({ ...prevState, expanded: isExpanded ? true : false })); }; - + return ( - - + + } aria-controls="panel1a-content" id="panel1a-header" > - - + {state.patient?.name ? ( // Display the first name -

{state.patient?.name?.[0]?.given?.[0] + ' ' + state.patient?.name?.[0]?.family}

+ +

+ {state.patient?.name?.[0]?.given?.[0] + ' ' + state.patient?.name?.[0]?.family} +

+
) : ( -

All Patients

+ +

All Patients

+
)}
@@ -147,18 +161,17 @@ const TaskTab = props => { )}
-
- - getPatients()} size="large"> - - - -
- - +
+ + getPatients()} size="large"> + + + +
+
); -} +}; export default TaskTab; diff --git a/src/containers/BackOffice/styles.jsx b/src/containers/BackOffice/styles.jsx index 558b986..7cbd6d7 100644 --- a/src/containers/BackOffice/styles.jsx +++ b/src/containers/BackOffice/styles.jsx @@ -1,35 +1,33 @@ -import {styled} from '@mui/system'; -import { AppBar, Stack } from '@mui/material' +import { styled } from '@mui/system'; +import { AppBar, Stack } from '@mui/material'; import { makeStyles } from '@mui/styles'; -export default makeStyles( - theme => ({ - loginIcon: { - color: theme.palette.common.white, - fontSize: '19px', - marginLeft: 'auto', - fontFamily: 'Verdana', - float: 'right', - marginRight: '20px', - verticalAlign: 'middle' - }, - whiteButton: { - color: 'white !important', - borderColor: 'white !important', - marginRight: '5px !important', - marginLeft: '20px !important', - }, - patientButton: { - padding: '10px', - 'padding-left': '20px', - 'padding-right': '20px' - } - }) -); - +export default makeStyles(theme => ({ + loginIcon: { + color: theme.palette.common.white, + fontSize: '19px', + marginLeft: 'auto', + fontFamily: 'Verdana', + float: 'right', + marginRight: '20px', + verticalAlign: 'middle' + }, + whiteButton: { + color: 'white !important', + borderColor: 'white !important', + marginRight: '5px !important', + marginLeft: '20px !important' + }, + patientButton: { + padding: '10px', + 'padding-left': '20px', + 'padding-right': '20px' + } +})); -export const StyledStack = styled(Stack)(({ theme, selected, disabled, isscrolled, highlight }) => ({ - position: 'relative', +export const StyledStack = styled(Stack)( + ({ theme, selected, disabled, isscrolled, highlight }) => ({ + position: 'relative', // width: '200px', margin: '0 5px', padding: '8px 20px 8px 20px', @@ -37,28 +35,33 @@ export const StyledStack = styled(Stack)(({ theme, selected, disabled, isscrolle borderRadius: '8px', cursor: disabled ? 'default' : 'pointer', color: disabled ? theme.palette.text.gray : theme.palette.text.primary, - backgroundColor: highlight ? theme.palette.background.primary : (selected && !isscrolled) ? theme.palette.common.offWhite : 'inherit', - transition: `border 0.5s ease`, + backgroundColor: highlight + ? theme.palette.background.primary + : selected && !isscrolled + ? theme.palette.common.offWhite + : 'inherit', + transition: 'border 0.5s ease', border: '1px solid transparent', - borderBottomColor: (selected && isscrolled) ? theme.palette.primary.main : 'transparent', - boxShadow: (selected && !isscrolled) ? 'rgba(0,0,0,0.2) 8px -2px 12px 2px' : 'none', + borderBottomColor: selected && isscrolled ? theme.palette.primary.main : 'transparent', + boxShadow: selected && !isscrolled ? 'rgba(0,0,0,0.2) 8px -2px 12px 2px' : 'none', '&:hover': { - border: disabled ? '' : `1px solid ${theme.palette.common.gray}` + border: disabled ? '' : `1px solid ${theme.palette.common.gray}` } -})); + }) +); export const GlossaryDiv = styled('div')(({ theme, isscrolled }) => ({ - backgroundColor: 'white', - zIndex: 1200, - padding: '30px', + backgroundColor: 'white', + zIndex: 1200, + padding: '30px' })); export const StyledAppBarAlt = styled(AppBar, { - shouldForwardProp: (prop) => prop !== 'open', + shouldForwardProp: prop => prop !== 'open' })(({ theme, open, isscrolled, drawerwidth }) => { - console.log(theme); - console.log(theme.palette); - return { + console.log(theme); + console.log(theme.palette); + return { marginTop: '15px', marginBottom: '15px', marginLeft: '2%', @@ -70,10 +73,10 @@ export const StyledAppBarAlt = styled(AppBar, { color: theme.palette.common.black, boxShadow: isscrolled ? 'rgba(0,0,0,0.2)' : 'none', borderRadius: '8px', - border: isscrolled ? '1px solid #c1c1c1' : 'none', + border: isscrolled ? '1px solid #c1c1c1' : 'none', ...(open && { - marginRight: `calc(2% + ${drawerwidth})`, - width: `calc(96% - ${drawerwidth}px)`, - - }), -}}); \ No newline at end of file + marginRight: `calc(2% + ${drawerwidth})`, + width: `calc(96% - ${drawerwidth}px)` + }) + }; +}); diff --git a/src/containers/ContextProvider/SettingsProvider.jsx b/src/containers/ContextProvider/SettingsProvider.jsx index caaa2be..3cf836d 100644 --- a/src/containers/ContextProvider/SettingsProvider.jsx +++ b/src/containers/ContextProvider/SettingsProvider.jsx @@ -7,10 +7,9 @@ export const SettingsContext = React.createContext({ dispatch: () => null, updateSetting: () => null, readSettings: () => null, - saveSettings: () => null, + saveSettings: () => null }); - export const SettingsProvider = ({ children }) => { const [state, dispatch] = React.useReducer(reducer, initialState); @@ -44,5 +43,9 @@ export const SettingsProvider = ({ children }) => { localStorage.setItem('reqgenSettings', JSON.stringify(headers)); }; - return {children}; + return ( + + {children} + + ); }; diff --git a/src/containers/Gateway/Gateway.jsx b/src/containers/Gateway/Gateway.jsx index 16f3e09..4b9f54c 100644 --- a/src/containers/Gateway/Gateway.jsx +++ b/src/containers/Gateway/Gateway.jsx @@ -31,7 +31,7 @@ const Gateway = props => { FHIR.oauth2.authorize({ clientId: clientId, scope: scope.join(' '), - redirectUri: props.redirect + (backOffice ? "/backoffice": ""), + redirectUri: props.redirect + (backOffice ? '/backoffice' : ''), iss: fhirUrl }); }; @@ -102,7 +102,17 @@ const Gateway = props => { )} /> - {setBackOffice(!backOffice)}} />} label="Back Office" /> + { + setBackOffice(!backOffice); + }} + /> + } + label="Back Office" + /> - + {state.patient?.name ? ( // Display the first name -

{state.patient?.name?.[0]?.given?.[0] + ' ' + state.patient?.name?.[0]?.family}

+ +

+ {state.patient?.name?.[0]?.given?.[0] + ' ' + state.patient?.name?.[0]?.family} +

+
) : ( -

All Patients

+ +

All Patients

+
)} diff --git a/src/util/auth.js b/src/util/auth.js index 5465731..74e4a99 100644 --- a/src/util/auth.js +++ b/src/util/auth.js @@ -34,9 +34,11 @@ function login() { } function logout() { - window.location.replace(`${env.get('VITE_AUTH').asString()}/realms/${env - .get('VITE_REALM') - .asString()}/protocol/openid-connect/logout`); + window.location.replace( + `${env.get('VITE_AUTH').asString()}/realms/${env + .get('VITE_REALM') + .asString()}/protocol/openid-connect/logout` + ); } /** diff --git a/src/util/data.js b/src/util/data.js index 819f044..910383c 100644 --- a/src/util/data.js +++ b/src/util/data.js @@ -1,6 +1,16 @@ import env from 'env-var'; const headerDefinitions = { + includePharmacyInPreFetch: { + display: 'Include Pharmacy in Prefetch', + type: 'check', + default: env.get('VITE_USE_PHARMACY_IN_PREFETCH').asBool() + }, + pharmacyId: { + display: 'Pharmacy ID', + type: 'input', + default: env.get('VITE_PHARMACY_ID').asString() || 'pharm0111' + }, useIntermediary: { display: 'Use Intermediary', type: 'check', @@ -109,24 +119,32 @@ const CDS_SERVICE = 'cds-services'; const ETASU_ENDPOINT = 'GuidanceResponse/$rems-etasu'; const serviceEndpoints = { - 'order-sign': CDS_SERVICE+'/rems-'+ORDER_SIGN, - 'order-select': CDS_SERVICE+'/rems-'+ORDER_SELECT, - 'patient-view': CDS_SERVICE+'/rems-'+PATIENT_VIEW, - 'encounter-start': CDS_SERVICE+'/rems-'+ENCOUNTER_START, - '$rems-etasu': '4_0_0/'+ETASU_ENDPOINT + 'order-sign': CDS_SERVICE + '/rems-' + ORDER_SIGN, + 'order-select': CDS_SERVICE + '/rems-' + ORDER_SELECT, + 'patient-view': CDS_SERVICE + '/rems-' + PATIENT_VIEW, + 'encounter-start': CDS_SERVICE + '/rems-' + ENCOUNTER_START, + '$rems-etasu': '4_0_0/' + ETASU_ENDPOINT }; - const medicationRequestToRemsAdmins = Object.freeze([ { rxnorm: 2183126, display: 'Turalio 200 MG Oral Capsule', endpoints: [ { endpointType: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' }, - { endpointType: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' }, - { endpointType: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }, - { endpointType: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }, - { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/'+ETASU_ENDPOINT } + { + endpointType: ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + endpointType: PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + endpointType: ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + }, + { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/' + ETASU_ENDPOINT } ] }, { @@ -134,10 +152,19 @@ const medicationRequestToRemsAdmins = Object.freeze([ display: 'Isotretinoin 20 MG Oral Capsule', endpoints: [ { endpointType: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' }, - { endpointType: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' }, - { endpointType: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }, - { endpointType: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }, - { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/'+ETASU_ENDPOINT } + { + endpointType: ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + endpointType: PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + endpointType: ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + }, + { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/' + ETASU_ENDPOINT } ] }, { @@ -145,10 +172,19 @@ const medicationRequestToRemsAdmins = Object.freeze([ display: 'TIRF 200 UG Oral Transmucosal Lozenge', endpoints: [ { endpointType: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' }, - { endpointType: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' }, - { endpointType: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }, - { endpointType: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }, - { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/'+ETASU_ENDPOINT } + { + endpointType: ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + endpointType: PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + endpointType: ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + }, + { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/' + ETASU_ENDPOINT } ] }, { @@ -156,10 +192,19 @@ const medicationRequestToRemsAdmins = Object.freeze([ display: 'Addyi 100 MG Oral Tablet', endpoints: [ { endpointType: ORDER_SIGN, remsAdmin: 'http://localhost:8090/cds-services/rems-order-sign' }, - { endpointType: ORDER_SELECT, remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' }, - { endpointType: PATIENT_VIEW, remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' }, - { endpointType: ENCOUNTER_START, remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' }, - { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/'+ETASU_ENDPOINT } + { + endpointType: ORDER_SELECT, + remsAdmin: 'http://localhost:8090/cds-services/rems-order-select' + }, + { + endpointType: PATIENT_VIEW, + remsAdmin: 'http://localhost:8090/cds-services/rems-patient-view' + }, + { + endpointType: ENCOUNTER_START, + remsAdmin: 'http://localhost:8090/cds-services/rems-encounter-start' + }, + { endpointType: REMS_ETASU, remsAdmin: 'http://localhost:8090/4_0_0/' + ETASU_ENDPOINT } ] } ]); diff --git a/src/util/fhir.js b/src/util/fhir.js index 69f751f..7ad5bd3 100644 --- a/src/util/fhir.js +++ b/src/util/fhir.js @@ -68,7 +68,7 @@ function createMedicationFromMedicationRequest(medicationRequest) { medication.id = medicationRequest?.id + '-med'; if (medicationRequest.medicationCodeableConcept) { medication.code = medicationRequest.medicationCodeableConcept; - } else if (medicationRequest.medicationReference) { + } else if (medicationRequest.medicationReference) { const reference = medicationRequest?.medicationReference; medication.code = undefined; medicationRequest?.contained?.every(e => {