diff --git a/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.test.tsx b/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.test.tsx index 5e5550d2a8..c913e776c1 100644 --- a/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.test.tsx +++ b/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.test.tsx @@ -1,8 +1,23 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; +import { + DesignationSupportSalaryType, + DesignationSupportStatus, +} from 'src/graphql/types.generated'; import { PdsGoalCalculator } from './PdsGoalCalculator'; import { PdsGoalCalculatorTestWrapper } from './PdsGoalCalculatorTestWrapper'; +const completeSetupMock = { + id: 'goal-1', + name: 'Test Goal', + status: DesignationSupportStatus.FullTime, + salaryOrHourly: DesignationSupportSalaryType.Salaried, + payRate: 50000, + hoursWorkedPerWeek: null, + benefits: 1500, + geographicLocation: 'Orlando, FL', +}; + describe('PdsGoalCalculator', () => { it('renders the setup step by default', () => { const { getByRole } = render( @@ -15,4 +30,41 @@ describe('PdsGoalCalculator', () => { getByRole('heading', { level: 6, name: 'Calculator Setup' }), ).toBeInTheDocument(); }); + + it('disables Continue on Setup step when a required field is missing', async () => { + const { findByRole } = render( + + + , + ); + + const continueButton = await findByRole('button', { name: /continue/i }); + await waitFor(() => expect(continueButton).toBeDisabled()); + }); + + it('disables Continue on Setup step when geographicLocation is not set', async () => { + const { findByRole } = render( + + + , + ); + + const continueButton = await findByRole('button', { name: /continue/i }); + await waitFor(() => expect(continueButton).toBeDisabled()); + }); + + it('enables Continue on Setup step when all required fields are filled', async () => { + const { findByRole } = render( + + + , + ); + + const continueButton = await findByRole('button', { name: /continue/i }); + await waitFor(() => expect(continueButton).toBeEnabled()); + }); }); diff --git a/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.tsx b/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.tsx index a61dbf08f3..e259c0f80f 100644 --- a/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.tsx +++ b/src/components/Reports/PdsGoalCalculator/PdsGoalCalculator.tsx @@ -1,6 +1,10 @@ import React from 'react'; import { SectionList } from 'src/components/Reports/GoalCalculator/SharedComponents/SectionList'; import { DirectionButtons } from 'src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons'; +import { + AutosaveForm, + useAutosaveForm, +} from 'src/components/Shared/Autosave/AutosaveForm'; import { PdsGoalCalculatorStepEnum } from './PdsGoalCalculatorHelper'; import { ReimbursableExpensesStep } from './ReimbursableExpenses/ReimbursableExpensesStep'; import { SalaryStep } from './Salary/SalaryStep'; @@ -24,27 +28,38 @@ const CurrentStep: React.FC = () => { } }; -export const PdsGoalCalculator: React.FC = () => { +const StepContent: React.FC = () => { const { currentStep, stepIndex, steps, handleContinue, handlePreviousStep } = usePdsGoalCalculator(); - const sections = currentStep.sections; - + const { allValid } = useAutosaveForm(); const isLastStep = stepIndex === steps.length - 1; + return ( + <> + + {!isLastStep && ( + + )} + + ); +}; + +export const PdsGoalCalculator: React.FC = () => { + const { currentStep } = usePdsGoalCalculator(); + const sections = currentStep.sections; + return ( } mainContent={ - <> - - {!isLastStep && ( - - )} - + + + } /> ); diff --git a/src/components/Reports/PdsGoalCalculator/Setup/SetupStep.tsx b/src/components/Reports/PdsGoalCalculator/Setup/SetupStep.tsx index aeb2851d67..6d40ef1adc 100644 --- a/src/components/Reports/PdsGoalCalculator/Setup/SetupStep.tsx +++ b/src/components/Reports/PdsGoalCalculator/Setup/SetupStep.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import CalculateIcon from '@mui/icons-material/Calculate'; import { Autocomplete, @@ -21,6 +21,7 @@ import { CurrencyAdornment, PercentageAdornment, } from 'src/components/Reports/GoalCalculator/Shared/Adornments'; +import { useOptionalAutosaveForm } from 'src/components/Shared/Autosave/AutosaveForm'; import { useGetUserQuery } from 'src/components/User/GetUser.generated'; import { DesignationSupportSalaryType, @@ -47,20 +48,27 @@ export const SetupStep: React.FC = () => { () => yup.object({ name: yup.string().required(t('Goal Name is a required field')), - status: yup.string().nullable(), - salaryOrHourly: yup.string().nullable(), + status: yup + .string() + .required(t('Employment Status is a required field')), + salaryOrHourly: yup + .string() + .required(t('Pay Type is a required field')), payRate: yup .number() - .nullable() + .required(t('Pay Rate is a required field')) .min(0, t('Pay Rate must be a positive number')), hoursWorkedPerWeek: yup .number() - .nullable() + .required(t('Hours Worked is a required field')) .min(0, t('Hours Worked must be a positive number')), benefits: yup .number() - .nullable() + .required(t('Benefits is a required field')) .min(0, t('Benefits must be a positive number')), + geographicLocation: yup + .string() + .required(t('Geographic Multiplier is a required field')), }), [t], ); @@ -72,6 +80,20 @@ export const SetupStep: React.FC = () => { [goalGeographicConstantMap], ); + const autosaveForm = useOptionalAutosaveForm(); + const geographicLocationValue = calculation?.geographicLocation; + useEffect(() => { + if (!autosaveForm) { + return; + } + if (geographicLocationValue) { + autosaveForm.markValid('geographicLocation'); + } else { + autosaveForm.markInvalid('geographicLocation'); + } + return () => autosaveForm.markValid('geographicLocation'); + }, [autosaveForm, geographicLocationValue]); + const isSalaried = calculation?.salaryOrHourly === DesignationSupportSalaryType.Salaried; const isPartTime = calculation?.status === DesignationSupportStatus.PartTime; diff --git a/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx b/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx index fa4860e5e6..e91f76cb5b 100644 --- a/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx +++ b/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx @@ -25,6 +25,7 @@ interface TestComponentProps { showBackButton?: boolean; buttonTitle?: string; isEdit?: boolean; + disableNext?: boolean; } const TestComponent: React.FC = ({ @@ -33,6 +34,7 @@ const TestComponent: React.FC = ({ showBackButton = false, buttonTitle, isEdit, + disableNext, }) => ( = ({ showBackButton={showBackButton} buttonTitle={buttonTitle} isEdit={isEdit} + disableNext={disableNext} /> @@ -114,6 +117,24 @@ describe('DirectionButtons', () => { expect(await findByRole('button', { name: title })).toBeInTheDocument(); }); + it('disables Continue when disableNext is true', async () => { + const { findByRole } = render(); + + expect(await findByRole('button', { name: 'Continue' })).toBeDisabled(); + }); + + it('enables Continue when disableNext is false', async () => { + const { findByRole } = render(); + + expect(await findByRole('button', { name: 'Continue' })).toBeEnabled(); + }); + + it('leaves Continue enabled when disableNext is not provided', async () => { + const { findByRole } = render(); + + expect(await findByRole('button', { name: 'Continue' })).toBeEnabled(); + }); + it('renders Discard button', async () => { const { findByRole } = render(); diff --git a/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx b/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx index cb110d9525..d93617893a 100644 --- a/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx +++ b/src/components/Reports/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx @@ -21,6 +21,7 @@ interface DirectionButtonsProps { additionalApproval?: boolean; splitAsr?: boolean; disableSubmit?: boolean; + disableNext?: boolean; //Formik validation for submit modal isSubmission?: boolean; submitForm?: () => Promise; @@ -51,6 +52,7 @@ export const DirectionButtons: React.FC = ({ additionalApproval, splitAsr, disableSubmit, + disableNext, }) => { const { t } = useTranslation(); @@ -141,6 +143,7 @@ export const DirectionButtons: React.FC = ({ variant="contained" color="primary" onClick={overrideNext ?? handleNextStep} + disabled={disableNext} > {buttonTitle ?? t('Continue')}