Skip to content

MPDX-9834 Add year filter to MPGA#1929

Open
kegrimes wants to merge 14 commits into
mainfrom
mpdx-9834-add-year-filter-on-mpga
Open

MPDX-9834 Add year filter to MPGA#1929
kegrimes wants to merge 14 commits into
mainfrom
mpdx-9834-add-year-filter-on-mpga

Conversation

@kegrimes

@kegrimes kegrimes commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Description

MPGA UAT issues:

  • Add "Select Date Range" on report settings for MPGA
  • Add "Year to Date" option, as well as the previous years where transactions occurred
  • Grayed out future months if "Year to Date" filter is applied

Extra:

  • Created a shared context to remove prop drilling
  • Created a shared test wrapper for all tests

Jira ticket: MPDX-9834

Testing

Impersonate: courtney.campbell@cru.org

No filter (default) test:

  • Go to /reports/mpgaIncomeExpenses
  • Check that the data is from the last 12 months (August 2025 - July 2026)

Year to Date filter test:

  • Go to /reports/mpgaIncomeExpenses
  • Click "Report Settings" and select "Year to Date"
  • Check that the data is from this year only (January 2026 - July 2026)
  • Verify the future months are grayed out

Past year filter test:

  • Go to /reports/mpgaIncomeExpenses
  • Click "Report Settings" and select "2025"
  • Check that the data is from 2025 only (January 2025 - December 2025)

Checklist:

  • I have given my PR a title with the format "MPDX-(JIRA#) (summary sentence max 80 chars)"
  • I have applied the appropriate labels (Add the label "Preview" to automatically create a preview environment)
  • I have run the Claude Code /pr-review command locally and fixed any relevant suggestions
  • I have requested a review from another person on the project
  • I have tested my changes in preview or in staging
  • I have cleaned up my commit history

@kegrimes kegrimes self-assigned this Jul 20, 2026
@kegrimes kegrimes added the Preview Environment Add this label to create an Amplify Preview label Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Bundle sizes [mpdx-react]

Compared against ea57016

Route Size (gzipped) Diff
/accountLists/[accountListId]/reports/mpgaIncomeExpenses 428.21 KB +2.39 KB

@kegrimes kegrimes left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Code Review — ✅ APPROVED WITH SUGGESTIONS

PR #1929 — Add year filter to MPGA · Mode: standard · 6 specialized agents + dependency analysis + 2 gap-review agents + 3 adversarial verifiers.

A well-executed refactor. It deletes TotalsContext and a large amount of prop-drilling, consolidates fetch/derive/totals into one ReportContext, and the new year/YTD date-window + zero-fill logic is verified correct (no off-by-one in firstFutureMonthIndex, totals not inflated by future zeros, date windows serialize correctly, no broken imports). No blockers. All findings below are improvements.

Risk Assessment

10/10 · CRITICAL — driven by breadth (17 medium-risk feature files) + change volume + the financial-reporting domain, not by any inherently dangerous infra file. Recommended reviewer level: SENIOR (financial display logic).

Dependency Impact

ReportContext.tsx has 15 dependents, all updated in this PR (self-contained). Every breaking edit — deleted TotalsContext, useGetLastTwelveMonths(locale, year?) signature change, hideDateRangeisMpgaReport rename, un-exported getFiltersWithCalculatedDates — has its call sites fixed in-PR. External consumer StaffExpenseReport (not in PR) verified safe.


Findings not anchored to a diff line (coverage gaps, related files, pre-existing)

Medium (5.0–6.9)

🟠 [6.5] Positional month↔label alignment is fragile — src/components/Reports/MPGAIncomeExpensesReport/Helper/filterFunds.ts:92,135,187 (Data Integrity, PLAUSIBLE; related file, not in PR)
Row monthly[] is built by array index from breakdownByMonth; monthLabels and the zero-fill are indexed independently — nothing joins on month.month. Now that arbitrary years / partial YTD windows are queryable, a sparse or reordered breakdownByMonth from the server would silently shift every column (April's amount under March's header; a real month grayed as "future"). Fix: key months by month.month, or assert the one-ordered-entry-per-requested-month server contract.

🟠 [6.0] Deleted negative-net calc test not migrated — Charts/MonthlySummaryChart test (diff 374-407) (Testing)
The removed test was the only assertion covering a negative monthly net (expenses > income). Re-add an equivalent net-calc assertion driven through the context mock.

🟡 [5.0] Headline "graying" feature has no rendering test — Tables/{TableCard,PrintTables,TotalRow} (Testing, CONFIRMED; downgraded from 7.5 by adversarial verify)
The derivation is unit-tested in ReportContext.test.tsx, but no component test activates YTD, so the future-month classes / gradient underline / colSpan-split paths never execute under test. MPGAIncomeExpensesReportTestWrapper can't seed filters — closing this needs an initialFilters seam on ReportProvider/the wrapper. Real regression risk, not a live bug.

Suggestions (<5.0)

  • [4.0] Empty-state copy hardcodes "in the last 12 months"Tables/PrintTables.tsx:259-260, DisplayModes/ScreenOnlyReport.tsx:42,57 (UX, in-scope but pre-existing lines). Now contradicts the dynamic subtitle when a specific year / YTD is selected and yields no data. Thread subtitle (already on useReport()) into the empty-state copy.
  • [3.0] Average column mixes two divisor conventions in YTD — server averagePerMonth (plain/subcategory rows) vs client total / monthly.length (split/combined rows). Verified the client divisor uses the elapsed-month array N, so they agree if the server also divides by N; normalize to one convention. No demonstrable miscalc today.

Pre-existing (informational, not counted toward verdict)

  • Hardcoded currency = 'USD' also in BreakdownModal.tsx:41 (not migrated to useReport() like its siblings). No currencyCode is read from the data → wrong symbol for non-USD accounts. Worth a follow-up ticket given the report's purpose.
  • SettingsDialog <Dialog> missing aria-labelledbyDialogTitle; empty-state colSpan={15} magic number in PrintTables; Cancel button sx={{ color: 'black' }} (hardcoded, use a theme token).

Agent Summary

Agent Blockers Medium Suggestions Confidence
Security (skipped — no auth/api/env/apollo-link files)
Architecture 0 0 5 Med-High
Data Integrity 0 1 2 High
Testing 0 2 3 High
UX 0 1 4 High
Standards 0 1 0 High
Financial Reporting 0 0 2 Med-High

Financial Checklist: Money arithmetic safe ✅ · Currency mixing prevented ✅ (single server-aggregated figure) · Rounding at display boundary ✅ · Null/undefined handled ✅ · Luxon (not new Date()) ✅ · Server aggregations preferred ✅ (caveat: Average mixes server+client — see above)

Inline comments below carry a <!-- severity:X.X --> tag. Reply /dismiss: <reason> on any finding with severity < 7 you disagree with, then re-run the review.

🤖 Generated with Claude Code

Comment thread src/components/Reports/MPGAIncomeExpensesReport/Tables/TableCard.tsx Outdated
Comment thread src/components/Reports/MPGAIncomeExpensesReport/Tables/TotalRow.tsx Outdated
Comment thread src/components/Reports/Shared/SettingsDialog/SettingsDialog.tsx Outdated
Comment thread src/components/Reports/MPGAIncomeExpensesReport/BreakdownModal/BreakdownModal.tsx Outdated
@kegrimes
kegrimes requested a review from wjames111 July 22, 2026 18:55
@kegrimes
kegrimes removed the request for review from wjames111 July 23, 2026 18:08
@kegrimes
kegrimes requested a review from zweatshirt July 23, 2026 19:27
@kegrimes

Copy link
Copy Markdown
Contributor Author

@zweatshirt I realized Will was out! So sorry for the large PR🫣, but most of it is removing prop drilling. I noticed it was getting bad when I was adding the filters. Can you just make sure nothing is broken with the filters and the code (unrelated to context) looks decent?

@zweatshirt zweatshirt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to comment on whatever I was able to find. The UI looks amazing and seems to behave as expected so I am going to approve. Just had some suggestions.

Comment on lines +48 to +49
const incomeData = data.income ?? [];
const expenseData = data.expenses ?? [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: We can use de-structuring here

</GqlMockedProvider>
</LocalizationProvider>
</ThemeProvider>,
it('displays the tables that should be showing', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test checks that there are 2 tables but only seem to add an assertion for one table by text. What is the second table that is supposed to display? Can we add an assertion for it in this test?

Comment on lines +240 to +247
incomeTotal,
expensesTotal,
ministryTotal,
healthcareTotal,
assessmentTotal,
benefitsTotal,
salaryTotal,
otherTotal,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would group these, something like this:

 totals: {
  income, expenses, ministry, healthcare,
  assessment, benefits, salary, other,
}

.slice(0, index)
.reduce((sum, group) => sum + group.count, 0);

const futureStart =

@zweatshirt zweatshirt Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of changing this to something like pastCount instead of futureStart?


const futureStart =
firstFutureMonthIndex !== undefined
? Math.max(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can futureStart ever go negative? Wondering if this Math.max is needed or not

Comment on lines +278 to +285
sx={{
'& .future-month-header .MuiDataGrid-columnHeaderTitle': {
color: theme.palette.text.disabled,
},
'& .future-month': {
backgroundColor: theme.palette.action.hover,
},
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional:

Suggested change
sx={{
'& .future-month-header .MuiDataGrid-columnHeaderTitle': {
color: theme.palette.text.disabled,
},
'& .future-month': {
backgroundColor: theme.palette.action.hover,
},
}}
sx={{
'.future-month-header .MuiDataGrid-columnHeaderTitle': {
color: theme.palette.text.disabled,
},
'.future-month': {
backgroundColor: theme.palette.action.hover,
},
}}

},
];
}, [monthCount, getBorderColor, t]);
}, [monthCount, getBorderColor, firstFutureMonthIndex, t]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably won't ever trigger but theme could go in the dependency array

Comment on lines +68 to +77
const isFilterActive = useMemo(
() =>
Boolean(
filters &&
(filters.selectedDateRange === DateRange.YearToDate ||
(filters.selectedYear !== null &&
filters.selectedYear !== undefined)),
),
[filters],
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably keep this un-memoized

Comment on lines +175 to +181
const hasActiveFilter = Boolean(
newFilters &&
(newFilters.categories ||
newFilters.selectedDateRange === DateRange.YearToDate ||
(newFilters.selectedYear !== null &&
newFilters.selectedYear !== undefined)),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and isFilterActive are nearly identical, what do you think about creating a shared variable for both conditions?

Comment on lines +148 to +173
const transformedData: Funds[] = useMemo(
() =>
(reportData?.reportsStaffExpenses?.funds ?? []).map((fund) => ({
...fund,
categories: (fund.categories ?? []).map((category) => ({
...category,
category: category.category,
breakdownByMonth: category.breakdownByMonth.map((month) => ({
...month,
})),
subcategories: (category.subcategories ?? []).map((subcategory) => ({
...subcategory,
subCategory: subcategory.subCategory,
breakdownByMonth: subcategory.breakdownByMonth.map((month) => ({
...month,
transactions: (month.transactions ?? []).map((transaction) => ({
transactedAt: transaction.transactedAt,
description: transaction.description ?? '',
amount: transaction.amount,
})),
})),
})),
})),
})),
[reportData],
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it is worth adding a comment for this function explaining why we transform the data here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Preview Environment Add this label to create an Amplify Preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants