From c2a381bb6398e94ff62dc6e14b064ba8ea63ffbc Mon Sep 17 00:00:00 2001 From: Jan Novak Date: Mon, 8 Jun 2026 11:28:40 +0200 Subject: [PATCH] fix(display): default from-selector to last N months; keep all months selectable Instead of hiding older months entirely, show all months in the from/to selectors but default the from-select to the last MONTHS_TO_SHOW months on page load. The "All" button resets to full history as before. Python: passes months_to_show to render_template, IIFE sets fromSelect.value. Go: adds MonthsToShow to response structs, data-months-to-show attr in templates, filters.js reads it and defaults fromSelect after hideFutureMonths. Co-Authored-By: Claude Opus 4.8 --- app.py | 18 ++++++------------ go/internal/web/api/adults.go | 1 + go/internal/web/api/build_adults.go | 1 + go/internal/web/api/build_juniors.go | 1 + go/internal/web/api/handler.go | 13 ++----------- go/internal/web/api/juniors.go | 1 + go/internal/web/static/js/filters.js | 9 ++++++++- go/internal/web/templates/adults.tmpl | 2 +- go/internal/web/templates/juniors.tmpl | 2 +- .../fixtures/api-schema/adults.schema.json | 6 +++++- .../fixtures/api-schema/juniors.schema.json | 6 +++++- templates/adults.html | 1 + templates/juniors.html | 1 + 13 files changed, 34 insertions(+), 28 deletions(-) diff --git a/app.py b/app.py index f0d1408..d83c0f8 100644 --- a/app.py +++ b/app.py @@ -33,12 +33,6 @@ from cache_utils import get_sheet_modified_time, read_cache, write_cache, _LAST_ from sync_fio_to_sheets import sync_to_sheets from infer_payments import infer_payments - -def _last_n_months(months): - """Return the last MONTHS_TO_SHOW months; 0 means show all.""" - return months[-MONTHS_TO_SHOW:] if MONTHS_TO_SHOW > 0 else months - - def get_cached_data(cache_key, sheet_id, fetch_func, *args, serialize=None, deserialize=None, **kwargs): mod_time = get_sheet_modified_time(cache_key) if mod_time: @@ -181,7 +175,7 @@ def api_adults(): ) result = reconcile(members, sorted_months, transactions, exceptions) vm = build_adults_view_model( - members, _last_n_months(sorted_months), result, transactions, + members, sorted_months, result, transactions, datetime.now().strftime("%Y-%m"), attendance_url=attendance_url, payments_url=payments_url, bank_account=BANK_ACCOUNT, ) @@ -205,7 +199,7 @@ def api_juniors(): adapted_members = adapt_junior_members(junior_members) result = reconcile(adapted_members, sorted_months, transactions, exceptions) vm = build_juniors_view_model( - junior_members, adapted_members, _last_n_months(sorted_months), result, transactions, + junior_members, adapted_members, sorted_months, result, transactions, datetime.now().strftime("%Y-%m"), attendance_url=attendance_url, payments_url=payments_url, bank_account=BANK_ACCOUNT, ) @@ -254,14 +248,14 @@ def adults_view(): record_step("reconcile") vm = build_adults_view_model( - members, _last_n_months(sorted_months), result, transactions, + members, sorted_months, result, transactions, datetime.now().strftime("%Y-%m"), attendance_url=attendance_url, payments_url=payments_url, bank_account=BANK_ACCOUNT, ) record_step("process_data") - return render_template("adults.html", **vm) + return render_template("adults.html", months_to_show=MONTHS_TO_SHOW, **vm) @app.route("/juniors") def juniors_view(): @@ -290,14 +284,14 @@ def juniors_view(): record_step("reconcile") vm = build_juniors_view_model( - junior_members, adapted_members, _last_n_months(sorted_months), result, transactions, + junior_members, adapted_members, sorted_months, result, transactions, datetime.now().strftime("%Y-%m"), attendance_url=attendance_url, payments_url=payments_url, bank_account=BANK_ACCOUNT, ) record_step("process_data") - return render_template("juniors.html", **vm) + return render_template("juniors.html", months_to_show=MONTHS_TO_SHOW, **vm) @app.route("/payments") def payments(): diff --git a/go/internal/web/api/adults.go b/go/internal/web/api/adults.go index f891e3a..a5fc7c2 100644 --- a/go/internal/web/api/adults.go +++ b/go/internal/web/api/adults.go @@ -39,4 +39,5 @@ type AdultsResponse struct { PaymentsURL string `json:"payments_url"` BankAccount string `json:"bank_account"` CurrentMonth string `json:"current_month"` + MonthsToShow int `json:"months_to_show"` } diff --git a/go/internal/web/api/build_adults.go b/go/internal/web/api/build_adults.go index 61dbaad..008f317 100644 --- a/go/internal/web/api/build_adults.go +++ b/go/internal/web/api/build_adults.go @@ -140,6 +140,7 @@ func buildAdultsResponse( PaymentsURL: "https://docs.google.com/spreadsheets/d/" + config.PaymentsSheetID + "/edit", BankAccount: cfg.QRAccount, CurrentMonth: currentMonth, + MonthsToShow: cfg.MonthsToShow, } } diff --git a/go/internal/web/api/build_juniors.go b/go/internal/web/api/build_juniors.go index 9b080cd..e4b8043 100644 --- a/go/internal/web/api/build_juniors.go +++ b/go/internal/web/api/build_juniors.go @@ -136,6 +136,7 @@ func buildJuniorsResponse( PaymentsURL: "https://docs.google.com/spreadsheets/d/" + config.PaymentsSheetID + "/edit", BankAccount: cfg.QRAccount, CurrentMonth: currentMonth, + MonthsToShow: cfg.MonthsToShow, } } diff --git a/go/internal/web/api/handler.go b/go/internal/web/api/handler.go index 118bb6f..234a84c 100644 --- a/go/internal/web/api/handler.go +++ b/go/internal/web/api/handler.go @@ -53,7 +53,7 @@ func (h *Handler) AssembleAdults(ctx context.Context) (AdultsResponse, error) { return AdultsResponse{}, err } result := domreconcile.Reconcile(members, sortedMonths, txns, exceptions, time.Now().Year()) - return buildAdultsResponse(members, lastNMonths(sortedMonths, h.Config.MonthsToShow), result, txns, h.Config, time.Now().Format("2006-01")), nil + return buildAdultsResponse(members, sortedMonths, result, txns, h.Config, time.Now().Format("2006-01")), nil } // ServeJuniors handles GET /api/juniors. @@ -74,16 +74,7 @@ func (h *Handler) AssembleJuniors(ctx context.Context) (JuniorsResponse, error) return JuniorsResponse{}, err } result := domreconcile.Reconcile(members, sortedMonths, txns, exceptions, time.Now().Year()) - return buildJuniorsResponse(members, lastNMonths(sortedMonths, h.Config.MonthsToShow), result, txns, h.Config, time.Now().Format("2006-01")), nil -} - -// lastNMonths returns the last n elements of months. -// If n <= 0 or n >= len(months), the full slice is returned unchanged. -func lastNMonths(months []string, n int) []string { - if n > 0 && len(months) > n { - return months[len(months)-n:] - } - return months + return buildJuniorsResponse(members, sortedMonths, result, txns, h.Config, time.Now().Format("2006-01")), nil } // ServePayments handles GET /api/payments. diff --git a/go/internal/web/api/juniors.go b/go/internal/web/api/juniors.go index d07c777..3e2c6b6 100644 --- a/go/internal/web/api/juniors.go +++ b/go/internal/web/api/juniors.go @@ -38,4 +38,5 @@ type JuniorsResponse struct { PaymentsURL string `json:"payments_url"` BankAccount string `json:"bank_account"` CurrentMonth string `json:"current_month"` + MonthsToShow int `json:"months_to_show"` } diff --git a/go/internal/web/static/js/filters.js b/go/internal/web/static/js/filters.js index 6fc5468..a238efc 100644 --- a/go/internal/web/static/js/filters.js +++ b/go/internal/web/static/js/filters.js @@ -12,7 +12,8 @@ const container = document.getElementById('filterContainer'); if (!container) return; - const currentMonth = container.dataset.currentMonth || ''; + const currentMonth = container.dataset.currentMonth || ''; + const monthsToShow = parseInt(container.dataset.monthsToShow || '0', 10); const nameInput = document.getElementById('nameFilter'); const fromSelect = document.getElementById('fromMonth'); @@ -88,4 +89,10 @@ // ── Initialise ──────────────────────────────────────────────────────────── hideFutureMonths(); + // Default the from-select to show only the last N months. + if (monthsToShow > 0 && toSelect.value !== '') { + const defaultFrom = Math.max(0, parseInt(toSelect.value, 10) - monthsToShow + 1); + fromSelect.value = String(defaultFrom); + applyMonthFilter(); + } }()); diff --git a/go/internal/web/templates/adults.tmpl b/go/internal/web/templates/adults.tmpl index 2a9129c..f8581bd 100644 --- a/go/internal/web/templates/adults.tmpl +++ b/go/internal/web/templates/adults.tmpl @@ -12,7 +12,7 @@ Payments Ledger -
+
diff --git a/go/internal/web/templates/juniors.tmpl b/go/internal/web/templates/juniors.tmpl index 0f794b4..dd9136a 100644 --- a/go/internal/web/templates/juniors.tmpl +++ b/go/internal/web/templates/juniors.tmpl @@ -12,7 +12,7 @@ Payments Ledger
-
+
diff --git a/go/tests/fixtures/api-schema/adults.schema.json b/go/tests/fixtures/api-schema/adults.schema.json index 437b227..a9c6af7 100644 --- a/go/tests/fixtures/api-schema/adults.schema.json +++ b/go/tests/fixtures/api-schema/adults.schema.json @@ -143,6 +143,9 @@ }, "current_month": { "type": "string" + }, + "months_to_show": { + "type": "integer" } }, "additionalProperties": false, @@ -161,7 +164,8 @@ "attendance_url", "payments_url", "bank_account", - "current_month" + "current_month", + "months_to_show" ] }, "Credit": { diff --git a/go/tests/fixtures/api-schema/juniors.schema.json b/go/tests/fixtures/api-schema/juniors.schema.json index 337a62e..f7d012d 100644 --- a/go/tests/fixtures/api-schema/juniors.schema.json +++ b/go/tests/fixtures/api-schema/juniors.schema.json @@ -187,6 +187,9 @@ }, "current_month": { "type": "string" + }, + "months_to_show": { + "type": "integer" } }, "additionalProperties": false, @@ -205,7 +208,8 @@ "attendance_url", "payments_url", "bank_account", - "current_month" + "current_month", + "months_to_show" ] }, "MemberOtherEntry": { diff --git a/templates/adults.html b/templates/adults.html index d9762b5..92cb081 100644 --- a/templates/adults.html +++ b/templates/adults.html @@ -1045,6 +1045,7 @@ }); toSelect.value = maxMonthIdx; + fromSelect.value = Math.max(0, maxMonthIdx - {{ months_to_show }} + 1); applyMonthFilter(); })(); diff --git a/templates/juniors.html b/templates/juniors.html index 2342d27..8efbad1 100644 --- a/templates/juniors.html +++ b/templates/juniors.html @@ -1026,6 +1026,7 @@ }); toSelect.value = maxMonthIdx; + fromSelect.value = Math.max(0, maxMonthIdx - {{ months_to_show }} + 1); applyMonthFilter(); })();