enhanced client results and export functionality with coach and supervisor details, added date filters for results
This commit is contained in:
@ -7,14 +7,20 @@ 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 = `
|
||||
<div class="page-header">
|
||||
@ -44,21 +50,35 @@ function renderResults() {
|
||||
const container = document.getElementById('resultsContent');
|
||||
|
||||
// Collect unique coaches and statuses for filters
|
||||
const coaches = [...new Set(allClients.map(c => c.coachID))].sort();
|
||||
const coachOptions = getCoachFilterOptions();
|
||||
const supervisorOptions = getSupervisorFilterOptions();
|
||||
const statuses = [...new Set(allClients.map(c => c.status))].filter(Boolean).sort();
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="filter-bar">
|
||||
<label style="font-size:.85rem;font-weight:500">Filter:</label>
|
||||
<select id="filterSupervisor">
|
||||
<option value="">All supervisors</option>
|
||||
${supervisorOptions.map(name => `<option value="${esc(name)}" ${filterSupervisor === name ? 'selected' : ''}>${esc(name)}</option>`).join('')}
|
||||
</select>
|
||||
<select id="filterCoach">
|
||||
<option value="">All coaches</option>
|
||||
${coaches.map(c => `<option value="${esc(c)}" ${filterCoach === c ? 'selected' : ''}>${esc(c)}</option>`).join('')}
|
||||
${coachOptions.map(([id, label]) => `<option value="${esc(id)}" ${filterCoach === id ? 'selected' : ''}>${esc(label)}</option>`).join('')}
|
||||
</select>
|
||||
<select id="filterStatus">
|
||||
<option value="">All statuses</option>
|
||||
${statuses.map(s => `<option value="${esc(s)}" ${filterStatus === s ? 'selected' : ''}>${esc(s)}</option>`).join('')}
|
||||
</select>
|
||||
<label style="font-size:.85rem;display:flex;align-items:center;gap:4px">
|
||||
Completed from
|
||||
<input type="date" id="filterDateFrom" value="${esc(filterDateFrom)}">
|
||||
</label>
|
||||
<label style="font-size:.85rem;display:flex;align-items:center;gap:4px">
|
||||
to
|
||||
<input type="date" id="filterDateTo" value="${esc(filterDateTo)}">
|
||||
</label>
|
||||
<button type="button" class="btn btn-sm" id="clearFiltersBtn">Clear filters</button>
|
||||
<span style="margin-left:auto;font-size:.85rem;color:var(--text-secondary)" id="resultCount"></span>
|
||||
<a id="exportCsvLink" class="btn btn-sm" href="#">Export CSV</a>
|
||||
</div>
|
||||
@ -73,6 +93,10 @@ function renderResults() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('filterSupervisor').addEventListener('change', (e) => {
|
||||
filterSupervisor = e.target.value;
|
||||
renderTableRows();
|
||||
});
|
||||
document.getElementById('filterCoach').addEventListener('change', (e) => {
|
||||
filterCoach = e.target.value;
|
||||
renderTableRows();
|
||||
@ -81,6 +105,22 @@ function renderResults() {
|
||||
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();
|
||||
@ -94,7 +134,8 @@ function renderTableHead() {
|
||||
const head = document.getElementById('resultsHead');
|
||||
const fixedCols = [
|
||||
{ key: 'clientCode', label: 'Client' },
|
||||
{ key: 'coachID', label: 'Coach' },
|
||||
{ key: 'coachUsername', label: 'Coach' },
|
||||
{ key: 'supervisorUsername', label: 'Supervisor' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
{ key: 'sumPoints', label: 'Score' },
|
||||
{ key: 'completedAt', label: 'Completed' },
|
||||
@ -123,13 +164,58 @@ function renderTableHead() {
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
@ -181,7 +267,8 @@ function renderTableRows() {
|
||||
|
||||
return `<tr>
|
||||
<td>${esc(c.clientCode)}</td>
|
||||
<td>${esc(c.coachID)}</td>
|
||||
<td>${esc(coachDisplayName(c))}</td>
|
||||
<td>${esc(supervisorDisplayName(c))}</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>${c.sumPoints !== null ? c.sumPoints : '—'}</td>
|
||||
<td>${completedDate}</td>
|
||||
@ -192,7 +279,8 @@ function renderTableRows() {
|
||||
|
||||
function getCellValue(client, key) {
|
||||
if (key === 'clientCode') return client.clientCode;
|
||||
if (key === 'coachID') return client.coachID;
|
||||
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;
|
||||
@ -216,12 +304,12 @@ function exportCSV() {
|
||||
});
|
||||
});
|
||||
|
||||
const headers = ['clientCode', 'coachID', 'status', 'sumPoints', 'completedAt',
|
||||
const headers = ['clientCode', 'coach', 'supervisor', 'status', 'sumPoints', 'completedAt',
|
||||
...questionsDef.map(q => q.defaultText)];
|
||||
|
||||
const rows = clients.map(c => {
|
||||
const base = [
|
||||
c.clientCode, c.coachID, c.status, c.sumPoints ?? '',
|
||||
c.clientCode, coachDisplayName(c), c.supervisorUsername || '', c.status, c.sumPoints ?? '',
|
||||
c.completedAt ? new Date(c.completedAt * 1000).toISOString() : ''
|
||||
];
|
||||
const answerCols = questionsDef.map(q => {
|
||||
|
||||
Reference in New Issue
Block a user