let data = {}; let showAllMatches = false; let viewingNextSeason = false; let initialLoad = true; function parseDate(str) { const [d, m, y] = str.split('/').map(Number); return new Date(2000 + y, m - 1, d); } function getSeasonYear(dateStr) { const [d, m, y] = dateStr.split('/').map(Number); return m >= 4 ? 2000 + y : 1999 + y; } function currentSeasonYear() { const now = new Date(); const m = now.getMonth() + 1; return m >= 4 ? now.getFullYear() : now.getFullYear() - 1; } function fetchData() { fetch('/api/data').then(r => r.json()).then(d => { data = d; updateSeasonButton(); renderTable(); }); } function updateSeasonButton() { const thisSeason = currentSeasonYear(); const hasNextSeason = (data.dates || []).some(d => getSeasonYear(d) === thisSeason + 1); const btn = document.getElementById('next-season-btn'); if (btn) btn.style.display = hasNextSeason ? '' : 'none'; } function saveData() { fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } function renderTable() { const container = document.getElementById('attendance-table'); const scrollTop = window.scrollY; container.innerHTML = ''; const table = document.createElement('table'); // Header row const thead = document.createElement('thead'); const headRow = document.createElement('tr'); const dateTh = document.createElement('th'); dateTh.innerText = 'Date'; dateTh.style.width = '120px'; dateTh.style.minWidth = '100px'; dateTh.style.maxWidth = '140px'; dateTh.style.textAlign = 'left'; headRow.appendChild(dateTh); data.players.forEach(name => { const th = document.createElement('th'); th.innerText = name; th.classList.add('name-col'); headRow.appendChild(th); }); const guestNameTh = document.createElement('th'); guestNameTh.innerText = 'Guest Name'; guestNameTh.classList.add('name-col'); headRow.appendChild(guestNameTh); thead.appendChild(headRow); table.appendChild(thead); // Body rows const tbody = document.createElement('tbody'); if (!data.guestNames) data.guestNames = {}; const today = new Date(); const thisSeason = currentSeasonYear(); const targetSeason = viewingNextSeason ? thisSeason + 1 : thisSeason; let filteredDates = (data.dates || []).filter(d => getSeasonYear(d) === targetSeason); if (!viewingNextSeason && !showAllMatches) { filteredDates = filteredDates.filter(date => parseDate(date) > today); } // Find closest date for auto-scroll let closestIdx = 0; let minDiff = Infinity; filteredDates.forEach((date, rowIdx) => { const diff = Math.abs(parseDate(date) - today); if (diff < minDiff) { minDiff = diff; closestIdx = rowIdx; } }); filteredDates.forEach((date, rowIdx) => { const tr = document.createElement('tr'); if (rowIdx === closestIdx) tr.id = 'current-match-row'; // Date cell const dateTd = document.createElement('td'); dateTd.style.width = '120px'; dateTd.style.minWidth = '100px'; dateTd.style.maxWidth = '140px'; dateTd.style.textAlign = 'left'; if (date === '29/05/25' || date === '25/12/25' || date === '01/01/26') { const strong = document.createElement('strong'); strong.innerText = date; dateTd.appendChild(strong); dateTd.title = 'Public Holiday'; } else { dateTd.innerText = date; } tr.appendChild(dateTd); // Player attendance cells data.players.forEach((player, colIdx) => { const td = document.createElement('td'); const key = `${date}|${colIdx}`; const setCell = val => { if (val === true) { td.innerText = 'Yes'; td.className = 'clickable name-col yes'; } else if (val === 'no') { td.innerText = 'No'; td.className = 'clickable name-col no'; } else if (val === 'maybe') { td.innerText = 'Maybe'; td.className = 'clickable name-col maybe'; } else { td.innerText = ''; td.className = 'clickable name-col'; } }; setCell(data.attendance[key]); td.onclick = () => { const cur = data.attendance[key]; if (!cur) data.attendance[key] = true; else if (cur === true) data.attendance[key] = 'no'; else if (cur === 'no') data.attendance[key] = 'maybe'; else delete data.attendance[key]; setCell(data.attendance[key]); saveData(); }; tr.appendChild(td); }); // Guest Name column const guestNameTd = document.createElement('td'); guestNameTd.classList.add('name-col'); const guestNameInput = document.createElement('input'); guestNameInput.type = 'text'; guestNameInput.value = data.guestNames[date] || ''; guestNameInput.placeholder = 'Enter guest name'; guestNameInput.maxLength = 50; guestNameInput.onchange = e => { let value = e.target.value; if (value === '') { delete data.guestNames[date]; saveData(); return; } if (//.test(value) || /["'`\\]/.test(value)) { alert('Guest name cannot contain special characters like <, >, ", \\, or backticks.'); guestNameInput.value = data.guestNames[date] || ''; return; } if (!/^([a-zA-Z0-9 .-]+)$/.test(value)) { alert('Guest name can only contain letters, numbers, spaces, hyphens, and periods.'); guestNameInput.value = data.guestNames[date] || ''; return; } if (value.length > 50) { value = value.slice(0, 50); guestNameInput.value = value; } data.guestNames[date] = value; saveData(); }; guestNameTd.appendChild(guestNameInput); tr.appendChild(guestNameTd); tbody.appendChild(tr); }); table.appendChild(tbody); container.appendChild(table); setTimeout(() => { if (initialLoad) { const row = document.getElementById('current-match-row'); if (row) row.scrollIntoView({ behavior: 'smooth', block: 'center' }); initialLoad = false; } else { window.scrollTo({ top: scrollTop }); } }, 0); } document.addEventListener('DOMContentLoaded', () => { fetchData(); const toggleBtn = document.getElementById('toggle-matches-btn'); if (toggleBtn) { toggleBtn.textContent = 'Show All Matches'; toggleBtn.onclick = () => { showAllMatches = !showAllMatches; toggleBtn.textContent = showAllMatches ? 'Show Future Matches' : 'Show All Matches'; renderTable(); }; } const nextSeasonBtn = document.getElementById('next-season-btn'); if (nextSeasonBtn) { nextSeasonBtn.onclick = () => { viewingNextSeason = !viewingNextSeason; nextSeasonBtn.textContent = viewingNextSeason ? 'Current Season' : 'Next Season'; // Reset "show all" toggle when switching seasons showAllMatches = false; if (toggleBtn) toggleBtn.textContent = 'Show All Matches'; initialLoad = true; renderTable(); }; } });