document.addEventListener('DOMContentLoaded', function () { // --- Utility Functions --- function escapeHTML(str) { if (str === null || str === undefined) return ''; return str.toString().replace(/[&<>()"']/g, match => ({ '&': '&', '<': '<', '>': '>', '"': '"', '\'' : ''' }[match])); } // --- General UI --- const navbar = document.querySelector('.navbar'); if (navbar) { window.addEventListener('scroll', () => { navbar.classList.toggle('scrolled', window.scrollY > 50); }); } document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' }); }); }); // --- Contact Form --- const contactForm = document.getElementById('contactForm'); if (contactForm) { contactForm.addEventListener('submit', function (e) { e.preventDefault(); // ... (existing contact form logic) ... }); } // --- Attendance Page --- const attendancePage = document.getElementById('attendance-page'); if (attendancePage) { const attendanceState = { selfieStream: null, qrScanner: null, qrLocationId: null }; const elements = { startCameraBtn: document.getElementById('start-camera-btn'), scanQrBtn: document.getElementById('scan-qr-btn'), checkInBtn: document.getElementById('check-in-btn'), videoPreview: document.getElementById('video-preview'), qrReader: document.getElementById('qr-reader'), selfieCanvas: document.getElementById('selfie-canvas'), status: document.getElementById('attendance-status'), courseSelect: document.getElementById('course-select'), studentIdInput: document.getElementById('student-id'), qrLocationIdInput: document.getElementById('qr-location-id') }; // --- Initialization --- fetch('api/attendance_handler.php?action=get_courses') .then(res => res.json()) .then(data => { if (data.status === 'success' && data.courses) { data.courses.forEach(course => { const option = new Option(course.name, course.id); elements.courseSelect.add(option); }); } }); // --- Event Listeners --- elements.startCameraBtn.addEventListener('click', startSelfieCamera); elements.scanQrBtn.addEventListener('click', startQrScanner); elements.checkInBtn.addEventListener('click', handleCheckIn); elements.courseSelect.addEventListener('change', updateCheckInButtonState); // --- Functions --- async function startSelfieCamera() { stopAllStreams(); elements.qrReader.classList.add('d-none'); elements.videoPreview.classList.remove('d-none'); try { attendanceState.selfieStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } }); elements.videoPreview.srcObject = attendanceState.selfieStream; showAttendanceMessage('info', 'Selfie camera started. You can now check in.'); updateCheckInButtonState(); } catch (err) { showAttendanceMessage('error', 'Could not access camera. Please grant permission.'); } } function startQrScanner() { stopAllStreams(); elements.videoPreview.classList.add('d-none'); elements.qrReader.classList.remove('d-none'); showAttendanceMessage('info', 'Point the camera at a QR code.'); attendanceState.qrScanner = new Html5Qrcode("qr-reader"); const config = { fps: 10, qrbox: { width: 250, height: 250 } }; attendanceState.qrScanner.start({ facingMode: "environment" }, config, onScanSuccess, onScanError); } function onScanSuccess(decodedText, decodedResult) { stopAllStreams(); try { const data = JSON.parse(decodedText); if (data.courseId && data.locationId) { elements.courseSelect.value = data.courseId; elements.qrLocationIdInput.value = data.locationId; attendanceState.qrLocationId = data.locationId; showAttendanceMessage('success', `QR code scanned successfully! Course selected. Please start your selfie camera to complete check-in.`); elements.courseSelect.disabled = true; updateCheckInButtonState(); } else { throw new Error('Invalid QR code format.'); } } catch (e) { showAttendanceMessage('error', 'Invalid QR code. Please scan a valid attendance code.'); } } function onScanError(error) { // console.warn(`QR scan error: ${error}`); } function handleCheckIn() { if (!elements.courseSelect.value || !attendanceState.selfieStream) { showAttendanceMessage('error', 'Please select a course and start the selfie camera.'); return; } setCheckInButtonState('loading'); navigator.geolocation.getCurrentPosition( (position) => { const { latitude, longitude } = position.coords; captureAndSubmit(latitude, longitude); }, (error) => { showAttendanceMessage('error', 'Could not get location. Please grant permission.'); setCheckInButtonState('reset'); }, { enableHighAccuracy: true } ); } function captureAndSubmit(latitude, longitude) { const context = elements.selfieCanvas.getContext('2d'); elements.selfieCanvas.width = elements.videoPreview.videoWidth; elements.selfieCanvas.height = elements.videoPreview.videoHeight; context.drawImage(elements.videoPreview, 0, 0, elements.selfieCanvas.width, elements.selfieCanvas.height); elements.selfieCanvas.toBlob((blob) => { const formData = new FormData(); formData.append('student_id', elements.studentIdInput.value); formData.append('course_id', elements.courseSelect.value); formData.append('latitude', latitude); formData.append('longitude', longitude); formData.append('selfie', blob, 'selfie.jpg'); if (attendanceState.qrLocationId) { formData.append('location_id', attendanceState.qrLocationId); } submitAttendance(formData); }, 'image/jpeg'); } function submitAttendance(formData) { fetch('api/attendance_handler.php?action=check_in', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { if (data.status === 'success') { showAttendanceMessage('success', data.message); setCheckInButtonState('success'); stopAllStreams(); } else { showAttendanceMessage('error', data.message); setCheckInButtonState('reset'); } }) .catch(err => { showAttendanceMessage('error', 'An unexpected error occurred.'); setCheckInButtonState('reset'); }); } function stopAllStreams() { if (attendanceState.selfieStream) { attendanceState.selfieStream.getTracks().forEach(track => track.stop()); attendanceState.selfieStream = null; } if (attendanceState.qrScanner && attendanceState.qrScanner.isScanning) { attendanceState.qrScanner.stop().catch(err => console.error('QR Scanner stop error', err)); } elements.qrReader.classList.add('d-none'); } function updateCheckInButtonState() { elements.checkInBtn.disabled = !(elements.courseSelect.value && attendanceState.selfieStream); } function showAttendanceMessage(type, message) { const classMap = { success: 'alert-success', error: 'alert-danger', info: 'alert-info' }; elements.status.className = `alert ${classMap[type] || 'alert-secondary'}`; elements.status.textContent = message; } function setCheckInButtonState(state) { const btn = elements.checkInBtn; switch (state) { case 'loading': btn.disabled = true; btn.innerHTML = ' Checking In...'; break; case 'success': btn.disabled = true; btn.innerHTML = 'Checked In Successfully'; btn.classList.replace('btn-primary', 'btn-success'); break; case 'reset': btn.disabled = false; btn.innerHTML = 'Check In Now'; break; } } } // --- Attendance History Page --- const historyBody = document.getElementById('attendance-history-body'); if (historyBody) { historyBody.innerHTML = 'Loading history...'; fetch('api/attendance_handler.php?action=get_attendance_history') .then(response => response.json()) .then(data => { if (data.status === 'success' && data.history) { if (data.history.length > 0) { historyBody.innerHTML = ''; // Clear loading message data.history.forEach(record => { const row = document.createElement('tr'); let statusClass = ''; if (record.status === 'success') statusClass = 'text-success'; if (record.status.startsWith('failed')) statusClass = 'text-danger'; row.innerHTML = ` ${escapeHTML(record.course_name)} ${new Date(record.check_in_time).toLocaleString()} ${escapeHTML(record.status.replace('_', ' '))} ${record.selfie_path ? `View` : 'N/A'} `; historyBody.appendChild(row); }); } else { historyBody.innerHTML = 'No attendance records found.'; } } else { throw new Error(data.message || 'Failed to load history.'); } }) .catch(error => { console.error('Error fetching attendance history:', error); historyBody.innerHTML = `Error: ${error.message}`; }); } // --- Faculty Attendance Dashboard --- const facultyAttendancePage = document.getElementById('faculty-attendance-page'); if (facultyAttendancePage) { const courseSelect = document.getElementById('faculty-course-select'); const attendanceBody = document.getElementById('faculty-attendance-body'); fetch('api/attendance_handler.php?action=get_courses') .then(response => response.json()) .then(data => { if (data.status === 'success' && data.courses) { data.courses.forEach(course => { const option = new Option(course.name, course.id); courseSelect.add(option); }); } }); courseSelect.addEventListener('change', () => { const courseId = courseSelect.value; if (!courseId) { attendanceBody.innerHTML = 'Please select a course to view records.'; return; } attendanceBody.innerHTML = 'Loading records...'; fetch(`api/attendance_handler.php?action=get_faculty_attendance&course_id=${courseId}`) .then(response => response.json()) .then(data => { if (data.status === 'success' && data.records) { if (data.records.length > 0) { attendanceBody.innerHTML = ''; data.records.forEach(record => { const row = document.createElement('tr'); let statusClass = record.status === 'success' ? 'text-success' : 'text-danger'; row.innerHTML = ` ${escapeHTML(record.student_id)} ${new Date(record.check_in_time).toLocaleString()} ${escapeHTML(record.status.replace('_', ' '))} ${record.selfie_path ? `View` : 'N/A'} ${record.latitude ? `${parseFloat(record.latitude).toFixed(5)}, ${parseFloat(record.longitude).toFixed(5)}` : 'N/A'} `; attendanceBody.appendChild(row); }); } else { attendanceBody.innerHTML = 'No records found.'; } } else { throw new Error(data.message || 'Failed to load records.'); } }) .catch(error => { attendanceBody.innerHTML = `Error: ${error.message}`; }); }); } // --- QR Code Generator Page --- const qrGeneratorPage = document.getElementById('qr-generator-page'); if (qrGeneratorPage) { const courseSelect = document.getElementById('qr-course-select'); const locationSelect = document.getElementById('qr-location-select'); const generateBtn = document.getElementById('generate-qr-btn'); const qrCodeContainer = document.getElementById('qr-code-container'); const qrCodeImg = document.getElementById('qr-code-img'); const qrCodeCaption = document.getElementById('qr-code-caption'); const loadingSpinner = document.getElementById('qr-loading-spinner'); fetch('api/attendance_handler.php?action=get_courses') .then(response => response.json()) .then(data => { if (data.status === 'success' && data.courses) { data.courses.forEach(course => { const option = new Option(course.name, course.id); courseSelect.add(option); }); } }); courseSelect.addEventListener('change', () => { const courseId = courseSelect.value; locationSelect.innerHTML = ''; locationSelect.disabled = true; generateBtn.disabled = true; qrCodeContainer.classList.add('d-none'); if (!courseId) { locationSelect.innerHTML = ''; return; } fetch(`api/attendance_handler.php?action=get_locations_for_course&course_id=${courseId}`) .then(response => response.json()) .then(data => { locationSelect.innerHTML = ''; if (data.status === 'success' && data.locations && data.locations.length > 0) { data.locations.forEach(loc => { const option = new Option(`${loc.name} (Radius: ${loc.radius}m)`, loc.id); locationSelect.add(option); }); locationSelect.disabled = false; } else { locationSelect.innerHTML = ''; } }); }); locationSelect.addEventListener('change', () => { generateBtn.disabled = !locationSelect.value; }); generateBtn.addEventListener('click', () => { const courseId = courseSelect.value; const locationId = locationSelect.value; if (!courseId || !locationId) return; const qrData = JSON.stringify({ courseId, locationId }); const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(qrData)}`; loadingSpinner.classList.remove('d-none'); qrCodeContainer.classList.add('d-none'); qrCodeImg.onload = () => { loadingSpinner.classList.add('d-none'); qrCodeContainer.classList.remove('d-none'); qrCodeCaption.textContent = `QR Code for ${courseSelect.options[courseSelect.selectedIndex].text}`; }; qrCodeImg.src = qrApiUrl; }); } });