import { apiGet } from '../api.js'; import { showToast } from '../app.js'; let sortCol = null; let sortDir = 'asc'; let allClients = []; let questionsDef = []; let questionnaireMeta = null; let filterCoach = ''; let filterSupervisor = ''; let filterStatus = ''; let filterDateFrom = ''; let filterDateTo = ''; export async function resultsPage(params) { const app = document.getElementById('app'); sortCol = null; sortDir = 'asc'; filterCoach = ''; filterSupervisor = ''; filterStatus = ''; filterDateFrom = ''; filterDateTo = ''; app.innerHTML = `
`; try { const data = await apiGet(`results.php?questionnaireID=${encodeURIComponent(params.id)}`); questionnaireMeta = data.questionnaire; questionsDef = data.questions || []; allClients = data.clients || []; document.getElementById('resultsTitle').textContent = `Results: ${questionnaireMeta.name} v${questionnaireMeta.version}`; renderResults(); } catch (e) { showToast(e.message, 'error'); document.getElementById('resultsContent').innerHTML = `

${esc(e.message)}

`; } } function renderResults() { const container = document.getElementById('resultsContent'); // Collect unique coaches and statuses for filters const coachOptions = getCoachFilterOptions(); const supervisorOptions = getSupervisorFilterOptions(); const statuses = [...new Set(allClients.map(c => c.status))].filter(Boolean).sort(); container.innerHTML = `
Export CSV
`; document.getElementById('filterSupervisor').addEventListener('change', (e) => { filterSupervisor = e.target.value; renderTableRows(); }); document.getElementById('filterCoach').addEventListener('change', (e) => { filterCoach = e.target.value; renderTableRows(); }); document.getElementById('filterStatus').addEventListener('change', (e) => { filterStatus = e.target.value; renderTableRows(); }); document.getElementById('filterDateFrom').addEventListener('change', (e) => { filterDateFrom = e.target.value; renderTableRows(); }); document.getElementById('filterDateTo').addEventListener('change', (e) => { filterDateTo = e.target.value; renderTableRows(); }); document.getElementById('clearFiltersBtn').addEventListener('click', () => { filterCoach = ''; filterSupervisor = ''; filterStatus = ''; filterDateFrom = ''; filterDateTo = ''; renderResults(); }); document.getElementById('exportCsvLink').addEventListener('click', (e) => { e.preventDefault(); exportCSV(); }); renderTableHead(); renderTableRows(); } function renderTableHead() { const head = document.getElementById('resultsHead'); const fixedCols = [ { key: 'clientCode', label: 'Client' }, { key: 'coachUsername', label: 'Coach' }, { key: 'supervisorUsername', label: 'Supervisor' }, { key: 'status', label: 'Status' }, { key: 'sumPoints', label: 'Score' }, { key: 'completedAt', label: 'Completed' }, ]; const questionCols = questionsDef.map(q => ({ key: `q_${q.questionID}`, label: q.defaultText, questionID: q.questionID, })); const allCols = [...fixedCols, ...questionCols]; head.innerHTML = allCols.map(col => { let cls = 'sortable'; if (sortCol === col.key) cls += sortDir === 'asc' ? ' sort-asc' : ' sort-desc'; return `${esc(col.label)}`; }).join(''); head.querySelectorAll('th.sortable').forEach(th => { th.addEventListener('click', () => { const key = th.dataset.key; if (sortCol === key) sortDir = sortDir === 'asc' ? 'desc' : 'asc'; else { sortCol = key; sortDir = 'asc'; } renderTableHead(); renderTableRows(); }); }); } function getCoachFilterOptions() { const map = new Map(); allClients.forEach(c => { if (!c.coachID || map.has(c.coachID)) return; map.set(c.coachID, c.coachUsername || c.coachID); }); return [...map.entries()].sort((a, b) => a[1].localeCompare(b[1])); } function getSupervisorFilterOptions() { return [...new Set(allClients.map(c => c.supervisorUsername).filter(Boolean))].sort((a, b) => a.localeCompare(b) ); } function coachDisplayName(c) { return c.coachUsername || c.coachID || '—'; } function supervisorDisplayName(c) { return c.supervisorUsername || '—'; } function getFilteredClients() { let clients = allClients; if (filterSupervisor) clients = clients.filter(c => c.supervisorUsername === filterSupervisor); if (filterCoach) clients = clients.filter(c => c.coachID === filterCoach); if (filterStatus) clients = clients.filter(c => c.status === filterStatus); if (filterDateFrom || filterDateTo) { const fromTs = filterDateFrom ? dateInputToUnixStart(filterDateFrom) : null; const toTs = filterDateTo ? dateInputToUnixEnd(filterDateTo) : null; clients = clients.filter(c => { const ts = c.completedAt; if (!ts) return false; if (fromTs !== null && ts < fromTs) return false; if (toTs !== null && ts > toTs) return false; return true; }); } return clients; } function dateInputToUnixStart(yyyyMmDd) { const [y, m, d] = yyyyMmDd.split('-').map(Number); return Math.floor(new Date(y, m - 1, d).getTime() / 1000); } function dateInputToUnixEnd(yyyyMmDd) { const [y, m, d] = yyyyMmDd.split('-').map(Number); return Math.floor(new Date(y, m - 1, d, 23, 59, 59, 999).getTime() / 1000); } function renderTableRows() { const body = document.getElementById('resultsBody'); let clients = getFilteredClients(); // Sort if (sortCol) { clients = [...clients].sort((a, b) => { let va = getCellValue(a, sortCol); let vb = getCellValue(b, sortCol); if (va == null) va = ''; if (vb == null) vb = ''; if (typeof va === 'number' && typeof vb === 'number') { return sortDir === 'asc' ? va - vb : vb - va; } const cmp = String(va).localeCompare(String(vb), undefined, { numeric: true }); return sortDir === 'asc' ? cmp : -cmp; }); } document.getElementById('resultCount').textContent = `${clients.length} client${clients.length === 1 ? '' : 's'}`; if (!clients.length) { body.innerHTML = `No results found`; return; } // Build option text lookup const optionMap = {}; questionsDef.forEach(q => { (q.answerOptions || []).forEach(o => { optionMap[o.answerOptionID] = o.defaultText; }); }); body.innerHTML = clients.map(c => { const completedDate = c.completedAt ? new Date(c.completedAt * 1000).toLocaleDateString() : '—'; const statusBadge = c.status ? `${esc(c.status)}` : '—'; const questionCells = questionsDef.map(q => { const ans = c.answers && c.answers[q.questionID]; if (!ans) return '—'; if (ans.answerOptionID && optionMap[ans.answerOptionID]) { return `${esc(optionMap[ans.answerOptionID])}`; } if (ans.freeTextValue) return `${esc(ans.freeTextValue)}`; if (ans.numericValue !== null && ans.numericValue !== undefined) return `${ans.numericValue}`; return '—'; }).join(''); return ` ${esc(c.clientCode)} ${esc(coachDisplayName(c))} ${esc(supervisorDisplayName(c))} ${statusBadge} ${c.sumPoints !== null ? c.sumPoints : '—'} ${completedDate} ${questionCells} `; }).join(''); } function getCellValue(client, key) { if (key === 'clientCode') return client.clientCode; if (key === 'coachUsername') return coachDisplayName(client); if (key === 'supervisorUsername') return supervisorDisplayName(client); if (key === 'status') return client.status; if (key === 'sumPoints') return client.sumPoints; if (key === 'completedAt') return client.completedAt; if (key.startsWith('q_')) { const qid = key.substring(2); const ans = client.answers && client.answers[qid]; if (!ans) return null; if (ans.numericValue !== null && ans.numericValue !== undefined) return ans.numericValue; if (ans.freeTextValue) return ans.freeTextValue; return ans.answerOptionID || null; } return null; } function exportCSV() { const clients = getFilteredClients(); const optionMap = {}; questionsDef.forEach(q => { (q.answerOptions || []).forEach(o => { optionMap[o.answerOptionID] = o.defaultText; }); }); const headers = ['clientCode', 'coach', 'supervisor', 'status', 'sumPoints', 'completedAt', ...questionsDef.map(q => q.defaultText)]; const rows = clients.map(c => { const base = [ c.clientCode, coachDisplayName(c), c.supervisorUsername || '', c.status, c.sumPoints ?? '', c.completedAt ? new Date(c.completedAt * 1000).toISOString() : '' ]; const answerCols = questionsDef.map(q => { const ans = c.answers && c.answers[q.questionID]; if (!ans) return ''; if (ans.answerOptionID && optionMap[ans.answerOptionID]) return optionMap[ans.answerOptionID]; if (ans.freeTextValue) return ans.freeTextValue; if (ans.numericValue !== null && ans.numericValue !== undefined) return ans.numericValue; return ''; }); return [...base, ...answerCols]; }); let csv = '\uFEFF'; // BOM csv += headers.map(csvEsc).join(',') + '\n'; rows.forEach(r => { csv += r.map(csvEsc).join(',') + '\n'; }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${questionnaireMeta.name}_v${questionnaireMeta.version}_results.csv`; a.click(); URL.revokeObjectURL(url); showToast('CSV exported', 'success'); } function csvEsc(val) { const s = String(val ?? ''); if (s.includes(',') || s.includes('"') || s.includes('\n')) { return '"' + s.replace(/"/g, '""') + '"'; } return s; } function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }