/** * QR Code Destination Page JavaScript * Handles staff check-in functionality and form interactions */ // Global variables let isSubmitting = false; let currentTime = new Date(); // Initialize page when DOM is loaded document.addEventListener('DOMContentLoaded', function() { console.log('QR Destination page initialized'); initializePage(); setupEventListeners(); startTimeUpdater(); }); function initializePage() { // Focus on employee ID input const employeeInput = document.getElementById('employee_id'); if (employeeInput) { employeeInput.focus(); } // Initialize current time display updateCurrentTime(); // Add page load animation document.body.classList.add('page-loaded'); } function setupEventListeners() { const form = document.getElementById('checkinForm'); const employeeInput = document.getElementById('employee_id'); if (form) { form.addEventListener('submit', handleFormSubmit); } if (employeeInput) { // Real-time input validation employeeInput.addEventListener('input', handleInputChange); employeeInput.addEventListener('blur', validateEmployeeId); employeeInput.addEventListener('keypress', handleKeyPress); // Auto-uppercase input employeeInput.addEventListener('input', function() { this.value = this.value.toUpperCase(); }); } // Handle page visibility changes document.addEventListener('visibilitychange', handleVisibilityChange); } function handleFormSubmit(e) { e.preventDefault(); if (isSubmitting) { return false; } const employeeId = document.getElementById('employee_id').value.trim(); if (!validateEmployeeId()) { return false; } submitCheckin(employeeId); } function handleInputChange(e) { const input = e.target; const value = input.value.trim(); // Clear previous validation states input.classList.remove('error', 'success'); hideStatusMessage(); // Real-time validation feedback if (value.length >= 3) { if (isValidEmployeeId(value)) { input.classList.add('success'); } else { input.classList.add('error'); } } } function handleKeyPress(e) { // Allow only alphanumeric characters const char = String.fromCharCode(e.which); if (!/[A-Za-z0-9]/.test(char)) { e.preventDefault(); shakeInput(e.target); } // Submit on Enter key if (e.key === 'Enter') { e.preventDefault(); handleFormSubmit(e); } } function validateEmployeeId() { const employeeInput = document.getElementById('employee_id'); const employeeId = employeeInput.value.trim(); if (!employeeId) { showValidationError(employeeInput, 'Employee ID is required'); return false; } if (employeeId.length < 3) { showValidationError(employeeInput, 'Employee ID must be at least 3 characters'); return false; } if (employeeId.length > 20) { showValidationError(employeeInput, 'Employee ID must be less than 20 characters'); return false; } if (!isValidEmployeeId(employeeId)) { showValidationError(employeeInput, 'Employee ID can only contain letters and numbers'); return false; } // Clear validation error employeeInput.classList.remove('error'); employeeInput.classList.add('success'); hideStatusMessage(); return true; } function isValidEmployeeId(id) { return /^[A-Za-z0-9]{3,20}$/.test(id); } function showValidationError(input, message) { input.classList.add('error'); input.classList.remove('success'); showStatusMessage(message, 'error'); shakeInput(input); input.focus(); } function shakeInput(input) { input.classList.add('shake'); setTimeout(() => { input.classList.remove('shake'); }, 500); } function submitCheckin(employeeId) { if (isSubmitting) return; isSubmitting = true; showLoadingState(); showLoadingOverlay(); // Prepare form data const formData = new FormData(); formData.append('employee_id', employeeId); // Submit to server fetch(`/qr/${window.qrUrl}/checkin`, { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { handleCheckinResponse(data); }) .catch(error => { console.error('Check-in error:', error); handleCheckinError('Network error. Please check your connection and try again.'); }) .finally(() => { isSubmitting = false; hideLoadingState(); hideLoadingOverlay(); }); } function handleCheckinResponse(data) { if (data.success) { showSuccessCard(data.data); logSuccessfulCheckin(data.data); // Optional: Analytics tracking if (typeof gtag !== 'undefined') { gtag('event', 'checkin_success', { 'location': window.locationName, 'event_name': window.eventName }); } } else { handleCheckinError(data.message); } } function handleCheckinError(message) { showStatusMessage(message, 'error'); // Shake the form to draw attention const form = document.getElementById('checkinForm'); if (form) { form.classList.add('shake'); setTimeout(() => { form.classList.remove('shake'); }, 500); } // Re-focus on input const employeeInput = document.getElementById('employee_id'); if (employeeInput) { employeeInput.focus(); employeeInput.select(); } } function showSuccessCard(data) { // Hide the check-in form const checkinCard = document.querySelector('.checkin-card'); if (checkinCard) { checkinCard.style.display = 'none'; } // Populate and show success card const successCard = document.getElementById('successCard'); if (successCard) { document.getElementById('successEmployeeId').textContent = data.employee_id || '-'; document.getElementById('successLocation').textContent = data.location || '-'; document.getElementById('successEvent').textContent = data.event || '-'; document.getElementById('successTime').textContent = data.time || '-'; document.getElementById('successDate').textContent = data.date || '-'; successCard.style.display = 'block'; successCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // Optional: Auto-hide success card after some time setTimeout(() => { showAutoHideOption(); }, 10000); // 10 seconds } function showAutoHideOption() { const successCard = document.getElementById('successCard'); if (successCard && successCard.style.display !== 'none') { const actions = successCard.querySelector('.success-actions'); if (actions && !actions.querySelector('.auto-hide-btn')) { const autoHideBtn = document.createElement('button'); autoHideBtn.className = 'btn btn-outline auto-hide-btn'; autoHideBtn.innerHTML = ' Auto-hide in 30s'; actions.appendChild(autoHideBtn); startCountdown(30, () => { checkInAnother(); }); } } } function startCountdown(seconds, callback) { const countdownElement = document.getElementById('countdown'); let remaining = seconds; const interval = setInterval(() => { remaining--; if (countdownElement) { countdownElement.textContent = remaining; } if (remaining <= 0) { clearInterval(interval); callback(); } }, 1000); } function checkInAnother() { // Show the check-in form again const checkinCard = document.querySelector('.checkin-card'); const successCard = document.getElementById('successCard'); if (checkinCard) { checkinCard.style.display = 'block'; } if (successCard) { successCard.style.display = 'none'; } // Reset form const form = document.getElementById('checkinForm'); if (form) { form.reset(); } // Clear validation states const employeeInput = document.getElementById('employee_id'); if (employeeInput) { employeeInput.classList.remove('error', 'success'); employeeInput.focus(); } hideStatusMessage(); // Scroll back to form checkinCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function showLoadingState() { const btn = document.querySelector('.btn-primary'); if (btn) { const content = btn.querySelector('.btn-content'); const loader = btn.querySelector('.btn-loader'); if (content) content.style.display = 'none'; if (loader) loader.style.display = 'flex'; btn.disabled = true; } } function hideLoadingState() { const btn = document.querySelector('.btn-primary'); if (btn) { const content = btn.querySelector('.btn-content'); const loader = btn.querySelector('.btn-loader'); if (content) content.style.display = 'flex'; if (loader) loader.style.display = 'none'; btn.disabled = false; } } function showLoadingOverlay() { const overlay = document.getElementById('loadingOverlay'); if (overlay) { overlay.style.display = 'flex'; setTimeout(() => { overlay.classList.add('show'); }, 10); } } function hideLoadingOverlay() { const overlay = document.getElementById('loadingOverlay'); if (overlay) { overlay.classList.remove('show'); setTimeout(() => { overlay.style.display = 'none'; }, 200); } } function showStatusMessage(message, type = 'info') { const statusDiv = document.getElementById('statusMessage'); if (statusDiv) { statusDiv.textContent = message; statusDiv.className = `status-message ${type}`; statusDiv.style.display = 'block'; // Auto-hide success messages if (type === 'success') { setTimeout(() => { hideStatusMessage(); }, 5000); } // Scroll to message statusDiv.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } function hideStatusMessage() { const statusDiv = document.getElementById('statusMessage'); if (statusDiv) { statusDiv.style.display = 'none'; } } function updateCurrentTime() { const timeElement = document.getElementById('currentTime'); if (timeElement) { const now = new Date(); const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }; timeElement.textContent = now.toLocaleDateString('en-US', options); } } function startTimeUpdater() { updateCurrentTime(); setInterval(updateCurrentTime, 1000); } function handleVisibilityChange() { if (document.hidden) { // Page is hidden - pause operations console.log('Page hidden - pausing operations'); } else { // Page is visible - resume operations console.log('Page visible - resuming operations'); updateCurrentTime(); // Re-focus on input if form is visible const checkinCard = document.querySelector('.checkin-card'); const employeeInput = document.getElementById('employee_id'); if (checkinCard && checkinCard.style.display !== 'none' && employeeInput) { setTimeout(() => { employeeInput.focus(); }, 100); } } } function logSuccessfulCheckin(data) { console.log('Successful check-in:', { employee_id: data.employee_id, location: data.location, event: data.event, time: data.time, date: data.date }); } // Utility functions function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } } // Export functions for global access window.checkInAnother = checkInAnother; window.validateEmployeeId = validateEmployeeId; // Service Worker registration for offline support (optional) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js') .then(function(registration) { console.log('ServiceWorker registration successful'); }) .catch(function(err) { console.log('ServiceWorker registration failed: ', err); }); }); } // Error handling for unhandled promises window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled promise rejection:', event.reason); handleCheckinError('An unexpected error occurred. Please try again.'); event.preventDefault(); }); // Handle online/offline status window.addEventListener('online', function() { showStatusMessage('Connection restored', 'success'); }); window.addEventListener('offline', function() { showStatusMessage('No internet connection. Please check your network.', 'warning'); }); // Performance monitoring if ('performance' in window) { window.addEventListener('load', function() { setTimeout(function() { const perfData = performance.getEntriesByType('navigation')[0]; console.log('Page load time:', perfData.loadEventEnd - perfData.loadEventStart, 'ms'); }, 0); }); } // Accessibility enhancements document.addEventListener('keydown', function(e) { // Escape key to reset form if (e.key === 'Escape') { const successCard = document.getElementById('successCard'); if (successCard && successCard.style.display !== 'none') { checkInAnother(); } else { // Reset form const form = document.getElementById('checkinForm'); if (form) { form.reset(); hideStatusMessage(); const employeeInput = document.getElementById('employee_id'); if (employeeInput) { employeeInput.classList.remove('error', 'success'); employeeInput.focus(); } } } } // Ctrl+R to refresh (prevent default and reload page cleanly) if ((e.ctrlKey || e.metaKey) && e.key === 'r') { e.preventDefault(); window.location.reload(); } }); // Touch device optimizations if ('ontouchstart' in window) { // Add touch-friendly classes document.body.classList.add('touch-device'); // Prevent zoom on input focus for iOS const inputs = document.querySelectorAll('input[type="text"]'); inputs.forEach(input => { input.addEventListener('focus', function() { const viewport = document.querySelector('meta[name="viewport"]'); if (viewport) { viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); } }); input.addEventListener('blur', function() { const viewport = document.querySelector('meta[name="viewport"]'); if (viewport) { viewport.setAttribute('content', 'width=device-width, initial-scale=1.0'); } }); }); } // Auto-refresh page if idle for too long (optional) let idleTimer; const IDLE_TIME = 30 * 60 * 1000; // 30 minutes function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (confirm('This page has been idle for 30 minutes. Would you like to refresh it?')) { window.location.reload(); } else { resetIdleTimer(); // Reset timer if user chooses not to refresh } }, IDLE_TIME); } // Track user activity ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'].forEach(event => { document.addEventListener(event, resetIdleTimer, true); }); // Initialize idle timer resetIdleTimer();