From 002d853fcf2732b232a0f8d07f5b56af723b025e Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Thu, 11 Jan 2024 14:50:25 -0500 Subject: [PATCH 1/4] Update initial table add and workflow --- src/components/SMARTBox/PatientBox.js | 170 ++++++++++++++++++++------ src/components/SMARTBox/smart.css | 32 +++-- 2 files changed, 156 insertions(+), 46 deletions(-) diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index ea337028..6c279ba2 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -8,6 +8,14 @@ import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import FormControl from '@mui/material/FormControl'; import Select from '@mui/material/Select'; +import MedicationIcon from '@mui/icons-material/Medication'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; export default class PatientBox extends Component { constructor(props) { @@ -22,7 +30,10 @@ export default class PatientBox extends Component { questionnaireResponses: {}, openRequests: false, openQuestionnaires: false, - questionnaireTitles: {} + questionnaireTitles: {}, + showMedications: false, + showQuestionnaires: false, + numInProgressForms: 0, }; this.handleRequestChange = this.handleRequestChange.bind(this); @@ -111,6 +122,8 @@ export default class PatientBox extends Component { let option = { key: request.id, text: code.display + ' (Medication request: ' + code.code + ')', + code: code.code, + name: code.display, value: JSON.stringify(request) }; options.push(option); @@ -356,6 +369,7 @@ export default class PatientBox extends Component { }) .then(result => { this.setState({ questionnaireResponses: result }); + this.setState({ numInProgressForms: result.data.length }); }) .then(() => this.getQuestionnaireTitles()); } @@ -390,11 +404,53 @@ export default class PatientBox extends Component { }; } + makeTable(columns, options, type) { + return ( + + + + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {options.map((row) => ( + + + {row.name} + + {row.code} + + + ))} + +
+
+ ); + } + render() { const patient = this.props.patient; let name = ''; + let fullName = ''; + let formatBirthdate = ''; if (patient.name) { name = {`${patient.name[0].given[0]} ${patient.name[0].family}`} ; + 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 @@ -432,6 +488,20 @@ export default class PatientBox extends Component { returned = true; } + console.log('options -- > ', options); + console.log('responseOptions -- > ', responseOptions); + const medicationColumns = [ + { id: 'name', label: 'Medication'}, + { id: 'code', label: 'Request #'}, + { id: 'action', label: ''} + ]; + + const questionnaireColumns = [ + { id: 'name', label: 'Medication'}, + { id: 'code', label: 'Request #'}, + { id: 'action', label: ''} + ]; + let noResults = 'No results found.'; if (!returned) { noResults = 'Loading...'; @@ -446,47 +516,58 @@ export default class PatientBox extends Component {
- Gender: {patient.gender} + Full Name: {fullName}
- Age: {getAge(patient.birthDate)} + Gender: {patient.gender.charAt(0).toUpperCase() + patient.gender.slice(1)} +
+
+ DoB/Age: {formatBirthdate} ({getAge(patient.birthDate)} years old)
-
- Request: - {!options.length && returned ? ( - No requests - ) : ( - this.makeDropdown( - options, - 'Select a medication request', - this.state.request, - this.handleRequestChange - ) - )} -
-
- - In Progress Form: - - - - - {!responseOptions.length && returned ? ( - No in progress forms - ) : ( - this.makeDropdown( - responseOptions, - 'Choose an in-progress form', - this.state.response, - this.handleResponseChange - ) - )} -
+
+ { this.state.showMedications ? + //
+ // Request: + // {!options.length && returned ? ( + // No requests + // ) : ( + // this.makeDropdown( + // options, + // 'Select a medication request', + // this.state.request, + // this.handleRequestChange + // ) + // )} + //
+ + : } + { this.state.showQuestionnaires ? + //
+ // + // In Progress Form: + // + // + // + // + // {!responseOptions.length && returned ? ( + // No in progress forms + // ) : ( + // this.makeDropdown( + // responseOptions, + // 'Choose an in-progress form', + // this.state.response, + // this.handleResponseChange + // ) + // )} + //
+ + : } + + +
+ { this.state.showMedications ? +
+ { this.makeTable(medicationColumns, options, 'medication') } +
+ : } + { this.state.showQuestionnaires ? +
+ { this.makeTable(questionnaireColumns, responseOptions, 'questionnaire') } +
+ : } + ); } diff --git a/src/components/SMARTBox/smart.css b/src/components/SMARTBox/smart.css index 243de77e..0024922a 100644 --- a/src/components/SMARTBox/smart.css +++ b/src/components/SMARTBox/smart.css @@ -11,9 +11,28 @@ html{ padding:10px 10px 15px 10px; flex:1; background-color: #ddd; - display: grid; - grid-template-columns: 15% 35% 35% 10%; - column-gap: 5px; + display: flex; + justify-content: space-between; +} +.patient-info { + display:inline-block; +} +.button-options { + display: flex; + column-gap: 12px; +} + +.patient-table-info { + /* display: flex; */ + margin-bottom: 10px; +} +tr:nth-child(odd) { + background-color: #ddd !important; +} + +.big-button { + display: flex !important; + flex-direction: column !important; } .patient-box{ @@ -63,12 +82,9 @@ html{ float:right; } -.patient-info { - display:inline-block; -} - .request-info { - display: inherit; + display: flex; + flex-direction: column; } From c306e5692909360bd72d06e4b7b1afc1fd5c4c19 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Wed, 24 Jan 2024 09:21:24 -0500 Subject: [PATCH 2/4] Update workflow --- .../PatientSearchBar/PatientSearchBar.js | 6 + src/components/RequestBox/RequestBox.js | 1 + src/components/SMARTBox/PatientBox.js | 273 +++++++++++------- src/components/SMARTBox/smart.css | 2 +- src/containers/RequestBuilder.js | 8 +- 5 files changed, 181 insertions(+), 109 deletions(-) diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js index 250428f4..6a73f2f7 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js @@ -78,6 +78,12 @@ export default function PatientSearchBar(props) { key={patient.id} patient={props.searchablePatients.find(item => item.id === patient.id)} client={props.client} + pimsUrl={props.pimsUrl} + prefetchedResources={props.prefetchedResources} + request={props.request} + response={props.response} + launchUrl={props.launchUrl} + submitInfo={props.submitInfo} callback={props.callback} callbackList={props.callbackList} callbackMap={props.callbackMap} diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 408367a2..98a801e1 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -158,6 +158,7 @@ export default class RequestBox extends Component { if (prefetchMap.size > 0) { return this.renderRequestResources(prefetchMap); } + return
; } renderRequestResources(requestResources) { diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index 6c279ba2..1e188195 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -1,13 +1,8 @@ import React, { Component } from 'react'; import { getAge, getDrugCodeFromMedicationRequest } from '../../util/fhir'; import './smart.css'; -import { Button, IconButton } from '@mui/material'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import Box from '@mui/material/Box'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import FormControl from '@mui/material/FormControl'; -import Select from '@mui/material/Select'; +import { Button } from '@mui/material'; +import Tooltip from '@mui/material/Tooltip'; import MedicationIcon from '@mui/icons-material/Medication'; import Paper from '@mui/material/Paper'; import Table from '@mui/material/Table'; @@ -16,6 +11,7 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; 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) { @@ -48,7 +44,6 @@ export default class PatientBox extends Component { this.getQuestionnaireTitles = this.getQuestionnaireTitles.bind(this); this.makeQROption = this.makeQROption.bind(this); this.handleResponseChange = this.handleResponseChange.bind(this); - this.makeDropdown = this.makeDropdown.bind(this); } componentDidMount() { @@ -91,30 +86,6 @@ export default class PatientBox extends Component { return code; } - makeDropdown(options, label, stateVar, stateChange) { - return ( - - - {label} - - - - ); - } makeOption(request, options) { const code = this.getCoding(request); @@ -155,16 +126,19 @@ export default class PatientBox extends Component { if (this.state.response) { const response = JSON.parse(this.state.response); - this.updateQRResponse(patient, response); + this.updateQRResponse(response); } } - updateQRResponse(patient, response) { + updatePatient(patient) { + this.props.callback('patient', patient); + } + + updateQRResponse(response) { this.props.callback('response', response); } fetchResources(queries) { - console.log(queries); var requests = []; this.props.callback('prefetchCompleted', false); queries.forEach((query, queryKey) => { @@ -308,8 +282,9 @@ export default class PatientBox extends Component { }); } - handleRequestChange(e) { - const data = e.target.value; + handleRequestChange(data, patient) { + // const data = e.target.value; + console.log('data in request vchange is -- > ', data); if (data) { let coding = this.getCoding(JSON.parse(data)); this.setState({ @@ -319,6 +294,18 @@ export default class PatientBox extends Component { display: coding.display, response: '' }); + // update prefetch request for the medication + const request = JSON.parse(data); + if ( + request.resourceType === 'DeviceRequest' || + request.resourceType === 'ServiceRequest' || + request.resourceType === 'MedicationRequest' || + request.resourceType === 'MedicationDispense' + ) { + this.updatePrefetchRequest(request, patient, this.props.defaultUser); + } else { + this.props.clearCallback(); + } } else { this.setState({ request: '' @@ -326,12 +313,13 @@ export default class PatientBox extends Component { } } - handleResponseChange(e) { - const data = e.target.value; + handleResponseChange(data) { if (data) { this.setState({ response: data }); + const response = JSON.parse(data); + this.updateQRResponse(response); } else { this.setState({ response: '' @@ -396,15 +384,84 @@ export default class PatientBox extends Component { makeQROption(qr) { const questionnaireTitle = this.state.questionnaireTitles[qr.questionnaire]; - const display = `${questionnaireTitle}: created at ${qr.authored}`; + // const display = `${questionnaireTitle}: created at ${qr.authored}`; return { key: qr.id, - text: display, + text: questionnaireTitle, + time: qr.authored, value: JSON.stringify(qr) }; } - makeTable(columns, options, type) { + isOrderNotSelected() { + return Object.keys(this.props.request).length === 0; + } + + /** + * Launch In progress From + */ + + relaunch = (data) => { + this.handleResponseChange(data); + this.props.callback('expanded', false); + this.buildLaunchLink(data).then(link => { + //e.preventDefault(); + window.open(link.url, '_blank'); + }); + }; + + async buildLaunchLink(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 (order) { + appContext += `order=${order}`; + + if (coverage) { + appContext += `&coverage=${coverage}`; + } + } + + const resp = JSON.parse(data); + if (Object.keys(resp).length > 0) { + response = `QuestionnaireResponse/${resp.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); + + const result = await retrieveLaunchContext(linkCopy, this.props.patient.id, this.props.client.state); + linkCopy = result; + return linkCopy; + } + + formIsSelected(value) { + return this.state.response !== value; + } + makeResponseTable(columns, options, type, patient) { return ( @@ -431,7 +488,49 @@ export default class PatientBox extends Component { {row.name} {row.code} - + + + + + ))} + +
+
+ ); + } + + makeQuestionnaireTable(columns, options, type, patient) { + return ( + + + + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {options.map((row) => ( + + + {row.text} + + {row.time} + + + ))} @@ -456,28 +555,23 @@ export default class PatientBox extends Component { // add all of the requests to the list of options let options = []; let responseOptions = []; - let returned = false; if (this.state.deviceRequests.data) { - returned = true; 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); }); @@ -485,11 +579,8 @@ export default class PatientBox extends Component { if (this.state.questionnaireResponses.data) { responseOptions = this.state.questionnaireResponses.data.map(qr => this.makeQROption(qr)); - returned = true; } - console.log('options -- > ', options); - console.log('responseOptions -- > ', responseOptions); const medicationColumns = [ { id: 'name', label: 'Medication'}, { id: 'code', label: 'Request #'}, @@ -497,15 +588,13 @@ export default class PatientBox extends Component { ]; const questionnaireColumns = [ - { id: 'name', label: 'Medication'}, - { id: 'code', label: 'Request #'}, + { id: 'name', label: 'Title'}, + { id: 'time', label: 'Created'}, { id: 'action', label: ''} ]; - let noResults = 'No results found.'; - if (!returned) { - noResults = 'Loading...'; - } + 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 (
@@ -527,69 +616,39 @@ export default class PatientBox extends Component {
{ this.state.showMedications ? - //
- // Request: - // {!options.length && returned ? ( - // No requests - // ) : ( - // this.makeDropdown( - // options, - // 'Select a medication request', - // this.state.request, - // this.handleRequestChange - // ) - // )} - //
- : } + : + + + + } { this.state.showQuestionnaires ? - //
- // - // In Progress Form: - // - // - // - // - // {!responseOptions.length && returned ? ( - // No in progress forms - // ) : ( - // this.makeDropdown( - // responseOptions, - // 'Choose an in-progress form', - // this.state.response, - // this.handleResponseChange - // ) - // )} - //
- : } - - - + : + + + + + + }
{ this.state.showMedications ?
- { this.makeTable(medicationColumns, options, 'medication') } + { this.makeResponseTable(medicationColumns, options, 'medication', patient) }
: } { this.state.showQuestionnaires ?
- { this.makeTable(questionnaireColumns, responseOptions, 'questionnaire') } + { this.makeQuestionnaireTable(questionnaireColumns, responseOptions, 'questionnaire', patient) }
: } - ); } diff --git a/src/components/SMARTBox/smart.css b/src/components/SMARTBox/smart.css index 0024922a..cecbd792 100644 --- a/src/components/SMARTBox/smart.css +++ b/src/components/SMARTBox/smart.css @@ -15,7 +15,7 @@ html{ justify-content: space-between; } .patient-info { - display:inline-block; + display: flex; } .button-options { display: flex; diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index eba3a64c..f56540ec 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -28,7 +28,7 @@ export default class RequestBuilder extends Component { patient: {}, expanded: false, patientList: [], - response: null, + response: {}, code: null, codeSystem: null, display: null, @@ -298,6 +298,12 @@ export default class RequestBuilder extends Component { getPatients = {this.getPatients} searchablePatients={this.state.patientList} client={this.props.client} + pimsUrl={this.state.pimsUrl} + prefetchedResources={this.state.prefetchedResources} + request={this.state.request} + response={this.state.response} + submitInfo={this.submit_info} + launchUrl={this.state.launchUrl} callback={this.updateStateElement} callbackList={this.updateStateList} callbackMap={this.updateStateMap} From e12fc31529773dcd1d252df88ee788c1d0682d65 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Wed, 24 Jan 2024 10:10:16 -0500 Subject: [PATCH 3/4] Clean up code base --- .../RequestBox/PatientSearchBar/PatientSearchBar.js | 4 ---- src/components/SMARTBox/PatientBox.js | 9 ++++----- src/containers/RequestBuilder.js | 4 ---- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js index 6a73f2f7..338b366f 100644 --- a/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js +++ b/src/components/RequestBox/PatientSearchBar/PatientSearchBar.js @@ -78,12 +78,8 @@ export default function PatientSearchBar(props) { key={patient.id} patient={props.searchablePatients.find(item => item.id === patient.id)} client={props.client} - pimsUrl={props.pimsUrl} - prefetchedResources={props.prefetchedResources} request={props.request} - response={props.response} launchUrl={props.launchUrl} - submitInfo={props.submitInfo} callback={props.callback} callbackList={props.callbackList} callbackMap={props.callbackMap} diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js index 1e188195..383fc395 100644 --- a/src/components/SMARTBox/PatientBox.js +++ b/src/components/SMARTBox/PatientBox.js @@ -283,8 +283,6 @@ export default class PatientBox extends Component { } handleRequestChange(data, patient) { - // const data = e.target.value; - console.log('data in request vchange is -- > ', data); if (data) { let coding = this.getCoding(JSON.parse(data)); this.setState({ @@ -294,6 +292,7 @@ export default class PatientBox extends Component { display: coding.display, response: '' }); + this.props.callback('response', ''); // update prefetch request for the medication const request = JSON.parse(data); if ( @@ -306,6 +305,8 @@ export default class PatientBox extends Component { } else { this.props.clearCallback(); } + // close the accordian after selecting a medication, can change if we want to keep open + this.props.callback('expanded', false); } else { this.setState({ request: '' @@ -434,6 +435,7 @@ export default class PatientBox extends Component { } } + // using data passed in instead of waiting for state/props variables to be updated const resp = JSON.parse(data); if (Object.keys(resp).length > 0) { response = `QuestionnaireResponse/${resp.id}`; @@ -458,9 +460,6 @@ export default class PatientBox extends Component { return linkCopy; } - formIsSelected(value) { - return this.state.response !== value; - } makeResponseTable(columns, options, type, patient) { return ( diff --git a/src/containers/RequestBuilder.js b/src/containers/RequestBuilder.js index f56540ec..a75d266c 100644 --- a/src/containers/RequestBuilder.js +++ b/src/containers/RequestBuilder.js @@ -298,11 +298,7 @@ export default class RequestBuilder extends Component { getPatients = {this.getPatients} searchablePatients={this.state.patientList} client={this.props.client} - pimsUrl={this.state.pimsUrl} - prefetchedResources={this.state.prefetchedResources} request={this.state.request} - response={this.state.response} - submitInfo={this.submit_info} launchUrl={this.state.launchUrl} callback={this.updateStateElement} callbackList={this.updateStateList} From eb1c765e4592cee7842d0ddfd51306086f601e75 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto Date: Fri, 26 Jan 2024 11:27:21 -0500 Subject: [PATCH 4/4] Add hover and click on full rows of table instead of button and patient select button at high level --- src/components/RequestBox/RequestBox.js | 6 --- src/components/SMARTBox/PatientBox.js | 63 +++++++++++++------------ src/components/SMARTBox/smart.css | 10 ++-- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js index 98a801e1..fe39836e 100644 --- a/src/components/RequestBox/RequestBox.js +++ b/src/components/RequestBox/RequestBox.js @@ -386,12 +386,6 @@ export default class RequestBox extends Component {
- {Object.keys(this.props.response).length ? - - : } - - + + this.handleRequestChange(row.value, patient)} + > + + {row.name} + + {row.code} + + ))}
@@ -517,20 +518,19 @@ export default class PatientBox extends Component { {options.map((row) => ( - - - {row.text} - - {row.time} - - - - + + this.relaunch(row.value)} + className='hover-row' + > + + {row.text} + + {row.time} + + ))} @@ -583,13 +583,11 @@ export default class PatientBox extends Component { const medicationColumns = [ { id: 'name', label: 'Medication'}, { id: 'code', label: 'Request #'}, - { id: 'action', label: ''} ]; const questionnaireColumns = [ { id: 'name', label: 'Title'}, - { id: 'time', label: 'Created'}, - { id: 'action', label: ''} + { id: 'time', label: 'Created'} ]; const medicationTooltip = options.length === 0 ? 'No medications found.' : `${options.length} medications available`; @@ -636,6 +634,13 @@ export default class PatientBox extends Component { } +
{ this.state.showMedications ? diff --git a/src/components/SMARTBox/smart.css b/src/components/SMARTBox/smart.css index cecbd792..f95cd6dd 100644 --- a/src/components/SMARTBox/smart.css +++ b/src/components/SMARTBox/smart.css @@ -14,9 +14,6 @@ html{ display: flex; justify-content: space-between; } -.patient-info { - display: flex; -} .button-options { display: flex; column-gap: 12px; @@ -29,6 +26,9 @@ html{ tr:nth-child(odd) { background-color: #ddd !important; } +.hover-row:hover { + background-color: #E7F1FF !important; +} .big-button { display: flex !important; @@ -100,9 +100,7 @@ tr:nth-child(odd) { } .select-btn { - height: 40px; - align-self: center; - margin-top: 25px !important; + height: 52px; } .emptyForm {