Skip to content

Add monthly category spending visuals - #532

Open
Dustin-ONeil wants to merge 2 commits into
spliit-app:mainfrom
Dustin-ONeil:codex/monthly-category-spending-visuals
Open

Add monthly category spending visuals#532
Dustin-ONeil wants to merge 2 commits into
spliit-app:mainfrom
Dustin-ONeil:codex/monthly-category-spending-visuals

Conversation

@Dustin-ONeil

@Dustin-ONeil Dustin-ONeil commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Adds lightweight monthly spending visuals to the Stats page:

  • Monthly stacked spending by category/category group
  • Range category breakdown
  • Balance timeline showing participant balances and reimbursements over time

The charts use existing expense/category data and custom HTML/CSS/SVG rendering. No chart dependency, persisted settings, Prisma fields, or migrations are added.

Details

  • Adds category/group filtering controls, range controls, chart orientation controls, and rounded amount display.
  • Excludes reimbursements from category spending totals.
  • Uses UTC date parts for month/day grouping to avoid DATE timezone shifts.
  • Adds a balance timeline with reimbursement markers, optional category markers, and stack-order control.
  • Keeps visuals live through the existing stats query refresh path.

Testing

  • pnpm test
  • pnpm run check-types
  • pnpm run lint
  • targeted Prettier check on touched files

Transparency note

This is my third request using Codex for this kind of contribution. Happy to iterate to make sure it's up to standards

Reviewer Demo

Screenshots attached here. Any demo group/data should be fake and provided outside the deployed codebase.

  • Monthly Spending is traditional stacked bar chart with expenses by category
  • Balance Timeline is a stacked column chart showing outstanding balances ("Sam Owes" "Alex Is Owed") over the timeline of the group. I'm a data scientist (Masters in Statistics) so this is my best visual yet but I'm happy to iterate. I already went through hundreds of iterations to intuitively show the complexity of these interactions.
Monthly Spending Balance Timeline

@antonio-ivanovski

Copy link
Copy Markdown

Thanks for the inspiration! I've also added charts to the statistics page in my fork.

To avoid reinventing the wheel, I opted to use a charting library instead of custom implementations. The current charts are intentionally simple—they give a nice overview without adding much complexity—but I'd love to expand them over time. If you're interested, contributions toward richer statistics and charts would be very welcome!

Screenshot_20260711_191533_Chrome.jpg

Repo: https://github.com/antonio-ivanovski/spliit-cloud
Public instance: https://spliit.cloud/

Ecklebe pushed a commit to Ecklebe/spliit that referenced this pull request Jul 19, 2026
Ecklebe pushed a commit to Ecklebe/spliit that referenced this pull request Jul 19, 2026
…nd location parsing

Extracted getLocationFromSearchParams out of expense-form.tsx into
src/lib/location.ts, following this codebase's existing convention of
keeping pure logic separately testable from the UI (number-input.ts,
expense-date-groups.ts, etc.) - it was previously private to a large
client component file with no test coverage.

Added api.test.ts coverage for spliit-app#420's scheduleDeleteGroup/restoreGroup
(including the actual reaper sweep via getGroupExpenses, matching how
createRecurringExpenses is exercised) and spliit-app#165's comment CRUD functions
- both had zero test coverage despite being real, DB-touching features.

Verified all of this for real, not just against the graceful
Postgres-unavailable skip path: ran prisma migrate deploy against the
actual running spliit-db container (applying all 6 new migrations
cleanly on top of existing data), then the full suite against it.
That surfaced one more real cross-PR staleness bug: _app.test.ts's
stats.get() assertion used .toEqual and didn't know about spliit-app#532's new
monthlySpending field, so it failed against real data even though it
passed against the gracefully-skipped path. Switched to .toMatchObject,
matching the pattern already used one call above for the same reason.
@quintushr quintushr self-assigned this Aug 11, 2026
@quintushr quintushr added the enhancement New feature or request label Aug 11, 2026
@t0ma5

t0ma5 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@Dustin-ONeil thanks! This is great, I used it in my fork

@quintushr

Copy link
Copy Markdown
Collaborator

Hi @Dustin-ONeil,

Thanks a lot for your work!

The foundations are really good, but I don't think the balance timeline is useful enough as it stands, and that part also has a few bugs:

  • The chart has a hard-coded bg-white / fill-white, so it breaks in dark mode (the labels use dark: variants on top of a white background).
  • Several strings are hard-coded in English instead of going through next-intl: owes, is owed, is settled, X paid Y to Z, Expense. They show up in tooltips and aria-labels for every locale. The category name in the event tooltip is also the raw English value from the database, instead of going through the Categories messages.
  • A lot of what the server computes for the timeline is never displayed (peakBalance, repaymentCount, maxPositiveBalance, currentBalance…), but it's still computed and sent on every request.
  • Because the new fields have defaults on stats.get, the whole timeline is now computed and sent twice on each stats page load — the Totals component uses a different query key and downloads a payload it never uses.
  • The expense splitting logic in getExpenseDeltas duplicates the one in getBalances, including the "give the remainder to the last participant" rule. Two copies of that rule will drift apart.

Another thing, and it applies to the parts we keep as well: the files are really too big. balance-timeline-chart.tsx is 1,474 lines and monthly-spending.tsx is 1,188, which makes them the largest and third largest .tsx files in the whole repo — around a quarter of all the TSX in the project, for a single stats card. Could you split them into smaller modules? The color palettes alone are about 230 lines and would be much better in their own file, and the chart, the legend, the breakdown and the tooltip could each live separately. It would make the review a lot easier too.

If you're okay with it, for a first version of the analytics let's start with the two first parts — the monthly stacked chart and the category breakdown — and we'll see later whether we need a balance timeline.

One last thing: the CI is failing on Prettier, npm run prettier should take care of it.

Do you want to make the changes, or should I do them?

Thanks again for your PR!

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants