LearnSpace: full-stack educational whiteboard platform
Node.js/Express backend + vanilla JS frontend. Features: real-time collaborative whiteboard (SSE), multi-page support, LaTeX formulas, shapes/connectors, coordinate systems, number lines, compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with rotation & resize controls, minimap navigation overlay, auto-measurements, multi-page thumbnails sidebar, PNG export, page templates. Student/teacher workflows: classes, assignments, library, dashboard. Mobile responsive. SQLite (better-sqlite3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
const db = require('../db/db');
|
||||
|
||||
function teacherOverview(req, res) {
|
||||
const user = req.user;
|
||||
const classId = req.query.classId ? Number(req.query.classId) : null;
|
||||
|
||||
const classes = user.role === 'admin'
|
||||
? db.prepare('SELECT id, name FROM classes ORDER BY name').all()
|
||||
: db.prepare('SELECT id, name FROM classes WHERE teacher_id = ? ORDER BY name').all(user.id);
|
||||
|
||||
if (!classId) return res.json({ classes, data: null });
|
||||
|
||||
if (user.role !== 'admin') {
|
||||
const cls = db.prepare('SELECT id FROM classes WHERE id = ? AND teacher_id = ?').get(classId, user.id);
|
||||
if (!cls) return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
const overview = db.prepare(`
|
||||
SELECT
|
||||
COUNT(DISTINCT cm.user_id) AS students,
|
||||
COUNT(DISTINCT CASE WHEN ts.status='completed' THEN ts.id END) AS sessions,
|
||||
ROUND(AVG(CASE WHEN ts.status='completed' AND ts.total>0
|
||||
THEN ts.score*100.0/ts.total END), 1) AS avgScore
|
||||
FROM class_members cm
|
||||
LEFT JOIN test_sessions ts ON ts.user_id = cm.user_id
|
||||
WHERE cm.class_id = ?
|
||||
`).get(classId);
|
||||
|
||||
const scoreByWeek = db.prepare(`
|
||||
SELECT
|
||||
strftime('%Y-W%W', ts.finished_at) AS week,
|
||||
ROUND(AVG(ts.score*100.0/ts.total), 1) AS avg,
|
||||
COUNT(*) AS sessions
|
||||
FROM test_sessions ts
|
||||
JOIN class_members cm ON cm.user_id = ts.user_id AND cm.class_id = ?
|
||||
WHERE ts.status='completed' AND ts.total>0
|
||||
AND ts.finished_at >= datetime('now','-56 days')
|
||||
GROUP BY week ORDER BY week
|
||||
`).all(classId);
|
||||
|
||||
const hardQuestions = db.prepare(`
|
||||
SELECT q.id, q.text, q.difficulty,
|
||||
COUNT(ua.id) AS attempts,
|
||||
ROUND(SUM(CASE WHEN ua.is_correct=0 THEN 1.0 ELSE 0 END)*100/COUNT(ua.id),1) AS errorRate,
|
||||
t.name AS topic
|
||||
FROM user_answers ua
|
||||
JOIN questions q ON q.id = ua.question_id
|
||||
JOIN test_sessions ts ON ts.id = ua.session_id
|
||||
JOIN class_members cm ON cm.user_id = ts.user_id AND cm.class_id = ?
|
||||
LEFT JOIN topics t ON t.id = q.topic_id
|
||||
WHERE ts.status='completed'
|
||||
GROUP BY q.id HAVING attempts >= 3
|
||||
ORDER BY errorRate DESC LIMIT 10
|
||||
`).all(classId);
|
||||
|
||||
const heatmap = db.prepare(`
|
||||
SELECT date(ts.started_at) AS day,
|
||||
COUNT(DISTINCT ts.user_id) AS students,
|
||||
COUNT(ts.id) AS sessions
|
||||
FROM test_sessions ts
|
||||
JOIN class_members cm ON cm.user_id = ts.user_id AND cm.class_id = ?
|
||||
WHERE ts.started_at >= datetime('now','-90 days')
|
||||
GROUP BY day ORDER BY day
|
||||
`).all(classId);
|
||||
|
||||
const assignments = db.prepare(`
|
||||
SELECT a.id, a.title, a.deadline,
|
||||
COUNT(DISTINCT cm.user_id) AS total,
|
||||
COUNT(DISTINCT CASE WHEN ass.session_id IS NOT NULL THEN ass.user_id END) AS done
|
||||
FROM assignments a
|
||||
JOIN class_members cm ON cm.class_id = a.class_id
|
||||
LEFT JOIN assignment_sessions ass ON ass.assignment_id=a.id AND ass.user_id=cm.user_id
|
||||
WHERE a.class_id = ?
|
||||
GROUP BY a.id ORDER BY a.created_at DESC LIMIT 10
|
||||
`).all(classId);
|
||||
|
||||
res.json({ classes, overview, scoreByWeek, hardQuestions, heatmap, assignments });
|
||||
}
|
||||
|
||||
module.exports = { teacherOverview };
|
||||
Reference in New Issue
Block a user