From 7d9bc32bbb186f16877567bf6095c7a1b63e25c4 Mon Sep 17 00:00:00 2001 From: Pavel R Date: Fri, 31 May 2024 13:56:48 +0200 Subject: [PATCH] WIP: Remove react-final-form from QRFormWrapper Ref: https://github.com/beda-software/sdc-ide/issues/66 --- .../components/choice/hook.ts | 5 +- .../components/choice/index.tsx | 45 +++---- .../components/choice/select.tsx | 14 +- .../components/decimal.tsx | 31 ++--- .../components/reference/index.tsx | 49 +++---- .../components/string.tsx | 31 ++--- .../BaseQuestionnaireResponseForm/index.tsx | 121 +++++++++++++++--- web/src/components/QRFormWrapper/index.tsx | 60 +++++++-- 8 files changed, 247 insertions(+), 109 deletions(-) diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/choice/hook.ts b/web/src/components/BaseQuestionnaireResponseForm/components/choice/hook.ts index 60a2ad7b..ef6b1454 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/choice/hook.ts +++ b/web/src/components/BaseQuestionnaireResponseForm/components/choice/hook.ts @@ -1,7 +1,7 @@ import { AnswerValue, QuestionItemProps } from '@beda.software/fhir-questionnaire/vendor/sdc-qrf'; import _ from 'lodash'; -import { isSuccess, RemoteDataResult, success } from 'aidbox-react/lib/libs/remoteData'; +import { RemoteDataResult, isSuccess, success } from 'aidbox-react/lib/libs/remoteData'; import { applyDataTransformer, service } from 'aidbox-react/lib/services/service'; import { @@ -10,6 +10,7 @@ import { ValueSet, } from 'shared/src/contrib/aidbox'; + export function getDisplay(value: AnswerValue): string { const valueType = _.keys(value)[0]; //@ts-ignore @@ -104,5 +105,5 @@ export function useAnswerChoice({ questionItem, parentPath }: QuestionItemProps) const deps = [linkId]; - return { fieldName, loadOptions, validate, deps }; + return { fieldName, loadOptions, validate, deps, fieldPath }; } diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/choice/index.tsx b/web/src/components/BaseQuestionnaireResponseForm/components/choice/index.tsx index 0d88a300..7ea2b685 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/choice/index.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/components/choice/index.tsx @@ -1,3 +1,4 @@ +import { useFieldController } from '@beda.software/fhir-questionnaire/components/QuestionnaireResponseForm'; import { QuestionItemProps, useQuestionnaireResponseFormContext, @@ -13,9 +14,13 @@ import { QuestionLabel } from '../label'; export function QuestionChoice(props: QuestionItemProps) { const { questionItem } = props; - const { fieldName, loadOptions, validate, deps } = useAnswerChoice(props); + const { fieldName, loadOptions, validate, deps, fieldPath } = useAnswerChoice(props); const { text, repeats, readOnly, hidden, linkId } = questionItem; const qrfContext = useQuestionnaireResponseFormContext(); + const { value, onChange, disabled, formItem, onBlur } = useFieldController( + fieldPath, + questionItem, + ); if (hidden) { return null; @@ -24,26 +29,22 @@ export function QuestionChoice(props: QuestionItemProps) { const fieldProps = { validate }; return ( - - {({ input }) => { - return ( - <> - - - key={`answer-choice-${deps.join('-')}`} - data-testid={`choice-${linkId}`} - id={fieldName} - input={input} - label={text} - loadOptions={loadOptions} - isMulti={!!repeats} - getOptionLabel={(option) => getAnswerDisplay(option.value)} - getOptionValue={(option) => getAnswerCode(option.value)} - readOnly={qrfContext.readOnly || readOnly} - /> - - ); - }} - + <> + + + key={`answer-choice-${deps.join('-')}`} + data-testid={`choice-${linkId}`} + id={fieldName} + onChange={onChange} + value={value} + // input={input} + label={text} + loadOptions={loadOptions} + isMulti={!!repeats} + getOptionLabel={(option) => getAnswerDisplay(option.value)} + getOptionValue={(option) => getAnswerCode(option.value)} + readOnly={qrfContext.readOnly || readOnly} + /> + ); } diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/choice/select.tsx b/web/src/components/BaseQuestionnaireResponseForm/components/choice/select.tsx index d3a5cdd6..95a54f14 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/choice/select.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/components/choice/select.tsx @@ -1,15 +1,14 @@ +import { QuestionnaireResponseItemAnswer } from 'fhir/r4b'; import _ from 'lodash'; -// eslint-disable-next-line import/named -import { FieldInputProps } from 'react-final-form'; import { ActionMeta, PropsValue } from 'react-select'; import { AsyncSelect } from 'web/src/components/Select'; +// eslint-disable-next-line import/named interface Props { label?: string; id?: string; placeholder?: string; helpText?: string; - input: FieldInputProps; formItemProps?: any; loadOptions: (searchText: string) => Promise; readOnly?: boolean; @@ -18,12 +17,12 @@ interface Props { getOptionValue: (option: T) => string; isMulti?: boolean; testId?: string; + value?: QuestionnaireResponseItemAnswer[]; } export function AsyncSelectField(props: Props) { const { readOnly, - input, id, placeholder = 'Select...', getOptionLabel, @@ -31,6 +30,7 @@ export function AsyncSelectField(props: Props) { onChange, isMulti, loadOptions, + value, } = props; const debouncedLoadOptions = _.debounce( @@ -42,6 +42,7 @@ export function AsyncSelectField(props: Props) { return ( (props: Props) { getOptionLabel={getOptionLabel} getOptionValue={getOptionValue} id={id} - {...input} onChange={(value, action) => { - input.onChange(value ?? undefined); + // onChange(value ?? undefined); if (onChange) { onChange(value, action); } }} isMulti={isMulti} - styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }) }} + // styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }) }} menuPortalTarget={document.body} /> ); diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/decimal.tsx b/web/src/components/BaseQuestionnaireResponseForm/components/decimal.tsx index e2c4b75d..86af3b35 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/decimal.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/components/decimal.tsx @@ -1,3 +1,4 @@ +import { useFieldController } from '@beda.software/fhir-questionnaire/components/QuestionnaireResponseForm'; import { QuestionItemProps, useQuestionnaireResponseFormContext, @@ -11,22 +12,22 @@ export function QuestionDecimal({ parentPath, questionItem }: QuestionItemProps) const { linkId, readOnly, hidden } = questionItem; const fieldPath = [...parentPath, linkId, 0, 'value', 'decimal']; const fieldName = fieldPath.join('.'); + const { value, onChange, disabled, formItem, onBlur } = useFieldController( + fieldPath, + questionItem, + ); return ( - - {({ input, meta }) => ( - <> - - input.onChange(parseInt(e.target.value))} - /> - {meta.touched && meta.error && {meta.error}} - - )} - + <> + + onChange(Number(e.target.value))} + /> + ); } diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/reference/index.tsx b/web/src/components/BaseQuestionnaireResponseForm/components/reference/index.tsx index b590b265..4ee4bb1c 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/reference/index.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/components/reference/index.tsx @@ -1,3 +1,4 @@ +import { useFieldController } from '@beda.software/fhir-questionnaire/components/QuestionnaireResponseForm'; import { useQuestionnaireResponseFormContext } from '@beda.software/fhir-questionnaire/vendor/sdc-qrf'; import { getAnswerCode, getAnswerDisplay } from 'web/src/utils/questionnaire'; @@ -8,43 +9,43 @@ import { AsyncSelectField } from '../choice/select'; import { QuestionField } from '../field'; import { QuestionLabel } from '../label'; + function QuestionReferenceUnsafe( props: AnswerReferenceProps, ) { const { questionItem, parentPath } = props; - const { loadOptions, onChange, deps, validate } = useAnswerReference(props); + const { loadOptions, deps, validate } = useAnswerReference(props); const { text, repeats, linkId, helpText, readOnly } = questionItem; const qrfContext = useQuestionnaireResponseFormContext(); const fieldPath = [...parentPath, questionItem.linkId!]; const fieldName = fieldPath.join('.'); + const { value, onChange, disabled, formItem, onBlur } = useFieldController( + fieldPath, + questionItem, + ); + const fieldProps = { validate }; return ( - - {({ input }) => { - return ( - <> - - - key={`answer-choice-${deps.join('-')}`} - input={input} - id={fieldName} - testId={linkId!} - label={text} - loadOptions={loadOptions} - isMulti={!!repeats} - getOptionLabel={(option) => getAnswerDisplay(option.value)} - getOptionValue={(option) => getAnswerCode(option.value)} - onChange={onChange} - readOnly={qrfContext.readOnly || readOnly} - helpText={helpText} - /> - - ); - }} - + <> + + + key={`answer-choice-${deps.join('-')}`} + value={value} + id={fieldName} + testId={linkId!} + label={text} + loadOptions={loadOptions} + isMulti={!!repeats} + getOptionLabel={(option) => getAnswerDisplay(option.value)} + getOptionValue={(option) => getAnswerCode(option.value)} + onChange={onChange} + readOnly={qrfContext.readOnly || readOnly} + helpText={helpText} + /> + ); } diff --git a/web/src/components/BaseQuestionnaireResponseForm/components/string.tsx b/web/src/components/BaseQuestionnaireResponseForm/components/string.tsx index a099ba6c..8f331fed 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/components/string.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/components/string.tsx @@ -1,3 +1,4 @@ +import { useFieldController } from '@beda.software/fhir-questionnaire/components/QuestionnaireResponseForm'; import { QuestionItemProps, useQuestionnaireResponseFormContext, @@ -11,21 +12,21 @@ export function QuestionString({ parentPath, questionItem }: QuestionItemProps) const { linkId, readOnly, hidden } = questionItem; const fieldPath = [...parentPath, linkId, 0, 'value', 'string']; const fieldName = fieldPath.join('.'); - + const { value, onChange, disabled, formItem, onBlur } = useFieldController( + fieldPath, + questionItem, + ); return ( - - {({ input, meta }) => ( - <> - - - {meta.touched && meta.error && {meta.error}} - - )} - + <> + + onChange(e.target.value)} + /> + ); } diff --git a/web/src/components/BaseQuestionnaireResponseForm/index.tsx b/web/src/components/BaseQuestionnaireResponseForm/index.tsx index d391eb89..832774ca 100644 --- a/web/src/components/BaseQuestionnaireResponseForm/index.tsx +++ b/web/src/components/BaseQuestionnaireResponseForm/index.tsx @@ -1,4 +1,5 @@ import { + FormWrapperProps, QuestionnaireResponseForm, questionnaireIdWOAssembleLoader, } from '@beda.software/fhir-questionnaire'; @@ -7,6 +8,7 @@ import { ParametersParameter } from '@beda.software/fhir-questionnaire/contrib/a import { FormItems, QuestionnaireResponseFormData, + QuestionnaireResponseFormProvider, } from '@beda.software/fhir-questionnaire/vendor/sdc-qrf'; import { QuestionnaireResponse } from 'fhir/r4b'; import _ from 'lodash'; @@ -15,8 +17,8 @@ import { Form, FormSpy } from 'react-final-form'; import { Col, - Group, GTable, + Group, QuestionBoolean, QuestionChoice, QuestionDate, @@ -30,6 +32,7 @@ import { QuestionInteger } from './components/integer'; import { QuestionReference } from './components/reference'; import s from './QuestionnaireResponseForm.module.scss'; + interface Props { formData: QuestionnaireResponseFormData; onSubmit: (formData: QuestionnaireResponseFormData) => Promise; @@ -88,26 +91,112 @@ export function BaseQuestionnaireResponseForm({ }} readOnly={readOnly} FormWrapper={({ handleSubmit, items }) => ( -
onSubmit({ ...formData, formValues: values })} - initialValues={formData.formValues} - render={({ handleSubmit, values, form }) => ( - - { - return onFormChange(formState.values); - }} - /> - {items} - - )} + )} - ItemWrapper={({ children }) => <>{children}} groupItemComponent={Group} widgetsByGroupQuestionItemControl={{ col: Col, row: Row, gtable: GTable }} autosave /> ); } + +interface BaseFormProps extends FormWrapperProps { + formData: QuestionnaireResponseFormData; + onSubmit: (formData: QuestionnaireResponseFormData) => Promise; + onFormChange: (values: FormItems) => void; +} + +function BaseForm(props: BaseFormProps) { + const { handleSubmit, items, formData, onSubmit, onFormChange } = props; + + return ( +
onSubmit({ ...formData, formValues: values })} + initialValues={formData.formValues} + render={({ handleSubmit }) => ( + + { + return onFormChange(formState.values); + }} + /> + {items} + + )} + /> + ); +} + +// interface Props { +// formData: QuestionnaireResponseFormData; +// onSubmit: (formData: QuestionnaireResponseFormData) => Promise; +// readOnly?: boolean; +// onChange?: (data: QuestionnaireResponseFormData) => any; +// } + +// type FormValues = FormItems; + +// export function BaseQuestionnaireResponseForm({ formData, onSubmit, readOnly, onChange }: Props) { +// const previousValues = useRef(null); + +// const onFormChange = (values: FormValues) => { +// if (_.isEqual(values, previousValues.current)) { +// return; +// } + +// onChange?.({ ...formData, formValues: values }); + +// previousValues.current = values; +// }; + +// return ( +//
onSubmit({ ...formData, formValues: values })} +// initialValues={formData.formValues} +// render={({ handleSubmit, values, form }) => ( +// +// { +// return onFormChange(formState.values); +// }} +// /> +// form.change('', newValues)} +// groupItemComponent={Group} +// itemControlGroupItemComponents={{ col: Col, row: Row, gtable: GTable }} +// questionItemComponents={{ +// date: QuestionDate, +// dateTime: QuestionDateTime, +// string: QuestionString, +// text: QuestionString, +// choice: QuestionChoice, +// boolean: QuestionBoolean, +// display: QuestionDisplay, +// decimal: QuestionDecimal, +// reference: QuestionReference, +// integer: QuestionInteger, +// }} +// readOnly={readOnly} +// > +// <> +// +// +// +// +// )} +// /> +// ); +// } diff --git a/web/src/components/QRFormWrapper/index.tsx b/web/src/components/QRFormWrapper/index.tsx index a892fe83..b9b899c6 100644 --- a/web/src/components/QRFormWrapper/index.tsx +++ b/web/src/components/QRFormWrapper/index.tsx @@ -1,3 +1,4 @@ +import { BaseQuestionnaireResponseForm } from '@beda.software/fhir-questionnaire/components/QuestionnaireResponseForm/BaseQuestionnaireResponseForm'; import { fromFirstClassExtension, mapFormToResponse, @@ -11,8 +12,8 @@ import { } from 'fhir/r4b'; import _ from 'lodash'; import { useCallback } from 'react'; +import { useFormContext } from 'react-hook-form'; import { RenderRemoteData } from 'web/src/components/RenderRemoteData'; -import { useFHIRServiceProvider } from 'web/src/services/fhir'; import { RemoteData } from 'fhir-react/lib/libs/remoteData'; import { sequenceMap } from 'fhir-react/lib/services/service'; @@ -20,7 +21,23 @@ import { formatError } from 'fhir-react/lib/utils/error'; import { QuestionnaireResponse as FCEQuestionnaireResponse } from 'shared/src/contrib/aidbox'; -import { BaseQuestionnaireResponseForm } from '../BaseQuestionnaireResponseForm'; +import { + Col, + GTable, + Group, + QuestionBoolean, + QuestionChoice, + QuestionDate, + QuestionDateTime, + QuestionDecimal, + QuestionDisplay, + QuestionString, + Row, +} from '../BaseQuestionnaireResponseForm/components'; +import { QuestionInteger } from '../BaseQuestionnaireResponseForm/components/integer'; +import { QuestionReference } from '../BaseQuestionnaireResponseForm/components/reference'; +import s from '../BaseQuestionnaireResponseForm/QuestionnaireResponseForm.module.scss'; + interface QRFormWrapperProps { questionnaireRD: RemoteData; @@ -39,11 +56,9 @@ export function QRFormWrapper({ const onChange = useCallback(_.debounce(saveQuestionnaireResponse, 1000), [ saveQuestionnaireResponse, ]); - const serviceProvider = useFHIRServiceProvider(); const remoteDataResult = sequenceMap({ questionnaireRD, questionnaireResponseRD, - serviceProvider, }); return ( @@ -56,7 +71,6 @@ export function QRFormWrapper({ {(data) => ( {}} - onChange={(newFormData) => { + widgetsByQuestionType={{ + // date: QuestionDate, + // dateTime: QuestionDateTime, + string: QuestionString, + // text: QuestionString, + // choice: QuestionChoice, + // boolean: QuestionBoolean, + // display: QuestionDisplay, + decimal: QuestionDecimal, + // reference: QuestionReference, + // integer: QuestionInteger, + }} + widgetsByQuestionItemControl={{ + 'inline-choice': QuestionChoice, + }} + onSubmit={(newFormData) => { const fceQR: FCEQuestionnaireResponse = { ...toFirstClassExtension(data.questionnaireResponseRD), ...mapFormToResponse(newFormData.formValues, data.questionnaireRD), }; onChange(fromFirstClassExtension(fceQR)); }} + groupItemComponent={Group} + widgetsByGroupQuestionItemControl={{ col: Col, row: Row, gtable: GTable }} + FormWrapper={FormWrapper} /> )} ); } + +function FormWrapper({ handleSubmit, items }: { handleSubmit: any; items: any }) { + const { watch } = useFormContext(); + + watch(() => { + handleSubmit(); + }); + + return ( +
+ {items} +
+ ); +}