diff --git a/public/admin.js b/public/admin.js
index bbc7538..1a1718e 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -844,15 +844,21 @@ function attachPlannerEvents() {
);
}
-function renderDayCell(day, tasks, compact = false) {
+function renderDayCell(day, tasks, compact = false, spellingTests = []) {
const dayTasks = tasks.filter((t) => t.date === day.date);
+ const dayTests = spellingTests.filter((t) => t.date === day.date);
+ const testBadges = dayTests.map((t) => {
+ const pct = Math.round((t.score / t.total) * 100);
+ return `
⭐ Spelling ${t.score}/${t.total} (${pct}%)
`;
+ }).join('');
return `
${compact ? day.name.slice(0,3) : day.name}${compact ? day.date.slice(5) : day.date}
+ ${testBadges}
${dayTasks.map((t) => `
${t.itemId ? TYPE_ICON[t.type] : '📌'} ${esc(t.itemTitle || t.offlineTitle)}
-
`).join('') || `
—
`}
+
`).join('') || (!testBadges ? `
—
` : '')}
@@ -878,22 +884,28 @@ async function renderPlanner() {
}
const rangeStart = weeks[0][0].date;
const rangeEnd = weeks[weeks.length - 1][4].date;
- const { tasks } = await api(`/api/schedule-range/${plannerStudentId}?start=${rangeStart}&end=${rangeEnd}`);
+ const [{ tasks }, { tests: spellingTests }] = await Promise.all([
+ api(`/api/schedule-range/${plannerStudentId}?start=${rangeStart}&end=${rangeEnd}`),
+ api(`/api/admin/students/${plannerStudentId}/tests-range?from=${rangeStart}&to=${rangeEnd}`),
+ ]);
$('#planner-week-label').textContent = monthLabel(plannerMonthRef);
$('#planner-grid').className = 'planner-grid planner-month';
$('#planner-grid').innerHTML =
`` +
weeks.map((week) =>
- `
${week.map((day) => renderDayCell(day, tasks, true)).join('')}
`
+ `
${week.map((day) => renderDayCell(day, tasks, true, spellingTests)).join('')}
`
).join('');
} else {
const end = addDays(plannerWeekStart, 4);
$('#planner-week-label').textContent = `${plannerWeekStart} → ${end}`;
- const { tasks } = await api(`/api/schedule-week/${plannerStudentId}?start=${plannerWeekStart}`);
+ const [{ tasks }, { tests: spellingTests }] = await Promise.all([
+ api(`/api/schedule-week/${plannerStudentId}?start=${plannerWeekStart}`),
+ api(`/api/admin/students/${plannerStudentId}/tests-range?from=${plannerWeekStart}&to=${end}`),
+ ]);
const days = DAY_NAMES.map((name, i) => ({ name, date: addDays(plannerWeekStart, i) }));
$('#planner-grid').className = 'planner-grid';
- $('#planner-grid').innerHTML = days.map((day) => renderDayCell(day, tasks)).join('');
+ $('#planner-grid').innerHTML = days.map((day) => renderDayCell(day, tasks, false, spellingTests)).join('');
}
attachPlannerEvents();
@@ -1215,7 +1227,8 @@ async function loadGradebookPanel() {
}
async function loadSpellingTests() {
- const { students } = await api('/api/spelling-tests');
+ const since = $('#spelling-tests-since').value;
+ const { students } = await api(since ? `/api/spelling-tests?since=${since}` : '/api/spelling-tests');
const container = $('#spelling-tests-table');
const active = students.filter((s) => s.tests.length > 0);
if (!active.length) {
@@ -1248,6 +1261,11 @@ $('#gradebook-csv-btn').addEventListener('click', () => {
if (!gradebookCourseId) return;
window.location.href = `/api/gradebook/${gradebookCourseId}/csv`;
});
+$('#spelling-tests-since').addEventListener('change', loadSpellingTests);
+$('#spelling-tests-csv-btn').addEventListener('click', () => {
+ const since = $('#spelling-tests-since').value;
+ window.location.href = `/api/spelling-tests/csv${since ? '?since=' + since : ''}`;
+});
$('#history-modal-close').addEventListener('click', () => { $('#history-modal').hidden = true; });
async function showHistory(studentId, itemId, label) {
diff --git a/public/app.js b/public/app.js
index 855cf01..58d18d3 100644
--- a/public/app.js
+++ b/public/app.js
@@ -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 `
+ ${new Date(t.at + 'Z').toLocaleDateString()}
+ ${esc(t.list)}
+ ${t.score}/${t.total} (${pct}%)
+
`;
+ }).join('');
+ histEl.innerHTML = `
Recent Tests
${rows}`;
+ }
}
$('#btn-print-list').addEventListener('click', async () => {
diff --git a/public/index.html b/public/index.html
index 8fa61f6..dbb4008 100644
--- a/public/index.html
+++ b/public/index.html
@@ -55,6 +55,7 @@