diff --git a/.github/workflows/docker-cd-dev.yml b/.github/workflows/docker-cd-dev.yml index cb807a3f..3bb6c929 100644 --- a/.github/workflows/docker-cd-dev.yml +++ b/.github/workflows/docker-cd-dev.yml @@ -27,4 +27,4 @@ jobs: context: . platforms: linux/amd64,linux/arm64 push: true - tags: codexrems/crd-request-generator:REMSvCurrent + tags: codexrems/request-generator:REMSvCurrent diff --git a/.github/workflows/docker-cd.yml b/.github/workflows/docker-cd.yml index 5a327945..ee467398 100644 --- a/.github/workflows/docker-cd.yml +++ b/.github/workflows/docker-cd.yml @@ -27,4 +27,4 @@ jobs: context: . platforms: linux/amd64,linux/arm64 push: true - tags: codexrems/crd-request-generator:REMSvCurrent + tags: codexrems/request-generator:REMSvCurrent diff --git a/.github/workflows/docker-tag-cd.yml b/.github/workflows/docker-tag-cd.yml index a9a19b76..b695536d 100644 --- a/.github/workflows/docker-tag-cd.yml +++ b/.github/workflows/docker-tag-cd.yml @@ -23,7 +23,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: codexrems/crd-request-generator + images: codexrems/request-generator - name: Login to DockerHub if: github.event_name != 'pull_request' diff --git a/.vscode/launch.json b/.vscode/launch.json index 624ae545..2b18427d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,11 +7,11 @@ { "type": "chrome", "request": "launch", - "name": "Debug CRD-Request-Generator (Launch Docker)", + "name": "Debug Request-Generator (Launch Docker)", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}", "sourceMapPathOverrides": { - "/home/node/app/crd-request-generator/*": "${webRoot}/*", + "/home/node/app/request-generator/*": "${webRoot}/*", }, "runtimeArgs": [ "--remote-debugging-port=9222" @@ -20,18 +20,18 @@ { "type": "chrome", "request": "attach", - "name": "Debug CRD-Request-Generator (Attach Docker)", + "name": "Debug Request-Generator (Attach Docker)", "port": 9222, "urlFilter": "http://localhost:3000/*", "webRoot": "${workspaceFolder}", "sourceMapPathOverrides": { - "/home/node/app/crd-request-generator/*": "${webRoot}/*", + "/home/node/app/request-generator/*": "${webRoot}/*", } }, { "type": "chrome", "request": "launch", - "name": "Debug CRD-Request-Generator (Launch Local)", + "name": "Debug Request-Generator (Launch Local)", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}", "runtimeArgs": [ @@ -42,7 +42,7 @@ "type": "chrome", "request": "attach", "port": 9222, - "name": "Debug CRD-Request-Generator (Attach Local)", + "name": "Debug Request-Generator (Attach Local)", "urlFilter": "http://localhost:3000/*", "webRoot": "${workspaceFolder}", } diff --git a/Dockerfile b/Dockerfile index 1e73e6af..efc4563f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM node:14-alpine -WORKDIR /home/node/app/crd-request-generator +WORKDIR /home/node/app/request-generator COPY --chown=node:node . . RUN npm install COPY --chown=node:node . . diff --git a/Dockerfile.dev b/Dockerfile.dev index fa117b4f..2d637e9b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,5 @@ FROM node:14-alpine -WORKDIR /home/node/app/crd-request-generator +WORKDIR /home/node/app/request-generator COPY --chown=node:node . . RUN npm install EXPOSE 3000 diff --git a/README.md b/README.md index c7acb0f4..ba9f4c6f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# CRD Request Generator -This subproject provides a small web application that is capable of generating CRD requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). +# Request Generator +This subproject provides a small web application that is capable of generating requests and displaying the CDS Hooks cards that are provided as a response. This project is written in JavaScript and runs in [node.js](https://nodejs.org/en/). ## Running the request generator standalone 1. Install node.js 2. Clone the repository - * `git clone https://github.com/mcode/crd-request-generator.git` + * `git clone https://github.com/mcode/request-generator.git` 3. Install the dependencies * `cd request-generator` * `npm install` diff --git a/src/components/ConsoleBox/ConsoleBox.js b/src/components/ConsoleBox/ConsoleBox.js index e637834b..fed877d4 100644 --- a/src/components/ConsoleBox/ConsoleBox.js +++ b/src/components/ConsoleBox/ConsoleBox.js @@ -1,3 +1,4 @@ +import { Button } from '@mui/material'; import React, {Component} from 'react'; export default class ConsoleBox extends Component { @@ -46,17 +47,15 @@ export default class ConsoleBox extends Component { let i = 0; return (
- - - - -
- - {this.props.logs.map(element => { - i++; - return
{element.content}
- }) } -
+ +
+ {this.props.logs.map(element => { + i++; + return
{element.content}
+ }) } +
) diff --git a/src/components/ConsoleBox/consoleBox.css b/src/components/ConsoleBox/consoleBox.css index d6a68ce2..69359024 100644 --- a/src/components/ConsoleBox/consoleBox.css +++ b/src/components/ConsoleBox/consoleBox.css @@ -45,22 +45,12 @@ transition-delay: 0s; } -.showHeader:hover{ - color:#CCCCCC; -} - .showHeader{ width:100%; } -.showHeader::after{ - content:"-"; -} -.collapseHeader::after{ - content:"+"; -} .collapseHeader{ - width:25px; + width: 250px; color: black; text-align:center; border-width: 1px 1px 1px 1px; diff --git a/src/components/DisplayBox/DisplayBox.js b/src/components/DisplayBox/DisplayBox.js index 735a9ea8..761a1196 100644 --- a/src/components/DisplayBox/DisplayBox.js +++ b/src/components/DisplayBox/DisplayBox.js @@ -1,12 +1,11 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import FHIR from "fhirclient"; -import styles from './card-list.css'; -import Button from 'terra-button'; -import TerraCard from 'terra-card'; -import Text from 'terra-text'; +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'; @@ -164,8 +163,9 @@ export default class DisplayBox extends Component{ * Prevent the source link from opening in the same tab * @param {*} e - Event emitted when source link is clicked */ - launchSource(e) { + launchSource(e, link) { e.preventDefault(); + window.open(link.url, '_blank'); } exitSmart(e) { @@ -231,23 +231,23 @@ export default class DisplayBox extends Component{ if (!source.label) { return null; } let icon; if (source.icon) { - icon = Could not fetch icon; + icon = Could not fetch icon; } if (!this.props.isDemoCard) { return ( -
- Source: this.launchSource(e)}>{source.label} +
+ Source: this.launchSource(e, source)}>{source.label} {icon}
); } return ( -
+
Source: this.launchSource(e)} + onClick={e => this.launchSource(e, source)} > {source.label} @@ -280,13 +280,13 @@ export default class DisplayBox extends Component{ const card = JSON.parse(JSON.stringify(c)); // -- Summary -- - const summarySection = {card.summary}; + const summarySection =

{card.summary}

; // -- Source -- const sourceSection = card.source && Object.keys(card.source).length ? this.renderSource(card.source) : ''; // -- Detail (ReactMarkdown supports Github-flavored markdown) -- - const detailSection = card.detail ?
: None; + const detailSection = card.detail ?
:

None

; // -- Suggestions -- let suggestionsSection = []; @@ -299,10 +299,9 @@ export default class DisplayBox extends Component{ ); }); } @@ -311,54 +310,75 @@ export default class DisplayBox extends Component{ let linksSection; if (card.links) { card.links = this.modifySmartLaunchUrls(card) || card.links; - linksSection = card.links.map((link, ind) => ( - + ) + } + const pdfIcon = ; + return ( + ) + } + ); } const cardSectionHeaderStyle = { marginBottom: '2px', color: 'black' }; const builtCard = ( - -

Summary

-
{summarySection}
- -

Details

-
{detailSection}
- -
-
{sourceSection}
- -
- {suggestionsSection} -
-
- {linksSection} -
-

Type

-
{typeSection}
-
); + + + + + Summary + + + {summarySection} + +
+ + Details + + {detailSection} +
+ + {sourceSection} + +
+ + {linksSection} + +
+
); renderedCards.push(builtCard); }); + + if (renderedCards.length === 0) { + return
+ Nofication Cards ({renderedCards.length}) +
; + } + return
+ Nofication Cards ({renderedCards.length}) +
+ {renderedCards} +
+
; + } else { + return
+
; } - if (renderedCards.length === 0) { return
No Cards
; } - return
-
- {renderedCards} -
-
; } componentDidUpdate() { diff --git a/src/components/DisplayBox/card-list.css b/src/components/DisplayBox/card-list.css index e781a730..229f5a8c 100644 --- a/src/components/DisplayBox/card-list.css +++ b/src/components/DisplayBox/card-list.css @@ -2,9 +2,10 @@ padding: 15px; margin: 10px; background: #fcfcfc; - border: 2px solid #ddd; + border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px; border-left-width: 3px; + max-width: 800px; } .decision-card img { @@ -33,9 +34,9 @@ margin: 0 0 5px; } - .source-link { - color: blue; - text-decoration: none; + .source-link:hover { + color: rgb(0, 121, 190); + text-decoration: underline; } .card-summary { @@ -56,4 +57,14 @@ .alert-error { border-left-color: #333; - } \ No newline at end of file + } + + .links-section { + display: inline !important; + } + + .form-link { + display: flex; + align-items: center; + margin-right: 5px; + } diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 74dfa969..9c9fa0d5 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -11,6 +11,8 @@ import "./request.css"; import { PrefetchTemplate } from "../../PrefetchTemplate"; import { retrieveLaunchContext } from "../../util/util"; import env from 'env-var'; +import PersonIcon from '@mui/icons-material/Person'; +import { Button, ButtonGroup } from '@mui/material'; export default class RequestBox extends Component { constructor(props) { @@ -136,15 +138,23 @@ export default class RequestBox extends Component { }); }; + emptyField = empty; + renderPatientInfo() { const patient = this.state.patient; + if (Object.keys(patient).length === 0) { + return ( +
+
+ ); + } let name; if (patient.name) { name = ( {`${patient.name[0].given[0]} ${patient.name[0].family}`} ); } else { - name = "N/A"; + name = this.emptyField; } return (
@@ -153,13 +163,13 @@ export default class RequestBox extends Component {
Name: {name}
- Age: {patient.birthDate ? getAge(patient.birthDate) : "N/A"} + Age: {patient.birthDate ? getAge(patient.birthDate) : this.emptyField}
- Gender: {patient.gender ? patient.gender : "N/A"} + Gender: {patient.gender ? patient.gender : this.emptyField}
- State: {this.state.patientState ? this.state.patientState : "N/A"} + State: {this.state.patientState ? this.state.patientState : this.emptyField}
{this.renderOtherInfo()} {this.renderQRInfo()} @@ -174,14 +184,14 @@ export default class RequestBox extends Component { Coding
- Code: {this.state.code ? this.state.code : "N/A"} + Code: {this.state.code ? this.state.code : this.emptyField}
System:{" "} - {this.state.codeSystem ? shortNameMap[this.state.codeSystem] : "N/A"} + {this.state.codeSystem ? shortNameMap[this.state.codeSystem] : this.emptyField}
- Display: {this.state.display ? this.state.display : "N/A"} + Display: {this.state.display ? this.state.display : this.emptyField}
); @@ -191,16 +201,22 @@ export default class RequestBox extends Component { const qrResponse = this.state.response; return (
-
- In Progress Form -
-
Form: { qrResponse.questionnaire ? qrResponse.questionnaire : "N/A"}
-
- Author: {qrResponse.author ? qrResponse.author.reference : "N/A"} -
-
- Date: {qrResponse.authored ? qrResponse.authored : "N/A"} -
+ {qrResponse.questionnaire ? + <> +
+ In Progress Form +
+
Form: { qrResponse.questionnaire ? qrResponse.questionnaire : this.emptyField}
+
+ Author: {qrResponse.author ? qrResponse.author.reference : this.emptyField} +
+
+ Date: {qrResponse.authored ? qrResponse.authored : this.emptyField} +
+ + : +
+ }
); } @@ -417,7 +433,7 @@ export default class RequestBox extends Component { params['tokenResponse'] = {access_token: this.props.access_token.access_token}; } const disableSendToCRD = this.isOrderNotSelected() || this.props.loading ; - const disableLaunchDTR = this.isOrderNotSelected() && Object.keys(this.state.response).length === 0; + const disableLaunchDTR = !this.state.response.questionnaire; const disableSendRx = this.isOrderNotSelected() || this.props.loading; const disableLaunchSmartOnFhir = this.isPatientNotSelected(); return ( @@ -456,41 +472,39 @@ export default class RequestBox extends Component { )}
- +
- {this.state.patient.id ? this.state.patient.id : "N/A"} + {this.state.patient.id ? Patient ID: {this.state.patient.id} : No patient selected}
{this.renderPatientInfo()} {this.renderPrefetchedResources()}
-
- Deidentify Records - -
+ + {this.state.patient.id ? +
+ Deidentify Records + +
:
+ }
-
-
-
- - - - + {this.state.patient.id ? +
+ + + + + + +
+ : }
); } diff --git a/src/components/RequestBox/request.css b/src/components/RequestBox/request.css index b31ead0b..0e1e0845 100644 --- a/src/components/RequestBox/request.css +++ b/src/components/RequestBox/request.css @@ -1,4 +1,6 @@ .request-header{ + margin-top: 10px; + margin-left: 8px; border-width: 1px 1px 2px 2px; border-style: solid; border-color: transparent transparent black black; @@ -117,4 +119,13 @@ .questionnaire-response { width: 100%; padding:10px 10px 10px 0px; +} + +.action-btns { + margin-top: 5px; +} + +.empty-field { + color: dimgrey; + font-style: italic; } \ No newline at end of file diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index 13018ff6..4d07a1c2 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -3,7 +3,8 @@ import { Dropdown, Header } from 'semantic-ui-react' import { getAge } from "../../util/fhir"; import FHIR from "fhirclient"; import "./smart.css"; - +import { Button, IconButton } from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; export default class SMARTBox extends Component { constructor(props) { @@ -31,6 +32,12 @@ export default class SMARTBox extends Component { this.handleResponseChange = this.handleResponseChange.bind(this); } + componentDidMount() { + // get requests and responses on open of patients + this.getRequests() + this.getResponses(); + } + getCoding(request) { let code = null; if (request.resourceType === "DeviceRequest") { @@ -64,7 +71,7 @@ export default class SMARTBox extends Component { let option = { key: request.id, - text: "(" + code.code + ") " + code.display, + text: code.display + " (Medication request: " + code.code + ")", value: JSON.stringify(request), content: (
@@ -256,31 +263,33 @@ export default class SMARTBox extends Component { let returned = false; if (this.state.deviceRequests.data) { returned = true; - console.log(this.state.deviceRequests); this.state.deviceRequests.data.forEach((e) => { this.makeOption(e, options); }); } if (this.state.serviceRequests.data) { + returned = true; this.state.serviceRequests.data.forEach((e) => { this.makeOption(e, options); }); } if (this.state.medicationRequests.data) { + returned = true; this.state.medicationRequests.data.forEach((e) => { this.makeOption(e, options); }); } if (this.state.medicationDispenses.data) { + returned = true; this.state.medicationDispenses.data.forEach((e) => { this.makeOption(e, options); }) }; if (this.state.questionnaireResponses.data) { - returned = true; this.state.questionnaireResponses.data.forEach(qr => this.makeQROption(qr, responseOptions)); + returned = true; } let noResults = 'No results found.' @@ -293,15 +302,9 @@ export default class SMARTBox extends Component {
{ - this.updateValues(patient); - }} >
ID: {patient.id} - {/* - {text} - */}
Name:{" "} {name ? name : "N/A"} @@ -319,28 +322,37 @@ export default class SMARTBox extends Component { Request: - + { !options.length && returned ? + No reqeusts + : + + }
- + In Progress Form: + + + - + { !responseOptions.length && returned ? + No in progress forms : + + }
+
); diff --git a/src/components/SMARTBox/smart.css b/src/components/SMARTBox/smart.css index cdef0b26..0bb23c26 100644 --- a/src/components/SMARTBox/smart.css +++ b/src/components/SMARTBox/smart.css @@ -19,16 +19,12 @@ iframe{ .patient-selection-box{ width:100%; - height:100px; padding:10px; flex:.5; border-bottom:3px solid black; - background-color:#e2e2e2; -} - -.patient-selection-box:hover{ - background-color:white; - cursor: pointer; + display: grid; + grid-template-columns: 15% 35% 35% 10%; + column-gap: 5px; } .smartExit{ @@ -74,13 +70,10 @@ iframe{ .patient-info { display:inline-block; - width: 300px; } .request-info { - display: inline-block; - width:350px; - vertical-align: top; /* here */ + display: inherit; } @@ -93,4 +86,23 @@ iframe{ } .ui.header.text { font-size: 1em; +} + +.select-btn { + height: 40px; + align-self: center; +} + +.emptyForm { + margin-left: 8px; + color: dimgray; + font-style: italic; +} + +.ui.fluid.dropdown { + height: fit-content; +} + +.ui.dropdown>.text { + display: block; } \ No newline at end of file diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index 4f794a72..5473188f 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -148,6 +148,7 @@ export default class RequestBuilder extends Component { }).then(response => { clearTimeout(this.timeout) response.json().then((fhirResponse) => { + console.log(fhirResponse); if (fhirResponse && fhirResponse.status) { this.consoleLog("Server returned status " + fhirResponse.status + ": " @@ -160,6 +161,7 @@ export default class RequestBuilder extends Component { }) }).catch(() => { this.consoleLog("No response received from the server", types.error); + this.setState({ response: null }); this.setState({loading: false}); }); } catch (error) { @@ -391,16 +393,9 @@ export default class RequestBuilder extends Component { return (
- -
- - {/* {this.state.ehrLaunch? - - - :null} */}
@@ -432,22 +427,10 @@ export default class RequestBuilder extends Component {

- {/* */} - {/* - - - -
- -
*/} +
+
+
diff --git a/src/index.css b/src/index.css index ba377cd9..ce75eedf 100644 --- a/src/index.css +++ b/src/index.css @@ -308,6 +308,7 @@ input:not(:focus):not([value=""]):valid ~ .floating-label{ .nav-header{ margin-bottom: 10px; + height: 55px; padding:10px; border-bottom: 1px solid black; background-color: #005B94;