diff --git a/class_manager.js b/class_manager.js new file mode 100644 index 0000000..6ca3629 --- /dev/null +++ b/class_manager.js @@ -0,0 +1,85 @@ +// class_manager.js – Handles teacher class sessions and progress aggregation + +const CLASS_SESSIONS_KEY = 'learnsphere_class_sessions_v1'; + +function _loadClasses() { + try { + const raw = localStorage.getItem(CLASS_SESSIONS_KEY); + return raw ? JSON.parse(raw) : []; + } catch (e) { + console.warn('LearnSphere: Failed to load class sessions', e); + return []; + } +} + +function _saveClasses(classes) { + try { + localStorage.setItem(CLASS_SESSIONS_KEY, JSON.stringify(classes)); + } catch (e) { + console.warn('LearnSphere: Failed to save class sessions', e); + } +} + +function generateInviteCode(length = 6) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let code = ''; + for (let i = 0; i < length; i++) { + code += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return code; +} + +function createClassSession({ name, topics = [], dueDate }) { + const classes = _loadClasses(); + const id = 'cls_' + Date.now(); + const inviteCode = generateInviteCode(); + const newClass = { + id, + name, + inviteCode, + topics, + dueDate: dueDate || null, + attempts: [], // each { studentId, quizId, score } + createdAt: new Date().toISOString() + }; + classes.push(newClass); + _saveClasses(classes); + return newClass; +} + +function recordClassAttempt(classId, { studentId = 'anonymous', quizId, score }) { + if (!classId) return; + const classes = _loadClasses(); + const cls = classes.find(c => c.id === classId); + if (!cls) return; + cls.attempts.push({ studentId, quizId, score, timestamp: Date.now() }); + _saveClasses(classes); +} + +function getClassStats(classId) { + const classes = _loadClasses(); + const cls = classes.find(c => c.id === classId); + if (!cls) return null; + const total = cls.attempts.length; + const avgScore = total ? (cls.attempts.reduce((a, b) => a + b.score, 0) / total).toFixed(2) : '-'; + const bestScore = total ? Math.max(...cls.attempts.map(a => a.score)).toFixed(2) : '-'; + return { ...cls, totalAttempts: total, avgScore, bestScore }; +} + +function getAllClassStats() { + const classes = _loadClasses(); + return classes.map(c => { + const total = c.attempts.length; + const avgScore = total ? (c.attempts.reduce((a, b) => a + b.score, 0) / total).toFixed(2) : '-'; + const bestScore = total ? Math.max(...c.attempts.map(a => a.score)).toFixed(2) : '-'; + return { id: c.id, name: c.name, inviteCode: c.inviteCode, totalAttempts: total, avgScore, bestScore }; + }); +} + +// Expose globally for other modules +window.classManager = { + createClassSession, + recordClassAttempt, + getClassStats, + getAllClassStats +}; diff --git a/parents.html b/parents.html index af40d12..42d5441 100644 --- a/parents.html +++ b/parents.html @@ -131,6 +131,7 @@

Topic-wise performance

+ diff --git a/teachers.html b/teachers.html index 34daea8..b1c6da6 100644 --- a/teachers.html +++ b/teachers.html @@ -72,6 +72,8 @@

Empowering Teachers

Teacher guiding students + +
diff --git a/weeklyReport.js b/weeklyReport.js new file mode 100644 index 0000000..e446d78 --- /dev/null +++ b/weeklyReport.js @@ -0,0 +1,118 @@ +// weeklyReport.js — Parent Weekly Summary +// Generates a concise weekly overview: streak, top strengths, and areas to improve. +// Assumes window.quizProgress and window.studyProgress are already loaded. + +(function () { + /** Helper: map skillId → human readable label */ + const _skillLabelMap = (() => { + const map = {}; + if (window.quizProgress && window.quizProgress.SKILL_TAXONOMY) { + Object.values(window.quizProgress.SKILL_TAXONOMY).forEach(t => { + if (t && t.skillId) map[t.skillId] = t.label; + }); + } + return map; + })(); + + function _updateStreak() { + const streakEl = document.getElementById('streakValue'); + if (!streakEl) return; + let streak = null; + if (window.studyProgress && typeof window.studyProgress.loadStreakState === 'function') { + const state = window.studyProgress.loadStreakState(); + streak = state.currentStreak; + } else if (window.quizProgress && typeof window.quizProgress.getStreak === 'function') { + streak = window.quizProgress.getStreak().currentStreak; + } + streakEl.textContent = streak !== null && streak !== undefined ? streak : '—'; + } + + /** Render top strength skills (high accuracy, enough attempts) */ + function _renderStrengths() { + const container = document.getElementById('masterySkillsList'); + if (!container) return; + const mastery = (window.quizProgress && typeof window.quizProgress.getMasteryStats === 'function') + ? window.quizProgress.getMasteryStats() + : {}; + const strengths = Object.entries(mastery) + .filter(([, m]) => m.attempts && m.attempts >= 3) + .map(([skillId, m]) => ({ + skillId, + attempts: m.attempts, + correct: m.correct, + accuracy: m.attempts ? m.correct / m.attempts : 0, + label: _skillLabelMap[skillId] || skillId, + })) + .sort((a, b) => b.accuracy - a.accuracy) + .slice(0, 3); + + container.innerHTML = ''; + if (strengths.length === 0) { + const empty = document.createElement('div'); + empty.textContent = 'No strength data yet.'; + container.appendChild(empty); + return; + } + strengths.forEach(s => { + const card = document.createElement('div'); + card.style.display = 'flex'; + card.style.justifyContent = 'space-between'; + card.style.padding = '4px 0'; + + const label = document.createElement('span'); + label.textContent = s.label; + label.style.fontWeight = '500'; + const info = document.createElement('span'); + const pct = Math.round(s.accuracy * 100); + info.textContent = `${pct}% (${s.attempts} attempts)`; + info.style.color = 'var(--text-muted)'; + + card.appendChild(label); + card.appendChild(info); + container.appendChild(card); + }); + } + + /** Render weak concepts (recommended focus) */ + function _renderWeaknesses() { + const container = document.getElementById('weakSkillsRecommendations'); + if (!container) return; + const weak = (window.quizProgress && typeof window.quizProgress.getWeakestSkills === 'function') + ? window.quizProgress.getWeakestSkills({ limit: 3 }) + : []; + + container.innerHTML = ''; + if (weak.length === 0) { + const empty = document.createElement('div'); + empty.textContent = 'No weak concepts identified yet.'; + container.appendChild(empty); + return; + } + weak.forEach(w => { + const card = document.createElement('div'); + card.style.display = 'flex'; + card.style.justifyContent = 'space-between'; + card.style.padding = '4px 0'; + + const label = document.createElement('span'); + label.textContent = w.label; + label.style.fontWeight = '500'; + const info = document.createElement('span'); + const pct = w.accuracy !== null ? Math.round(w.accuracy * 100) : 'N/A'; + info.textContent = `${pct}% (${w.attempts} attempts)`; + info.style.color = 'var(--text-muted)'; + + card.appendChild(label); + card.appendChild(info); + container.appendChild(card); + }); + } + + function _renderWeeklyReport() { + _updateStreak(); + _renderStrengths(); + _renderWeaknesses(); + } + + document.addEventListener('DOMContentLoaded', _renderWeeklyReport); +})();