`;
errorWrapper.style.display = 'block';
contentWrapper.style.display = 'none';
return;
}
// --- STATE MANAGEMENT & SAMPLE DATA ---
// The dashboard's data is fully customizable by editing this section.
let learners = [
{
id: 1, name: 'Amelia Yusuf',
modules: [
{ id: 101, name: 'Introduction to Algebra', status: 'Completed', score: 95, timeSpent: 45, mastery: 'Mastered' },
{ id: 102, name: 'Linear Equations', status: 'Completed', score: 88, timeSpent: 70, mastery: 'Mastered' },
{ id: 103, name: 'Quadratic Functions', status: 'In Progress', score: 72, timeSpent: 55, mastery: 'Proficient' },
{ id: 104, name: 'Polynomials', status: 'Not Started', score: null, timeSpent: 0, mastery: 'Novice' },
]
},
{
id: 2, name: 'Benjamin Carter',
modules: [
{ id: 101, name: 'Introduction to Algebra', status: 'Completed', score: 75, timeSpent: 65, mastery: 'Proficient' },
{ id: 102, name: 'Linear Equations', status: 'In Progress', score: 60, timeSpent: 85, mastery: 'Novice' },
{ id: 103, name: 'Quadratic Functions', status: 'Not Started', score: null, timeSpent: 0, mastery: 'Novice' },
{ id: 104, name: 'Polynomials', status: 'Not Started', score: null, timeSpent: 0, mastery: 'Novice' },
]
},
{
id: 3, name: 'Chloe Sharma',
modules: [
{ id: 101, name: 'Introduction to Algebra', status: 'Completed', score: 100, timeSpent: 30, mastery: 'Mastered' },
{ id: 102, name: 'Linear Equations', status: 'Completed', score: 98, timeSpent: 45, mastery: 'Mastered' },
{ id: 103, name: 'Quadratic Functions', status: 'Completed', score: 92, timeSpent: 60, mastery: 'Mastered' },
{ id: 104, name: 'Polynomials', status: 'Completed', score: 89, timeSpent: 75, mastery: 'Mastered' },
]
},
];
let activeTabIndex = 0;
let selectedLearnerId = null;
let masteryChart, timeChart;
const moduleNames = [...new Set(learners.flatMap(l => l.modules.map(m => m.name)))];
// --- DOM ELEMENTS ---
const tabButtons = document.querySelectorAll('.al-tab-button');
const tabPanes = document.querySelectorAll('.al-tab-pane');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const learnerListEl = document.getElementById('learner-list');
const learnerDetailsSection = document.getElementById('learner-details-section');
const downloadPdfBtn = document.getElementById('download-pdf-btn');
// --- INITIALIZATION ---
function initialize() {
initCharts();
render();
attachEventListeners();
}
function initCharts() {
const commonOptions = { responsive: true, maintainAspectRatio: false };
masteryChart = new Chart(document.getElementById('masteryDistributionChart'), { type: 'doughnut', options: {...commonOptions, plugins: { title: { display: true, text: 'Cohort Mastery Distribution' }}} });
timeChart = new Chart(document.getElementById('timePerModuleChart'), { type: 'bar', options: {...commonOptions, plugins: { title: { display: true, text: 'Average Time Spent per Module' }}, scales: { y: { beginAtZero: true, title: { display: true, text: 'Time (minutes)' }} } } });
}
// --- RENDER FUNCTIONS ---
function render() {
renderCohortDashboard();
renderLearnerList();
renderLearnerDetails(selectedLearnerId);
updateNavButtons();
}
function renderCohortDashboard() {
const totalLearners = learners.length;
const allModules = learners.flatMap(l => l.modules);
const completedModules = allModules.filter(m => m.status === 'Completed');
const totalProgress = allModules.length > 0 ? (completedModules.length / allModules.length) * 100 : 0;
const totalTime = allModules.reduce((sum, m) => sum + m.timeSpent, 0);
const avgTime = totalLearners > 0 ? totalTime / totalLearners : 0;
const scoredModules = allModules.filter(m => m.score !== null);
const avgScore = scoredModules.length > 0 ? scoredModules.reduce((sum, m) => sum + m.score, 0) / scoredModules.length : 0;
document.getElementById('cohort-total-learners').textContent = totalLearners;
document.getElementById('cohort-avg-progress').textContent = `${totalProgress.toFixed(0)}%`;
document.getElementById('cohort-avg-score').textContent = avgScore.toFixed(0);
document.getElementById('cohort-avg-time').textContent = `${avgTime.toFixed(0)}m`;
// Update charts
const masteryCounts = allModules.reduce((acc, m) => { acc[m.mastery] = (acc[m.mastery] || 0) + 1; return acc; }, {});
masteryChart.data = { labels: Object.keys(masteryCounts), datasets: [{ data: Object.values(masteryCounts), backgroundColor: ['#dc3545', '#ffc107', '#28a745'] }] };
masteryChart.update();
const timePerModule = moduleNames.map(name => {
const relevantModules = allModules.filter(m => m.name === name && m.timeSpent > 0);
return relevantModules.length > 0 ? relevantModules.reduce((sum, m) => sum + m.timeSpent, 0) / relevantModules.length : 0;
});
timeChart.data = { labels: moduleNames, datasets: [{ label: 'Avg. Time (minutes)', data: timePerModule, backgroundColor: '#007bff' }] };
timeChart.update();
}
function renderLearnerList() {
learnerListEl.innerHTML = '';
learners.forEach(learner => {
const li = document.createElement('li');
li.textContent = learner.name;
li.dataset.learnerId = learner.id;
if (learner.id === selectedLearnerId) {
li.classList.add('active');
}
learnerListEl.appendChild(li);
});
}
function renderLearnerDetails(learnerId) {
if (!learnerId) {
learnerDetailsSection.innerHTML = `
${m.name}
${m.mastery}
${m.score !== null ? m.score : 'N/A'}
${m.timeSpent} min
${m.status}
`).join('');
learnerDetailsSection.innerHTML = `
`;
}
// --- EVENT HANDLERS & NAVIGATION ---
function attachEventListeners() {
tabButtons.forEach(button => button.addEventListener('click', () => switchTab(parseInt(button.dataset.tab))));
prevBtn.addEventListener('click', () => switchTab(activeTabIndex - 1));
nextBtn.addEventListener('click', () => switchTab(activeTabIndex + 1));
learnerListEl.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
selectedLearnerId = parseInt(e.target.dataset.learnerId);
renderLearnerList(); // re-render list to show active state
renderLearnerDetails(selectedLearnerId);
}
});
downloadPdfBtn.addEventListener('click', generatePdf);
}
function switchTab(index) {
if (index < 0 || index >= tabPanes.length) return;
activeTabIndex = index;
tabButtons.forEach((btn, i) => btn.classList.toggle('active', i === index));
tabPanes.forEach((pane, i) => pane.classList.toggle('active', i === index));
updateNavButtons();
}
function updateNavButtons() {
prevBtn.disabled = activeTabIndex === 0;
nextBtn.disabled = activeTabIndex === tabPanes.length - 1;
}
// --- PDF GENERATION ---
async function generatePdf() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait' });
doc.setFontSize(18);
if (activeTabIndex === 0) { // Cohort Report
doc.text("Cohort Performance Report", 14, 22);
doc.addImage(masteryChart.toBase64Image(), 'PNG', 14, 40, 90, 90);
doc.addImage(timeChart.toBase64Image(), 'PNG', 105, 40, 90, 90);
const summaryData = [
['Total Learners', document.getElementById('cohort-total-learners').textContent],
['Average Progress', document.getElementById('cohort-avg-progress').textContent],
['Average Score', document.getElementById('cohort-avg-score').textContent],
['Average Time Spent', document.getElementById('cohort-avg-time').textContent]
];
doc.autoTable({ startY: 140, head: [['Metric', 'Value']], body: summaryData, theme: 'striped' });
doc.save(`Cohort_Report_${new Date().toISOString().slice(0,10)}.pdf`);
} else if (activeTabIndex === 1) { // Learner Report
if (!selectedLearnerId) { alert("Please select a learner to generate a report."); return; }
const learner = learners.find(l => l.id === selectedLearnerId);
doc.text(`Performance Report for: ${learner.name}`, 14, 22);
const tableData = learner.modules.map(m => [m.name, m.mastery, m.score !== null ? m.score : 'N/A', `${m.timeSpent} min`, m.status]);
doc.autoTable({ startY: 30, head: [['Module', 'Mastery', 'Score', 'Time Spent', 'Status']], body: tableData });
doc.save(`Learner_Report_${learner.name.replace(' ','_')}.pdf`);
}
}
initialize();
});
Select a learner to see their details.
`;
return;
}
const learner = learners.find(l => l.id === learnerId);
if (!learner) return;
const tableRows = learner.modules.map(m => `
Report for: ${learner.name}
| Module | Mastery | Score | Time Spent | Status |
|---|
