From 7547805b1a5dee3d5b2f1ae5835848aec89d7c7f Mon Sep 17 00:00:00 2001 From: Daniel Belcher <47116905+daniel-belcher@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:40:30 -0800 Subject: [PATCH] feat(webform): new ABP11 form and updated Upload component (#360) * fix: change Upload to follow other form Input management standard * feat: add Upload to slots - also remove unused import * feat: add new abp form * fix: verbiage update for abp * fix: set initializer for upload * fix: allow ref pass in to be used at some point * spell check * fix: wf location and file upload changes --------- Co-authored-by: Benjamin Paige --- src/packages/shared-types/forms.ts | 4 + src/services/api/webforms/ABP11/index.ts | 1 + src/services/api/webforms/ABP11/v202401.ts | 40 +++++ src/services/api/webforms/index.ts | 4 + .../ui/src/components/Inputs/upload.tsx | 149 +++++++++--------- .../ui/src/components/RHF/Document.tsx | 1 - src/services/ui/src/components/RHF/Slot.tsx | 8 + .../src/components/RHF/utils/initializer.ts | 3 + .../ui/src/components/Webform/index.tsx | 6 + .../ui/src/pages/actions/renderSlots.tsx | 2 +- src/services/ui/src/pages/form/chip-form.tsx | 5 +- .../ui/src/pages/form/medicaid-form.tsx | 5 +- .../capitated-1915-b-waiver-amendment.tsx | 9 +- .../capitated-1915-b-waiver-initial.tsx | 9 +- .../capitated-1915-b-waiver-renewal.tsx | 9 +- .../contracting-1915-b-waiver-amendment.tsx | 9 +- .../contracting-1915-b-waiver-initial.tsx | 9 +- .../contracting-1915-b-waiver-renewal.tsx | 9 +- 18 files changed, 157 insertions(+), 125 deletions(-) create mode 100644 src/services/api/webforms/ABP11/index.ts create mode 100644 src/services/api/webforms/ABP11/v202401.ts diff --git a/src/packages/shared-types/forms.ts b/src/packages/shared-types/forms.ts index 8d28ddf1ea..c1031ea88e 100644 --- a/src/packages/shared-types/forms.ts +++ b/src/packages/shared-types/forms.ts @@ -64,6 +64,10 @@ export type RHFComponentMap = { Checkbox: { options: RHFOption[]; }; + Upload: { + maxFiles?: number; + maxSize?: number; + }; FieldArray: { appendText?: string; }; diff --git a/src/services/api/webforms/ABP11/index.ts b/src/services/api/webforms/ABP11/index.ts new file mode 100644 index 0000000000..e5cf77f4fe --- /dev/null +++ b/src/services/api/webforms/ABP11/index.ts @@ -0,0 +1 @@ +export * from "./v202401"; diff --git a/src/services/api/webforms/ABP11/v202401.ts b/src/services/api/webforms/ABP11/v202401.ts new file mode 100644 index 0000000000..51a6cf8268 --- /dev/null +++ b/src/services/api/webforms/ABP11/v202401.ts @@ -0,0 +1,40 @@ +import { FormSchema } from "shared-types"; + +export const v202401: FormSchema = { + header: "ABP 11: Payment methodology", + sections: [ + { + title: "Alternative Benefit Plans - Payment methodologies", + form: [ + { + slots: [ + { + rhf: "Checkbox", + name: "economy_and_efficiency_of_plans", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The state or territory provides assurance that, for each benefit provided under an Alternative Benefit Plan that is not provided through managed care, it will use the payment methodology in its approved state plan or hereby submits state plan amendment Attachment 4.19a, 4.19b, or 4.19d, as appropriate, describing the payment methodology for the benefit.", + value: "assures_alternative_benefit_plan_in_accordance", + }, + ], + }, + }, + { + rhf: "Upload", + name: "state_plan_attchmnt_alt_payment_method", + label: "State plan amendment attachment", + labelStyling: "font-bold", + description: + "Only required if not using the payment methodology in the approved state plan", + descriptionAbove: true, + props: { maxFiles: 3 }, + }, + ], + }, + ], + }, + ], +}; diff --git a/src/services/api/webforms/index.ts b/src/services/api/webforms/index.ts index 9dd0b94507..8e12d567d2 100644 --- a/src/services/api/webforms/index.ts +++ b/src/services/api/webforms/index.ts @@ -1,5 +1,6 @@ import * as ABP1 from "./ABP1"; import * as ABP10 from "./ABP10"; +import * as ABP11 from "./ABP11"; import * as ABP3 from "./ABP3"; import * as ABP3_1 from "./ABP3_1"; import { FormSchema } from "shared-types"; @@ -18,4 +19,7 @@ export const webformVersions: Record> = { ABP10: { v202401: ABP10.v202401, }, + ABP11: { + v202401: ABP11.v202401, + }, }; diff --git a/src/services/ui/src/components/Inputs/upload.tsx b/src/services/ui/src/components/Inputs/upload.tsx index 2c1f824ce8..27eff3d180 100644 --- a/src/services/ui/src/components/Inputs/upload.tsx +++ b/src/services/ui/src/components/Inputs/upload.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib"; -import { useCallback, useState } from "react"; +import { useCallback, useState, forwardRef } from "react"; import { useDropzone, FileRejection, Accept } from "react-dropzone"; import * as I from "@/components/Inputs"; import { X } from "lucide-react"; @@ -7,84 +7,87 @@ import { FILE_TYPES } from "shared-types/uploads"; type UploadProps = { maxFiles?: number; - files: File[]; - setFiles: (files: File[]) => void; + value?: File[]; + onChange: (files: File[]) => void; + setError?: (e: string) => void; }; -export const Upload = ({ maxFiles, files, setFiles }: UploadProps) => { - const [errorMessage, setErrorMessage] = useState(null); +export const Upload = forwardRef( + ({ maxFiles, value = [], onChange, setError }, _ref) => { + const [errorMessage, setErrorMessage] = useState(null); - const onDrop = useCallback( - (acceptedFiles: File[], fileRejections: FileRejection[]) => { - if (fileRejections.length > 0) { - setErrorMessage( - "Selected file(s) is too large or of a disallowed file type." - ); - } else { - setErrorMessage(null); - setFiles([...files, ...acceptedFiles]); - } - }, - [files] - ); + const onDrop = useCallback( + (acceptedFiles: File[], fileRejections: FileRejection[]) => { + if (fileRejections.length > 0) { + const errormsg = + "Selected file(s) is too large or of a disallowed file type."; + setError ? setError(errormsg) : setErrorMessage(errormsg); + } else { + setErrorMessage(null); + onChange([...value, ...acceptedFiles]); + } + }, + [value] + ); - const accept: Accept = {}; - FILE_TYPES.map((type) => - accept[type.mime] - ? accept[type.mime].push(type.extension) - : (accept[type.mime] = [type.extension]) - ); + const accept: Accept = {}; + FILE_TYPES.map((type) => + accept[type.mime] + ? accept[type.mime].push(type.extension) + : (accept[type.mime] = [type.extension]) + ); - const { getRootProps, getInputProps, isDragActive } = useDropzone({ - onDrop, - accept, - maxFiles, - maxSize: 80 * 1024 * 1024, // 80MB, - }); + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + accept, + maxFiles, + maxSize: 80 * 1024 * 1024, // 80MB, + }); - return ( - <> -
- {files.map((file) => ( - //
-
- {file.name} - { - e.preventDefault(); - setFiles(files.filter((a) => a.name !== file.name)); - }} - variant="ghost" - className="p-0 h-0" + return ( + <> +
+ {value?.map((file) => ( + //
+
- - - {/*
*/} -
- ))} -
- {errorMessage && {errorMessage}} -
-

- Drag file here or{" "} - - choose from folder - -

- - {/* {isDragActive &&

Drag is Active

} */} -
- - ); -}; + {file.name} + { + e.preventDefault(); + onChange(value?.filter((a) => a.name !== file.name)); + }} + variant="ghost" + className="p-0 h-0" + > + + + {/*
*/} +
+ ))} +
+ {errorMessage && {errorMessage}} +
+

+ Drag file here or{" "} + + choose from folder + +

+ + {/* {isDragActive &&

Drag is Active

} */} +
+ + ); + } +); Upload.displayName = "Upload"; diff --git a/src/services/ui/src/components/RHF/Document.tsx b/src/services/ui/src/components/RHF/Document.tsx index a71c111a83..8edcf949ba 100644 --- a/src/services/ui/src/components/RHF/Document.tsx +++ b/src/services/ui/src/components/RHF/Document.tsx @@ -1,6 +1,5 @@ import { Control, FieldValues } from "react-hook-form"; -import { FormLabel } from "../Inputs"; import { RHFSection } from "./Section"; import { FormSchema } from "shared-types"; diff --git a/src/services/ui/src/components/RHF/Slot.tsx b/src/services/ui/src/components/RHF/Slot.tsx index ec6497b627..f1b2e441ca 100644 --- a/src/services/ui/src/components/RHF/Slot.tsx +++ b/src/services/ui/src/components/RHF/Slot.tsx @@ -20,6 +20,7 @@ import { Calendar, FormField, Checkbox, + Upload, } from "../Inputs"; import { RHFFormGroup } from "./FormGroup"; import { CalendarIcon } from "lucide-react"; @@ -295,6 +296,13 @@ export const RHFSlot = < ); })()} + {/* ----------------------------------------------------------------------------- */} + {rhf === "Upload" && + (() => { + const hops = props as RHFComponentMap["Upload"]; + + return ; + })()} {/* ----------------------------------------------------------------------------- */} {rhf === "FieldArray" && ( diff --git a/src/services/ui/src/components/RHF/utils/initializer.ts b/src/services/ui/src/components/RHF/utils/initializer.ts index 444bfcee3e..2bf98f8c71 100644 --- a/src/services/ui/src/components/RHF/utils/initializer.ts +++ b/src/services/ui/src/components/RHF/utils/initializer.ts @@ -39,6 +39,9 @@ export const slotInitializer = (ACC: GL, SLOT: T.RHFSlotProps): GL => { case "FieldGroup": ACC[SLOT.name] = [SLOT.fields?.reduce(fieldInitializer, {})]; break; + case "Upload": + ACC[SLOT.name] = []; + break; case "Input": case "Select": case "Textarea": diff --git a/src/services/ui/src/components/Webform/index.tsx b/src/services/ui/src/components/Webform/index.tsx index 4651b48362..fee40137ae 100644 --- a/src/services/ui/src/components/Webform/index.tsx +++ b/src/services/ui/src/components/Webform/index.tsx @@ -49,6 +49,12 @@ export const Webforms = () => { > ABP 10 + + ABP 11 + Implementation Guide diff --git a/src/services/ui/src/pages/actions/renderSlots.tsx b/src/services/ui/src/pages/actions/renderSlots.tsx index 824112c51e..e94a6c4dcd 100644 --- a/src/services/ui/src/pages/actions/renderSlots.tsx +++ b/src/services/ui/src/pages/actions/renderSlots.tsx @@ -34,7 +34,7 @@ export const SlotAttachments = < return ( {label} - + {message} ); diff --git a/src/services/ui/src/pages/form/chip-form.tsx b/src/services/ui/src/pages/form/chip-form.tsx index 7238330a88..23800b4c5e 100644 --- a/src/services/ui/src/pages/form/chip-form.tsx +++ b/src/services/ui/src/pages/form/chip-form.tsx @@ -187,10 +187,7 @@ export const ChipSpaFormPage = () => { At least one attachment is required )} - + )} diff --git a/src/services/ui/src/pages/form/medicaid-form.tsx b/src/services/ui/src/pages/form/medicaid-form.tsx index 2fc1bfd9f0..330a924a89 100644 --- a/src/services/ui/src/pages/form/medicaid-form.tsx +++ b/src/services/ui/src/pages/form/medicaid-form.tsx @@ -197,10 +197,7 @@ export const MedicaidSpaFormPage = () => { : ""} } - + )} diff --git a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-amendment.tsx b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-amendment.tsx index 36f883ed36..269afc4366 100644 --- a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-amendment.tsx +++ b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-amendment.tsx @@ -131,9 +131,7 @@ export const Capitated1915BWaiverAmendmentPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )} diff --git a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-initial.tsx b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-initial.tsx index 56d235089d..5e4230a5c9 100644 --- a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-initial.tsx +++ b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-initial.tsx @@ -131,9 +131,7 @@ export const Capitated1915BWaiverInitialPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )} diff --git a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-renewal.tsx b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-renewal.tsx index 5578fd8a30..764cc89b39 100644 --- a/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-renewal.tsx +++ b/src/services/ui/src/pages/form/waiver/capitated/capitated-1915-b-waiver-renewal.tsx @@ -158,9 +158,7 @@ export const Capitated1915BWaiverRenewalPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )} diff --git a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-amendment.tsx b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-amendment.tsx index f3cb807ddb..ac4b49b676 100644 --- a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-amendment.tsx +++ b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-amendment.tsx @@ -127,9 +127,7 @@ export const Contracting1915BWaiverAmendmentPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )} diff --git a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-initial.tsx b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-initial.tsx index 9e51654d67..e5932168ee 100644 --- a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-initial.tsx +++ b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-initial.tsx @@ -127,9 +127,7 @@ export const Contracting1915BWaiverInitialPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )} diff --git a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-renewal.tsx b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-renewal.tsx index 41f6f7e65a..1fc2f77259 100644 --- a/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-renewal.tsx +++ b/src/services/ui/src/pages/form/waiver/contracting/contracting-1915-b-waiver-renewal.tsx @@ -151,9 +151,7 @@ export const Contracting1915BWaiverRenewalPage = () => { Waiver Authority - - 1915(b) - + 1915(b) { {label} {required ? : null} - + )}