feat(go): M6.2 — adults page (table, filters, credits/debts/unmatched, Pay buttons)
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
- Extract AssembleAdults(ctx) from ServeAdults so HTML and JSON API share one reconcile path. - HTMLHandler gains *api.Handler; ServeAdults loads real data and renders adults.tmpl. - AdultsPageData view model + qrHref/qrHrefAll funcMap (URL-encode /qr params, YYYY-MM→MM/YYYY). - adults.tmpl: full reconcile table, per-cell status classes + cell-unpaid-current, Pay button hrefs, totals row, credits/debts/unmatched sections, filter controls, sheet links. - static/js/filters.js: NFD-normalize name filter + month-range column hiding; future months hidden by default. - TestAdultsPage asserts member name and cell text against fixture data. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
91
go/internal/web/static/js/filters.js
Normal file
91
go/internal/web/static/js/filters.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// Client-side filters for the Adults (and future Juniors) dashboard table.
|
||||
// Mirrors adults.html:864-1051 from the Python frontend.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// NFD-normalize + strip diacritics + lowercase, matching Python's
|
||||
// unicodedata.normalize('NFD', s).encode('ascii', 'ignore').decode().lower()
|
||||
function normalize(s) {
|
||||
return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
|
||||
}
|
||||
|
||||
const container = document.getElementById('filterContainer');
|
||||
if (!container) return;
|
||||
|
||||
const currentMonth = container.dataset.currentMonth || '';
|
||||
|
||||
const nameInput = document.getElementById('nameFilter');
|
||||
const fromSelect = document.getElementById('fromMonth');
|
||||
const toSelect = document.getElementById('toMonth');
|
||||
const applyBtn = document.getElementById('applyFilter');
|
||||
const clearBtn = document.getElementById('clearFilter');
|
||||
|
||||
// ── Month column visibility ───────────────────────────────────────────────
|
||||
|
||||
// Hide columns whose raw month is in the future by default.
|
||||
function hideFutureMonths() {
|
||||
if (!currentMonth) return;
|
||||
document.querySelectorAll('[data-raw-month]').forEach(el => {
|
||||
if (el.dataset.rawMonth > currentMonth) {
|
||||
el.classList.add('month-hidden');
|
||||
}
|
||||
});
|
||||
// Sync toMonth select to the last non-hidden month.
|
||||
const ths = [...document.querySelectorAll('thead th[data-month-idx]')];
|
||||
const visibleIdxs = ths
|
||||
.filter(th => !th.classList.contains('month-hidden'))
|
||||
.map(th => parseInt(th.dataset.monthIdx, 10));
|
||||
if (visibleIdxs.length) {
|
||||
toSelect.value = String(visibleIdxs[visibleIdxs.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyMonthFilter() {
|
||||
const from = fromSelect.value !== '' ? parseInt(fromSelect.value, 10) : -Infinity;
|
||||
const to = toSelect.value !== '' ? parseInt(toSelect.value, 10) : Infinity;
|
||||
|
||||
document.querySelectorAll('[data-month-idx]').forEach(el => {
|
||||
const idx = parseInt(el.dataset.monthIdx, 10);
|
||||
if (idx < from || idx > to) {
|
||||
el.classList.add('month-hidden');
|
||||
} else {
|
||||
el.classList.remove('month-hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearMonthFilter() {
|
||||
document.querySelectorAll('[data-month-idx]').forEach(el => {
|
||||
el.classList.remove('month-hidden');
|
||||
});
|
||||
fromSelect.value = '';
|
||||
toSelect.value = '';
|
||||
}
|
||||
|
||||
// ── Name row visibility ───────────────────────────────────────────────────
|
||||
|
||||
function applyNameFilter() {
|
||||
const query = normalize(nameInput.value.trim());
|
||||
document.querySelectorAll('tr.member-row').forEach(row => {
|
||||
const name = normalize(row.dataset.name || '');
|
||||
row.style.display = (!query || name.includes(query)) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Event wiring ─────────────────────────────────────────────────────────
|
||||
|
||||
nameInput.addEventListener('input', applyNameFilter);
|
||||
|
||||
applyBtn.addEventListener('click', applyMonthFilter);
|
||||
|
||||
clearBtn.addEventListener('click', function () {
|
||||
nameInput.value = '';
|
||||
applyNameFilter();
|
||||
clearMonthFilter();
|
||||
hideFutureMonths();
|
||||
});
|
||||
|
||||
// ── Initialise ────────────────────────────────────────────────────────────
|
||||
|
||||
hideFutureMonths();
|
||||
}());
|
||||
Reference in New Issue
Block a user