diff --git a/src/actions/action-types.js b/src/actions/action-types.js
index a758f25..02e2de6 100644
--- a/src/actions/action-types.js
+++ b/src/actions/action-types.js
@@ -58,6 +58,10 @@ export const STORE_MED_DOSAGE_AMOUNT = 'STORE_MED_DOSAGE_AMOUNT';
export const STORE_DATE = 'STORE_DATE';
export const TOGGLE_DATE = 'TOGGLE_DATE';
+// Medication Order on RxSign
+export const STORE_DISPENSE_REQUEST = 'STORE_DISPENSE_REQUEST';
+export const ORDER_SIGN_BUTTON_PRESS = 'ORDER_SIGN_BUTTON_PRESS';
+
// Order Imaging
export const APPLY_PAMA_RATING = 'APPLY_PAMA_RATING';
export const UPDATE_STUDY = 'UPDATED_STUDY';
diff --git a/src/actions/medication-sign-actions.js b/src/actions/medication-sign-actions.js
new file mode 100644
index 0000000..e2809e8
--- /dev/null
+++ b/src/actions/medication-sign-actions.js
@@ -0,0 +1,94 @@
+import * as types from './action-types';
+
+/**
+ * Sets the user input from the medication select input box
+ * @param {*} input - User input string
+ */
+export function storeUserMedInput(input) {
+ return {
+ type: types.STORE_USER_MED_INPUT,
+ input,
+ };
+}
+
+/**
+ * Sets the specific medication from the medication select input box
+ * @param {*} medication - String of the medication ID
+ */
+export function storeUserChosenMedication(medication) {
+ return {
+ type: types.STORE_USER_CHOSEN_MEDICATION,
+ medication,
+ };
+}
+
+/**
+ * Sets the medication amount and frequency set on the UI in the store
+ * @param {*} amount - Dosage amount of the medication to take
+ * @param {*} frequency - String dosage frequency of the medication
+ */
+export function storeMedDosageAmount(amount, frequency) {
+ return {
+ type: types.STORE_MED_DOSAGE_AMOUNT,
+ amount,
+ frequency,
+ };
+}
+
+/**
+ * Sets the dispense request on the UI in the store
+ * @param {*} supplyDuration - Duration of the expected supply dispense
+ */
+export function storeDispenseRequest(supplyDuration) {
+ return {
+ type: types.STORE_DISPENSE_REQUEST,
+ supplyDuration,
+ };
+}
+
+/**
+ * Sets the date for the medication to be taken at a specific time (range)
+ * @param {*} range - String stating the date is the 'start' or 'end' date
+ * @param {*} date - String of the date
+ */
+export function storeDate(range, date) {
+ return {
+ type: types.STORE_DATE,
+ range,
+ date,
+ };
+}
+
+/**
+ * Toggle the start or end date so that it is either included or excluded from the MedicationOrder FHIR object in the request
+ * @param {*} range - String stating the date is the 'start' or 'end' date
+ */
+export function toggleDate(range) {
+ return {
+ type: types.TOGGLE_DATE,
+ range,
+ };
+}
+
+/**
+ * Call service when sign order button is selected
+ */
+export function signOrder(event) {
+ return {
+ type: types.ORDER_SIGN_BUTTON_PRESS,
+ event,
+ };
+}
+
+/**
+ * Takes action on the user-clicked suggestion from a card. The suggestion will be the suggestion chosen
+ * from the CDS service response (exact format from specification).
+ *
+ * @param {*} suggestion - Object containing the suggestion chosen from the user (see format here: https://cds-hooks.org/specification/current/#suggestion)
+ */
+export function takeSuggestion(suggestion) {
+ return {
+ type: types.TAKE_SUGGESTION,
+ suggestion,
+ };
+}
diff --git a/src/components/CardDemo/card-demo.jsx b/src/components/CardDemo/card-demo.jsx
index 12e00dd..452c203 100644
--- a/src/components/CardDemo/card-demo.jsx
+++ b/src/components/CardDemo/card-demo.jsx
@@ -175,7 +175,7 @@ export class CardDemo extends Component {
value={this.props.tempUserJson || exampleCode}
ref={(el) => { this.cm = el; }}
onChange={this.updateCard}
- style={{ 'fontFamily': 'Inconsolata, Menlo, Consolas, monospace !important' }}
+ style={{ fontFamily: 'Inconsolata, Menlo, Consolas, monospace !important' }}
options={options}
/>
diff --git a/src/components/ConfigureServices/configure-services.jsx b/src/components/ConfigureServices/configure-services.jsx
index 7472bbc..eaf88ae 100644
--- a/src/components/ConfigureServices/configure-services.jsx
+++ b/src/components/ConfigureServices/configure-services.jsx
@@ -39,10 +39,11 @@ export class ConfigureServices extends Component {
this.handleCloseModal = this.handleCloseModal.bind(this);
}
- componentWillReceiveProps(nextProps) {
- if (this.props.isOpen !== nextProps.isOpen) {
- this.setState({ isOpen: nextProps.isOpen });
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.isOpen !== prevState.isOpen) {
+ return ({ isOpen: nextProps.isOpen });
}
+ return null;
}
handleCloseModal() {
diff --git a/src/components/FhirServerEntry/fhir-server-entry.jsx b/src/components/FhirServerEntry/fhir-server-entry.jsx
index b91c350..ec8111e 100644
--- a/src/components/FhirServerEntry/fhir-server-entry.jsx
+++ b/src/components/FhirServerEntry/fhir-server-entry.jsx
@@ -77,10 +77,11 @@ export class FhirServerEntry extends Component {
this.handleResetDefaultServer = this.handleResetDefaultServer.bind(this);
}
- componentWillReceiveProps(nextProps) {
- if (this.props.isOpen !== nextProps.isOpen) {
- this.setState({ isOpen: nextProps.isOpen });
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.isOpen !== prevState.isOpen) {
+ return ({ isOpen: nextProps.isOpen });
}
+ return null;
}
handleCloseModal() {
diff --git a/src/components/Header/header.jsx b/src/components/Header/header.jsx
index f41d4da..b4e86f3 100644
--- a/src/components/Header/header.jsx
+++ b/src/components/Header/header.jsx
@@ -159,7 +159,7 @@ export class Header extends Component {
// If the tab is clicked again, make sure the Sandbox is qualified to call out to EHR's based
// on current context (i.e. for the Rx View, ensure a medication has been prescribed before
// re-invoking the services on that hook if the Rx View tab is clicked multiple times)
- if (hook === 'order-select') {
+ if (hook === 'order-select' || hook === 'order-sign') {
const medicationPrescribed = state.medicationState.decisions.prescribable
&& state.medicationState.medListPhase === 'done';
if (medicationPrescribed) {
@@ -293,6 +293,7 @@ export class Header extends Component {
+
diff --git a/src/components/MainView/main-view.jsx b/src/components/MainView/main-view.jsx
index 072f6f8..28b5ac1 100644
--- a/src/components/MainView/main-view.jsx
+++ b/src/components/MainView/main-view.jsx
@@ -13,6 +13,7 @@ import styles from './main-view.css';
import Header from '../Header/header';
import PatientView from '../PatientView/patient-view';
import RxView from '../RxView/rx-view';
+import RxSign from '../RxSign/rx-sign';
import Pama from '../Pama/pama';
import ContextView from '../ContextView/context-view';
import FhirServerEntry from '../FhirServerEntry/fhir-server-entry';
@@ -111,7 +112,7 @@ export class MainView extends Component {
async componentDidMount() {
// Set the loading spinner face-up
this.props.setLoadingStatus(true);
- const validHooks = ['patient-view', 'order-select'];
+ const validHooks = ['patient-view', 'order-select', 'order-sign'];
let parsedHook = this.getQueryParam('hook');
const parsedScreen = this.getQueryParam('screen');
if (validHooks.indexOf(parsedHook) < 0) {
@@ -215,6 +216,7 @@ export class MainView extends Component {
const hookView = {
'patient-view': ,
'rx-view': ,
+ 'rx-sign': ,
pama: ,
}[this.props.screen];
diff --git a/src/components/PatientEntry/patient-entry.jsx b/src/components/PatientEntry/patient-entry.jsx
index e1be94c..5396f0d 100644
--- a/src/components/PatientEntry/patient-entry.jsx
+++ b/src/components/PatientEntry/patient-entry.jsx
@@ -70,10 +70,11 @@ export class PatientEntry extends Component {
this.handleChange = this.handleChange.bind(this);
}
- componentWillReceiveProps(nextProps) {
- if (this.props.isOpen !== nextProps.isOpen) {
- this.setState({ isOpen: nextProps.isOpen });
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.isOpen !== prevState.isOpen) {
+ return ({ isOpen: nextProps.isOpen });
}
+ return null;
}
handleCloseModal() {
diff --git a/src/components/RxSign/rx-sign.css b/src/components/RxSign/rx-sign.css
new file mode 100644
index 0000000..d064e38
--- /dev/null
+++ b/src/components/RxSign/rx-sign.css
@@ -0,0 +1,47 @@
+.rx-sign {
+ height: auto;
+ display: inline-block;
+ padding: 30px;
+ margin: 0 0 20px;
+ vertical-align: top;
+ width: 100%;
+}
+
+.half-view {
+ width: 50%;
+}
+
+.view-title {
+ padding: 0 0 10px;
+ margin: 0 0 10px;
+ font-size: 1.5em;
+ letter-spacing: -0.025em;
+ color: #384E77; /* $color-primary */
+ border-bottom: 2px solid #eee;
+}
+
+.dose-instruction {
+ border: 1px solid #ddd;
+ padding: 5px 5px 0 10px;
+}
+
+.dosage-amount {
+ margin-right: 3em;
+}
+
+.dosage-timing {
+ margin-top: 1em;
+}
+
+@media (max-width: 975px) {
+ .rx-sign {
+ padding: 10px;
+ display: inline-block;
+ position: relative;
+ }
+
+ .half-view {
+ width: 100%;
+ display: inline-block;
+ }
+}
diff --git a/src/components/RxSign/rx-sign.jsx b/src/components/RxSign/rx-sign.jsx
new file mode 100644
index 0000000..1d599a3
--- /dev/null
+++ b/src/components/RxSign/rx-sign.jsx
@@ -0,0 +1,447 @@
+/* eslint-disable react/forbid-prop-types */
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import forIn from 'lodash/forIn';
+import cx from 'classnames';
+import Field from 'terra-form-field';
+// import Checkbox from 'terra-form-checkbox';
+import Select from 'react-select';
+import SelectField from 'terra-form-select';
+import Text from 'terra-text';
+import Input, { InputField } from 'terra-form-input';
+import DatePicker from 'terra-date-picker';
+import List, { Item } from 'terra-list';
+import Button from 'terra-button';
+
+import debounce from 'debounce';
+
+import cdsExecution from '../../middleware/cds-execution';
+import CardList from '../CardList/card-list';
+import PatientBanner from '../PatientBanner/patient-banner';
+import styles from './rx-sign.css';
+import { createFhirResource } from '../../reducers/medication-reducers';
+
+import {
+ storeUserMedInput, storeUserChosenMedication,
+ storeMedDosageAmount, storeDispenseRequest, storeDate, toggleDate,
+ takeSuggestion, signOrder,
+} from '../../actions/medication-sign-actions';
+
+import * as types from '../../actions/action-types';
+
+cdsExecution.registerTriggerHandler('rx-sign/order-sign', {
+ needExplicitTrigger: types.ORDER_SIGN_BUTTON_PRESS,
+ onSystemActions: () => { },
+ onMessage: () => { },
+ generateContext: (state) => {
+ const { fhirVersion } = state.fhirServerState;
+ const resource = createFhirResource(fhirVersion, state.patientState.currentPatient.id, state.medicationState);
+
+ return {
+ draftOrders: {
+ resourceType: 'Bundle',
+ entry: [{ resource }],
+ },
+ };
+ },
+});
+
+const propTypes = {
+ /**
+ * Flag to determine if the CDS Developer Panel view is visible
+ */
+ isContextVisible: PropTypes.bool.isRequired,
+ /**
+ * Patient resource in context
+ */
+ patient: PropTypes.object,
+ /**
+ * Array of medications a user may choose from at a given moment
+ */
+ medications: PropTypes.arrayOf(PropTypes.object),
+ /**
+ * Prescribed medicine chosen by the user for the patient
+ */
+ prescription: PropTypes.object,
+ /**
+ * Hash detailing the dosage and frequency of the prescribed medicine
+ */
+ medicationInstructions: PropTypes.object,
+ /**
+ * Hash detailing the supply duration of the prescribed medicine
+ */
+ dispenseRequest: PropTypes.object,
+ /**
+ * Hash detailing the start/end dates of the prescribed medication
+ */
+ prescriptionDates: PropTypes.object,
+ /**
+ * Coding code from the selected Condition resource in context
+ */
+ selectedConditionCode: PropTypes.string,
+ /**
+ * Function for storing user input when the medication field changes
+ */
+ onMedicationChangeInput: PropTypes.func.isRequired,
+ /**
+ * Function to signal a chosen medication
+ */
+ chooseMedication: PropTypes.func.isRequired,
+ /**
+ * Function to signal a change in the dosage instructions (amount or frequency)
+ */
+ updateDosageInstructions: PropTypes.func.isRequired,
+ /**
+ * Function to signal a change in the dispense request (supplyDuration)
+ */
+ updateDispenseRequest: PropTypes.func.isRequired,
+ /**
+ * Function to signal a change in a date (start or end)
+ */
+ updateDate: PropTypes.func.isRequired,
+ /**
+ * Function to signal a change in the toggled status of the date (start or end)
+ */
+ toggleEnabledDate: PropTypes.func.isRequired,
+ /**
+ * Function to signal the selected service to be called
+ */
+ signOrder: PropTypes.func.isRequired,
+ /**
+ * Function callback to take a specific suggestion from a card
+ */
+ takeSuggestion: PropTypes.func.isRequired,
+};
+
+/**
+ * Left-hand side on the mock-EHR view that displays the cards and relevant UI for the order-select hook.
+ * The services are not called until a medication is chosen, or a change in prescription is made
+ */
+export class RxSign extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ /**
+ * Value of the input box for medication
+ */
+ value: '',
+ /**
+ * Coding code of the Condition chosen from a dropdown list for the patient
+ */
+ conditionCode: '',
+ /**
+ * Coding display of the Condition chosen from a dropdown list for the patient
+ */
+ conditionDisplay: '',
+ /**
+ * Tracks the dosage amount chosen from the form field
+ */
+ dosageAmount: 1,
+ /**
+ * Tracks the dosage frequency chosen from the form field
+ */
+ dosageFrequency: 'daily',
+ /**
+ * Tracks the supply duration chosen from the form field
+ */
+ supplyDuration: 1,
+ /**
+ * Tracks the start date value and toggle of the prescription
+ */
+ startRange: {
+ enabled: true,
+ value: undefined,
+ },
+ /**
+ * Tracks the end date value and toggle of the prescription
+ */
+ endRange: {
+ enabled: true,
+ value: undefined,
+ },
+ };
+
+ this.changeMedicationInput = this.changeMedicationInput.bind(this);
+ this.selectCondition = this.selectCondition.bind(this);
+ this.changeDosageAmount = this.changeDosageAmount.bind(this);
+ this.changeDosageFrequency = this.changeDosageFrequency.bind(this);
+ this.changeSupplyDuration = this.changeSupplyDuration.bind(this);
+ this.selectStartDate = this.selectStartDate.bind(this);
+ this.selectEndDate = this.selectEndDate.bind(this);
+ this.toggleEnabledDate = this.toggleEnabledDate.bind(this);
+ this.signOrder = this.signOrder.bind(this);
+ }
+
+ /**
+ * Update any incoming values that change for state
+ */
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.medicationInstructions.number !== prevState.dosageAmount
+ || nextProps.medicationInstructions.frequency !== prevState.dosageFrequency
+ || nextProps.medicationInstructions.supplyDuration !== prevState.supplyDuration
+ || nextProps.selectedConditionCode !== prevState.conditionCode
+ || nextProps.prescriptionDates.start.value !== prevState.startRange.value
+ || nextProps.prescriptionDates.end.value !== prevState.endRange.value) {
+ return ({
+ conditionCode: nextProps.selectedConditionCode,
+ dosageAmount: nextProps.medicationInstructions.number,
+ dosageFrequency: nextProps.medicationInstructions.frequency,
+ supplyDuration: nextProps.medicationInstructions.supplyDuration,
+ startRange: {
+ // enabled: nextProps.startRange.enabled,
+ value: nextProps.prescriptionDates.start.value,
+ },
+ endRange: {
+ // enabled: nextProps.endRange.enabled,
+ value: nextProps.prescriptionDates.end.value,
+ },
+ });
+ }
+ return null;
+ }
+
+ changeMedicationInput(event) {
+ this.setState({ value: event.target.value });
+ debounce(this.props.onMedicationChangeInput(event.target.value), 50);
+ }
+
+ // Note: A second parameter (selected value) is supplied automatically by the Terra onChange function for the Form Select component
+ selectCondition(event) {
+ this.props.chooseCondition(event.value);
+ this.setState({ conditionCode: event.value });
+ this.setState({ conditionDisplay: event.label });
+ }
+
+ // Note: Bound the dosage amount to a value between 1 and 5
+ changeDosageAmount(event) {
+ let transformedNumber = Number(event.target.value) || 1;
+ if (transformedNumber > 5) { transformedNumber = 5; }
+ if (transformedNumber < 1) { transformedNumber = 1; }
+ this.setState({ dosageAmount: transformedNumber });
+ this.props.updateDosageInstructions(transformedNumber, this.state.dosageFrequency);
+ }
+
+ // Note: A second parameter (selected value) is supplied automatically by the Terra onChange function for the Form Select component
+ changeDosageFrequency(event, value) {
+ this.setState({ dosageFrequency: value });
+ this.props.updateDosageInstructions(this.state.dosageAmount, value);
+ }
+
+ changeSupplyDuration(event) {
+ let transformedNumber = Number(event.target.value) || 1;
+ if (transformedNumber > 90) { transformedNumber = 90; }
+ if (transformedNumber < 1) { transformedNumber = 1; }
+ this.setState({ supplyDuration: transformedNumber });
+ this.props.updateDispenseRequest(transformedNumber, this.state.supplyDuration);
+ }
+
+ // Note: A second parameter (date value) is supplied automatically by the Terra onChange function for the DatePicker component
+ selectStartDate(event, value) {
+ const newStartRange = {
+ enabled: this.state.startRange.enabled,
+ value,
+ };
+ this.setState({
+ startRange: newStartRange,
+ });
+ this.props.updateDate('start', newStartRange);
+ }
+
+ // Note: A second parameter (date value) is supplied automatically by the Terra onChange function for the DatePicker component
+ selectEndDate(event, value) {
+ const newEndRange = {
+ enabled: this.state.endRange.enabled,
+ value,
+ };
+ this.setState({
+ endRange: newEndRange,
+ });
+ this.props.updateDate('end', newEndRange);
+ }
+
+ toggleEnabledDate(event, range) {
+ this.setState({ [`${range}Range`]: event.target.value });
+ this.props.toggleEnabledDate(range);
+ }
+
+ signOrder(event) {
+ this.props.signOrder(event);
+ }
+
+ /**
+ * Create an array of key-value pair objects that React Select component understands
+ * given the Conditions present for the patient
+ */
+ createDropdownConditions() {
+ const conditions = [];
+ forIn(this.props.patient.conditionsResources, (c) => {
+ const { code } = c.resource.code.coding[0];
+ conditions.push({
+ value: code,
+ label: c.resource.code.text,
+ });
+ });
+ return conditions;
+ }
+
+ render() {
+ const isHalfView = this.props.isContextVisible ? styles['half-view'] : '';
+ const medicationArray = this.props.medications;
+
+ return (
+