Skip to content

Reports: an Accounts view, seller grouping, and an even header - #30

Merged
42piratas merged 5 commits into
mainfrom
feat/reports-accounts-sellers
Aug 3, 2026
Merged

Reports: an Accounts view, seller grouping, and an even header#30
42piratas merged 5 commits into
mainfrom
feat/reports-accounts-sellers

Conversation

@42piratas

Copy link
Copy Markdown
Contributor

What

Three asks on the Reports view, plus the header work they turned up.

View → Accounts. One bar per account of the user's own — a withdrawal's source, a deposit's destination. A transfer touches two of them and counts under both: it left one and arrived at the other, and dropping either end would under-report that account's movement.

Group (Transactions view only). Collapses the list onto the seller — the far end of the transaction, which Firefly models as an expense account on a withdrawal and a revenue account on a deposit, so the side follows the direction. On the demo fixture that is 195 rows down to 30.

Both toggles go inert where they mean nothing. Per month under a Month period (already one month; splitting it yields the identical single card), Group under a View that has already rolled up. The setting is kept across the inert window and masked at the point of use, so switching back restores what you had rather than silently clearing it.

How

buildReport already collapsed transactions onto a subject and ranked them, so this is one seam, not three: the view === 'categories' branch becomes a grain — transaction, seller, category, account — and the three that roll up share a path. A transaction with nothing on the end being ranked is bucketed, never dropped, the same way an uncategorised one always was.

No server change. /api/transactions already carried source/destination.

Header

The two toggles are icon-only now, and everything from View rightward is one right-aligned run at a uniform 8px gap. Two things had to change for that to be true:

  • On/off was invisible in dark mode. "On" was a bg-secondary class laid over the outline variant, which took the button from bg-input/30 to /50 — the same colour, 20% more opaque. Both toggles now use the DS secondary variant solid vs a dashed outline, which is how FacetedFilter already signals set/unset two controls to the right.
  • FilterBar reserved a slot for its clear-filters button, always mounted and merely invisible, leaving a 48px hole in a run whose every other gap is 8px. It now renders only when there is something to clear. The slot existed to avoid a shift when a filter goes on or off; that trade is the wrong way round — the asymmetry is on screen every load, the shift happens once, and you are inside the popover when it does. This component is shared, so Outstanding & Upcoming evens out too.

Verification

  • Every grain's rows sum exactly to the ledger, checked against the demo fixture — except Accounts, which sums to ledger + transfers, by the double-count above. Row keys unique in all four.
  • All four inert/live combinations driven in the browser; both themes.
  • tsc --noEmit clean · npm run build clean · 58/58 server tests pass (unchanged — no server code touched).
  • DS compliance: PASS — gates 1–3 clean, tokens unforked at 0.7.0.

Screenshot in docs/reports.png reshot on the new header.

…oggles

Three asks, one seam. buildReport already collapsed transactions onto a
subject and ranked them; all three of these are new subjects, so the
`view === 'categories'` branch becomes a `grain` — transaction, seller,
category, or account — and the rollup path is shared by the three that
roll up. A transaction with nothing on the end being ranked is bucketed,
never dropped, the same way an uncategorised one always was.

- View gains Accounts: one bar per account of the user's own. A transfer
  touches two of them and is counted under both — it left one and arrived
  at the other, and dropping either end would under-report that account.
  Every other grain still sums exactly to the ledger; verified against the
  demo fixture in all four.

- Group collapses the Transactions view onto the seller: the far end of
  the transaction, which Firefly models as an expense account on a
  withdrawal and a revenue account on a deposit, so the side flips with
  the direction.

Both toggles now go inert where they mean nothing — Per month under a
Month period (already one month; splitting it yields the identical single
card), Group under a View that has already rolled up. The state is kept
across the inert window and masked at the point of use, so switching back
restores what the user had set rather than silently clearing it.

That made the on/off read matter, and it did not: "on" was a bg-secondary
class laid over the outline variant, which in dark mode took the button
from bg-input/30 to /50 — the same colour, 20% more opaque, invisible in
practice. Both now use the DS `secondary` variant solid vs a dashed
outline, which is how FacetedFilter signals set/unset two controls to the
right, so the whole header reads by one rule.

DS compliance: PASS (gates 1-3 clean, tokens unforked at 0.7.0).
…only

The facets were pushed to the far edge by `ml-auto`, which at this width
made them wrap onto their own line and read as a second bar rather than
part of the same group as View. Dropped it — View, the two toggles and
the facets now flow as one run.

Per month and Group lose their labels: they are switches, like Refresh
beside them, and two words apiece bought nothing the icon and the tooltip
do not already say. Group also loses the storefront icon for lucide's
`group`, which is about the operation rather than about sellers.
The operator's grouping: Period and its navigator on the left (what
window), everything from View rightward pushed right at a single 8px gap
(what you are looking at, and how you are narrowing it). The separator
that used to sit before View is gone — the `ml-auto` whitespace is the
boundary now, and a rule inside the run would break the even spacing the
group is built on. FilterBar renders a fragment, so its facets are direct
children of the run and inherit that same gap rather than nesting their
own.
The clear-filters button was always mounted and merely `invisible`, so an
empty 32px slot plus its two gaps opened a 48px hole between Group and
Type in a run whose every other gap is 8px. It is now rendered only when
there is something to clear.

The slot existed to keep the bar from shifting when a filter goes on or
off. That trade is the wrong way round: the asymmetry is on screen every
load, the shift happens once, at the moment you set the first filter, and
you are inside the popover when it does. Both headers use this component,
so Outstanding & Upcoming evens out too — and on Reports the 40px it
gives back is exactly enough for the whole run to fit on one line at
1440px instead of wrapping.
…dule

README gains the two new bar subjects and both toggles' inert conditions,
and its category-that-both-spends note generalises to any bucket — which
is now the visible case, since an account you are paid into and spend from
shows as two bars. Screenshot reshot on the new header, at the same 1600px
scale as the forecast one and palette-quantised back under 100 KB.

Code: `Grain` stops being exported (nothing outside the module needs it),
and ReportsNav hoists the two `x && !inert` masks it was recomputing four
times each into `perMonthOn` / `groupOn`, which is also what the props now
read as. Its docstring described a layout that no longer exists.
@42piratas
42piratas merged commit 0af8f7c into main Aug 3, 2026
1 of 2 checks passed
@42piratas
42piratas deleted the feat/reports-accounts-sellers branch August 3, 2026 23:19
42piratas added a commit that referenced this pull request Aug 3, 2026
…#31)

Mine, in #30. I symlink `web/node_modules` at a sibling checkout so a
fresh worktree can typecheck without a second install, and on the last
commit of that PR I did not remove it before `git add -A`. It went in as a
mode 120000 blob pointing at `../../../ff3e-app/web/node_modules` — a path
that exists on exactly one machine — so a fresh clone gets a dangling
symlink that escapes the repo root. CI did not catch it: the Pages build
runs its own install over the top and deployed fine.

Both .gitignore files said `node_modules/`. The trailing slash means "a
directory of that name", and a symlink is not a directory, so nothing
matched it. Dropping the slash matches both — which is why this could
happen at all, and why it now cannot: with the fix in place, re-creating
the same symlink leaves `git status` clean.
42piratas added a commit that referenced this pull request Aug 3, 2026
The other half of #31, which I said was in that PR and was not: I edited
both .gitignore files but only `git rm` had staged anything, so the commit
carried the deleted symlink and neither ignore change. The symlink is gone
from main; the rule that would have stopped it going in was never
committed.

`node_modules/` with a trailing slash means "a directory of that name". A
symlink is not a directory, which is why nothing matched the one I
committed in #30. Without the slash it matches both.

Verified on this branch, not on an unstaged working tree: with the change
staged, re-creating the same symlink leaves `git status` clean and
`git check-ignore -v web/node_modules` reports `web/.gitignore:3`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant