diff --git a/public/admin.html b/public/admin.html
index a8ed4f0..32bfd0f 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -197,6 +197,18 @@
+
+ âš¡ Auto-schedule a course
+
Add a kid first.
`; return; @@ -547,9 +547,25 @@ async function loadPlannerPanel() { $('#planner-student').innerHTML = students.map((s) => ``).join(''); if (!plannerStudentId || !students.some((s) => s.id === plannerStudentId)) plannerStudentId = students[0].id; $('#planner-student').value = plannerStudentId; + $('#auto-course').innerHTML = courses.map((c) => ``).join(''); + if (!$('#auto-start-date').value) $('#auto-start-date').value = plannerWeekStart; await renderPlanner(); } +$('#auto-schedule-btn').addEventListener('click', async () => { + const courseId = Number($('#auto-course').value); + const startDate = $('#auto-start-date').value; + const itemsPerDay = Number($('#auto-items-per-day').value) || 1; + if (!courseId || !startDate) { $('#auto-schedule-msg').textContent = 'Pick a course and start date.'; return; } + const { scheduled } = await api('/api/schedule/auto', { + method: 'POST', + body: { studentId: plannerStudentId, courseId, startDate, itemsPerDay }, + }); + $('#auto-schedule-msg').textContent = `Scheduled ${scheduled} item${scheduled === 1 ? '' : 's'} starting ${startDate}.`; + plannerWeekStart = mondayOf(startDate + 'T12:00:00'); + renderPlanner(); +}); + $('#planner-student').addEventListener('change', () => { plannerStudentId = Number($('#planner-student').value); renderPlanner(); diff --git a/server.js b/server.js index 964e5b4..739a6f8 100644 --- a/server.js +++ b/server.js @@ -951,6 +951,52 @@ app.get('/api/schedule/:studentId', (req, res) => { res.json({ tasks }); }); +app.post('/api/schedule/auto', requirePin, (req, res) => { + const { studentId, courseId, startDate, itemsPerDay = 1 } = req.body; + if (!isDateStr(startDate)) return res.status(400).json({ error: 'startDate=YYYY-MM-DD required' }); + const items = db.prepare(` + SELECT i.id, i.title FROM items i + JOIN units u ON u.id = i.unit_id + WHERE u.course_id = ? + ORDER BY u.sort, i.sort + `).all(courseId); + if (!items.length) return res.json({ scheduled: 0 }); + + const perDay = Math.max(1, Math.min(20, Number(itemsPerDay))); + const ins = db.prepare(`INSERT INTO schedule (student_id, date, item_id, title, sort) VALUES (?, ?, ?, '', ?)`); + const sortQ = db.prepare(`SELECT COALESCE(MAX(sort), -1) + 1 AS n FROM schedule WHERE student_id = ? AND date = ?`); + + let d = new Date(startDate + 'T12:00:00Z'); + let scheduled = 0; + for (let i = 0; i < items.length; ) { + const dow = d.getUTCDay(); + if (dow !== 0 && dow !== 6) { + const dateStr = d.toISOString().slice(0, 10); + for (let j = 0; j < perDay && i < items.length; j++, i++) { + const sort = sortQ.get(studentId, dateStr).n + j; + ins.run(studentId, dateStr, items[i].id, sort); + scheduled++; + } + } + d = new Date(d.getTime() + 86400000); + } + res.json({ scheduled }); +}); + +app.post('/api/schedule/copy', requirePin, (req, res) => { + const { studentId, from, to } = req.body; + if (!isDateStr(from) || !isDateStr(to)) return res.status(400).json({ error: 'from/to dates required' }); + const offsetDays = Math.round((new Date(to) - new Date(from)) / 86400000); + const rows = db.prepare(` + SELECT date, item_id, title, sort FROM schedule WHERE student_id = ? AND date BETWEEN ? AND date(?, '+4 days') + `).all(studentId, from, from); + const ins = db.prepare(` + INSERT INTO schedule (student_id, date, item_id, title, sort) VALUES (?, date(?, ?), ?, ?, ?) + `); + for (const r of rows) ins.run(studentId, r.date, `+${offsetDays} days`, r.item_id, r.title, r.sort); + res.json({ copied: rows.length }); +}); + // Kid updates a standalone offline task: status (not_started/in_progress/done) + optional evidence app.post('/api/schedule/:id/done', (req, res) => { const row = db.prepare(`SELECT item_id FROM schedule WHERE id = ?`).get(req.params.id); @@ -1005,21 +1051,6 @@ app.get('/api/schedule-week/:studentId', requirePin, (req, res) => { res.json({ tasks }); }); -// Copy one week's plan to another week -app.post('/api/schedule/copy', requirePin, (req, res) => { - const { studentId, from, to } = req.body; - if (!isDateStr(from) || !isDateStr(to)) return res.status(400).json({ error: 'from/to dates required' }); - const offsetDays = Math.round((new Date(to) - new Date(from)) / 86400000); - const rows = db.prepare(` - SELECT date, item_id, title, sort FROM schedule WHERE student_id = ? AND date BETWEEN ? AND date(?, '+4 days') - `).all(studentId, from, from); - const ins = db.prepare(` - INSERT INTO schedule (student_id, date, item_id, title, sort) VALUES (?, date(?, ?), ?, ?, ?) - `); - for (const r of rows) ins.run(studentId, r.date, `+${offsetDays} days`, r.item_id, r.title, r.sort); - res.json({ copied: rows.length }); -}); - // Printable weekly report: schedule completion + any graded work that week app.get('/api/week-report/:studentId', requirePin, (req, res) => { const start = isDateStr(req.query.start) ? req.query.start : null;