diff --git a/services/app-web/src/formHelpers/formatters.ts b/services/app-web/src/formHelpers/formatters.ts index 10c1a49a7c..cdf6c2274e 100644 --- a/services/app-web/src/formHelpers/formatters.ts +++ b/services/app-web/src/formHelpers/formatters.ts @@ -145,7 +145,7 @@ const formatDocumentsForForm = ({ name: doc.name, key: key, s3URL: doc.s3URL, - sha256: doc.sha256, + sha256: doc.sha256 ?? undefined, status: 'UPLOAD_COMPLETE', } }) || [] diff --git a/services/app-web/src/pages/App/AppRoutes.tsx b/services/app-web/src/pages/App/AppRoutes.tsx index 2bf67b4544..d7d70df89b 100644 --- a/services/app-web/src/pages/App/AppRoutes.tsx +++ b/services/app-web/src/pages/App/AppRoutes.tsx @@ -37,8 +37,9 @@ import { UploadQuestions, } from '../QuestionResponse' import { GraphQLExplorer } from '../GraphQLExplorer/GraphQLExplorer' -import { RateSummary } from '../SubmissionSummary/RateSummary' -import { RateEdit } from '../RateEdit/RateEdit' +import { RateSummary } from '../RateSummary' +import { RateDetailsV2 } from '../StateSubmission/RateDetails/RateDetailsV2' +import { RateEdit } from '../RateSubmission/RateEdit/RateEdit' function componentForAuthMode( authMode: AuthModeType @@ -108,10 +109,16 @@ const StateUserRoutes = ({ element={} /> {showRatePages && ( + <> } /> + } + /> + )} }> {showQuestionResponse && ( diff --git a/services/app-web/src/pages/RateEdit/RateEdit.test.tsx b/services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.test.tsx similarity index 84% rename from services/app-web/src/pages/RateEdit/RateEdit.test.tsx rename to services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.test.tsx index 420d69c305..e62c45f946 100644 --- a/services/app-web/src/pages/RateEdit/RateEdit.test.tsx +++ b/services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.test.tsx @@ -1,8 +1,8 @@ import { screen, waitFor } from '@testing-library/react' -import { renderWithProviders } from "../../testHelpers" +import { renderWithProviders } from "../../../testHelpers" import { RateEdit } from "./RateEdit" -import { fetchCurrentUserMock, fetchRateMockSuccess, mockValidStateUser } from "../../testHelpers/apolloMocks" -import { RoutesRecord } from '../../constants' +import { fetchCurrentUserMock, fetchRateMockSuccess, mockValidStateUser } from "../../../testHelpers/apolloMocks" +import { RoutesRecord } from '../../../constants' import { Route, Routes } from 'react-router-dom' // Wrap test component in some top level routes to allow getParams to be tested @@ -33,8 +33,8 @@ describe('RateEdit', () => { routerProvider: { route: '/rates/1337/edit' }, - featureFlags: { - 'rate-edit-unlock': true + featureFlags: { + 'rate-edit-unlock': true } }) diff --git a/services/app-web/src/pages/RateEdit/RateEdit.tsx b/services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.tsx similarity index 79% rename from services/app-web/src/pages/RateEdit/RateEdit.tsx rename to services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.tsx index 552c094d96..2866d1baa6 100644 --- a/services/app-web/src/pages/RateEdit/RateEdit.tsx +++ b/services/app-web/src/pages/RateSubmission/RateEdit/RateEdit.tsx @@ -1,10 +1,10 @@ import React from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { useFetchRateQuery } from "../../gen/gqlClient"; +import { useFetchRateQuery } from "../../../gen/gqlClient"; import { GridContainer } from "@trussworks/react-uswds"; -import { Loading } from "../../components"; -import { GenericErrorPage } from "../Errors/GenericErrorPage"; -import { RateDetailsV2 } from "../StateSubmission/RateDetails/RateDetailsV2"; +import { Loading } from "../../../components"; +import { GenericErrorPage } from "../../Errors/GenericErrorPage"; +import { RateDetailsV2 } from "../../StateSubmission/RateDetails/RateDetailsV2"; type RouteParams = { id: string diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx b/services/app-web/src/pages/RateSummary/RateSummary.test.tsx similarity index 92% rename from services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx rename to services/app-web/src/pages/RateSummary/RateSummary.test.tsx index 490f69e143..931131939c 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx +++ b/services/app-web/src/pages/RateSummary/RateSummary.test.tsx @@ -1,15 +1,15 @@ import { screen, waitFor } from '@testing-library/react' -import { renderWithProviders, testS3Client } from '../../../testHelpers' +import { renderWithProviders, testS3Client } from '../../testHelpers' import { fetchCurrentUserMock, fetchRateMockSuccess, mockValidCMSUser, mockValidStateUser, -} from '../../../testHelpers/apolloMocks' +} from '../../testHelpers/apolloMocks' import { RateSummary } from './RateSummary' -import { RoutesRecord } from '../../../constants' +import { RoutesRecord } from '../../constants' import { Route, Routes } from 'react-router-dom' -import { RateEdit } from '../../RateEdit/RateEdit' +import { RateEdit } from '../RateSubmission/RateEdit/RateEdit' // Wrap test component in some top level routes to allow getParams to be tested const wrapInRoutes = (children: React.ReactNode) => { @@ -146,15 +146,15 @@ describe('RateSummary', () => { it('redirects to RateEdit component from RateSummary without errors for unlocked rate', async () => { renderWithProviders( - } + } /> - } + } /> - , + , { apolloProvider: { mocks: [ @@ -168,8 +168,8 @@ describe('RateSummary', () => { routerProvider: { route: '/rates/1337' }, - featureFlags: { - 'rate-edit-unlock': true + featureFlags: { + 'rate-edit-unlock': true } }) diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx b/services/app-web/src/pages/RateSummary/RateSummary.tsx similarity index 86% rename from services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx rename to services/app-web/src/pages/RateSummary/RateSummary.tsx index bffa5e076f..d4aa0cc2a0 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx +++ b/services/app-web/src/pages/RateSummary/RateSummary.tsx @@ -2,14 +2,14 @@ import { GridContainer, Icon, Link } from '@trussworks/react-uswds' import React, { useEffect, useState } from 'react' import { NavLink, useNavigate, useParams } from 'react-router-dom' -import { Loading } from '../../../components' -import { usePage } from '../../../contexts/PageContext' -import { useFetchRateQuery } from '../../../gen/gqlClient' +import { Loading } from '../../components' +import { usePage } from '../../contexts/PageContext' +import { useFetchRateQuery } from '../../gen/gqlClient' import styles from '../SubmissionSummary.module.scss' -import { GenericErrorPage } from '../../Errors/GenericErrorPage' -import { RoutesRecord } from '../../../constants' -import { SingleRateSummarySection } from '../../../components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection' -import { useAuth } from '../../../contexts/AuthContext' +import { GenericErrorPage } from '../Errors/GenericErrorPage' +import { RoutesRecord } from '../../constants' +import { SingleRateSummarySection } from '../../components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection' +import { useAuth } from '../../contexts/AuthContext' type RouteParams = { id: string diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/index.ts b/services/app-web/src/pages/RateSummary/index.ts similarity index 100% rename from services/app-web/src/pages/SubmissionSummary/RateSummary/index.ts rename to services/app-web/src/pages/RateSummary/index.ts diff --git a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsSchema.ts b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsSchema.ts index 3ec15222c7..63dafdc998 100644 --- a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsSchema.ts +++ b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsSchema.ts @@ -14,7 +14,7 @@ const SingleRateCertSchema = (_activeFeatureFlags: FeatureFlagSettings) => rateDocuments: validateFileItemsListSingleUpload({ required: true }), supportingDocuments: validateFileItemsList({ required: false }), hasSharedRateCert: Yup.string().defined('You must select yes or no'), - packagesWithSharedRateCerts: Yup.array() + packagesWithSharedRateCerts: _activeFeatureFlags['rate-edit-unlock']? Yup.array() .when('hasSharedRateCert', { is: 'YES', then: Yup.array().min( @@ -22,7 +22,7 @@ const SingleRateCertSchema = (_activeFeatureFlags: FeatureFlagSettings) => 'You must select at least one submission' ), }) - .required(), + .required(): Yup.array().optional(), rateProgramIDs: Yup.array().min(1, 'You must select a program'), rateType: Yup.string().defined( 'You must choose a rate certification type' diff --git a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsV2.tsx b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsV2.tsx index f5ee893fbc..b5eaf1af5b 100644 --- a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsV2.tsx +++ b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetailsV2.tsx @@ -1,13 +1,11 @@ import React, { useEffect } from 'react' import { Form as UswdsForm } from '@trussworks/react-uswds' -import { FieldArray, FieldArrayRenderProps, Formik, FormikErrors } from 'formik' +import { Formik, FormikErrors } from 'formik' import { useNavigate, useParams } from 'react-router-dom' import { v4 as uuidv4 } from 'uuid' import styles from '../StateSubmissionForm.module.scss' -import { RateInfoType } from '../../../common-code/healthPlanFormDataType' - import { ErrorSummary, GenericApiErrorBanner, Loading } from '../../../components' import { formatFormDateForDomain } from '../../../formHelpers' import { RateDetailsFormSchema } from './RateDetailsSchema' @@ -20,24 +18,23 @@ import { formatDocumentsForForm, formatForForm, } from '../../../formHelpers/formatters' -import { - RateCertFormType, - SingleRateCert, -} from './SingleRateCert/SingleRateCert' import { useS3 } from '../../../contexts/S3Context' import { S3ClientT } from '../../../s3' import { isLoadingOrHasFileErrors } from '../../../components/FileUpload' import { RoutesRecord } from '../../../constants' -import { SectionCard } from '../../../components/SectionCard' import { Rate, RateRevision, useFetchRateQuery, useSubmitRateMutation } from '../../../gen/gqlClient' import { Error404 } from '../../Errors/Error404Page' import { GenericErrorPage } from '../../Errors/GenericErrorPage' +import { SingleRateCertV2 } from './SingleRateCert/SingleRateCertV2' + + +export type RateCertForm = {key: string} & RateRevision['formData'] // This function is used to get initial form values as well return empty form values when we add a new rate or delete a rate // We need to include the getKey function in params because there are no guarantees currently file is in s3 even if when we load data from API const generateRateCertFormValues = ( getKey: S3ClientT['getKey'], - rateRev?: RateRevision): RateCertFormType => { + rateRev?: RateRevision): RateCertForm => { const rateInfo = rateRev?.formData const newRateID = uuidv4(); @@ -49,12 +46,12 @@ const generateRateCertFormValues = ( rateDateStart: formatForForm(rateInfo?.rateDateStart), rateDateEnd: formatForForm(rateInfo?.rateDateEnd), rateDateCertified: formatForForm(rateInfo?.rateDateCertified), - effectiveDateStart: formatForForm( - rateInfo?.amendmentEffectiveDateStart - ), - effectiveDateEnd: formatForForm( - rateInfo?.amendmentEffectiveDateEnd - ), + effectiveDateStart: formatForForm( + rateInfo?.amendmentEffectiveDateStart + ), + effectiveDateEnd: formatForForm( + rateInfo?.amendmentEffectiveDateEnd + ), rateProgramIDs: rateInfo?.rateProgramIDs ?? [], rateDocuments: formatDocumentsForForm({ documents: rateInfo?.rateDocuments, @@ -69,15 +66,9 @@ const generateRateCertFormValues = ( ), actuaryCommunicationPreference: rateInfo?.actuaryCommunicationPreference ?? undefined, - packagesWithSharedRateCerts: - rateInfo?.packagesWithSharedRateCerts ?? [], } } -interface RateInfoArrayType { - rateInfos: RateCertFormType[] -} - export const rateErrorHandling = ( error: string | FormikErrors | undefined ): FormikErrors | undefined => { @@ -104,34 +95,10 @@ export const RateDetailsV2 = ({ } const { getKey } = useS3() - // API handling - const [submitRate, { loading: submitRateLoading }] = useSubmitRateMutation() - const { data: fetchRateQuery, loading: fetchRateLoading, error: fetchRateError } = useFetchRateQuery({ - variables: { - input: { - rateID: id - } - } - }) - - if (fetchRateLoading){ - return - } - - if( fetchRateError) { - return - } else if (!fetchRateQuery) { - return - } - const rate = fetchRateQuery?.fetchRate.rate - const previousDocuments: string[] = [] // Form validation const [shouldValidate, setShouldValidate] = React.useState(showValidations) - const rateDetailsFormSchema = RateDetailsFormSchema() - const rateInfosInitialValues: RateInfoArrayType = { - rateInfos: [generateRateCertFormValues(getKey, rate.draftRevision ?? undefined)], - } + const rateDetailsFormSchema = RateDetailsFormSchema({'rate-edit-unlock': true}) // UI focus state management const [focusNewRate, setFocusNewRate] = React.useState(false) @@ -158,21 +125,43 @@ export const RateDetailsV2 = ({ }, [focusErrorSummaryHeading]) + // API handling + const [submitRate, { loading: submitRateLoading }] = useSubmitRateMutation() + const { data: fetchRateQuery, loading: fetchRateLoading, error: fetchRateError } = useFetchRateQuery({ + variables: { + input: { + rateID: id + } + } + }) + + if (fetchRateLoading){ + return + } + + if( fetchRateError) { + return + } else if (!fetchRateQuery) { + return + } + const rate = fetchRateQuery?.fetchRate.rate + const previousDocuments: string[] = [] + + // Formik setup + const initialValues: RateCertForm = generateRateCertFormValues(getKey, rate.draftRevision ?? undefined) + const handleFormSubmit = async ( - form: RateInfoArrayType, + form: RateCertForm , setSubmitting: (isSubmitting: boolean) => void, // formik setSubmitting options: { shouldValidateDocuments: boolean redirectPath: string } ) => { - const { rateInfos } = form if (options.shouldValidateDocuments) { - const fileErrorsNeedAttention = rateInfos.some((rateInfo) => - isLoadingOrHasFileErrors( - rateInfo.supportingDocuments.concat(rateInfo.rateDocuments) + const fileErrorsNeedAttention = isLoadingOrHasFileErrors( + initialValues.supportingDocuments.concat(initialValues.rateDocuments) ) - ) if (fileErrorsNeedAttention) { // make inline field errors visible so user can correct documents, direct user focus to errors, and manually exit formik submit setShouldValidate(true) @@ -182,49 +171,41 @@ export const RateDetailsV2 = ({ } } - const cleanedRateInfos = rateInfos.map((rateInfo) => { - return { - id: rateInfo.id, - rateType: rateInfo.rateType, - rateCapitationType: rateInfo.rateCapitationType, - rateDocuments: formatDocumentsForDomain(rateInfo.rateDocuments), + const cleanedrateCertForms = { + id: form.id, + rateType: form.rateType, + rateCapitationType: form.rateCapitationType, + rateDocuments: formatDocumentsForDomain(form.rateDocuments), supportingDocuments: formatDocumentsForDomain( - rateInfo.supportingDocuments + form.supportingDocuments ), - rateDateStart: formatFormDateForDomain(rateInfo.rateDateStart), - rateDateEnd: formatFormDateForDomain(rateInfo.rateDateEnd), + rateDateStart: formatFormDateForDomain(form.rateDateStart), + rateDateEnd: formatFormDateForDomain(form.rateDateEnd), rateDateCertified: formatFormDateForDomain( - rateInfo.rateDateCertified + form.rateDateCertified ), rateAmendmentInfo: - rateInfo.rateType === 'AMENDMENT' + form.rateType === 'AMENDMENT' ? { effectiveDateStart: formatFormDateForDomain( - rateInfo.effectiveDateStart + form.effectiveDateStart ), effectiveDateEnd: formatFormDateForDomain( - rateInfo.effectiveDateEnd + form.effectiveDateEnd ), } : undefined, - rateProgramIDs: rateInfo.rateProgramIDs, - actuaryContacts: rateInfo.actuaryContacts, + rateProgramIDs: form.rateProgramIDs, + actuaryContacts: form.actuaryContacts, actuaryCommunicationPreference: - rateInfo.actuaryCommunicationPreference, - packagesWithSharedRateCerts: - rateInfo.hasSharedRateCert === 'YES' - ? rateInfo.packagesWithSharedRateCerts - : [], - } - }) - - const initialRateRevision = cleanedRateInfos[0] // only submit the first rate revision right now from RateDetailsV2, adapt later for mulit-rate workflow + form.actuaryCommunicationPreference, + } try { const updatedSubmission = await submitRate({ variables: { input: { - rateID: initialRateRevision.id, + rateID: rate.id, // formData: initialRateRevision } }, @@ -247,9 +228,9 @@ export const RateDetailsV2 = ({ // Error summary object keys will be used as DOM focus point from error-summary. Must be valid html selector // Error summary object values will be used as messages displays in error summary const generateErrorSummaryErrors = ( - errors: FormikErrors + errors: FormikErrors ) => { - const rateErrors = errors.rateInfos + const rateErrors = errors const errorObject: { [field: string]: string } = {} if (rateErrors && Array.isArray(rateErrors)) { @@ -258,12 +239,11 @@ export const RateDetailsV2 = ({ Object.entries(rateError).forEach(([field, value]) => { if (typeof value === 'string') { + //rateProgramIDs error message needs a # proceeding the key name because this is the only way to be able to link to the Select component element see comments in ErrorSummaryMessage component. const errorKey = - field === 'rateProgramIDs' || - field === 'packagesWithSharedRateCerts' - ? `#rateInfos.${index}.${field}` - : `rateInfos.${index}.${field}` + field === 'rateProgramIDs' ? `#rateCertForms.${index}.${field}` + : `rateCertForms.${index}.${field}` errorObject[errorKey] = value } // If the field is actuaryContacts then the value should be an array with at least one object of errors @@ -277,7 +257,7 @@ export const RateDetailsV2 = ({ Object.entries(actuaryContact).forEach( ([contactField, contactValue]) => { if (typeof contactValue === 'string') { - const errorKey = `rateInfos.${index}.actuaryContacts.0.${contactField}` + const errorKey = `rateCertForms.${index}.actuaryContacts.0.${contactField}` errorObject[errorKey] = contactValue } } @@ -292,9 +272,9 @@ export const RateDetailsV2 = ({ return ( { - return handleFormSubmit({ rateInfos }, setSubmitting, { + initialValues={initialValues} + onSubmit={({ rateCertForm }, { setSubmitting }) => { + return handleFormSubmit({ rateCertForm }, setSubmitting, { shouldValidateDocuments: true, redirectPath: `../contacts`, }) @@ -302,7 +282,7 @@ export const RateDetailsV2 = ({ validationSchema={rateDetailsFormSchema} > {({ - values: { rateInfos }, + values: { rateCertForm }, errors, dirty, handleSubmit, @@ -313,7 +293,7 @@ export const RateDetailsV2 = ({ <> { @@ -333,69 +313,25 @@ export const RateDetailsV2 = ({ headingRef={errorSummaryHeadingRef} /> )} - - {({ - remove, - push, - }: FieldArrayRenderProps) => ( - <> - {rateInfos.map( - (rateInfo, index) => ( - { - remove(index) - setNewRateButtonFocus() - }, - reassignNewRateRef: - (el) => - (newRateNameRef.current = - el), - }} - /> - ) - )} - -

- Additional rate - certification -

- -
- - )} -
+ { const redirectPath = `../contract-details` if (dirty) { await handleFormSubmit( - { rateInfos }, + { rateCertForms }, setSubmitting, { shouldValidateDocuments: false, @@ -408,7 +344,7 @@ export const RateDetailsV2 = ({ }} saveAsDraftOnClick={async () => { await handleFormSubmit( - { rateInfos }, + { rateCertForms }, setSubmitting, { shouldValidateDocuments: true, diff --git a/services/app-web/src/pages/SubmissionSummary/index.ts b/services/app-web/src/pages/SubmissionSummary/index.ts index 9694162a2b..055197426d 100644 --- a/services/app-web/src/pages/SubmissionSummary/index.ts +++ b/services/app-web/src/pages/SubmissionSummary/index.ts @@ -1,2 +1,2 @@ export { SubmissionSummary } from './SubmissionSummary' -export { RateSummary } from './RateSummary' +export { RateSummary } from '../RateSummary'