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 = '