Add spelling test polish: date filter, CSV export, student history, planner badges

- Gradebook: period dropdown (all time / 30 / 90 days) and CSV export for
  Spelling Test History section
- Student portal spelling tab: shows last 10 tests as a Recent Tests list
- Planner: completed standalone spelling tests appear as read-only ⭐ badges
  on the day they were taken (week and month views)
- Removed dead s.tests query from /api/overview (now served by dedicated endpoints)
- New endpoints: GET /api/spelling-tests/csv, GET /api/students/:id/tests,
  GET /api/admin/students/:id/tests-range

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-27 14:26:47 -04:00
1 parent 183b43b452
commit b048ebbd85
6 files changed
+110 -20

No files matched your search

+19 -3
View File
@@ -238,10 +238,11 @@ function switchTab(name) {
}
async function openKid(id, tab = 'today') {
const [state, agenda, courses] = await Promise.all([
const [state, agenda, courses, { tests: recentTests }] = await Promise.all([
api(`/api/state/${id}`),
api(`/api/schedule/${id}?date=${todayStr()}`),
api(`/api/courses/mine/${id}`),
api(`/api/students/${id}/tests`),
]);
currentStudent = state.student;
applyTheme(currentStudent.theme || 'blue');
@@ -261,7 +262,7 @@ async function openKid(id, tab = 'today') {
renderToday(agenda.tasks);
renderCourseCards(courses);
renderSpellingTab(state);
renderSpellingTab(state, recentTests);
show('kid');
switchTab(tab);
@@ -433,7 +434,7 @@ function renderCourseCards(courses) {
let currentSpellingListId = null;
let currentSpellingListName = '';
function renderSpellingTab(state) {
function renderSpellingTab(state, tests = []) {
let html;
if (state.assignment) {
const { mastered, total } = state.listProgress;
@@ -452,6 +453,21 @@ function renderSpellingTab(state) {
$('#kid-week').innerHTML = html;
$('#btn-test').disabled = !state.assignment;
$('#btn-print-list').disabled = !state.assignment;
const histEl = $('#kid-test-history');
if (!tests.length) {
histEl.innerHTML = '';
} else {
const rows = tests.map((t) => {
const pct = Math.round((t.score / t.total) * 100);
return `<div class="test-history-row">
<span class="thr-date">${new Date(t.at + 'Z').toLocaleDateString()}</span>
<span class="thr-list">${esc(t.list)}</span>
<span class="thr-score ${pct >= 80 ? 'score-good' : 'score-bad'}">${t.score}/${t.total} (${pct}%)</span>
</div>`;
}).join('');
histEl.innerHTML = `<h3 class="thr-heading">Recent Tests</h3>${rows}`;
}
}
$('#btn-print-list').addEventListener('click', async () => {