${overallAttendanceRate}%
Overall Attendance
${totalSessions}
Total Sessions
`;
}
function renderStudentList() {
const list = document.getElementById('studentList');
list.innerHTML = '';
appData.students.forEach(student => {
const item = document.createElement('div');
item.className = 'flex justify-between items-center bg-white p-3 rounded-md border';
item.innerHTML = `
${student.firstName} ${student.lastName}
`;
list.appendChild(item);
});
}
function renderSessionSelect() {
const select = document.getElementById('sessionSelect');
select.innerHTML = '
';
appData.sessions.forEach(session => {
const className = appData.classes.find(c => c.id === session.classId).name;
const option = document.createElement('option');
option.value = session.id;
option.textContent = `${className} - ${new Date(session.date).toLocaleDateString('en-US', { timeZone: 'UTC' })}`;
select.appendChild(option);
});
}
function renderAttendanceSheet() {
const sessionId = document.getElementById('sessionSelect').value;
const sheet = document.getElementById('attendanceSheet');
if (!sessionId) {
sheet.innerHTML = '
Please select a class session to begin.
';
return;
}
const session = appData.sessions.find(s => s.id == sessionId);
sheet.innerHTML = '';
appData.students.forEach(student => {
const attendanceRecord = session.attendance.find(a => a.studentId === student.id);
const status = attendanceRecord ? attendanceRecord.status : 'unmarked';
const item = document.createElement('div');
item.className = 'flex flex-col sm:flex-row justify-between items-center bg-white p-3 rounded-md border';
item.innerHTML = `
${student.firstName} ${student.lastName}
`;
sheet.appendChild(item);
});
}
function renderReport(filteredData) {
const output = document.getElementById('reportOutput');
if(!filteredData || filteredData.length === 0) {
output.innerHTML = '
No attendance records found for the selected date range.
';
document.getElementById('downloadPdfBtn').disabled = true;
return;
}
let tableHTML = '
| Student | ';
const dates = [...new Set(filteredData.map(item => item.date))].sort();
dates.forEach(date => {
tableHTML += `${new Date(date).toLocaleDateString('en-US', { timeZone: 'UTC' })} | `;
});
tableHTML += '
';
const students = [...new Set(filteredData.map(item => item.studentId))].map(id => appData.students.find(s => s.id === id));
students.forEach(student => {
tableHTML += `| ${student.firstName} ${student.lastName} | `;
dates.forEach(date => {
const record = filteredData.find(item => item.studentId === student.id && item.date === date);
const status = record ? record.status : '-';
let statusClass = '';
if (status === 'present') statusClass = 'text-green-600';
if (status === 'absent') statusClass = 'text-red-600';
if (status === 'late') statusClass = 'text-orange-600';
tableHTML += `${status} | `;
});
tableHTML += '
';
});
tableHTML += '
';
output.innerHTML = tableHTML;
document.getElementById('downloadPdfBtn').disabled = false;
}
// --- LOGIC & EVENT HANDLERS ---
function updateTabs() {
tabs.forEach(tab => tab.classList.toggle('active', parseInt(tab.dataset.tab) === currentTab));
tabPanes.forEach(pane => pane.classList.toggle('hidden', parseInt(pane.id.split('-')[1]) !== currentTab));
prevBtn.disabled = currentTab === 1;
nextBtn.disabled = currentTab === totalTabs;
}
function goToTab(tabNumber) {
if (tabNumber >= 1 && tabNumber <= totalTabs) {
currentTab = tabNumber;
updateTabs();
}
}
function addStudent() {
const firstName = document.getElementById('newStudentFirstName').value.trim();
const lastName = document.getElementById('newStudentLastName').value.trim();
if (firstName && lastName) {
const newId = appData.students.length > 0 ? Math.max(...appData.students.map(s => s.id)) + 1 : 1;
appData.students.push({ id: newId, firstName, lastName });
document.getElementById('newStudentFirstName').value = '';
document.getElementById('newStudentLastName').value = '';
renderStudentList();
renderDashboard();
} else {
alert('Please enter both a first and last name.');
}
}
function deleteStudent(studentId) {
if (confirm('Are you sure you want to remove this student? This will also remove their attendance records.')) {
appData.students = appData.students.filter(s => s.id !== studentId);
appData.sessions.forEach(session => {
session.attendance = session.attendance.filter(a => a.studentId !== studentId);
});
renderStudentList();
renderDashboard();
}
}
function markAttendance(sessionId, studentId, status) {
const session = appData.sessions.find(s => s.id == sessionId);
if (!session) return;
let record = session.attendance.find(a => a.studentId === studentId);
if (record) {
record.status = status;
} else {
session.attendance.push({ studentId, status });
}
renderAttendanceSheet();
renderDashboard(); // Update stats
}
function generateReport() {
const startDate = document.getElementById('reportStartDate').value;
const endDate = document.getElementById('reportEndDate').value;
if (!startDate || !endDate) {
alert('Please select both a start and end date.');
return;
}
const reportData = [];
appData.sessions.forEach(session => {
if (session.date >= startDate && session.date <= endDate) {
session.attendance.forEach(att => {
reportData.push({
...att,
date: session.date
});
});
}
});
renderReport(reportData);
}
function downloadPdf() {
const reportTitle = `Attendance Report (${document.getElementById('reportStartDate').value} to ${document.getElementById('reportEndDate').value})`;
document.getElementById('pdf-report-title').textContent = reportTitle;
document.getElementById('pdf-report-content').innerHTML = document.getElementById('reportOutput').innerHTML;
const elementToCapture = document.getElementById('pdf-report-container');
html2canvas(elementToCapture, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'landscape', unit: 'px', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const ratio = canvasWidth / canvasHeight;
let newCanvasWidth = pdfWidth;
let newCanvasHeight = newCanvasWidth / ratio;
if (newCanvasHeight > pdfHeight) {
newCanvasHeight = pdfHeight;
newCanvasWidth = newCanvasHeight * ratio;
}
const x = (pdfWidth - newCanvasWidth) / 2;
const y = (pdfHeight - newCanvasHeight) / 2;
pdf.addImage(imgData, 'PNG', x, y, newCanvasWidth, newCanvasHeight);
pdf.save('attendance-report.pdf');
});
}
// --- EVENT LISTENERS ---
tabs.forEach(tab => tab.addEventListener('click', (e) => goToTab(parseInt(e.currentTarget.dataset.tab))));
prevBtn.addEventListener('click', () => goToTab(currentTab - 1));
nextBtn.addEventListener('click', () => goToTab(currentTab + 1));
document.getElementById('addStudentBtn').addEventListener('click', addStudent);
document.getElementById('sessionSelect').addEventListener('change', renderAttendanceSheet);
document.getElementById('generateReportBtn').addEventListener('click', generateReport);
document.getElementById('downloadPdfBtn').addEventListener('click', downloadPdf);
document.getElementById('studentList').addEventListener('click', (e) => {
if (e.target.classList.contains('delete-student-btn')) {
deleteStudent(parseInt(e.target.dataset.studentId));
}
});
document.getElementById('attendanceSheet').addEventListener('click', (e) => {
if (e.target.classList.contains('attendance-btn')) {
const { sessionId, studentId, status } = e.target.dataset;
markAttendance(parseInt(sessionId), parseInt(studentId), status);
}
});
// --- INITIALIZATION ---
renderAll();
updateTabs();
});