diff --git a/.env b/.env index c2e72b7..d42d2f7 100644 --- a/.env +++ b/.env @@ -9,6 +9,7 @@ REACT_APP_SEND_RX_ENABLED = true PORT=4040 REACT_APP_CLIENT_ID = app-login REACT_APP_CLIENT_SCOPES = launch openid profile user/Patient.read patient/Patient.read user/Practitioner.read +REACT_APP_DEVELOPER_MODE = true GENERATE_SOURCEMAP=false BROWSER=none diff --git a/README.md b/README.md index c2f94cd..5c3b76c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Following are a list of modifiable paths: | REACT_APP_PHARMACY_SERVER_BASE | `http://localhost:5051` | | REACT_APP_ETASU_STATUS_ENABLED | `true` | | REACT_APP_PHARMACY_STATUS_ENABLED | `true` | +| REACT_APP_DEVELOPER_MODE | `true` | | REACT_APP_SEND_RX_ENABLED | `true` | | PORT | `4040`| diff --git a/src/views/Questionnaire/QuestionnaireForm.css b/src/views/Questionnaire/QuestionnaireForm.css index 644b08e..899d6d0 100644 --- a/src/views/Questionnaire/QuestionnaireForm.css +++ b/src/views/Questionnaire/QuestionnaireForm.css @@ -47,10 +47,19 @@ } .submit-button-panel { - float:right; margin-right: 10px; margin-bottom: 10px; } + + .btn-row { + display: flex; + } + + .error-text { + text-align: end; + font-style: italic; + color: #8b0000; + } .submit-button { margin-left: 5px; @@ -74,7 +83,6 @@ } .status-panel { - float:left; margin-left: 10px; margin-bottom: 10px; } diff --git a/src/views/Questionnaire/QuestionnaireForm.tsx b/src/views/Questionnaire/QuestionnaireForm.tsx index fcf33ef..16a74e6 100644 --- a/src/views/Questionnaire/QuestionnaireForm.tsx +++ b/src/views/Questionnaire/QuestionnaireForm.tsx @@ -27,6 +27,8 @@ import { searchQuestionnaire } from './questionnaireUtil'; import './QuestionnaireForm.css'; +import { Button, Typography } from '@mui/material'; +import Tooltip from '@mui/material/Tooltip'; import Client from 'fhirclient/lib/Client'; import ConfigData from '../../config.json'; @@ -56,6 +58,7 @@ interface QuestionnaireProps { updateQuestionnaire: (n: Questionnaire) => void; fhirVersion: string; filterChecked: boolean; + ignoreRequiredChecked: boolean; filterFieldsFn: (n: boolean) => void; renderButtons: (n: Element) => void; adFormResponseFromServer?: QuestionnaireResponse; @@ -199,8 +202,6 @@ export function QuestionnaireForm(props: QuestionnaireProps) { } }); const loadAndMergeForms = (newResponse: QuestionnaireResponse | null) => { - console.log(JSON.stringify(props.qform)); - console.log(JSON.stringify(newResponse)); let lform = LForms.Util.convertFHIRQuestionnaireToLForms( props.qform, @@ -225,7 +226,6 @@ export function QuestionnaireForm(props: QuestionnaireProps) { ); } - console.log(lform); LForms.Util.addFormToPage(lform, questionnaireFormId); const specificForm = document.getElementById(questionnaireFormId); @@ -1032,62 +1032,101 @@ export function QuestionnaireForm(props: QuestionnaireProps) { return isAdaptiveForm() && props.qform && props.qform.item && props.qform.item.length > 0; }; + const isFilledOut = () => { + // if checked to ignore required fields, return true to enable the submit button + if (props.ignoreRequiredChecked) { + return true; + } else { + // check if form is fully filled out based on required fields + const requiredFieldErrors = formValidationErrors ? formValidationErrors.filter((error) => { + return error.includes('requires a value'); + }) : []; + return !(formValidationErrors && requiredFieldErrors.length); + } + }; + + // Get tooltip for Submit button + const getMissingFieldsTooltip = () => { + const tooltip = isFilledOut() ? 'Submit to REMS admin' : 'Fill out missing fields'; + return {tooltip}; + }; + + // Get missing fields to display + const getMissingFields = () => { + const fields: string[] = []; + const requiredFieldErrors = formValidationErrors ? formValidationErrors.filter((error) => { + return error.includes('requires a value'); + }) : []; + if (requiredFieldErrors.length) { + requiredFieldErrors.forEach((err) => { + const name = err.split(' requires a value')[0]; + fields.push(name); + }); + } + return fields.join(', '); + }; + const getDisplayButtons = () => { if (!isAdaptiveForm()) { return ( -
- - - +
+
+ + + + + + + +
+ {!isFilledOut() ?

You must include a value for {getMissingFields()}

: <>}
); } else { if (props.adFormCompleted) { return (
- + + +
); } else { return (
{isAdaptiveFormWithoutItem() ? ( - + ) : null} {isAdaptiveFormWithItem() ? ( - + ) : null}
); @@ -1640,15 +1679,17 @@ export function QuestionnaireForm(props: QuestionnaireProps) { {!props.adFormCompleted ? (
{' '} - +
) : null}
) : null} - {!isAdaptive ?
Form Loaded: {formLoaded}
: null} - {getDisplayButtons()} +
+ {!isAdaptive ?
Form Loaded: {formLoaded}
:
} + {getDisplayButtons()} +
); } diff --git a/src/views/Questionnaire/SmartApp.tsx b/src/views/Questionnaire/SmartApp.tsx index f9015b3..f6f62db 100644 --- a/src/views/Questionnaire/SmartApp.tsx +++ b/src/views/Questionnaire/SmartApp.tsx @@ -25,6 +25,7 @@ import executeElm from './elm/executeElm'; import PatientSelect from './components/PatientSelect/PatientSelect'; import RemsInterface from './components/RemsInterface/RemsInterface'; import { createRoot } from 'react-dom/client'; +import * as env from 'env-var'; interface SmartAppProps { standalone: boolean; @@ -111,6 +112,9 @@ export function SmartApp(props: SmartAppProps) { const [adFormCompleted, setAdFormCompleted] = useState(false); const [adFormResponseFromServer, setAdFormResponseFromServer] = useState(); const [formElement, setFormElement] = useState(); + const [ignoreRequiredCheckbox, setIgnoreRequiredCheckbox] = useState(false); + + const showRequiredCheckbox: boolean = env.get('REACT_APP_DEVELOPER_MODE').asBool() ? true : false; const smart = props.smartClient; let FHIR_VERSION = 'r4'; const toggleOverlay = () => { @@ -339,6 +343,19 @@ export function SmartApp(props: SmartAppProps) { setFormFilled(document.querySelector('input.ng-empty:not([disabled])') == null); } }; + + // update the ignore required checkbox + const updateRequired = (defaultFilter: boolean) => { + let checked: boolean, requiredCheckbox: HTMLInputElement; + if (!defaultFilter) { + requiredCheckbox = document.getElementById('required-fields-checkbox') as HTMLInputElement; + checked = requiredCheckbox ? requiredCheckbox.checked : false; + } else { + checked = true; + } + setIgnoreRequiredCheckbox(checked); + }; + const fetchResourcesAndExecuteCql = ( order: string, coverage: string, @@ -573,6 +590,15 @@ export function SmartApp(props: SmartAppProps) { filterCheckbox.checked = filterChecked; } }; + + // update required checkbox ref + const onRequiredCheckboxRefChange = () => { + const requiredCheckbox = document.getElementById(questionnaire ? `required-fields-checkbox-${questionnaire.id}` : 'required-fields-checkbox') as HTMLInputElement; + if (requiredCheckbox != null) { + requiredCheckbox.checked = ignoreRequiredCheckbox; + } + }; + const getFhirWrapper = (fhirVersion: string): cqlfhir.FHIRWrapper => { if (fhirVersion == 'r4') { return cqlfhir.FHIRWrapper.FHIRv400(); @@ -614,6 +640,19 @@ export function SmartApp(props: SmartAppProps) { ref={onFilterCheckboxRefChange} > + { showRequiredCheckbox ? +
+ {' '} + { + updateRequired(false); + }} + id={questionnaire ? `required-fields-checkbox-${questionnaire.id}` : 'required-fields-checkbox'} + ref={onRequiredCheckboxRefChange} + > +
+ :
}
); @@ -661,6 +700,7 @@ export function SmartApp(props: SmartAppProps) { renderButtons={renderButtons} filterFieldsFn={filter} filterChecked={filterChecked} + ignoreRequiredChecked={ignoreRequiredCheckbox} formFilled={formFilled} updateQuestionnaire={updateQuestionnaire} ehrLaunch={ehrLaunch}