Files
fuj-management/docs/plans/2026-05-08-1123-go-m6-3-juniors-page.md
Jan Novak 9f0e4b0ac3 feat(go): M6.3 — juniors page (table, filters, credits/debts, Pay buttons)
- Extract AssembleJuniors(ctx) from ServeJuniors JSON handler so HTML
  and JSON share the same view-model path (mirrors AssembleAdults pattern)
- Add JuniorsPageData wrapper in render.go
- Wire HTMLHandler.ServeJuniors to AssembleJuniors + render template
- Replace 4-line placeholder juniors.tmpl with full template:
  member table, name filter, month-range filter, totals row,
  Credits + Debts sections, Pay / Pay All buttons via /qr links
  (no Unmatched section — matches Python juniors.html parity)
- J/A attendance breakdown ("3/500 CZK (4:2J,1A)") and "?" sentinel
  rendered via MonthCell.Text from buildJuniorMemberRow, no extra
  template logic needed
- All tests pass; make parity reports 3/3 routes OK

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 11:25:50 +02:00

6.4 KiB
Raw Blame History

M6.3 — Go-native /juniors page

Context

The Go rewrite is in milestone M6 (Go-native HTML frontend). M6.2 shipped the /adults page in commit c85748b; the progress tracker line 115 names M6.3 as:

/juniors page: same structure + per-month J/A attendance breakdown + "?" sentinel rendering

The Go side already has buildJuniorsResponse (M5.2) that produces the full JSON view model, and the JuniorsResponse uses the same MemberRow/MonthCell/TotalCell types as AdultsResponse. Critically, buildJuniorMemberRow (go/internal/web/api/build_juniors.go:142) already bakes the J/A breakdown (e.g. "3/500 CZK (4:2J,2A)") and the "?" sentinel into MonthCell.Text — no template-level junior-specific logic is required for the M6.3 table.

Today juniors.tmpl is a 4-line "Coming in M6.3" placeholder and HTMLHandler.ServeJuniors (html_handler.go:35) just renders that placeholder. Goal of M6.3: produce a working /juniors page that mirrors the structure of /adults and reaches feature parity with the Python /juniors route.

Approach

Mirror the M6.2 pattern exactly:

  1. Extract AssembleJuniors(ctx) from the JSON handler so HTML + JSON share one view-model assembly path (the parity contract).
  2. Add JuniorsPageData wrapper type next to AdultsPageData in render.go.
  3. Replace juniors.tmpl with an adults-shaped layout. Because JuniorsResponse shares the same field names used by adults.tmpl (Months, RawMonths, Results, Totals, Credits, Debts, AttendanceURL, PaymentsURL, BankAccount, CurrentMonth), the template body is essentially copy/paste with one parity tweak (see "Decisions" below).
  4. Wire HTMLHandler.ServeJuniors to call AssembleJuniors and render the new template, parallel to ServeAdults.

No new CSS, no new JS — filters.js and the cell-* classes (lifted in M6.1) already cover juniors.

Files to modify

File Change
go/internal/web/api/handler.go Add AssembleJuniors(ctx) (JuniorsResponse, error); refactor existing ServeJuniors to call it (mirrors AssembleAdults at line 47).
go/internal/web/render.go Add JuniorsPageData struct (PageData + Data api.JuniorsResponse + Error string).
go/internal/web/html_handler.go Replace stub ServeJuniors with the same shape as ServeAdults (lines 2033).
go/internal/web/templates/juniors.tmpl Replace placeholder with a structurally identical copy of adults.tmpl, with the title swapped to "Juniors Dashboard" and the Unmatched section removed (parity with Python).
docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md Tick M6.3 once merged, append merge SHA.
CHANGELOG.md New top-most entry per CLAUDE.md format.

Decisions captured

  • No "Unmatched Transactions" section on /juniors. Python's templates/juniors.html renders only Credits + Debts (no Unmatched block). Although JuniorsResponse.Unmatched is populated, parity says we don't render it on juniors. The data stays available for the modal in M6.5.
  • Reuse qrHref / qrHrefAll template funcs from render.go:3557. They are name-only (not adult-specific) and already on tmplFuncs.
  • Reuse the existing cell-empty / cell-unpaid / cell-unpaid-current / cell-ok / cell-overridden cell-class branching from adults.tmpl lines 6067 verbatim. The "?" sentinel is rendered as plain {{$cell.Text}} text — no template branch needed because buildJuniorMemberRow produces "?" or "? CZK" already.
  • No template helper extraction (e.g. shared _table_section.tmpl). The two templates will be near-duplicates, but extracting a shared partial now is premature — wait until M6.4 or later when patterns settle. M6.2 didn't extract one either.
  • Branch: feat/go-m6-3-juniors-page, MR via tea pr create per CLAUDE.md.

Verification

End-to-end smoke (parallel-running both backends as in M5/M6.2):

  1. cd go && make run (or whatever target boots the Go server on :8080).
  2. Browser-load http://localhost:8080/juniors. Confirm:
    • Member rows render with cells containing fee + attendance breakdown like 0/500 CZK (3:2J,1A).
    • At least one "?" cell visible (any junior with exactly one session in some month).
    • Filter input narrows rows by name; From/To month selects hide columns via data-month-idx.
    • Pay button visible on past-month unpaid cells; clicking links to /qr?.... (The QR endpoint itself lands in M6.6 — only the link target is in scope here.)
    • Pay All visible on rows with Balance < 0.
    • TOTAL row sums correctly per month.
    • Credits + Debts sections render when present; no Unmatched section.
  3. JSON parity: make parity against the M3 fixture corpus must still report zero non-allowlisted diffs for /api/juniors (the AssembleJuniors extraction must not change wire output).
  4. Compare side-by-side against Python /juniors on :5001 for the same fixture; the table cells should match cell-for-cell modulo the known M5 allowlist.

Out of scope

  • Modal ([i] info button + #memberModal) — that is M6.5.
  • QR endpoint, /sync-bank, /flush-cache, /version pages — M6.6.
  • Embed.FS deploy verification — M6.7.

Plan-file relocation

Per CLAUDE.md "Plans" section, plan files belong in docs/plans/YYYY-MM-DD-HHMM-<slug>.md inside the repo. Plan mode forced this draft into ~/.claude/plans/; first step after ExitPlanMode is to copy this file to docs/plans/2026-05-08-HHMM-go-m6-3-juniors-page.md (resolving the timestamp at that moment) and commit it on the feature branch alongside the implementation.