Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const HousingAllowanceRequestPageContent: React.FC = () => {
const { requestData, loading, isMutating, pageType } =
useMinisterHousingAllowance();

const title = t("{{mode}} Minister's Housing Allowance Request", {
const title = t("{{mode}} Minister's Housing Allowance Calculation Tool", {
mode: pageType,
});

Expand All @@ -76,7 +76,7 @@ export const HousingAllowanceRequestPageContent: React.FC = () => {
isOpen={isNavListOpen}
selectedId={'mhaCalculator' + pageType}
onClose={handleNavListToggle}
navType={NavTypeEnum.Reports}
navType={NavTypeEnum.HrTools}
/>
}
leftOpen={isNavListOpen}
Expand All @@ -87,7 +87,7 @@ export const HousingAllowanceRequestPageContent: React.FC = () => {
<MultiPageHeader
isNavListOpen={isNavListOpen}
onNavListToggle={handleNavListToggle}
title={t("Minister's Housing Allowance Request")}
title={t("Minister's Housing Allowance Calculation Tool")}
rightExtra={
<SavingStatus
loading={loading}
Expand All @@ -96,7 +96,7 @@ export const HousingAllowanceRequestPageContent: React.FC = () => {
lastSavedAt={requestData?.updatedAt ?? null}
/>
}
headerType={HeaderTypeEnum.Report}
headerType={HeaderTypeEnum.HrTools}
/>
</SimpleScreenOnly>
<RequestPage />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const MinisterHousingAllowancePage: React.FC = () => {
<MultiPageHeader
isNavListOpen={isNavListOpen}
onNavListToggle={handleNavListToggle}
title={t("Minister's Housing Allowance Request")}
title={t("Minister's Housing Allowance Calculation Tool")}
headerType={HeaderTypeEnum.HrTools}
/>
<MinisterHousingAllowanceProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ mutation SubmitMinistryHousingAllowanceRequest(
) {
submitMinistryHousingAllowanceRequest(input: $input) {
ministryHousingAllowanceRequest {
id
requestAttributes {
...RequestAttributes
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,159 +24,141 @@
const locale = useLocale();
const currency = 'USD';

const { values, touched, errors } = useFormikContext<CalculationFormValues>();
const { values } = useFormikContext<CalculationFormValues>();

const { totalCostOfHome, annualCostOfHome } = useAnnualTotal(
values,
rentOrOwn,
);

return (
<FormCard title={t('Cost of Providing a Home')}>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={1}>
<Typography component="li">
{rentOrOwn === MhaRentOrOwnEnum.Own
? t(
'Monthly mortgage payment, taxes, insurance, and any extra principal you pay.',
)
: t('Monthly rent.')}
</Typography>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.mortgageOrRentPayment && errors.mortgageOrRentPayment
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="mortgageOrRentPayment"
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={2}>
<Typography component="li">
{t(
'Monthly value for furniture, appliances, decorations, and cleaning.',
)}
</Typography>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.furnitureCostsTwo && errors.furnitureCostsTwo
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="furnitureCostsTwo"
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={3}>
<Typography component="li">
{t(
'Estimated monthly cost of repairs and upkeep, include lawn maintenance, pest control, paint, etc.',
)}
</Typography>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.repairCosts && errors.repairCosts ? '2px solid red' : '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="repairCosts"
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={4}>
<Typography component="li">
{t('Average monthly utility costs.')}
</Typography>
{rentOrOwn === MhaRentOrOwnEnum.Own && (
<Box sx={{ color: 'text.secondary' }}>
{t('Entered in the previous section.')}
</Box>
)}
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.avgUtilityTwo && errors.avgUtilityTwo
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="avgUtilityTwo"
additionalSaveFields={['avgUtilityOne']}
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={5}>
<Typography component="li">
{t('Average monthly amount for unexpected expenses.')}
</Typography>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',

Check notice on line 161 in src/components/HrTools/MinisterHousingAllowance/Steps/StepThree/CalcComponents/CostOfHome.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Large Method

CostOfHome:React.FC<CostOfHomeProps> decreases from 208 to 190 lines of code, threshold = 100. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
border:
touched.unexpectedExpenses && errors.unexpectedExpenses
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,104 +20,94 @@
const locale = useLocale();
const currency = 'USD';

const { values, touched, errors } = useFormikContext<CalculationFormValues>();
const { values } = useFormikContext<CalculationFormValues>();

const { totalFairRental, annualFairRental } = useAnnualTotal(
values,
MhaRentOrOwnEnum.Own,
);

return (
<FormCard title={t('Fair Rental Value')}>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={1}>
<Typography component="li">
{t('Monthly market rental value of your home.')}
</Typography>
<Box sx={{ color: 'text.secondary' }}>
<Trans>
The best way to determine this amount is to have an appraiser or
rental real estate specialist provide you with a written
estimate of the monthly rental value. If this is not possible,
you may estimate it by calculating 1% of the value of your home
and lot.
</Trans>
</Box>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.rentalValue && errors.rentalValue ? '2px solid red' : '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="rentalValue"
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={2}>
<Typography component="li">
{t(
'Monthly value for furniture, appliances, decorations, and cleaning.',
)}
</Typography>
<Box sx={{ color: 'text.secondary' }}>
<Trans>
This is a reasonable amount by which the monthly rental of your
home would increase if it were furnished.
</Trans>
</Box>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',
border:
touched.furnitureCostsOne && errors.furnitureCostsOne
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
fullWidth
size="small"
variant="standard"
placeholder={currencyFormat(0, currency, locale)}
InputProps={{ disableUnderline: true, inputMode: 'decimal' }}
fieldName="furnitureCostsOne"
schema={schema}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ width: '70%' }}>
<StyledOrderedList component="ol" start={3}>
<Typography component="li">
{t('Average monthly utility costs.')}
</Typography>
</StyledOrderedList>
</TableCell>
<TableCell
sx={{
width: '30%',
color: 'text.secondary',

Check notice on line 110 in src/components/HrTools/MinisterHousingAllowance/Steps/StepThree/CalcComponents/FairRentalValue.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Large Method

FairRentalValue:React.FC<FairRentalValueProps> decreases from 149 to 139 lines of code, threshold = 100. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
border:
touched.avgUtilityOne && errors.avgUtilityOne
? '2px solid red'
: '',
}}
>
<AutosaveCustomTextField
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';

Check notice on line 1 in src/components/HrTools/MinisterHousingAllowance/Steps/StepThree/Calculation.test.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Lines of Code in a Single File

The lines of code decreases from 393 to 351, improve code health by reducing it to 300. The number of Lines of Code in a single file. More Lines of Code lowers the code health.
import { ThemeProvider } from '@mui/material/styles';
import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
Expand Down Expand Up @@ -130,44 +130,6 @@
expect(getByRole('button', { name: /submit/i })).toBeInTheDocument();
});

it('should show validation error when inputs are invalid', async () => {
const { findByText, getByRole, findByRole, getByText } = render(
<TestComponent
contextValue={{
...defaultContext,
requestData: {
...mockMHARequest,
id: 'request-id',
},
}}
/>,
);

const row = await findByRole('row', {
name: /average monthly amount for unexpected/i,
});
const input = within(row).getByPlaceholderText(/\$0/i);

userEvent.type(input, '100');
expect(input).toHaveValue('100');
userEvent.clear(input);
expect(input).toHaveValue('');

input.focus();
userEvent.tab();

expect(await findByText('Required field.')).toBeInTheDocument();

const submitButton = getByRole('button', { name: /submit/i });

userEvent.click(submitButton);

expect(await findByRole('alert')).toBeInTheDocument();
expect(
getByText('Please enter a value for all required fields.'),
).toBeInTheDocument();
});

it('should show validation error when checkbox is not checked', async () => {
const { findByText, findByRole, getByText } = render(
<TestComponent contextValue={defaultContext} />,
Expand Down Expand Up @@ -230,34 +192,6 @@
expect(await findByText('Invalid email address.')).toBeInTheDocument();
});

it('shows validation error when input is 0', async () => {
const { findByRole, findByText } = render(
<TestComponent
contextValue={{
...defaultContext,
requestData: {
...mockMHARequest,
id: 'request-id',
},
}}
/>,
);

const row = await findByRole('row', {
name: /average monthly amount for unexpected/i,
});
const input = within(row).getByPlaceholderText(/\$0/i);

userEvent.type(input, '0');

input.focus();
userEvent.tab();

expect(input).toHaveValue('$0.00');

expect(await findByText('Must be greater than $0.')).toBeInTheDocument();
});

it('shows confirmation modal when submit is clicked', async () => {
const { getByRole, getByText, findByRole } = render(
<TestComponent
Expand All @@ -268,6 +202,7 @@
id: 'request-id',
requestAttributes: {
...mockMHARequest.requestAttributes,
rentOrOwn: MhaRentOrOwnEnum.Rent,
iUnderstandMhaPolicy: false,
phoneNumber: '1234567890',
emailAddress: 'john.doe@cru.org',
Expand Down Expand Up @@ -296,16 +231,10 @@
});
const input4 = within(row4).getByPlaceholderText(/\$0/i);

const row5 = getByRole('row', {
name: /average monthly amount for unexpected/i,
});
const input5 = within(row5).getByPlaceholderText(/\$0/i);

userEvent.type(input1, '1000');
userEvent.type(input2, '200');
userEvent.type(input3, '300');
userEvent.type(input4, '400');
userEvent.type(input5, '500');
const checkbox = getByRole('checkbox', {
name: /i understand that my approved/i,
});
Expand Down Expand Up @@ -337,7 +266,22 @@
userEvent.click(confirmButton);

await waitFor(() => {
expect(mutationSpy).toHaveBeenCalledTimes(6);
expect(mutationSpy).toHaveBeenCalledTimes(5); // 4 field updates + 1 submit
});

expect(updateMutation).toHaveBeenCalledWith({
variables: {
input: {
requestId: 'request-id',
requestAttributes: {
mortgageOrRentPayment: 1000,
furnitureCostsTwo: 200,
repairCosts: 300,
avgUtilityTwo: 400,
unexpectedExpenses: 0,
},
},
},
});

expect(mutationSpy).toHaveGraphqlOperation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,11 @@

const getValidationSchema = (rentOrOwn?: MhaRentOrOwnEnum) => {
const baseSchema = {
mortgageOrRentPayment: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
furnitureCostsTwo: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
repairCosts: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
avgUtilityTwo: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
unexpectedExpenses: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
mortgageOrRentPayment: yup.number().nullable(),
furnitureCostsTwo: yup.number().nullable(),
repairCosts: yup.number().nullable(),
avgUtilityTwo: yup.number().nullable(),
unexpectedExpenses: yup.number().nullable(),
phoneNumber: phoneNumber(i18n.t).required(
i18n.t('Phone Number is required.'),
),
Expand All @@ -99,18 +84,9 @@
if (rentOrOwn === MhaRentOrOwnEnum.Own) {
return yup.object({
...baseSchema,
rentalValue: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
furnitureCostsOne: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
avgUtilityOne: yup
.number()
.moreThan(0, i18n.t('Must be greater than $0.'))
.required(i18n.t('Required field.')),
rentalValue: yup.number().nullable(),
furnitureCostsOne: yup.number().nullable(),
avgUtilityOne: yup.number().nullable(),
});
}

Expand Down Expand Up @@ -154,62 +130,88 @@
},
});

const transformNullValues = (values: CalculationFormValues) => {
Comment thread
kegrimes marked this conversation as resolved.
const isOwn = rentOrOwn === MhaRentOrOwnEnum.Own;

return updateMutation({
variables: {
input: {
requestId: requestData?.id ?? '',
requestAttributes: {
mortgageOrRentPayment: values.mortgageOrRentPayment ?? 0,
furnitureCostsTwo: values.furnitureCostsTwo ?? 0,
repairCosts: values.repairCosts ?? 0,
avgUtilityTwo: values.avgUtilityTwo ?? 0,
unexpectedExpenses: values.unexpectedExpenses ?? 0,
...(isOwn && {
rentalValue: values.rentalValue ?? 0,
furnitureCostsOne: values.furnitureCostsOne ?? 0,
avgUtilityOne: values.avgUtilityOne ?? 0,
}),
},
},
},
});
};

const request = requestData ? requestData.requestAttributes : null;

const actionRequired =
pageType === PageEnum.Edit || pageType === PageEnum.View;
const isViewPage = pageType === PageEnum.View;
const isEdit = pageType === PageEnum.Edit;

const initialValues: CalculationFormValues = {
rentalValue: request?.rentalValue ?? null,
furnitureCostsOne: request?.furnitureCostsOne ?? null,
avgUtilityOne: request?.avgUtilityOne ?? null,
mortgageOrRentPayment: request?.mortgageOrRentPayment ?? null,
furnitureCostsTwo: request?.furnitureCostsTwo ?? null,
repairCosts: request?.repairCosts ?? null,
avgUtilityTwo: request?.avgUtilityTwo ?? null,
unexpectedExpenses: request?.unexpectedExpenses ?? null,
phoneNumber:
request?.phoneNumber ?? userHcmData?.staffInfo.primaryPhoneNumber ?? null,
emailAddress:
request?.emailAddress ?? userHcmData?.staffInfo.emailAddress ?? null,
iUnderstandMhaPolicy: request?.iUnderstandMhaPolicy ?? false,
};

const boardDateFormatted = boardApprovedAt
? dateFormatShort(DateTime.fromISO(boardApprovedAt), locale)
: null;

const availableDateFormatted = availableDate
? dateFormatShort(DateTime.fromISO(availableDate), locale)
: null;

const after = boardDateFormatted
? t('number after {{boardDateFormatted}}', { boardDateFormatted })
: t('number');

const approval = availableDateFormatted
? t('approval effective {{availableDateFormatted}}', {
availableDateFormatted,
})
: t('approval soon');

const schema = getValidationSchema(rentOrOwn);

if (loading) {
return <Loading loading={loading} />;
}

return (
<Formik<CalculationFormValues>
initialValues={initialValues}
validationSchema={schema}
validateOnChange
validateOnBlur
onSubmit={() => {
onSubmit={async (values) => {
try {
submitMutation({
await transformNullValues(values);

await submitMutation({

Check warning on line 214 in src/components/HrTools/MinisterHousingAllowance/Steps/StepThree/Calculation.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Complex Method

Calculation:React.FC<CalculationProps> increases in cyclomatic complexity from 43 to 45, threshold = 15. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
Comment thread
kegrimes marked this conversation as resolved.
variables: { input: { requestId: requestData?.id ?? '' } },
});
enqueueSnackbar(t('MHA request submitted successfully.'), {
Expand Down
Loading