Skip to content
Merged

Dev #178

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ VITE_USE_PHARMACY_IN_PREFETCH = true
VITE_INTERMEDIARY = http://localhost:3003
VITE_DISABLE_MEDICATION_STATUS = false
VITE_PHARMACY_ID = pharm0111
VITE_PACIO_EHR_URL = https://gw.interop.community/paciosandbox2/open/Bundle
VITE_PACIO_NEW_PRESCRIBER_ID=pra1234
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,13 @@ Following are a list of modifiable paths:
| 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_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 |
| VITE_PHARMACY_ID | `pharm0111` | The pharmacy ID to use in the CDS Hooks Prefetch |
| HTTPS | `false` | Enable HTTPS for the server. Set to `true` to use HTTPS with valid certificate and key paths. |
| HTTPS_CERT_PATH | `server.cert` | Path to a certificate for encryption, allowing HTTPS. Unnecessary if using HTTP. |
| HTTPS_KEY_PATH | `server.key` | Path to a key for encryption, allowing HTTPS. Unnecessary if using HTTP. |
| VITE_HOOK_TO_SEND | `patient-view` | Specifies which CDS Hook type to send by default (e.g., patient-view, order-sign, order-select, encounter-start). |
| VITE_URL_FILTER | `http://localhost:3000/*` | URL filtering pattern for request validation. |
| VITE_DISABLE_MEDICATION_STATUS | `false` | When set to true, disables the medication status display in the UI. |

# 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.
Expand Down
13 changes: 6 additions & 7 deletions src/PrefetchTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
export class PrefetchTemplate {
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 includePharmacy =
settings?.includePharmacyInPreFetch ?? headerDefinitions.includePharmacyInPreFetch.default;
const pharmacyId = settings?.pharmacyId ?? headerDefinitions.pharmacyId.default;

const prefetchMap = new Map();

Expand Down Expand Up @@ -64,7 +63,7 @@ export class PrefetchTemplate {
) {
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];
Expand All @@ -73,7 +72,7 @@ export class PrefetchTemplate {
// Regex source: https://regexland.com/all-between-specified-characters/
var parametersToFill = query.match(/(?<={{).*?(?=}})/gs);
var resolvedQuery = query.slice();

if (parametersToFill) {
for (var j = 0; j < parametersToFill.length; j++) {
var unresolvedParameter = parametersToFill[j];
Expand Down Expand Up @@ -135,4 +134,4 @@ export class PrefetchTemplate {
getQuery() {
return this.query;
}
}
}
11 changes: 8 additions & 3 deletions src/components/RequestBox/RequestBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,13 @@ const RequestBox = props => {
* Send NewRx for new Medication to the Pharmacy Information System (PIMS)
*/
const sendRx = async () => {
console.log('Sending NewRx to: ' + pimsUrl);
console.log('Getting case number ');
// Use intermediary or direct based on toggle
const ncpdpEndpoint = globalState.usePharmacyIntermediary
? globalState.pharmacyIntermediaryUrl
: pimsUrl;

console.log('Sending NewRx to: ' + ncpdpEndpoint);
console.log('Getting case number');
const medication = createMedicationFromMedicationRequest(request);
const body = makeBody(medication);
const standardEtasuUrl = getMedicationSpecificEtasuUrl(
Expand Down Expand Up @@ -260,7 +265,7 @@ const RequestBox = props => {
const serializer = new XMLSerializer();

// Sending NewRx to the Pharmacy
fetch(pimsUrl, {
fetch(ncpdpEndpoint, {
method: 'POST',
//mode: 'no-cors',
headers: {
Expand Down
44 changes: 44 additions & 0 deletions src/components/RequestDashboard/Communication.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Button, Grid } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import useStyles from './styles';

const Communication = props => {
const classes = useStyles();
const { communication, deleteCommunication } = props;

const convertTimeStamp = timeStamp => {
const date = new Date(timeStamp);
return date.toLocaleString();
};

return (
<div>
<Grid container>
<Grid className={classes.communicationHeader} item xs={2}>
{`ID: ${communication.id}`}
</Grid>
<Grid className={classes.communicationHeader} item xs={8.7}>
{`Received: ${convertTimeStamp(communication.received)}`}
</Grid>
<Grid className={classes.communicationHeaderButton} item xs={1.3}>
<Button
fullWidth
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={() => {
deleteCommunication(communication.id);
}}
>
Clear
</Button>
</Grid>
<Grid className={classes.communicationDescription} item xs={12}>
{communication.payload[0].contentString}
</Grid>
</Grid>
</div>
);
};

export default Communication;
143 changes: 143 additions & 0 deletions src/components/RequestDashboard/CommunicationsDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { useEffect, useState } from 'react';

import { Button, Grid } from '@mui/material';
import NotificationsIcon from '@mui/icons-material/Notifications';
import Badge from '@mui/material/Badge';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import { Refresh } from '@mui/icons-material';

import { styled } from '@mui/material/styles';
import Paper from '@mui/material/Paper';
import Communication from './Communication';

const CommunicationsDialog = props => {
const { client, token } = props;
const [state, setState] = useState({
client: client,
token: token,
initialLoad: true,
communicationCount: 0,
communications: [],
open: false
});

const debugLog = message => {
console.log('CommunicationsDialog: ' + message);
};

useEffect(() => {
// reload on page load and dialog open
if (state.initialLoad) {
setState(prevState => ({ ...prevState, initialLoad: false }));
getCommunications();
}

const interval = setInterval(() => {
// page load...
getCommunications();
}, 1000 * 5); // reload every 5 seconds

return () => clearInterval(interval);
});

const getCommunications = () => {
if (state.client) {
// try to read communications from FHIR server
state.client
.request(`Communication?recipient=${props.token?.userId}`, {
graph: false,
flat: true
})
.then(bundle => {
loadCommunications(bundle);
});
}
};

const deleteCommunication = id => {
debugLog('deleteCommunication: ' + id);
if (id) {
state.client.delete(`Communication/${id}`).then(() => {
debugLog(`Deleted communication: ${id}`);
getCommunications();
});
}
};

const loadCommunications = bundle => {
let count = bundle.length;
setState(prevState => ({ ...prevState, communicationCount: count, communications: bundle }));
};

const handleClose = () => {
setState(prevState => ({ ...prevState, open: false }));
};

const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#EDF6FF',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'left',
color: theme.palette.text.secondary
}));

const renderCommunications = () => {
return (
<Grid container spacing={2} sx={{ mt: 2 }}>
{state.communications.map(communication => {
return (
<Grid item xs={12} sm={12}>
<Item>
<Communication
communication={communication}
deleteCommunication={deleteCommunication}
/>
</Item>
</Grid>
);
})}
</Grid>
);
};

return (
<span>
<span
onClick={() => {
setState(prevState => ({ ...prevState, open: true, initialLoad: true }));
}}
>
<Badge badgeContent={state.communicationCount} color="primary">
<NotificationsIcon sx={{ fontSize: 26, verticalAlign: 'middle' }} />
</Badge>
</span>
<Dialog fullWidth maxWidth="md" onClose={handleClose} open={state.open}>
<DialogTitle>
<Grid container>
<Grid item xs={10}>
<NotificationsIcon sx={{ fontSize: 26, verticalAlign: 'middle' }} />
Communications ({state.communicationCount})
</Grid>
<Grid item xs={2}>
<Button
variant="contained"
startIcon={<Refresh />}
onClick={() => {
getCommunications();
}}
>
Refresh
</Button>
</Grid>
</Grid>
</DialogTitle>

<DialogContent>{renderCommunications()}</DialogContent>
</Dialog>
</span>
);
};

export default CommunicationsDialog;
7 changes: 5 additions & 2 deletions src/components/RequestDashboard/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SettingsIcon from '@mui/icons-material/Settings';
import AccountBoxIcon from '@mui/icons-material/AccountBox';
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';

import CommunicationsDialog from './CommunicationsDialog';
import useStyles from './styles';
import PatientSection from './PatientSection';
import SettingsSection from './SettingsSection';
Expand Down Expand Up @@ -82,8 +83,10 @@ const Home = props => {
{/* spacer */}
{/** */}
{section ? (
<Grid className={classes.spacer} item xs={4}>
<Grid className={classes.spacer} item xs={5}>
<span className={classes.loginIcon}>
<CommunicationsDialog client={props.client} token={token} />
&nbsp;&nbsp;
<AccountBoxIcon sx={{ fontSize: 48, verticalAlign: 'middle' }} /> {token.name}
<Button variant="outlined" className={classes.whiteButton} onClick={logout}>
Logout
Expand Down Expand Up @@ -115,7 +118,7 @@ const Home = props => {
<TasksSection client={props.client} userName={token.name} userId={token.userId} />
</div>
<div className={settingsRenderClass}>
<SettingsSection client={props.client} />
<SettingsSection client={props.client} userId={token.userId} />
</div>
</div>
);
Expand Down
Loading
Loading