Skip to content
Open
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 @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { blockImpersonatingNonDevelopers } from 'pages/api/utils/pagePropsHelpers';
import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout';
import { MPGAIncomeExpensesReport } from 'src/components/Reports/MPGAIncomeExpensesReport/MPGAIncomeExpensesReport';
import { ReportProvider } from 'src/components/Reports/MPGAIncomeExpensesReport/ReportContext/ReportContext';
import {
MultiPageMenu,
NavTypeEnum,
Expand Down Expand Up @@ -49,11 +50,13 @@ const MPGAReportPage: React.FC = () => {
leftOpen={isNavListOpen}
leftWidth="290px"
mainContent={
<MPGAIncomeExpensesReport
isNavListOpen={isNavListOpen}
onNavListToggle={handleNavListToggle}
title={t('Ministry Partner Giving Analysis')}
/>
<ReportProvider>
<MPGAIncomeExpensesReport
isNavListOpen={isNavListOpen}
onNavListToggle={handleNavListToggle}
title={t('Ministry Partner Giving Analysis')}
/>
</ReportProvider>
}
/>
</MPGAReportPageWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('BreakdownAccordion', () => {
);

expect(getByText('$6,770.00')).toHaveStyle({
color: theme.palette.chipRedDark.main,
color: theme.palette.error.main,
});
});

Expand Down Expand Up @@ -117,7 +117,7 @@ describe('BreakdownAccordion', () => {
userEvent.click(getByRole('button'));

const amount = await findByRole('cell', { name: '($50.00)' });
expect(amount).toHaveStyle({ color: theme.palette.chipRedDark.main });
expect(amount).toHaveStyle({ color: theme.palette.error.main });
});

it('marks a positive transaction inside an expense accordion', async () => {
Expand All @@ -139,7 +139,7 @@ describe('BreakdownAccordion', () => {
userEvent.click(getByRole('button'));

const amount = await findByRole('cell', { name: '($50.00)' });
expect(amount).toHaveStyle({ color: theme.palette.statusSuccess.main });
expect(amount).toHaveStyle({ color: theme.palette.success.main });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export const BreakdownAccordion: React.FC<BreakdownAccordionProps> = ({
sx={{
marginLeft: 'auto',
color: isIncomeTotal
? theme.palette.statusSuccess.main
: theme.palette.chipRedDark.main,
? theme.palette.success.main
: theme.palette.error.main,
}}
>
{currencyFormat(Math.abs(total), currency, locale, {
Expand Down Expand Up @@ -130,9 +130,9 @@ export const BreakdownAccordion: React.FC<BreakdownAccordionProps> = ({
align="right"
sx={{
color: isStrayExpenseTransaction
? theme.palette.chipRedDark.main
? theme.palette.error.main
: isStrayIncomeTransaction
? theme.palette.statusSuccess.main
? theme.palette.success.main
: null,
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import { render } from '@testing-library/react';
import { DateTime } from 'luxon';
import { StaffExpenseCategoryEnum } from 'src/graphql/types.generated';
import theme from 'src/theme';
import { TotalsProvider } from '../TotalsContext/TotalsContext';
import { MPGAIncomeExpensesReportTestWrapper } from '../MPGAIncomeExpensesReportTestWrapper';
import { mockBreakdownData } from '../mockData';
import { BreakdownModal, BreakdownModalProps } from './BreakdownModal';

const mutationSpy = jest.fn();

const defaultProps: BreakdownModalProps = {
open: true,
onClose: jest.fn(),
Expand All @@ -16,15 +15,9 @@ const defaultProps: BreakdownModalProps = {
};

const TestComponent: React.FC<BreakdownModalProps> = (props) => (
<ThemeProvider theme={theme}>
<TotalsProvider
data={{ income: [], expenses: [] }}
startDate={DateTime.local(2019, 2, 1)}
endDate={DateTime.local(2020, 1, 1)}
>
<BreakdownModal {...props} />
</TotalsProvider>
</ThemeProvider>
<MPGAIncomeExpensesReportTestWrapper onCall={mutationSpy}>
<BreakdownModal {...props} />
</MPGAIncomeExpensesReportTestWrapper>
);

describe('BreakdownModal', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { DialogSkeleton } from '../../Shared/DialogSkeleton/DialogSkeleton';
import { getLocalizedCategory } from '../../Shared/Helpers/transformStaffExpenseEnums';
import { BreakdownAccordion } from '../BreakdownAccordion/BreakdownAccordion';
import { useTotals } from '../TotalsContext/TotalsContext';
import { useReport } from '../ReportContext/ReportContext';
import { TransactionBreakdown } from '../mockData';

export interface BreakdownModalProps {
Expand All @@ -39,117 +39,125 @@
const { t } = useTranslation();
const locale = useLocale();
const currency = 'USD';
const { startDate, endDate } = useTotals();
const { startDate, endDate } = useReport();

const subcategoryBreakdown = useMemo(() => {
const categoryBreakdown = breakdownData[category] ?? [];
const grouped = new Map<
StaffExpensesSubCategoryEnum,
TransactionBreakdown[]
>();

categoryBreakdown.forEach((transaction) => {
const transactions = grouped.get(transaction.subCategory);
if (transactions) {
transactions.push(transaction);
} else {
grouped.set(transaction.subCategory, [transaction]);
}
});

return Array.from(grouped, ([subCategory, transactions]) => ({
category,
subCategory,
transactions,
total: transactions.reduce((sum, { amount }) => sum + amount, 0),
}));
}, [breakdownData, category]);

const overallTotal = useMemo(
() => subcategoryBreakdown.reduce((sum, { total }) => sum + total, 0),
[subcategoryBreakdown],
);

return (
<DialogSkeleton
categoryName={getLocalizedCategory(category, t)}
open={open}
onClose={onClose}
>
<TableContainer>
<TableContainer
sx={{
borderBottom: `1px solid ${theme.palette.divider}`,
}}
>
<Table>
<TableHead>
<TableRow
sx={{ backgroundColor: theme.palette.mpdxGrayLight.main }}
sx={{
backgroundColor: theme.palette.mpdxGrayLight.main,
position: 'sticky',
top: 0,
zIndex: 1,
}}
>
<TableCell>{t('Category')}</TableCell>
<TableCell sx={{ textAlign: 'right' }}>
{`${monthYearFormat(
startDate.month,
startDate.year,
locale,
true,
true,
)} - ${monthYearFormat(
endDate.month,
endDate.year,
locale,
true,
true,
)}`}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{subcategoryBreakdown.map(
({ subCategory, transactions, total }) => (
<TableRow key={subCategory}>
<TableCell colSpan={2} sx={{ padding: 0, border: 0 }}>
<BreakdownAccordion
category={category}
subCategory={subCategory}
transactions={transactions}
total={total}
/>
</TableCell>
</TableRow>
),
)}
</TableBody>
<TableFooter
sx={{
'& .MuiTableCell-footer': {
position: 'sticky',
bottom: -1,
bottom: 0,
backgroundColor: 'background.paper',
borderBottom: 0,
boxShadow: `inset 0 -1px 0 ${theme.palette.divider}`,
},
}}
>
<TableRow>
<TableCell>
<Typography
color={theme.palette.text.primary}
fontWeight="bold"
>
{overallTotal >= 0
? t('Total {{category}} Income', {
category: getLocalizedCategory(category, t),
})
: t('Total {{category}} Expense', {
category: getLocalizedCategory(category, t),
})}
</Typography>
</TableCell>
<TableCell align="right">
<Typography
fontWeight="bold"
sx={{
color:
overallTotal >= 0
? theme.palette.statusSuccess.main
: theme.palette.chipRedDark.main,
? theme.palette.success.main
: theme.palette.error.main,

Check warning on line 160 in src/components/Reports/MPGAIncomeExpensesReport/BreakdownModal/BreakdownModal.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Large Method

BreakdownModal:React.FC<BreakdownModalProps> increases from 129 to 137 lines of code, threshold = 120 Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
}}
>
{currencyFormat(Math.abs(overallTotal), currency, locale, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { ThemeProvider } from '@emotion/react';
import { render } from '@testing-library/react';
import theme from 'src/theme';
import { MPGAIncomeExpensesReportTestWrapper } from '../MPGAIncomeExpensesReportTestWrapper';
import { CardSkeleton } from './CardSkeleton';

const title = 'Test Title';
const subtitle = 'Test Subtitle';

describe('CardSkeleton', () => {
it('should render card title and subtitle', () => {
const { getByText } = render(
<ThemeProvider theme={theme}>
<CardSkeleton title={title} subtitle={subtitle} />
</ThemeProvider>,
<MPGAIncomeExpensesReportTestWrapper>
<CardSkeleton title={title} />
</MPGAIncomeExpensesReportTestWrapper>,
);

expect(getByText(title)).toBeInTheDocument();
expect(getByText(subtitle)).toBeInTheDocument();
expect(getByText('Last 12 Months')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import {
SxProps,
Theme,
} from '@mui/material';
import { useReport } from '../ReportContext/ReportContext';

export interface CardSkeletonProps {
title: string;
subtitle?: string;
children?: React.ReactNode;
styling?: SxProps<Theme>;
}

export const CardSkeleton: React.FC<CardSkeletonProps> = ({
title,
subtitle,
children,
styling,
}) => {
const { subtitle } = useReport();

return (
<Card>
<CardHeader title={title} subheader={subtitle} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import theme from 'src/theme';
import { ChartLegendContent } from './ChartLegendContent';

const payload = [
{ value: 'Income', color: theme.palette.statusSuccess.main },
{ value: 'Expenses', color: theme.palette.chipRedDark.main },
{ value: 'Income', color: theme.palette.success.main },
{ value: 'Expenses', color: theme.palette.error.main },
];

describe('ChartLegendContent', () => {
Expand All @@ -25,10 +25,10 @@ describe('ChartLegendContent', () => {
const boxes = getAllByRole('listitem').map((item) => item.firstChild);

expect(boxes[0]).toHaveStyle({
backgroundColor: theme.palette.statusSuccess.main,
backgroundColor: theme.palette.success.main,
});
expect(boxes[1]).toHaveStyle({
backgroundColor: theme.palette.chipRedDark.main,
backgroundColor: theme.palette.error.main,
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import '../sharedRechartMock';
import React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { render, waitFor, within } from '@testing-library/react';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import theme from 'src/theme';
import { TotalsProvider } from '../../TotalsContext/TotalsContext';
import { mockData } from '../../mockData';
import { MPGAIncomeExpensesReportTestWrapper } from '../../MPGAIncomeExpensesReportTestWrapper';
import { ExpensesPieChart } from './ExpensesPieChart';

const mutationSpy = jest.fn();

const TestComponent: React.FC = () => (
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<GqlMockedProvider onCall={mutationSpy}>
<TotalsProvider data={mockData}>
<ExpensesPieChart aspect={1.35} width={100} />
</TotalsProvider>
</GqlMockedProvider>
</LocalizationProvider>
</ThemeProvider>
<MPGAIncomeExpensesReportTestWrapper onCall={mutationSpy}>
<ExpensesPieChart aspect={1.35} width={100} />
</MPGAIncomeExpensesReportTestWrapper>
);

describe('ExpensesPieChart', () => {
Expand Down Expand Up @@ -79,20 +67,9 @@ describe('ExpensesPieChart', () => {

it('shows a message when there is no data', async () => {
const { findByText } = render(
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<GqlMockedProvider onCall={mutationSpy}>
<TotalsProvider
data={{
income: [],
expenses: [],
}}
>
<ExpensesPieChart aspect={1.35} width={100} />
</TotalsProvider>
</GqlMockedProvider>
</LocalizationProvider>
</ThemeProvider>,
<MPGAIncomeExpensesReportTestWrapper onCall={mutationSpy} isEmpty>
<ExpensesPieChart aspect={1.35} width={100} />
</MPGAIncomeExpensesReportTestWrapper>,
);

expect(
Expand All @@ -101,17 +78,7 @@ describe('ExpensesPieChart', () => {
});

it('renders a spinner instead of the chart while loading', () => {
const { getByTestId, queryByRole, queryByText } = render(
<ThemeProvider theme={theme}>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<GqlMockedProvider onCall={mutationSpy}>
<TotalsProvider data={mockData} loading>
<ExpensesPieChart aspect={1.35} width={100} />
</TotalsProvider>
</GqlMockedProvider>
</LocalizationProvider>
</ThemeProvider>,
);
const { getByTestId, queryByRole, queryByText } = render(<TestComponent />);

expect(getByTestId('loading-spinner')).toBeInTheDocument();
expect(queryByRole('region')).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Cell, Legend, Pie, PieChart } from 'recharts';
import theme from 'src/theme';
import { useTotals } from '../../TotalsContext/TotalsContext';
import { useReport } from '../../ReportContext/ReportContext';
import { ChartFrame } from '../ChartFrame';
import { ChartLegendContent } from '../ChartLegendContent/ChartLegendContent';

Expand Down Expand Up @@ -32,7 +32,7 @@ export const ExpensesPieChart: React.FC<ExpensesPieChartProps> = ({
salaryTotal,
otherTotal,
dataLoading,
} = useTotals();
} = useReport();

const data = [
{ name: t('Ministry'), value: ministryTotal ?? 0 },
Expand Down
Loading
Loading