Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions class_manager.js
Original file line number Diff line number Diff line change
@@ -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
};
1 change: 1 addition & 0 deletions parents.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ <h2 style="margin-top:0">Topic-wise performance</h2>
<script src="progress.js"></script>
<script src="quizProgress.js"></script>
<script src="dashboardProgress.js"></script>
<script src="weeklyReport.js"></script>
</body>
</html>

2 changes: 2 additions & 0 deletions teachers.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ <h1>Empowering Teachers</h1>
</div>
<div class="hero-image">
<img src="images/teacher.jpeg" alt="Teacher guiding students">
<button id="createClassBtn" style="margin-top:12px;background:var(--accent-color);color:#fff;padding:10px 16px;border:none;border-radius:8px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,0.2);transition:transform 0.2s;" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">Create Class</button>
<div id="leaderboardContainer" style="margin-top:24px;"></div>
</div>
</section>

Expand Down
118 changes: 118 additions & 0 deletions weeklyReport.js
Original file line number Diff line number Diff line change
@@ -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);
})();
Loading