Skip to content

Commit

Permalink
Merge pull request #1218 from CruGlobal/8480-hi-preferences
Browse files Browse the repository at this point in the history
[MPDX-8480] Health Indicator preferences page
  • Loading branch information
canac authored Jan 9, 2025
2 parents 5146750 + 259374e commit e9e0beb
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ const Preferences: React.FC = () => {
}
accountListId={accountListId}
currency={
accountPreferencesData?.accountList?.settings?.currency || ''
accountPreferencesData?.accountList?.settings?.currency || null
}
disabled={onSetupTour && setup !== 1}
handleSetupChange={handleSetupChange}
Expand All @@ -267,7 +267,7 @@ const Preferences: React.FC = () => {
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
currency={
accountPreferencesData?.accountList?.settings?.currency || ''
accountPreferencesData?.accountList?.settings?.currency || null
}
accountListId={accountListId}
disabled={onSetupTour}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const preferencesSchema: yup.ObjectSchema<
interface CurrencyAccordionProps {
handleAccordionChange: (panel: string) => void;
expandedPanel: string;
currency: string;
currency: string | null;
accountListId: string;
disabled?: boolean;
}
Expand Down Expand Up @@ -73,7 +73,7 @@ export const CurrencyAccordion: React.FC<CurrencyAccordionProps> = ({
onAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
label={label}
value={currency}
value={currency ?? ''}
fullWidth
disabled={disabled}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query MachineCalculatedGoal($accountListId: ID!) {
healthIndicatorData(accountListId: $accountListId) {
id
machineCalculatedGoal
machineCalculatedGoalCurrency
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import theme from 'src/theme';
import { MachineCalculatedGoalQuery } from './MachineCalculatedGoal.generated';
import { MonthlyGoalAccordion } from './MonthlyGoalAccordion';

jest.mock('next-auth/react');
Expand Down Expand Up @@ -33,17 +34,32 @@ const mutationSpy = jest.fn();

interface ComponentsProps {
monthlyGoal: number | null;
machineCalculatedGoal?: number;
machineCalculatedGoalCurrency?: string;
expandedPanel: string;
}

const Components: React.FC<ComponentsProps> = ({
monthlyGoal,
machineCalculatedGoal,
machineCalculatedGoalCurrency = 'USD',
expandedPanel,
}) => (
<SnackbarProvider>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider onCall={mutationSpy}>
<GqlMockedProvider<{
MachineCalculatedGoal: MachineCalculatedGoalQuery;
}>
mocks={{
MachineCalculatedGoal: {
healthIndicatorData: machineCalculatedGoal
? [{ machineCalculatedGoal, machineCalculatedGoalCurrency }]
: [],
},
}}
onCall={mutationSpy}
>
<MonthlyGoalAccordion
handleAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
Expand Down Expand Up @@ -134,4 +150,56 @@ describe('MonthlyGoalAccordion', () => {
]);
});
});

describe('calculated goal', () => {
it('resets goal to calculated goal', async () => {
const { getByRole, findByText } = render(
<Components
monthlyGoal={1000}
machineCalculatedGoal={1500}
machineCalculatedGoalCurrency="EUR"
expandedPanel={label}
/>,
);

expect(
await findByText(
/Based on the past year, NetSuite estimates that you need at least 1,500 of monthly support./,
),
).toBeInTheDocument();

const resetButton = getByRole('button', { name: /Reset/ });
userEvent.click(resetButton);

await waitFor(() =>
expect(mutationSpy).toHaveGraphqlOperation('UpdateAccountPreferences', {
input: {
id: accountListId,
attributes: {
settings: {
monthlyGoal: null,
},
},
},
}),
);
});

it('hides reset button if goal is null', async () => {
const { findByText, queryByRole } = render(
<Components
monthlyGoal={null}
machineCalculatedGoal={1000}
expandedPanel={label}
/>,
);

expect(
await findByText(
/Based on the past year, NetSuite estimates that you need at least \$1,000 of monthly support./,
),
).toBeInTheDocument();
expect(queryByRole('button', { name: /Reset/ })).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
import React, { ReactElement, useMemo } from 'react';
import { TextField } from '@mui/material';
import { Box, Button, TextField, Tooltip } from '@mui/material';
import { Formik } from 'formik';
import { useSnackbar } from 'notistack';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
import { AccordionItem } from 'src/components/Shared/Forms/Accordions/AccordionItem';
import { FieldWrapper } from 'src/components/Shared/Forms/FieldWrapper';
import { FormWrapper } from 'src/components/Shared/Forms/FormWrapper';
import { AccountListSettingsInput } from 'src/graphql/types.generated';
import { useLocale } from 'src/hooks/useLocale';
import { currencyFormat } from 'src/lib/intlFormat';
import { useUpdateAccountPreferencesMutation } from '../UpdateAccountPreferences.generated';
import { useMachineCalculatedGoalQuery } from './MachineCalculatedGoal.generated';

const accountPreferencesSchema: yup.ObjectSchema<
Pick<AccountListSettingsInput, 'monthlyGoal'>
> = yup.object({
monthlyGoal: yup.number().required(),
});

const formatMonthlyGoal = (
goal: number | null,
currency: string | null,
locale: string,
): string => {
if (goal === null) {
return '';
}

if (currency) {
return currencyFormat(goal, currency, locale);
}
return goal.toString();
};

interface MonthlyGoalAccordionProps {
handleAccordionChange: (panel: string) => void;
expandedPanel: string;
monthlyGoal: number | null;
accountListId: string;
currency: string;
currency: string | null;
disabled?: boolean;
handleSetupChange: () => Promise<void>;
}

export const MonthlyGoalAccordion: React.FC<MonthlyGoalAccordionProps> = ({
handleAccordionChange,
expandedPanel,
monthlyGoal,
monthlyGoal: initialMonthlyGoal,
accountListId,
currency,
disabled,
Expand All @@ -43,13 +58,29 @@ export const MonthlyGoalAccordion: React.FC<MonthlyGoalAccordionProps> = ({
const locale = useLocale();
const label = t('Monthly Goal');

const monthlyGoalString = useMemo(() => {
return monthlyGoal && locale && currency
? currencyFormat(monthlyGoal, currency, locale)
: monthlyGoal
? String(monthlyGoal)
: '';
}, [monthlyGoal, locale, currency]);
const { data } = useMachineCalculatedGoalQuery({
variables: {
accountListId,
},
});
const {
machineCalculatedGoal: calculatedGoal,
machineCalculatedGoalCurrency: calculatedCurrency,
} = data?.healthIndicatorData.at(-1) ?? {};
const formattedCalculatedGoal = useMemo(
() =>
formatMonthlyGoal(
calculatedGoal ?? null,
calculatedCurrency ?? null,
locale,
),
[calculatedGoal, calculatedCurrency, locale],
);

const formattedMonthlyGoal = useMemo(
() => formatMonthlyGoal(initialMonthlyGoal, currency, locale),
[initialMonthlyGoal, currency, locale],
);

const onSubmit = async (
attributes: Pick<AccountListSettingsInput, 'monthlyGoal'>,
Expand Down Expand Up @@ -79,18 +110,38 @@ export const MonthlyGoalAccordion: React.FC<MonthlyGoalAccordionProps> = ({
handleSetupChange();
};

const getInstructions = () => {
if (typeof calculatedGoal !== 'number') {
return t(
'This amount should be set to the amount your organization has determined is your target monthly goal. If you do not know, make your best guess for now. You can change it at any time.',
);
}

if (initialMonthlyGoal) {
return t(
'Based on the past year, NetSuite estimates that you need at least {{goal}} of monthly support. You can choose your own target monthly goal or leave it blank to use the estimate.',
{ goal: formattedCalculatedGoal },
);
} else {
return t(
'Based on the past year, NetSuite estimates that you need at least {{goal}} of monthly support. You can choose your own target monthly goal or reset it to use the estimate.',
{ goal: formattedCalculatedGoal },
);
}
};

return (
<AccordionItem
onAccordionChange={handleAccordionChange}
expandedPanel={expandedPanel}
label={label}
value={monthlyGoalString}
value={formattedMonthlyGoal}
fullWidth
disabled={disabled}
>
<Formik
initialValues={{
monthlyGoal: monthlyGoal,
monthlyGoal: initialMonthlyGoal,
}}
validationSchema={accountPreferencesSchema}
onSubmit={onSubmit}
Expand All @@ -105,16 +156,8 @@ export const MonthlyGoalAccordion: React.FC<MonthlyGoalAccordionProps> = ({
isValid,
handleChange,
}): ReactElement => (
<FormWrapper
onSubmit={handleSubmit}
isValid={isValid}
isSubmitting={isSubmitting}
>
<FieldWrapper
helperText={t(
'This amount should be set to the amount your organization has determined is your target monthly goal. If you do not know, make your best guess for now. You can change it at any time.',
)}
>
<form onSubmit={handleSubmit}>
<FieldWrapper helperText={getInstructions()}>
<TextField
value={monthlyGoal}
onChange={handleChange}
Expand All @@ -132,7 +175,37 @@ export const MonthlyGoalAccordion: React.FC<MonthlyGoalAccordionProps> = ({
id="monthlyGoalInput"
/>
</FieldWrapper>
</FormWrapper>
<Box display="flex" gap={2} mt={2}>
<Button
variant="contained"
color="primary"
type="submit"
disabled={!isValid || isSubmitting}
>
{t('Save')}
</Button>
{calculatedGoal && initialMonthlyGoal !== null && (
<Tooltip
title={t(
'Reset to NetSuite estimated goal of {{calculatedGoal}}',
{
calculatedGoal: formattedCalculatedGoal,
},
)}
>
<Button
variant="outlined"
type="button"
onClick={() => {
onSubmit({ monthlyGoal: null });
}}
>
{t('Reset to Calculated Goal')}
</Button>
</Tooltip>
)}
</Box>
</form>
)}
</Formik>
</AccordionItem>
Expand Down
4 changes: 1 addition & 3 deletions src/components/Shared/Forms/FormWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ interface FormWrapperProps {
onSubmit: () => void;
isValid: boolean;
isSubmitting: boolean;
formAttrs?: { action?: string; method?: string };
children: React.ReactNode;
buttonText?: string;
}
Expand All @@ -16,15 +15,14 @@ export const FormWrapper: React.FC<FormWrapperProps> = ({
onSubmit,
isValid,
isSubmitting,
formAttrs = {},
children,
buttonText,
}) => {
const { t } = useTranslation();
const theme = useTheme();

return (
<form {...formAttrs} onSubmit={onSubmit}>
<form onSubmit={onSubmit}>
{children}
<Button
variant="contained"
Expand Down

0 comments on commit e9e0beb

Please sign in to comment.