// Member-detail modal for /adults and /juniors pages. // Fetches /api/ once on load, caches the response, renders on row click. // Mirrors templates/adults.html JS (lines 718-993) but as a standalone module. (function () { 'use strict'; const container = document.getElementById('filterContainer'); if (!container) return; const page = container.dataset.page; // "adults" | "juniors" if (!page) return; let apiData = null; let currentMemberName = null; // ── Data load ───────────────────────────────────────────────────────────── async function loadData() { if (apiData) return apiData; const r = await fetch('/api/' + page); if (!r.ok) throw new Error('[member-detail] failed to fetch /api/' + page + ': ' + r.status); apiData = await r.json(); return apiData; } // Pre-warm on page load so first click is instant. loadData().catch(function (err) { console.error(err); }); // ── Modal render ────────────────────────────────────────────────────────── async function showMember(name) { currentMemberName = name; const data = (await loadData()); const member = data.member_data[name]; if (!member) return; document.getElementById('modalMemberName').textContent = name; document.getElementById('modalTier').textContent = 'Tier: ' + (member.tier || '-'); const monthLabels = data.month_labels || {}; const statusBody = document.getElementById('modalStatusBody'); statusBody.innerHTML = ''; const allTransactions = []; const monthKeys = Object.keys(member.months || {}).sort().reverse(); monthKeys.forEach(function (m) { const md = member.months[m]; const expected = md.expected; // int for adults; int or "?" for juniors const paid = md.paid || 0; const attendance = md.attendance_count || 0; const originalExpected = md.original_expected; const isUnknown = (expected === '?'); let status = '-'; let statusClass = ''; if (!isUnknown && (expected > 0 || paid > 0)) { if (paid >= expected && expected > 0) { status = paid + '/' + expected; statusClass = 'cell-ok'; } else if (paid > 0) { status = paid + '/' + expected; } else { status = '0/' + expected; statusClass = 'cell-unpaid'; } } else if (isUnknown) { status = paid > 0 ? paid + '/?' : '?'; } let expectedCell; if (isUnknown) { expectedCell = '?'; } else if (md.exception) { expectedCell = '' + expected + '*'; } else { expectedCell = expected; } const displayMonth = monthLabels[m] || m; const row = document.createElement('tr'); row.innerHTML = '' + displayMonth + '' + '' + attendance + '' + '' + expectedCell + '' + '' + paid + '' + '' + status + ''; statusBody.appendChild(row); if (md.transactions) { md.transactions.forEach(function (tx) { allTransactions.push(Object.assign({ month: m }, tx)); }); } }); // Exceptions const exList = document.getElementById('modalExceptionList'); const exSection = document.getElementById('modalExceptionSection'); exList.innerHTML = ''; const exceptions = []; monthKeys.forEach(function (m) { if (member.months[m].exception) { exceptions.push(Object.assign({ month: m }, member.months[m].exception)); } }); if (exceptions.length > 0) { exSection.style.display = 'block'; exceptions.forEach(function (ex) { const displayMonth = monthLabels[ex.month] || ex.month; const item = document.createElement('div'); item.className = 'tx-item'; item.innerHTML = '
' + displayMonth + '
' + '
' + ex.amount + ' CZK
' + '
' + (ex.note || 'No details provided.') + '
'; exList.appendChild(item); }); } else { exSection.style.display = 'none'; } // Other transactions const otherList = document.getElementById('modalOtherList'); const otherSection = document.getElementById('modalOtherSection'); otherList.innerHTML = ''; if (member.other_transactions && member.other_transactions.length > 0) { otherSection.style.display = 'block'; member.other_transactions.forEach(function (tx) { const item = document.createElement('div'); item.className = 'tx-item'; item.innerHTML = '
' + tx.date + ' | ' + (tx.purpose || 'Other') + '
' + '
' + '' + tx.amount + ' CZK' + '' + (tx.sender || '') + '' + '
' + '
' + (tx.message || '') + '
'; otherList.appendChild(item); }); } else { otherSection.style.display = 'none'; } // Matched transactions (payment history) const txList = document.getElementById('modalTxList'); txList.innerHTML = ''; if (allTransactions.length === 0) { txList.innerHTML = '
No transactions matched to this member.
'; } else { allTransactions.sort(function (a, b) { return b.date.localeCompare(a.date); }); allTransactions.forEach(function (tx) { const displayMonth = monthLabels[tx.month] || tx.month; const item = document.createElement('div'); item.className = 'tx-item'; item.innerHTML = '
' + tx.date + ' | matched to ' + displayMonth + '
' + '
' + '' + tx.amount + ' CZK' + '' + (tx.sender || '') + '' + '
' + '
' + (tx.message || '') + '
'; txList.appendChild(item); }); } // Raw payments — hidden by default; reset toggle on each open const rawList = document.getElementById('modalRawList'); const rawToggle = document.getElementById('rawPaymentsToggle'); rawList.style.display = 'none'; rawToggle.textContent = '[show]'; rawList.innerHTML = ''; const rawRows = (data.raw_payments || {})[name] || []; if (rawRows.length === 0) { rawList.innerHTML = '
No raw payments tied to this member.
'; } else { rawRows.forEach(function (tx) { const inferredNote = (tx.inferred_amount && tx.inferred_amount !== '' && tx.inferred_amount !== tx.amount) ? ' (inferred: ' + tx.inferred_amount + ')' : ''; const manualNote = tx.manual_fix ? ' [manual fix]' : ''; const bankIdNote = tx.bank_id ? ' · bank_id: ' + tx.bank_id + '' : ''; const item = document.createElement('div'); item.className = 'tx-item'; item.innerHTML = '
' + tx.date + ' | purpose: ' + (tx.purpose || '—') + manualNote + '
' + '
' + '' + tx.amount + ' CZK' + inferredNote + '' + '' + (tx.sender || '') + '' + '
' + '
' + (tx.message || '') + '
' + '
' + (tx.person || '') + bankIdNote + '
'; rawList.appendChild(item); }); } document.getElementById('memberModal').classList.add('active'); } // ── Raw-payments toggle ─────────────────────────────────────────────────── function toggleRawPayments(ev) { ev.preventDefault(); const list = document.getElementById('modalRawList'); const link = document.getElementById('rawPaymentsToggle'); const hidden = list.style.display === 'none'; list.style.display = hidden ? 'block' : 'none'; link.textContent = hidden ? '[hide]' : '[show]'; } // ── Close + keyboard nav ────────────────────────────────────────────────── function closeModal() { document.getElementById('memberModal').classList.remove('active'); } function navigateMember(direction) { const rows = Array.from(document.querySelectorAll('tr.member-row')); const visible = rows.filter(function (r) { return r.style.display !== 'none'; }); const idx = visible.findIndex(function (r) { return r.dataset.name === currentMemberName; }); if (idx === -1) return; const next = idx + direction; if (next >= 0 && next < visible.length) { showMember(visible[next].dataset.name); } } // ── Wiring ──────────────────────────────────────────────────────────────── document.querySelectorAll('.info-icon[data-name]').forEach(function (el) { el.addEventListener('click', function (ev) { ev.stopPropagation(); showMember(el.dataset.name); }); }); document.getElementById('rawPaymentsToggle').addEventListener('click', toggleRawPayments); document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { closeModal(); return; } const modal = document.getElementById('memberModal'); if (!modal.classList.contains('active')) return; if (e.key === 'ArrowDown') { e.preventDefault(); navigateMember(1); } if (e.key === 'ArrowUp') { e.preventDefault(); navigateMember(-1); } }); }());