From 6b7b77829e30add44e7ade0ab22c7f291234c546 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:11:23 -0400 Subject: [PATCH] Add school name, passing threshold, and week-start-day settings - School/family name: saved to DB, appears in printed week/month report headers - Passing grade threshold: replaces hardcoded 80% in all gradebook/history views - Week start day (Mon vs Sun): shifts the planner 5-day grid and month view accordingly - Settings are loaded at unlock time so they apply before any panel opens Co-Authored-By: Claude Sonnet 4.6 --- db.js | 3 ++ public/admin.html | 30 +++++++++++++++ public/admin.js | 93 ++++++++++++++++++++++++++++++++++------------- server.js | 21 ++++++++++- 4 files changed, 120 insertions(+), 27 deletions(-) diff --git a/db.js b/db.js index daa9e25..934914d 100644 --- a/db.js +++ b/db.js @@ -244,6 +244,9 @@ const seedSetting = (key, val) => { if (!db.prepare(`SELECT value FROM settings WHERE key = ?`).get(key)) db.prepare(`INSERT INTO settings (key, value) VALUES (?, ?)`).run(key, val); }; +seedSetting('school_name', ''); +seedSetting('passing_pct', '80'); +seedSetting('week_start_day', 'monday'); // Seed bundled word lists on first run if (db.prepare(`SELECT COUNT(*) AS n FROM lists`).get().n === 0) { diff --git a/public/admin.html b/public/admin.html index 64cbd18..d7a3ddd 100644 --- a/public/admin.html +++ b/public/admin.html @@ -498,11 +498,41 @@

Settings

+ +

Parent PIN

+

School

+
+
+ + +
+ +
+ +
+ % +
+

Scores at or above this show green in gradebooks and reports.

+
+ +
+ + + +
+ + +
+
diff --git a/public/admin.js b/public/admin.js index 8f78f94..f200c14 100644 --- a/public/admin.js +++ b/public/admin.js @@ -8,6 +8,7 @@ const esc = (s) => sessionStorage.removeItem('pin'); let parentPin = null; +const appSettings = { schoolName: '', passingPct: 80, weekStartDay: 'monday' }; async function api(path, opts = {}) { const headers = { 'Content-Type': 'application/json' }; @@ -57,7 +58,17 @@ $('#pin-form').addEventListener('submit', async (e) => { unlock(); }); +async function loadAppSettings() { + try { + const s = await api('/api/admin/app-settings'); + appSettings.schoolName = s.school_name || ''; + appSettings.passingPct = Number(s.passing_pct) || 80; + appSettings.weekStartDay = s.week_start_day || 'monday'; + } catch {} +} + async function unlock() { + await loadAppSettings(); $('#pin-gate').hidden = true; $('#console').hidden = false; showPanel('kids'); @@ -95,7 +106,7 @@ function showPanel(name) { const loaders = { kids: loadKids, courses: loadCourses, planner: loadPlannerPanel, grading: loadGrading, gradebook: loadGradebookPanel, spelling: loadSpelling, decks: loadDecks, - settings: () => {}, + settings: loadSettings, }; if (loaders[name]) loaders[name](); if (name === 'course-detail') openCourseDetail(currentCourseId); @@ -1142,11 +1153,12 @@ $('#ie-ws-parse').addEventListener('click', () => { // Planner // ============================================================ -function mondayOf(d) { +function weekStartOf(d) { const date = new Date(d); const day = date.getDay(); - const diff = day === 0 ? -6 : 1 - day; - date.setDate(date.getDate() + diff); + const target = appSettings.weekStartDay === 'sunday' ? 0 : 1; + const diff = (day - target + 7) % 7; + date.setDate(date.getDate() - diff); return date.toISOString().slice(0, 10); } function addDays(dateStr, n) { @@ -1155,17 +1167,17 @@ function addDays(dateStr, n) { return d.toISOString().slice(0, 10); } -let plannerWeekStart = mondayOf(new Date()); +let plannerWeekStart = null; // initialized lazily after settings load let plannerQueue = []; let plannerStudentId = null; let plannerCoursesCache = []; let plannerMode = 'week'; // 'week' | 'month' let plannerMonthRef = null; // YYYY-MM-01 of the month being viewed -function firstMondayOfMonth(dateStr) { +function firstWeekStartOfMonth(dateStr) { const d = new Date(dateStr + 'T00:00:00'); d.setDate(1); - return mondayOf(d); + return weekStartOf(d); } function addMonths(dateStr, n) { const d = new Date(dateStr + 'T00:00:00'); @@ -1177,6 +1189,7 @@ function monthLabel(dateStr) { } async function loadPlannerPanel() { + if (!plannerWeekStart) plannerWeekStart = weekStartOf(new Date()); const [students, courses] = await Promise.all([api('/api/students'), api('/api/admin/courses')]); if (students.length === 0) { $('#planner-grid').innerHTML = `

Add a kid first.

`; @@ -1200,7 +1213,7 @@ $('#auto-schedule-btn').addEventListener('click', async () => { body: { studentId: plannerStudentId, courseId, startDate, itemsPerDay }, }); $('#auto-schedule-msg').textContent = `Scheduled ${scheduled} item${scheduled === 1 ? '' : 's'} starting ${startDate}.`; - plannerWeekStart = mondayOf(startDate + 'T12:00:00'); + plannerWeekStart = weekStartOf(startDate + 'T12:00:00'); renderPlanner(); }); @@ -1222,7 +1235,7 @@ $('#planner-view-month').addEventListener('click', () => { // derive the month from the current week view date const ref = new Date(plannerWeekStart + 'T00:00:00'); plannerMonthRef = `${ref.getFullYear()}-${String(ref.getMonth() + 1).padStart(2, '0')}-01`; - plannerWeekStart = firstMondayOfMonth(plannerMonthRef); + plannerWeekStart = firstWeekStartOfMonth(plannerMonthRef); $('#planner-view-month').classList.add('active-view'); $('#planner-view-week').classList.remove('active-view'); $('#planner-copy').hidden = true; @@ -1231,7 +1244,7 @@ $('#planner-view-month').addEventListener('click', () => { $('#planner-prev').addEventListener('click', () => { if (plannerMode === 'month') { plannerMonthRef = addMonths(plannerMonthRef, -1); - plannerWeekStart = firstMondayOfMonth(plannerMonthRef); + plannerWeekStart = firstWeekStartOfMonth(plannerMonthRef); } else { plannerWeekStart = addDays(plannerWeekStart, -7); } @@ -1240,7 +1253,7 @@ $('#planner-prev').addEventListener('click', () => { $('#planner-next').addEventListener('click', () => { if (plannerMode === 'month') { plannerMonthRef = addMonths(plannerMonthRef, 1); - plannerWeekStart = firstMondayOfMonth(plannerMonthRef); + plannerWeekStart = firstWeekStartOfMonth(plannerMonthRef); } else { plannerWeekStart = addDays(plannerWeekStart, 7); } @@ -1259,7 +1272,9 @@ $('#planner-print').addEventListener('click', () => { else printWeekReport(plannerStudentId, plannerWeekStart); }); -const DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; +const DAY_NAMES_MON = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; +const DAY_NAMES_SUN = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday']; +const DAY_NAMES = () => appSettings.weekStartDay === 'sunday' ? DAY_NAMES_SUN : DAY_NAMES_MON; function attachPlannerEvents() { let draggingId = null; @@ -1332,7 +1347,7 @@ async function renderPlanner() { const weeks = []; let cursor = plannerWeekStart; for (let w = 0; w < 6; w++) { - const weekDays = DAY_NAMES.map((name, i) => ({ name, date: addDays(cursor, i) })); + const weekDays = DAY_NAMES().map((name, i) => ({ name, date: addDays(cursor, i) })); const fridayMonth = new Date(weekDays[4].date + 'T00:00:00').getMonth(); const mondayMonth = new Date(weekDays[0].date + 'T00:00:00').getMonth(); if (w > 0 && mondayMonth !== month && fridayMonth !== month) break; @@ -1349,7 +1364,7 @@ async function renderPlanner() { $('#planner-week-label').textContent = monthLabel(plannerMonthRef); $('#planner-grid').className = 'planner-grid planner-month'; $('#planner-grid').innerHTML = - `
${DAY_NAMES.map((d) => `
${d.slice(0,3)}
`).join('')}
` + + `
${DAY_NAMES().map((d) => `
${d.slice(0,3)}
`).join('')}
` + weeks.map((week) => `
${week.map((day) => renderDayCell(day, tasks, true, spellingTests)).join('')}
` ).join(''); @@ -1360,7 +1375,7 @@ async function renderPlanner() { 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) })); + 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, false, spellingTests)).join(''); } @@ -1482,7 +1497,8 @@ async function printWeekReport(studentId, start) { .map((t) => `${esc(t.list)}${t.score}/${t.total}`) .join(''); const win = window.open('', '_blank'); - win.document.write(`Weekly Plan — ${esc(r.student)} + const schoolPrefix = appSettings.schoolName ? `${esc(appSettings.schoolName)} — ` : ''; + win.document.write(`${schoolPrefix}Weekly Plan — ${esc(r.student)} -

Weekly Plan — ${esc(r.student)}

+

${schoolPrefix}Weekly Plan — ${esc(r.student)}

Week of ${r.start}

Schedule

${dayBlocks} @@ -1519,7 +1535,7 @@ async function printMonthReport(studentId, monthMonday, monthRef) { const weeks = []; let cursor = monthMonday; for (let w = 0; w < 6; w++) { - const weekDays = DAY_NAMES.map((name, i) => ({ name, date: addDays(cursor, i) })); + const weekDays = DAY_NAMES().map((name, i) => ({ name, date: addDays(cursor, i) })); const fridayMonth = new Date(weekDays[4].date + 'T00:00:00').getMonth(); const mondayMonth = new Date(weekDays[0].date + 'T00:00:00').getMonth(); if (w > 0 && mondayMonth !== month && fridayMonth !== month) break; @@ -1531,7 +1547,7 @@ async function printMonthReport(studentId, monthMonday, monthRef) { const student = (await api('/api/students')).find((s) => s.id === studentId); const { tasks } = await api(`/api/schedule-range/${studentId}?start=${rangeStart}&end=${rangeEnd}`); - const DAY_SHORT = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; + const DAY_SHORT = DAY_NAMES().map((d) => d.slice(0, 3)); const calRows = weeks.map((week) => `${week.map((day) => { const dayTasks = tasks.filter((t) => t.date === day.date); @@ -1555,7 +1571,8 @@ async function printMonthReport(studentId, monthMonday, monthRef) { }).join(''); const win = window.open('', '_blank'); - win.document.write(`${monthLabel(monthRef)} — ${student?.name || ''} + const schoolPfx = appSettings.schoolName ? `${esc(appSettings.schoolName)} — ` : ''; + win.document.write(`${schoolPfx}${monthLabel(monthRef)} — ${student?.name || ''} -

${monthLabel(monthRef)} — ${esc(student?.name || '')}

+

${schoolPfx}${monthLabel(monthRef)} — ${esc(student?.name || '')}

${DAY_SHORT.map((d) => ``).join('')}${calRows}
${d}
${detailBlocks ? `

Assignment Detail

${detailBlocks}` : ''}