Move spelling test results from Spelling panel into Gradebook

Adds a GET /api/spelling-tests endpoint that returns all standalone test
results grouped by student. The Gradebook panel now shows a "Spelling Test
History" section below the course gradebook table. Removed the "Test results"
section from the Spelling panel left column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-27 13:55:23 -04:00
1 parent 774979f2e4
commit 183b43b452
3 files changed
+46 -25

No files matched your search

+2 -2
View File
@@ -283,6 +283,8 @@
<button id="gradebook-csv-btn" class="secondary">⬇ Export CSV</button>
</div>
<div id="gradebook-table"></div>
<h2>Spelling Test History</h2>
<div id="spelling-tests-table"></div>
<div id="history-modal" class="modal" hidden>
<div class="modal-box">
<h3 id="history-modal-title">Attempt History</h3>
@@ -305,8 +307,6 @@
<button type="button" id="list-show-hidden" class="secondary small" hidden>Show hidden</button>
</div>
<div id="list-rows"></div>
<h2>Test results</h2>
<div id="results-area"></div>
</div>
<div class="panel-col">
<div class="toggle-btns">
+31 -23
View File
@@ -1211,6 +1211,37 @@ async function loadGradebookPanel() {
const courses = await api('/api/admin/courses');
$('#gradebook-course').innerHTML = courses.map((c) => `<option value="${c.id}">${esc(c.name)}</option>`).join('');
if (courses.length) renderGradebook(courses[0].id);
loadSpellingTests();
}
async function loadSpellingTests() {
const { students } = await api('/api/spelling-tests');
const container = $('#spelling-tests-table');
const active = students.filter((s) => s.tests.length > 0);
if (!active.length) {
container.innerHTML = `<p class="hint">No spelling tests recorded yet.</p>`;
return;
}
container.innerHTML = active
.map((s) => {
const rows = s.tests
.map((t) => {
const pct = Math.round((t.score / t.total) * 100);
return `<tr>
<td>${new Date(t.at + 'Z').toLocaleDateString()}</td>
<td>${esc(t.list)}</td>
<td class="${pct >= 80 ? 'score-good' : 'score-bad'}">${t.score}/${t.total} (${pct}%)</td>
<td><button data-print="${t.id}">🖨 Print</button></td>
</tr>`;
})
.join('');
return `<div class="results-student"><h3>${esc(s.emoji)} ${esc(s.name)}</h3>
<table class="results"><tr><th>Date</th><th>List</th><th>Score</th><th></th></tr>${rows}</table></div>`;
})
.join('');
container.querySelectorAll('[data-print]').forEach((btn) =>
btn.addEventListener('click', () => printSpellingReport(btn.dataset.print))
);
}
$('#gradebook-course').addEventListener('change', () => renderGradebook($('#gradebook-course').value));
$('#gradebook-csv-btn').addEventListener('click', () => {
@@ -1281,7 +1312,6 @@ async function loadSpelling() {
cachedStudents = overview.students;
renderAssignRows(overview.students);
renderLists(lists);
renderResults(overview.students);
}
function renderAssignRows(students) {
@@ -1546,28 +1576,6 @@ $('#list-bulk-clear').addEventListener('click', () => {
$('#list-bulk-status').textContent = '';
});
function renderResults(students) {
$('#results-area').innerHTML = students
.map((s) => {
if (s.tests.length === 0) return `<div class="results-student"><h3>${esc(s.emoji)} ${esc(s.name)}</h3><p class="hint">No tests yet.</p></div>`;
const rows = s.tests
.map((t) => {
const pct = Math.round((t.score / t.total) * 100);
return `<tr>
<td>${new Date(t.at + 'Z').toLocaleDateString()}</td>
<td>${esc(t.list)}</td>
<td class="${pct >= 80 ? 'score-good' : 'score-bad'}">${t.score}/${t.total} (${pct}%)</td>
<td><button data-print="${t.id}">🖨 Print</button></td>
</tr>`;
})
.join('');
return `<div class="results-student"><h3>${esc(s.emoji)} ${esc(s.name)}</h3>
<table class="results"><tr><th>Date</th><th>List</th><th>Score</th><th></th></tr>${rows}</table></div>`;
})
.join('');
document.querySelectorAll('[data-print]').forEach((btn) => btn.addEventListener('click', () => printSpellingReport(btn.dataset.print)));
}
function printSpellingWorksheet(name, words) {
const rows = words