From 264b4dadc9b46b79de0a315c67ebcca111966923 Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Thu, 14 Nov 2024 14:10:22 -0600 Subject: [PATCH] WP-763: [APCD] Use field wrapper for registration, exception and extension forms. (#360) * core-wrapper and usage of field-wrapper * ExceptionForm fieldwrapper * Registration Changes --- .../Components/FormLabel/FormLabel.module.css | 4 - .../Components/FormLabel/FormLabel.tsx | 29 --- .../Forms/Registrations/FormContact.tsx | 1 + .../Forms/Registrations/FormEntity.tsx | 204 ++++++++---------- .../Forms/Registrations/RegistrationForm.tsx | 53 ++--- .../Forms/Registrations/TextFormField.tsx | 26 +-- .../Submitter/Exceptions/ExceptionForm.tsx | 141 +++++------- .../Exceptions/ExceptionFormPage.tsx | 155 ++++--------- .../Extensions/ExtensionFormInfo.tsx | 116 ++++------ .../Submitter/Extensions/ExtensionsForm.tsx | 86 +++----- .../FieldWrapperFormik.global.css | 14 ++ .../FieldWrapperFormik/FieldWrapperFormik.tsx | 37 ++++ .../core-wrappers/FieldWrapperFormik/index.ts | 2 + .../QueryWrapper/QueryWrapper.tsx | 37 ++++ .../src/core-wrappers/QueryWrapper/index.ts | 3 + .../SubmitWrapper/SubmitWrapper.css | 18 ++ .../SubmitWrapper/SubmitWrapper.module.css | 0 .../SubmitWrapper/SubmitWrapper.tsx | 47 ++++ .../src/core-wrappers/SubmitWrapper/index.ts | 3 + .../src/client/src/core-wrappers/index.ts | 3 + apcd-cms/src/client/tsconfig.json | 1 + apcd-cms/src/client/vite.config.ts | 1 + 22 files changed, 448 insertions(+), 533 deletions(-) delete mode 100644 apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.module.css delete mode 100644 apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.tsx create mode 100644 apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.global.css create mode 100644 apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.tsx create mode 100644 apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/index.ts create mode 100644 apcd-cms/src/client/src/core-wrappers/QueryWrapper/QueryWrapper.tsx create mode 100644 apcd-cms/src/client/src/core-wrappers/QueryWrapper/index.ts create mode 100644 apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.css create mode 100644 apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.module.css create mode 100644 apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.tsx create mode 100644 apcd-cms/src/client/src/core-wrappers/SubmitWrapper/index.ts create mode 100644 apcd-cms/src/client/src/core-wrappers/index.ts diff --git a/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.module.css b/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.module.css deleted file mode 100644 index b6606f78..00000000 --- a/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.module.css +++ /dev/null @@ -1,4 +0,0 @@ -/* To match CEP styles of required fields */ -.requiredBadge { - margin-left: 10px; -} diff --git a/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.tsx b/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.tsx deleted file mode 100644 index 44d5b8b6..00000000 --- a/apcd-cms/src/client/src/components/Components/FormLabel/FormLabel.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Label, Badge, LabelProps } from 'reactstrap'; -import styles from './FormLabel.module.css'; - -interface FormLabelProps extends LabelProps { - label: string; - labelFor: string; - isRequired?: boolean; -} - -export const FormLabel: React.FC = ({ - label, - labelFor, - isRequired = false, - children, - ...props -}) => { - return ( - - ); -}; diff --git a/apcd-cms/src/client/src/components/Forms/Registrations/FormContact.tsx b/apcd-cms/src/client/src/components/Forms/Registrations/FormContact.tsx index e3c74dfb..3a2beff6 100644 --- a/apcd-cms/src/client/src/components/Forms/Registrations/FormContact.tsx +++ b/apcd-cms/src/client/src/components/Forms/Registrations/FormContact.tsx @@ -3,6 +3,7 @@ import { Field, ErrorMessage } from 'formik'; import { FormGroup, Label, FormFeedback } from 'reactstrap'; import { TextFormField } from './TextFormField'; import styles from './RegistrationForm.module.css'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik/FieldWrapperFormik'; export const RegistrationContact: React.FC<{ index: number }> = ({ index }) => { return ( diff --git a/apcd-cms/src/client/src/components/Forms/Registrations/FormEntity.tsx b/apcd-cms/src/client/src/components/Forms/Registrations/FormEntity.tsx index 23954bdf..82da3314 100644 --- a/apcd-cms/src/client/src/components/Forms/Registrations/FormEntity.tsx +++ b/apcd-cms/src/client/src/components/Forms/Registrations/FormEntity.tsx @@ -3,6 +3,7 @@ import { Field, ErrorMessage } from 'formik'; import { FormGroup, Label, FormFeedback } from 'reactstrap'; import { TextFormField } from './TextFormField'; import styles from './RegistrationForm.module.css'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; export const RegistrationEntity: React.FC<{ index: number }> = ({ index }) => { return ( @@ -14,15 +15,13 @@ export const RegistrationEntity: React.FC<{ index: number }> = ({ index }) => { required={true} /> - - -
- Provide all available identifiers. At least one of the following is - required. -
+ +
= ({ index }) => { helpText="Enter digits only." /> - +
Type of Plan
-
- -
- - {['Commercial', 'Medicare', 'Medicaid'].map((planType) => ( - - - - - ))} - - + + {['Commercial', 'Medicare', 'Medicaid'].map((planType) => ( + + + + ))} + +
File Submission
-
- -
- Eligibility/Enrollment files are mandatory. At least one claims file - type (Medical, Pharmacy, and Dental) must be selected. -
-
- - {[ - 'Eligibility/Enrollment', - 'Provider', - 'Medical', - 'Pharmacy', - 'Dental', - ].map((fileType) => ( - - - - - ))} - - + .replace('/', '_')}.label`} + > + + {fileType} + + + ))} + +
Coverage Estimates diff --git a/apcd-cms/src/client/src/components/Forms/Registrations/RegistrationForm.tsx b/apcd-cms/src/client/src/components/Forms/Registrations/RegistrationForm.tsx index 36280699..5de6da7d 100644 --- a/apcd-cms/src/client/src/components/Forms/Registrations/RegistrationForm.tsx +++ b/apcd-cms/src/client/src/components/Forms/Registrations/RegistrationForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import * as Yup from 'yup'; import { FormGroup, Label, Button, FormFeedback } from 'reactstrap'; @@ -9,13 +9,13 @@ import { useRegFormData, usePostRegistration, } from 'hooks/registrations'; -import { fetchUtil } from 'utils/fetchUtil'; import USStates from './USStates.fixture'; import { TextFormField } from './TextFormField'; import { RegistrationEntity } from './FormEntity'; import { RegistrationContact } from './FormContact'; import SectionMessage from 'core-components/SectionMessage'; import LoadingSpinner from 'core-components/LoadingSpinner'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; import styles from './RegistrationForm.module.css'; const validationSchema = Yup.object().shape({ @@ -298,28 +298,27 @@ export const RegistrationForm: React.FC<{ ) : (

Organization

- - + -
- Whether you submit on behalf of your own organization (Self) - or another organization (Other) -
-
+ - - + - - + - - + {USStates.map((USState) => ( ))} - - + = ({ name, label, helpText, required }) => { +}> = ({ name, label, helpText, required = false }) => { return ( - - - {helpText ?
{helpText}
: <>} - -
+ ); }; diff --git a/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionForm.tsx b/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionForm.tsx index a2f758ef..76aed78b 100644 --- a/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionForm.tsx +++ b/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionForm.tsx @@ -1,13 +1,12 @@ import React, { useState, useEffect } from 'react'; import { useFormikContext, Field, ErrorMessage } from 'formik'; -import { FormGroup, Label, Badge } from 'reactstrap'; import { cdlObject, useCDLs, cdl } from 'hooks/cdls'; import { Entities, useEntities } from 'hooks/entities'; import styles from './ExceptionForm.module.css'; import LoadingSpinner from 'core-components/LoadingSpinner'; import SectionMessage from 'core-components/SectionMessage'; import { Link } from 'react-router-dom'; -import Pill from 'core-components/Pill'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; export const ExceptionForm: React.FC<{ index: number }> = ({ index }) => { const [cdlData, setCdlData] = useState(); @@ -64,13 +63,11 @@ export const ExceptionForm: React.FC<{ index: number }> = ({ index }) => { <>

Requested Threshold Reduction {index + 1}

- - + {submitterData && ( <> = ({ index }) => { name={`exceptions[${index}].businessName`} id={`exceptions[${index}].businessName`} > - + {submitterData?.submitters?.map((submitter: Entities) => ( ))} - )} {entitiesError && ( @@ -103,15 +95,12 @@ export const ExceptionForm: React.FC<{ index: number }> = ({ index }) => { )} - - - - + + = ({ index }) => { - - - - - + + = ({ index }) => { )} - - +
- - + - - - - + + Must be less than the {selectedCDL.threshold_value} required. + ) : ( + <> + This field code does not require an exception submission. +
Please choose another. + + )) + } + > = ({ index }) => { className={styles.thresholdRequested} max={selectedCDL?.threshold_value} > - - - {selectedCDL && - (selectedCDL.threshold_value != 0 ? ( -
- Must be less than the {selectedCDL.threshold_value} required. -
- ) : ( -
- This field code does not require an exception submission. -
Please choose another. -
- ))} -
- - + + = ({ index }) => { name={`exceptions[${index}].required_threshold`} id={`exceptions[${index}].required_threshold`} > - - +
); diff --git a/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionFormPage.tsx b/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionFormPage.tsx index 92472f8c..596116cc 100644 --- a/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionFormPage.tsx +++ b/apcd-cms/src/client/src/components/Submitter/Exceptions/ExceptionFormPage.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import * as Yup from 'yup'; -import { FormGroup, Label, Badge } from 'reactstrap'; +import { Label } from 'reactstrap'; import styles from './ExceptionForm.module.css'; import { ExceptionForm } from './'; import SectionMessage from 'core-components/SectionMessage'; @@ -10,6 +10,7 @@ import { Link } from 'react-router-dom'; import { Entities, useEntities } from 'hooks/entities'; import Button from 'core-components/Button'; import LoadingSpinner from 'core-components/LoadingSpinner'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; interface FormValues { exceptionType: string; @@ -222,7 +223,7 @@ export const ExceptionFormPage: React.FC = () => { of exception.

{' '}
- + { - - + {selectedExceptionType !== '' && ( -
+
Note: Your changes will not be saved if you change the exception type.
@@ -298,16 +294,11 @@ export const ExceptionFormPage: React.FC = () => {
{submitterData && ( <> - - + { ) )} - - + )}
@@ -347,28 +333,18 @@ export const ExceptionFormPage: React.FC = () => { )}
- - + - - +
)} @@ -376,36 +352,25 @@ export const ExceptionFormPage: React.FC = () => { <>

Request and Justification

- - - + become compliant.**" + required={true} + description="2000 character limit" + > - -
2000 character limit
-
+

Acknowledgment of Terms

- - + - - - - + + - - - - + + - - +
{isSuccess ? ( <> diff --git a/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionFormInfo.tsx b/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionFormInfo.tsx index 402b60b0..3962be02 100644 --- a/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionFormInfo.tsx +++ b/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionFormInfo.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; import { useFormikContext, Field, ErrorMessage } from 'formik'; -import { FormGroup } from 'reactstrap'; import styles from './ExtensionsForm.module.css'; import { SubmitterEntityData, @@ -9,7 +8,7 @@ import { } from 'hooks/entities'; import SectionMessage from 'core-components/SectionMessage'; import { Link } from 'react-router-dom'; -import { FormLabel } from 'apcd-components/Components/FormLabel/FormLabel'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; const maxDate = new Date(); maxDate.setFullYear(maxDate.getFullYear() + 1); @@ -45,12 +44,11 @@ const ExtensionFormInfo: React.FC<{

Extension Information {index + 1}

This extension is on behalf of the following organization:

- - + {submitterData && ( <> ))} - )} + {!submitterData && ( There was an error finding your associated businesses.{' '} @@ -90,14 +84,13 @@ const ExtensionFormInfo: React.FC<{ )} - + - - + - - +
Submission Dates
- - - Applicable Data Period 1 - + + Applicable Data Period 1 + + } + required={true} + description="Enter month and year" + > {item.data_period} ))} -
Enter month and year
- -
+ - + Requested Target Date 2 + + } + required={true} + className={`position-relative ${styles.dateInputContainer}`} > - - Requested Target Date 2 - - - + - + Current Expected Date 3 + + } + required={true} + className={`position-relative ${styles.dateInputContainer} `} > - - Current Expected Date 3 - - - +
); diff --git a/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionsForm.tsx b/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionsForm.tsx index 0824f3ca..24134c36 100644 --- a/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionsForm.tsx +++ b/apcd-cms/src/client/src/components/Submitter/Extensions/ExtensionsForm.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import * as Yup from 'yup'; -import { FormGroup } from 'reactstrap'; import styles from './ExtensionsForm.module.css'; import ExtensionFormInfo from './ExtensionFormInfo'; import { useEntities } from 'hooks/entities'; @@ -9,7 +8,7 @@ import { fetchUtil } from 'utils/fetchUtil'; import LoadingSpinner from 'core-components/LoadingSpinner'; import SectionMessage from 'core-components/SectionMessage'; import Button from 'core-components/Button'; -import { FormLabel } from 'apcd-components/Components/FormLabel/FormLabel'; +import FieldWrapper from 'core-wrappers/FieldWrapperFormik'; const validationSchema = Yup.object().shape({ extensions: Yup.array().of( @@ -206,12 +205,12 @@ export const ExtensionRequestForm: React.FC = () => { applicable, indicate how the organization plans to become compliant.**

- - + { rows="5" maxLength="2000" /> - -
2000 character limit
-
+

Acknowledgment of Terms

@@ -234,65 +227,44 @@ export const ExtensionRequestForm: React.FC = () => { submitted on this form.

- - + - - - - + + - - +
- + - -
- -
-
+
{isSuccess ? ( <> diff --git a/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.global.css b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.global.css new file mode 100644 index 00000000..cd6e1abf --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.global.css @@ -0,0 +1,14 @@ +.required .badge { + color: white; + font-weight: var(--medium); + + margin-left: 0.5em; + vertical-align: top; +} + +.isInvalid { + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; +} diff --git a/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.tsx b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.tsx new file mode 100644 index 00000000..ade523ce --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/FieldWrapperFormik.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { ErrorMessage } from 'formik'; +import { Badge, FormGroup } from 'reactstrap'; + +import './FieldWrapperFormik.global.css'; + +export type FieldWrapperProps = { + name: string; + label: React.ReactNode; + required?: boolean; + className?: string; + description?: React.ReactNode; +}; +const FieldWrapper: React.FC> = ({ + name, + label, + required, + description, + className, + children, +}) => { + return ( + + + {children} + + {description &&
{description}
} +
+ ); +}; + +export default FieldWrapper; diff --git a/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/index.ts b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/index.ts new file mode 100644 index 00000000..2a646c9f --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/FieldWrapperFormik/index.ts @@ -0,0 +1,2 @@ +import { default as FieldWrapperFormik } from './FieldWrapperFormik'; +export default FieldWrapperFormik; diff --git a/apcd-cms/src/client/src/core-wrappers/QueryWrapper/QueryWrapper.tsx b/apcd-cms/src/client/src/core-wrappers/QueryWrapper/QueryWrapper.tsx new file mode 100644 index 00000000..2afc3c98 --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/QueryWrapper/QueryWrapper.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import LoadingSpinner from 'core-components/LoadingSpinner'; +import Message from 'core-components/Message'; + +type QueryWrapperProps = React.PropsWithChildren<{ + isLoading: boolean; + error: Error | null; + className?: string; +}>; + +const QueryWrapper: React.FC = ({ + isLoading, + error, + children, + className = '', +}) => { + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ + {(error as any).message ?? error} + +
+ ); + } + return
{children}
; +}; + +export default QueryWrapper; diff --git a/apcd-cms/src/client/src/core-wrappers/QueryWrapper/index.ts b/apcd-cms/src/client/src/core-wrappers/QueryWrapper/index.ts new file mode 100644 index 00000000..9217387e --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/QueryWrapper/index.ts @@ -0,0 +1,3 @@ +import { default as QueryWrapper } from './QueryWrapper'; + +export default QueryWrapper; diff --git a/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.css b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.css new file mode 100644 index 00000000..d8b5a3a3 --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.css @@ -0,0 +1,18 @@ +.wrapper { + display: flex; + flex-direction: row; + align-items: center; +} + +.wrapper > * { + margin-right: 0.5em; +} + +.loading-spinner { + margin-left: 1em; + width: inherit; +} + +.reverse { + flex-direction: row-reverse; +} diff --git a/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.module.css b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.module.css new file mode 100644 index 00000000..e69de29b diff --git a/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.tsx b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.tsx new file mode 100644 index 00000000..3b846861 --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/SubmitWrapper.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { LoadingSpinner, Message } from '@tacc/core-components'; +import styles from './SubmitWrapper.module.css'; + +type SubmitWrapperProps = React.PropsWithChildren<{ + isLoading: boolean; + success: string | undefined; + error: Error | null; + className?: string; + reverse?: boolean; +}>; + +const SubmitWrapper: React.FC = ({ + isLoading, + error, + success, + children, + className = '', + reverse = false, +}) => { + return ( +
+ {children} + {isLoading && ( + + )} + {error ? ( + + {(error as any)?.message ?? error} + + ) : ( + success && ( + + {success} + + ) + )} +
+ ); +}; + +export default SubmitWrapper; diff --git a/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/index.ts b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/index.ts new file mode 100644 index 00000000..34d5f0f5 --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/SubmitWrapper/index.ts @@ -0,0 +1,3 @@ +import SubmitWrapper from './SubmitWrapper'; + +export default SubmitWrapper; diff --git a/apcd-cms/src/client/src/core-wrappers/index.ts b/apcd-cms/src/client/src/core-wrappers/index.ts new file mode 100644 index 00000000..3913c029 --- /dev/null +++ b/apcd-cms/src/client/src/core-wrappers/index.ts @@ -0,0 +1,3 @@ +export { default as QueryWrapper } from './QueryWrapper'; +export { default as SubmitWrapper } from './SubmitWrapper'; +export { FieldWrapperFormik } from './FieldWrapperFormik'; diff --git a/apcd-cms/src/client/tsconfig.json b/apcd-cms/src/client/tsconfig.json index da2f1d74..6287c20b 100644 --- a/apcd-cms/src/client/tsconfig.json +++ b/apcd-cms/src/client/tsconfig.json @@ -4,6 +4,7 @@ "paths": { "apcd-components/*":["src/components/*"], "core-components/*": ["src/core-components/*"], + "core-wrappers/*": ["src/core-wrappers/*"], "hooks/*": ["src/hooks/*"], "utils/*": ["src/utils/*"], }, diff --git a/apcd-cms/src/client/vite.config.ts b/apcd-cms/src/client/vite.config.ts index f358fe05..af414d26 100644 --- a/apcd-cms/src/client/vite.config.ts +++ b/apcd-cms/src/client/vite.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ resolve: { alias: { 'apcd-components': resolve(__dirname, 'src/components'), + 'core-wrappers': resolve(__dirname, 'src/core-wrappers'), 'core-components': resolve(__dirname, 'src/core-components'), 'hooks': resolve(__dirname, 'src/hooks'), 'utils': resolve(__dirname, 'src/utils'),