Attendance
${student.attendance}%
Avg. Assignment
${student.avgAssignment.toFixed(1)}%
Assignment Score Trend
`;
renderIndividualAssignmentChart(student);
};
// --- CHART RENDERING ---
const renderGradeDistributionChart = () => {
const ctx = document.getElementById('grade-dist-chart').getContext('2d');
if (charts.grade) charts.grade.destroy();
const grades = studentData.map(s => s.grade);
const bins = { 'F (0-59)': 0, 'D (60-69)': 0, 'C (70-79)': 0, 'B (80-89)': 0, 'A (90-100)': 0 };
grades.forEach(g => {
if (g < 60) bins['F (0-59)']++;
else if (g < 70) bins['D (60-69)']++;
else if (g < 80) bins['C (70-79)']++;
else if (g < 90) bins['B (80-89)']++;
else bins['A (90-100)']++;
});
charts.grade = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(bins),
datasets: [{ label: 'Number of Students', data: Object.values(bins), backgroundColor: 'rgba(59, 130, 246, 0.5)', borderColor: 'rgba(59, 130, 246, 1)', borderWidth: 1 }]
},
options: {
animation: false,
scales: { y: { beginAtZero: true, stepSize: 1 } }
}
});
};
const renderAttendanceChart = () => {
const ctx = document.getElementById('attendance-chart').getContext('2d');
if (charts.attendance) charts.attendance.destroy();
charts.attendance = new Chart(ctx, {
type: 'line',
data: {
labels: studentData.map(s => s.name),
datasets: [{ label: 'Attendance %', data: studentData.map(s => s.attendance), fill: false, borderColor: 'rgb(75, 192, 192)', tension: 0.1 }]
},
options: {
animation: false
}
});
};
const renderIndividualAssignmentChart = (student) => {
const ctx = document.getElementById('individual-assignment-chart').getContext('2d');
if (charts.individual) charts.individual.destroy();
charts.individual = new Chart(ctx, {
type: 'line',
data: {
labels: student.assignments.map((_, i) => `Assign. ${i+1}`),
datasets: [{ label: 'Score %', data: student.assignments, borderColor: 'rgb(239, 68, 68)', tension: 0.1 }]
},
options: {
animation: false
}
});
};
// --- TAB NAVIGATION ---
window.changeTab = (tabIndex) => {
tabs[currentTab].classList.remove('active');
tabContents[currentTab].classList.add('hidden');
currentTab = tabIndex;
tabs[currentTab].classList.add('active');
tabContents[currentTab].classList.remove('hidden');
};
// --- EVENT LISTENERS ---
updateDataBtn.addEventListener('click', renderAll);
studentSelect.addEventListener('change', renderIndividualReport);
// --- PDF DOWNLOAD ---
downloadPdfBtn.addEventListener('click', () => {
// **FIX:** Add a small delay to ensure charts are fully rendered before capturing
setTimeout(() => {
generatePdfReport();
}, 200);
});
const generatePdfReport = async () => {
try {
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'mm', 'a4');
const selectedStudent = studentData.find(s => s.id === parseInt(studentSelect.value, 10));
doc.setFontSize(24);
doc.setFont('helvetica', 'bold');
doc.text('Student Performance Report', 105, 20, { align: 'center' });
doc.setFontSize(12);
doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 105, 28, { align: 'center' });
doc.setFontSize(16);
doc.text('Class-wide Analytics', 15, 45);
const gradeChartImg = document.getElementById('grade-dist-chart').toDataURL('image/png', 1.0);
const attendanceChartImg = document.getElementById('attendance-chart').toDataURL('image/png', 1.0);
doc.addImage(gradeChartImg, 'PNG', 15, 50, 85, 45);
doc.addImage(attendanceChartImg, 'PNG', 110, 50, 85, 45);
if (selectedStudent) {
doc.addPage();
doc.setFontSize(20);
doc.text(`Individual Report: ${selectedStudent.name}`, 105, 20, { align: 'center' });
// **FIX:** Simplified and more robust capture logic for the individual report
const reportElement = document.getElementById('individual-report-view');
await html2canvas(reportElement, { scale: 2, backgroundColor: '#f8fafc' }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 30; // with margin
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, 'PNG', 15, 30, pdfWidth, pdfHeight);
});
}
doc.save('student-performance-report.pdf');
} catch (error) {
console.error("PDF Generation Error:", error);
alert("An error occurred while generating the PDF. Please try again.");
}
};
// --- INITIALIZATION ---
dataInputArea.value = "John Doe, 85, 95, 90,80,88,92\nJane Smith, 92, 98, 95,90,89,94\nPeter Jones, 55, 65, 50,60,58,42\nMary Williams, 78, 88, 75,82,79,80";
renderAll();
});