35132-vm/assets/js/main.js
Flatlogic Bot fd73082fc6 v3
2025-10-23 09:12:51 +00:00

403 lines
18 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
// --- Utility Functions ---
function escapeHTML(str) {
if (str === null || str === undefined) return '';
return str.toString().replace(/[&<>()"']/g, match => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'' : '&#39;'
}[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 = '<span class="spinner-border spinner-border-sm"></span> 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 = '<tr><td colspan="4" class="text-center">Loading history...</td></tr>';
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 = `
<td>${escapeHTML(record.course_name)}</td>
<td>${new Date(record.check_in_time).toLocaleString()}</td>
<td class="fw-bold ${statusClass}">${escapeHTML(record.status.replace('_', ' '))}</td>
<td>
${record.selfie_path ? `<a href="${escapeHTML(record.selfie_path)}" target="_blank" class="btn btn-sm btn-outline-secondary">View</a>` : 'N/A'}
</td>
`;
historyBody.appendChild(row);
});
} else {
historyBody.innerHTML = '<tr><td colspan="4" class="text-center">No attendance records found.</td></tr>';
}
} else {
throw new Error(data.message || 'Failed to load history.');
}
})
.catch(error => {
console.error('Error fetching attendance history:', error);
historyBody.innerHTML = `<tr><td colspan="4" class="text-center text-danger">Error: ${error.message}</td></tr>`;
});
}
// --- 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 = '<tr><td colspan="5" class="text-center">Please select a course to view records.</td></tr>';
return;
}
attendanceBody.innerHTML = '<tr><td colspan="5" class="text-center">Loading records...</td></tr>';
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 = `
<td>${escapeHTML(record.student_id)}</td>
<td>${new Date(record.check_in_time).toLocaleString()}</td>
<td class="fw-bold ${statusClass}">${escapeHTML(record.status.replace('_', ' '))}</td>
<td>${record.selfie_path ? `<a href="${escapeHTML(record.selfie_path)}" target="_blank" class="btn btn-sm btn-outline-secondary">View</a>` : 'N/A'}</td>
<td>${record.latitude ? `${parseFloat(record.latitude).toFixed(5)}, ${parseFloat(record.longitude).toFixed(5)}` : 'N/A'}</td>
`;
attendanceBody.appendChild(row);
});
} else {
attendanceBody.innerHTML = '<tr><td colspan="5" class="text-center">No records found.</td></tr>';
}
} else {
throw new Error(data.message || 'Failed to load records.');
}
})
.catch(error => {
attendanceBody.innerHTML = `<tr><td colspan="5" class="text-center text-danger">Error: ${error.message}</td></tr>`;
});
});
}
// --- 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 = '<option value="">-- Loading... --</option>';
locationSelect.disabled = true;
generateBtn.disabled = true;
qrCodeContainer.classList.add('d-none');
if (!courseId) {
locationSelect.innerHTML = '<option value="">-- Select course --</option>';
return;
}
fetch(`api/attendance_handler.php?action=get_locations_for_course&course_id=${courseId}`)
.then(response => response.json())
.then(data => {
locationSelect.innerHTML = '<option value="">-- Select location --</option>';
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 = '<option value="">-- No locations --</option>';
}
});
});
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;
});
}
});