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
@@ -0,0 +1,62 @@
import { DateTime } from 'luxon';
import { getStaffExpenseMonthRange } from './getMonthRange';

const baseTime = DateTime.fromISO('2020-01-15');

describe('getStaffExpenseMonthRange', () => {
it('falls back to baseTime month when filters is null', () => {
expect(getStaffExpenseMonthRange(null, baseTime)).toEqual({
startMonth: '2020-01-01',
endMonth: '2020-01-31',
});
});

it('falls back to baseTime month when filters is undefined', () => {
expect(getStaffExpenseMonthRange(undefined, baseTime)).toEqual({
startMonth: '2020-01-01',
endMonth: '2020-01-31',
});
});

it('falls back to baseTime month when both dates are null', () => {
expect(
getStaffExpenseMonthRange({ startDate: null, endDate: null }, baseTime),
).toEqual({
startMonth: '2020-01-01',
endMonth: '2020-01-31',
});
});

it('uses startDate and endDate months when both are present', () => {
const filters = {
startDate: DateTime.fromISO('2025-03-10'),
endDate: DateTime.fromISO('2025-05-20'),
};
expect(getStaffExpenseMonthRange(filters, baseTime)).toEqual({
startMonth: '2025-03-01',
endMonth: '2025-05-31',
});
});

it('uses startDate month and baseTime endMonth when only startDate is present', () => {
const filters = {
startDate: DateTime.fromISO('2025-03-10'),
endDate: null,
};
expect(getStaffExpenseMonthRange(filters, baseTime)).toEqual({
startMonth: '2025-03-01',
endMonth: '2020-01-31',
});
});

it('uses endDate month for both startMonth and endMonth when only endDate is present', () => {
const filters = {
startDate: null,
endDate: DateTime.fromISO('2025-05-20'),
};
expect(getStaffExpenseMonthRange(filters, baseTime)).toEqual({
startMonth: '2025-05-01',
endMonth: '2025-05-31',
});
});
});
19 changes: 19 additions & 0 deletions src/components/Reports/StaffExpenseReport/Helpers/getMonthRange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DateTime } from 'luxon';

interface DateWindow {
startDate?: DateTime | null;
endDate?: DateTime | null;
}

export const getStaffExpenseMonthRange = (
filters: DateWindow | null | undefined,
baseTime: DateTime,
): { startMonth: string | null; endMonth: string | null } => ({
startMonth:
filters?.startDate?.startOf('month').toISODate() ??
filters?.endDate?.startOf('month').toISODate() ??
baseTime.startOf('month').toISODate(),
endMonth:
filters?.endDate?.endOf('month').toISODate() ??
baseTime.endOf('month').toISODate(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import { ReportsStaffExpensesQuery } from '../GetStaffExpense.generated';
import { DateRange } from '../Helpers/StaffReportEnum';
import { Filters, SettingsDialog, SettingsDialogProps } from './SettingsDialog';

const TestComponent: React.FC<SettingsDialogProps> = ({
const mutationSpy = jest.fn();
const TestComponent: React.FC<
SettingsDialogProps & { onCallMock?: jest.Mock }
> = ({
isOpen,
onClose,
selectedFilters,
selectedFundType,
time,
onCallMock,
}) => (
<TestRouter>
<GqlMockedProvider<{ ReportsStaffExpenses: ReportsStaffExpensesQuery }>
onCall={onCallMock}
mocks={{
ReportsStaffExpenses: {
reportsStaffExpenses: {
Expand Down Expand Up @@ -46,9 +52,7 @@ const TestComponent: React.FC<SettingsDialogProps> = ({
breakdownByMonth: [
{
transactions: [
{
transactedAt: DateTime.now().toISO(),
},
{ transactedAt: DateTime.now().toISO() },
],
},
],
Expand All @@ -62,9 +66,7 @@ const TestComponent: React.FC<SettingsDialogProps> = ({
breakdownByMonth: [
{
transactions: [
{
transactedAt: DateTime.now().toISO(),
},
{ transactedAt: DateTime.now().toISO() },
],
},
],
Expand All @@ -84,6 +86,7 @@ const TestComponent: React.FC<SettingsDialogProps> = ({
onClose={onClose}
selectedFilters={selectedFilters}
selectedFundType={selectedFundType}
time={time}
/>
</LocalizationProvider>
</GqlMockedProvider>
Expand Down Expand Up @@ -418,4 +421,32 @@ describe('SettingsDialog', () => {

expect(await findByLabelText('Benefits')).not.toBeChecked();
});

it('should query using the time prop month when no date filter is set', async () => {
const time = DateTime.fromISO('2025-10-01');

render(
<TestComponent {...defaultProps} time={time} onCallMock={mutationSpy} />,
);

await waitFor(() => {
expect(mutationSpy).toHaveGraphqlOperation('ReportsStaffExpenses', {
startMonth: '2025-10-01',
endMonth: '2025-10-31',
fundTypes: ['Primary'],
});
});
});

it('should fall back to current month for category query when time prop is not provided', async () => {
render(<TestComponent {...defaultProps} onCallMock={mutationSpy} />);

await waitFor(() => {
expect(mutationSpy).toHaveGraphqlOperation('ReportsStaffExpenses', {
startMonth: '2020-01-01',
endMonth: '2020-01-31',
fundTypes: ['Primary'],
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Box,
Button,
Checkbox,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
Expand All @@ -18,20 +19,21 @@
import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
import Loading from 'src/components/Loading/Loading';
import { CustomDateField } from 'src/components/Shared/DateTimePickers/CustomDateField';
import { Fund, StaffExpenseCategoryEnum } from 'src/graphql/types.generated';
import i18n from 'src/lib/i18n';
import { getLocalizedCategory } from '../../Shared/Helpers/transformStaffExpenseEnums';
import { useReportsStaffExpensesQuery } from '../GetStaffExpense.generated';
import { DateRange } from '../Helpers/StaffReportEnum';
import { getAvailableCategories } from '../Helpers/filterTransactions';
import { getStaffExpenseMonthRange } from '../Helpers/getMonthRange';

export interface SettingsDialogProps {
isOpen: boolean;
selectedFilters?: Filters;
selectedFundType: string | null;
onClose: (filters?: Filters) => void;
time?: DateTime;
}

export interface Filters {
Expand Down Expand Up @@ -120,205 +122,209 @@
onClose,
selectedFilters,
selectedFundType,
time,
}) => {
const { t } = useTranslation();
const [previewFilters, setPreviewFilters] = useState<Filters | null>(null);

const currentTime = useMemo(() => DateTime.now().startOf('month'), []);
const currentTime = useMemo(
() => time ?? DateTime.now().startOf('month'),
[time],
);

const handleClose = () => {
setPreviewFilters(null);
onClose(selectedFilters);
};

const getQueryVariables = (filterParams: Filters | null) => ({
fundTypes: selectedFundType ? [selectedFundType] : null,
startMonth:
filterParams?.startDate?.startOf('month').toISODate() ??
filterParams?.endDate?.startOf('month').toISODate() ??
currentTime.startOf('month').toISODate(),
endMonth:
filterParams?.endDate?.endOf('month').toISODate() ??
currentTime.endOf('month').toISODate(),
...getStaffExpenseMonthRange(filterParams, currentTime),
});

const {
data: categoryData,
loading: categoryLoading,
error: categoryError,
} = useReportsStaffExpensesQuery({
variables: getQueryVariables(previewFilters ?? selectedFilters ?? null),
skip: !isOpen,
fetchPolicy: 'no-cache',
});

const availableCategories = useMemo(() => {
const categoryFunds: Fund[] =
categoryData?.reportsStaffExpenses?.funds ?? [];

const filtersToUse = previewFilters ?? selectedFilters ?? null;

return getAvailableCategories(categoryFunds, filtersToUse, currentTime);
}, [categoryData, previewFilters, selectedFilters, currentTime]);

const validateAndRefetch = (
validateForm: () => Promise<Record<string, unknown>>,
filters: Filters,
) => {
setTimeout(() => {
validateForm().then((errors) => {
if (Object.keys(errors).length === 0) {
setPreviewFilters(getFiltersWithCalculatedDates(filters));
}
});
}, 0);
};

const initialValues = {
selectedDateRange: selectedFilters?.selectedDateRange ?? null,
startDate:
selectedFilters?.selectedDateRange === null
? selectedFilters?.startDate
: null,
endDate:
selectedFilters?.selectedDateRange === null
? selectedFilters?.endDate
: null,
categories: selectedFilters?.categories ?? [],
};

const handleSubmit = (values: Filters) => {
setPreviewFilters(null); // Clear preview filters when dialog closes
onClose(getFiltersWithCalculatedDates(values));
};

return (
<Dialog open={isOpen} onClose={handleClose} fullWidth maxWidth="md">
<DialogTitle>{t('Report Settings')}</DialogTitle>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
validateOnChange
validateOnBlur
enableReinitialize
onSubmit={handleSubmit}
>
{({
values,
setFieldValue,
isValid,
dirty,
errors,
touched,
validateForm,
setTouched,
}) => {
return (
<Form>
<DialogContent>
<TextField
select
label={t('Select Date Range')}
fullWidth
value={values.selectedDateRange ?? ''}
onChange={(e) => {
const value = e.target.value === '' ? null : e.target.value;
setFieldValue('selectedDateRange', value);
if (value !== null) {
setFieldValue('startDate', null);
setFieldValue('endDate', null);

setTouched({
...touched,
startDate: false,
endDate: false,
});
}
validateAndRefetch(validateForm, {
...values,
selectedDateRange: value as DateRange | null,
...(value !== null && { startDate: null, endDate: null }),
});
}}
>
<MenuItem value="">{t('None')}</MenuItem>
<MenuItem value={DateRange.WeekToDate}>
{t('Week to Date')}
</MenuItem>
<MenuItem value={DateRange.MonthToDate}>
{t('Month to Date')}
</MenuItem>
<MenuItem value={DateRange.YearToDate}>
{t('Year to Date')}
</MenuItem>
</TextField>

<Typography variant="body2" sx={{ mt: 2, mb: 2 }}>
{t('Or enter a custom date range:')}
</Typography>

<Box display="flex" gap={2}>
<CustomDateField
label={t('Start Date')}
value={values.startDate ?? null}
onChange={(date) => {
setFieldValue('startDate', date);
setTouched({ ...touched, startDate: true });
if (date) {
setFieldValue('selectedDateRange', null);
}
validateAndRefetch(validateForm, {
...values,
startDate: date,
selectedDateRange: date
? null
: values.selectedDateRange,
});
}}
fullWidth
error={Boolean(errors.startDate && touched.startDate)}
helperText={
errors.startDate && touched.startDate
? errors.startDate
: ''
}
/>
<CustomDateField
label={t('End Date')}
value={values.endDate ?? null}
onChange={(date) => {
setFieldValue('endDate', date);
setTouched({ ...touched, endDate: true });
if (date) {
setFieldValue('selectedDateRange', null);
}
validateAndRefetch(validateForm, {
...values,
endDate: date,
selectedDateRange: date
? null
: values.selectedDateRange,
});
}}
fullWidth
error={Boolean(errors.endDate && touched.endDate)}
helperText={
errors.endDate && touched.endDate ? errors.endDate : ''
}
/>
</Box>

<Typography sx={{ mt: 2, whiteSpace: 'pre-line' }}>
{t(
`You can combine certain categories of data into single rows. This may be useful for long date ranges (e.g., "Year to Date").
Select which categories to consolidate. Each category remains separate.`,
)}
</Typography>

<Typography sx={{ mt: 2, mb: 1 }}>
{t('Select Categories:')}
</Typography>

{categoryLoading ? (
<Loading loading />
<Box
sx={{ display: 'flex', justifyContent: 'center', py: 2 }}
>
<CircularProgress size={24} />
</Box>

Check notice on line 327 in src/components/Reports/StaffExpenseReport/SettingsDialog/SettingsDialog.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Complex Method

SettingsDialog:React.FC<SettingsDialogProps> decreases in cyclomatic complexity from 37 to 31, 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.
) : categoryError ? (
<Alert severity="error">
{t('Failed to load categories. Please try again.')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Settings } from 'luxon';
import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
Expand Down Expand Up @@ -274,4 +275,62 @@ describe('StaffExpenseReport', () => {
expect(getAllByRole('row')).toHaveLength(2);
});
});

it('shows month title and navigation when only category filters are applied', async () => {
const { getByRole, findByRole, findByLabelText, queryByText } = render(
<TestComponent />,
);

userEvent.click(getByRole('button', { name: 'Filter Settings' }));

userEvent.click(await findByLabelText('Assessment'));
userEvent.click(await findByRole('button', { name: 'Apply Filters' }));

expect(
await findByRole('heading', { name: 'January 2020', level: 6 }),
).toBeInTheDocument();
expect(
await findByRole('button', { name: 'Previous Month' }),
).toBeInTheDocument();
expect(
await findByRole('button', { name: 'Next Month' }),
).toBeInTheDocument();
expect(queryByText('Clear Filters')).not.toBeInTheDocument();
});

it('shows filter date range title and hides month navigation when date filters are applied', async () => {
const originalNow = Settings.now;
Settings.now = () => new Date(2020, 0, 20).valueOf();

const { getByRole, findByRole, getByLabelText, queryByRole } = render(
<TestComponent />,
);

userEvent.click(getByRole('button', { name: 'Filter Settings' }));
await findByRole('heading', { name: 'Report Settings' });

userEvent.click(getByLabelText('Select Date Range'));
userEvent.click(getByRole('option', { name: 'Month to Date' }));

await waitFor(() =>
expect(getByRole('button', { name: 'Apply Filters' })).not.toBeDisabled(),
);
userEvent.click(getByRole('button', { name: 'Apply Filters' }));

expect(
await findByRole('heading', {
level: 6,
name: 'January 1, 2020 - January 20, 2020',
}),
).toBeInTheDocument();

expect(
queryByRole('button', { name: 'Previous Month' }),
).not.toBeInTheDocument();
expect(
queryByRole('button', { name: 'Next Month' }),
).not.toBeInTheDocument();

Settings.now = originalNow;
}, 10000);
});
12 changes: 4 additions & 8 deletions src/components/Reports/StaffExpenseReport/StaffExpenseReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
getIconColorForFundType,
getIconForFundType,
} from './Helpers/fundTypeHelpers';
import { getStaffExpenseMonthRange } from './Helpers/getMonthRange';
import { Filters, SettingsDialog } from './SettingsDialog/SettingsDialog';
import { PrintTables } from './Tables/PrintTables';
import { StaffReportTable } from './Tables/StaffReportTable';
Expand Down Expand Up @@ -98,332 +99,327 @@
const { data, loading } = useReportsStaffExpensesQuery({
variables: {
fundTypes: ['Primary', 'Savings', 'Staff Conference Savings'],
startMonth:
filters?.startDate?.startOf('month').toISODate() ??
filters?.endDate?.startOf('month').toISODate() ??
time.startOf('month').toISODate(),
endMonth:
filters?.endDate?.endOf('month').toISODate() ??
time.endOf('month').toISODate(),
...getStaffExpenseMonthRange(filters, time),
},
});

const { data: accountData } = useStaffAccountQuery();
const { id, name } = accountData?.staffAccount ?? {};

const handlePrint = () => window.print();

const timeTitle = time.toJSDate().toLocaleDateString(locale, {
month: 'long',
year: 'numeric',
});

const hasNext = time.hasSame(DateTime.now(), 'month');

const allFunds: Fund[] = useMemo(
() =>
(data?.reportsStaffExpenses?.funds ?? []).toSorted((a, b) =>
a.id.localeCompare(b.id),
),
[data],
);

const defaultFundType: string | null =
allFunds.find((f) => f.fundType === 'Primary')?.fundType ??
allFunds[0]?.fundType ??
null;

const [selectedFundType, setSelectedFundType] = useState<string | null>(
defaultFundType,
);

useEffect(() => {
if (!selectedFundType && defaultFundType) {
setSelectedFundType(defaultFundType);
}
}, [selectedFundType, defaultFundType]);

const selectedFund = allFunds.find(
(fund) => fund.fundType === selectedFundType,
);

const setPrevMonth = () => {
const prevTime = time.minus({ months: 1 });
setTime(prevTime);
};

const setNextMonth = () => {
const nextTime = time.plus({ months: 1 });
setTime(nextTime);
};

const transactions = useMemo(() => {
const newTransactions: Record<
string,
{ income: Transaction[]; expenses: Transaction[] }
> = {};

allFunds.forEach((fund) => {
const incomeTransactions = filterTransactions({
tableType: ReportType.Income,
targetTime: time,
fund,
filters,
t,
});
const expenseTransactions = filterTransactions({
tableType: ReportType.Expense,
targetTime: time,
fund,
filters,
t,
});

newTransactions[fund.fundType] = {
income: incomeTransactions,
expenses: expenseTransactions,
};
});

return newTransactions;
}, [allFunds, time, t, filters]);

const handleCardClick = (fundType: string) => {
setSelectedFundType(fundType);
};

const handleSettingsClick = () => {
setIsSettingsOpen(!isSettingsOpen);
};

const getPosOrNegTransactions = (tableType: ReportType, fundType: string) => {
const fundTransactions = transactions[fundType];

return tableType === ReportType.Income
? fundTransactions.income
: fundTransactions.expenses;
};

const getFilteredTotals = (tableType: ReportType, fundType: string) => {
const filtered = getPosOrNegTransactions(tableType, fundType);
return filtered.reduce((sum, transaction) => sum + transaction.amount, 0);
};

const transferTotals = useMemo(() => {
const totals: Record<string, { in: number; out: number }> = {};

for (const [fundType, fundTransactions] of Object.entries(transactions)) {
totals[fundType] = {
in: fundTransactions.income.reduce(
(sum, transaction) => sum + transaction.amount,
0,
),
out: fundTransactions.expenses.reduce(
(sum, transaction) => sum + transaction.amount,
0,
),
};
}

return totals;
}, [transactions]);

const filterTimeTitle = useMemo(() => {
if (filters?.selectedDateRange) {
return dateRangeToString(filters.selectedDateRange, locale);
} else if (filters?.startDate && filters?.endDate) {
return getFormattedDateString(filters.startDate, filters.endDate, locale);
} else if (filters?.startDate && !filters?.endDate) {
return getFormattedDateString(filters.startDate, DateTime.now(), locale);
} else if (!filters?.startDate && filters?.endDate) {
return getFormattedDateString(
filters.endDate.startOf('month'),
filters.endDate,
locale,
);
}
return null;
}, [filters, locale, t]);

return (
<Box>
<SimpleScreenOnly>
<MultiPageHeader
isNavListOpen={isNavListOpen}
onNavListToggle={onNavListToggle}
title={title}
headerType={HeaderTypeEnum.Report}
/>
</SimpleScreenOnly>
<Box mt={2}>
<Container>
<Box>
<StyledHeaderBox>
<SimplePrintOnly>
<Typography variant="h4">
{t('Income and Expenses: {{timeTitle}}', {
timeTitle: timeTitle,
})}
</Typography>
</SimplePrintOnly>
<SimpleScreenOnly>
<Typography variant="h4">{t('Income and Expenses')}</Typography>
</SimpleScreenOnly>
{Object.values(transactions).some(
(fundTransactions) =>
fundTransactions.income.length ||
fundTransactions.expenses.length,
) ? (
<SimpleScreenOnly
display="flex"
flexDirection="column"
alignItems="flex-end"
gap={1}
>
<StyledPrintButton
startIcon={
<SvgIcon fontSize="small">
<PrintIcon titleAccess={t('Print')} />
</SvgIcon>
}
onClick={handlePrint}
>
{t('Print')}
</StyledPrintButton>
</SimpleScreenOnly>
) : null}
</StyledHeaderBox>
{loading ? (
<AccountInfoBoxSkeleton />
) : (
<AccountInfoBox name={name} accountId={id} />
)}
<SimpleScreenOnly>
<Box
display="flex"
flexWrap="wrap"
gap={2}
sx={{
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<BalanceCardList
funds={allFunds}
selectedFundType={selectedFundType}
transferTotals={transferTotals}
onCardClick={handleCardClick}
loading={loading}
/>
</Box>
</SimpleScreenOnly>
<SimplePrintOnly>
<Box>
{selectedFundType && (
<PrintHeader
icon={getIconForFundType(selectedFundType)}
iconColor={getIconColorForFundType(selectedFundType, theme)}
title={selectedFundType}
startBalance={selectedFund?.startBalance ?? 0}
endBalance={selectedFund?.endBalance ?? 0}
transfersIn={transferTotals[selectedFundType]?.in}
transfersOut={transferTotals[selectedFundType]?.out}
/>
)}
</Box>
</SimplePrintOnly>
</Box>
</Container>
</Box>
<SimpleScreenOnly mt={2} mb={2}>
<Container>
<Divider />
</Container>
</SimpleScreenOnly>
<SimpleScreenOnly mt={2}>
<Container>
<StyledTimeNavBox>
{!filters ? (
{!isFilterDateSelected ? (
<Typography variant="h6">{timeTitle}</Typography>
) : (
<Typography variant="h6">{filterTimeTitle}</Typography>
)}
{!isFilterDateSelected ? (
<>
<Button
style={{ marginLeft: 'auto', maxHeight: 35 }}
variant="contained"
startIcon={<ChevronLeftIcon />}
size="small"
onClick={setPrevMonth}
>
{t('Previous Month')}
</Button>
<Button
style={{ maxHeight: 35 }}
variant="contained"
endIcon={<ChevronRightIcon />}
size="small"
onClick={setNextMonth}
disabled={hasNext}
>
{t('Next Month')}
</Button>
</>
) : null}
</StyledTimeNavBox>
</Container>
</SimpleScreenOnly>
<Box mt={2} mb={2}>
<Container>
<Divider></Divider>
</Container>
</Box>
<SimpleScreenOnly>
<Container sx={{ gap: 1, display: 'flex', flexDirection: 'row' }}>
<DownloadButtonGroup
transactions={
selectedFundType
? [
...(transactions[selectedFundType]?.income ?? []),
...(transactions[selectedFundType]?.expenses ?? []),
]
: []
}
/>
<Box display={'flex'} flexGrow={1} justifyContent="flex-end" gap={1}>
{isFilterDateSelected ? (
<StyledFilterButton
variant="outlined"
startIcon={<FilterListOff />}
size="small"
onClick={() => {
setFilters(null);
}}
>
{t('Clear Filters')}
</StyledFilterButton>
) : null}
<StyledFilterButton
variant="outlined"
startIcon={<Settings />}
size="small"
onClick={handleSettingsClick}
>
{t('Filter Settings')}
</StyledFilterButton>
</Box>
</Container>
</SimpleScreenOnly>
<SimpleScreenOnly mt={2} mb={2}>
<Container>
<Divider />
</Container>
</SimpleScreenOnly>
<Box>
<SettingsDialog
selectedFilters={filters || undefined}
selectedFundType={selectedFundType}
isOpen={isSettingsOpen}
time={time}

Check notice on line 422 in src/components/Reports/StaffExpenseReport/StaffExpenseReport.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Complex Method

StaffExpenseReport:React.FC<StaffExpenseReportProps> decreases in cyclomatic complexity from 59 to 53, 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.
onClose={(newFilters) => {
setFilters(newFilters ?? null);
setIsSettingsOpen(false);
Expand Down
Loading