Files
GOV_QR_Codes_Management/static/js/qr_destination.js
T
2025-08-02 08:55:07 -04:00

901 lines
25 KiB
JavaScript

/**
* QR Code Destination Page JavaScript - Complete with Location Tracking
* Handles staff check-in functionality with GPS location support
*/
// Global variables
let isSubmitting = false;
let currentTime = new Date();
// Location tracking variables
let userLocation = {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
timestamp: null,
source: "manual",
address: null,
};
let locationRequestActive = false;
let locationWatchId = null;
// Initialize page when DOM is loaded
document.addEventListener("DOMContentLoaded", function () {
console.log(
"🚀 QR Destination page initialized with enhanced location tracking"
);
// Initialize the page
initializePage();
setupEventListeners();
startTimeUpdater();
// Initialize geolocation
initializeGeolocation();
// Add hidden form fields for location data
ensureLocationFormFields();
});
// Initialize basic page functionality
function initializePage() {
console.log("🚀 Initializing page...");
// Focus on employee ID input
const employeeInput = document.getElementById("employee_id");
if (employeeInput) {
employeeInput.focus();
}
// Add page load animation
document.body.classList.add("page-loaded");
}
// Set up event listeners
function setupEventListeners() {
console.log("🎧 Setting up event listeners...");
const form = document.getElementById("checkinForm");
const employeeInput = document.getElementById("employee_id");
if (form) {
form.addEventListener("submit", handleFormSubmit);
}
if (employeeInput) {
employeeInput.addEventListener("input", handleInputChange);
employeeInput.addEventListener("keypress", handleKeyPress);
}
}
// Start time updater
function startTimeUpdater() {
console.log("⏰ Starting time updater...");
// Update current time display
setInterval(() => {
const timeElement = document.getElementById("currentTime");
if (timeElement) {
timeElement.textContent = new Date().toLocaleTimeString();
}
currentTime = new Date();
}, 1000);
}
// Handle form submission
function handleFormSubmit(e) {
e.preventDefault();
if (isSubmitting) {
return false;
}
const employeeId = document.getElementById("employee_id").value.trim();
if (!validateEmployeeId(employeeId)) {
return false;
}
// Ensure location data is up to date before submission
updateLocationFormFields();
submitCheckin(employeeId);
}
// Handle input changes
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");
}
}
}
// Handle key press
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);
}
}
// Validate employee ID
function validateEmployeeId(employeeId) {
if (!employeeId) {
showStatusMessage("Please enter your Employee ID", "error");
return false;
}
if (!employeeId.match(/^[A-Za-z0-9]{3,20}$/)) {
showStatusMessage(
"Invalid Employee ID format. Use 3-20 alphanumeric characters.",
"error"
);
return false;
}
return true;
}
// Check if employee ID is valid format
function isValidEmployeeId(employeeId) {
return /^[A-Za-z0-9]{3,20}$/.test(employeeId);
}
// Shake input on invalid character
function shakeInput(input) {
input.classList.add("shake");
setTimeout(() => {
input.classList.remove("shake");
}, 300);
}
// GEOLOCATION FUNCTIONS
// Initialize geolocation with better error handling
function initializeGeolocation() {
console.log("📍 Initializing geolocation system...");
if (!navigator.geolocation) {
console.log("⚠️ Geolocation not supported by this browser");
showLocationStatus("error", "Location services not supported");
return;
}
console.log("✅ Geolocation API available");
// Request location immediately
requestUserLocation();
// Set up continuous watching for better accuracy
if ("permissions" in navigator) {
navigator.permissions
.query({ name: "geolocation" })
.then(function (result) {
console.log("📍 Geolocation permission status:", result.state);
if (result.state === "granted") {
startLocationWatching();
}
result.onchange = function () {
console.log("📍 Geolocation permission changed to:", result.state);
if (result.state === "granted") {
requestUserLocation();
startLocationWatching();
} else {
stopLocationWatching();
}
};
});
}
}
// Request user location
function requestUserLocation() {
if (locationRequestActive) {
console.log("⏭️ Location request already active");
return;
}
locationRequestActive = true;
showLocationStatus("loading", "Getting your location...");
const options = {
enableHighAccuracy: true, // Use GPS for better accuracy
timeout: 15000, // Wait up to 15 seconds
maximumAge: 300000, // Accept cached location up to 5 minutes old
};
console.log("📡 Requesting location with options:", options);
navigator.geolocation.getCurrentPosition(
handleLocationSuccess,
handleLocationError,
options
);
// Set backup timeout
setTimeout(() => {
if (locationRequestActive && !userLocation.latitude) {
console.log("⏰ Location request backup timeout");
handleLocationError({ code: 3, message: "Request timed out" });
}
}, 16000);
}
// Handle successful location retrieval
function handleLocationSuccess(position) {
locationRequestActive = false;
const coords = position.coords;
console.log("✅ Location obtained:", {
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
altitude: coords.altitude,
timestamp: position.timestamp,
});
// Validate coordinates
if (!coords.latitude || !coords.longitude) {
console.log("⚠️ Invalid coordinates received");
handleLocationError({ code: 2, message: "Invalid coordinates" });
return;
}
// Store location data (keep as numbers for calculations)
userLocation = {
latitude: Number(coords.latitude),
longitude: Number(coords.longitude),
accuracy: coords.accuracy ? Math.round(coords.accuracy) : null,
altitude: coords.altitude ? Math.round(coords.altitude) : null,
timestamp: position.timestamp,
source: "gps",
address: null,
};
console.log("💾 Stored location data:", userLocation);
// Update form fields immediately
updateLocationFormFields();
// Update display
updateLocationDisplay();
// Show success status
const accuracyText = coords.accuracy
? ${Math.round(coords.accuracy)}m`
: "unknown";
showLocationStatus("success", `Location captured (${accuracyText} accuracy)`);
// Try to get address
reverseGeocodeLocation(coords.latitude, coords.longitude);
}
// Handle location errors
function handleLocationError(error) {
locationRequestActive = false;
let message = "Unable to get location";
console.log("❌ Location error:", error);
switch (error.code) {
case error.PERMISSION_DENIED:
message = "Location access denied - please enable in browser settings";
break;
case error.POSITION_UNAVAILABLE:
message = "Location unavailable - GPS signal weak";
break;
case error.TIMEOUT:
message = "Location request timed out";
break;
default:
message = "Location error occurred";
}
showLocationStatus(
"error",
`${message} - check-in will continue without location`
);
userLocation.source = "manual";
updateLocationFormFields();
}
// Update form fields with location data
function updateLocationFormFields() {
const fields = {
latitude: userLocation.latitude ? userLocation.latitude.toFixed(6) : "",
longitude: userLocation.longitude ? userLocation.longitude.toFixed(6) : "",
accuracy: userLocation.accuracy || "",
altitude: userLocation.altitude || "",
location_source: userLocation.source || "manual",
address: userLocation.address || "",
};
// Update hidden form fields
Object.keys(fields).forEach((fieldId) => {
let field = document.getElementById(fieldId);
if (!field) {
// Create hidden input if it doesn't exist
field = document.createElement("input");
field.type = "hidden";
field.id = fieldId;
field.name = fieldId;
document.getElementById("checkinForm").appendChild(field);
}
field.value = fields[fieldId];
});
console.log("📝 Updated form fields with location data:", fields);
}
// Update location display
function updateLocationDisplay() {
if (userLocation.latitude && userLocation.longitude) {
const elements = {
displayLatitude: userLocation.latitude.toFixed(6),
displayLongitude: userLocation.longitude.toFixed(6),
displayAccuracy: userLocation.accuracy
? ${Math.round(userLocation.accuracy)}m`
: "Unknown",
displayAddress: userLocation.address || "Loading...",
};
Object.keys(elements).forEach((elementId) => {
const element = document.getElementById(elementId);
if (element) {
element.textContent = elements[elementId];
}
});
console.log("🖥️ Updated location display");
}
}
// Start continuous location watching
function startLocationWatching() {
if (!navigator.geolocation || locationWatchId !== null) {
return;
}
const watchOptions = {
enableHighAccuracy: true,
timeout: 30000,
maximumAge: 600000, // 10 minutes
};
locationWatchId = navigator.geolocation.watchPosition(
handleLocationSuccess,
(error) => {
console.log("⚠️ Location watch error:", error);
// Don't show error for watch failures, just log them
},
watchOptions
);
console.log("👁️ Started location watching");
}
// Stop location watching
function stopLocationWatching() {
if (locationWatchId !== null) {
navigator.geolocation.clearWatch(locationWatchId);
locationWatchId = null;
console.log("⏹️ Stopped location watching");
}
}
// Reverse geocode coordinates to get address
function reverseGeocodeLocation(lat, lng) {
console.log("🏠 Getting address from coordinates...");
// Use a free geocoding service
const geocodeUrl = `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lng}&localityLanguage=en`;
fetch(geocodeUrl)
.then((response) => response.json())
.then((data) => {
if (data && (data.locality || data.city || data.principalSubdivision)) {
const address = [
data.locality || data.city,
data.principalSubdivision,
data.countryName,
]
.filter(Boolean)
.join(", ");
userLocation.address = address;
updateLocationFormFields();
updateLocationDisplay();
console.log("🏠 Address found:", address);
} else {
console.log("🏠 No address found");
userLocation.address = "Address not available";
updateLocationFormFields();
updateLocationDisplay();
}
})
.catch((error) => {
console.log("⚠️ Geocoding error:", error);
userLocation.address = "Address lookup failed";
updateLocationFormFields();
updateLocationDisplay();
});
}
// Ensure location form fields exist
function ensureLocationFormFields() {
const form = document.getElementById("checkinForm");
if (!form) {
console.log("⚠️ Check-in form not found");
return;
}
const locationFields = [
"latitude",
"longitude",
"accuracy",
"altitude",
"location_source",
"address",
];
locationFields.forEach((fieldName) => {
if (!document.getElementById(fieldName)) {
const input = document.createElement("input");
input.type = "hidden";
input.id = fieldName;
input.name = fieldName;
input.value = "";
form.appendChild(input);
console.log(`✅ Created hidden field: ${fieldName}`);
}
});
}
// FORM SUBMISSION
// Submit check-in with location data
function submitCheckin(employeeId) {
if (isSubmitting) {
console.log("⏭️ Already submitting, ignoring duplicate request");
return false;
}
isSubmitting = true;
updateSubmitButton(true);
hideStatusMessage();
console.log("📤 Starting check-in submission for:", employeeId);
console.log("📍 Current location data:", userLocation);
// Ensure location data is in the form
updateLocationFormFields();
// Prepare form data
const formData = new FormData();
formData.append("employee_id", employeeId);
// Add location data
formData.append(
"latitude",
userLocation.latitude ? userLocation.latitude.toFixed(6) : ""
);
formData.append(
"longitude",
userLocation.longitude ? userLocation.longitude.toFixed(6) : ""
);
formData.append("accuracy", userLocation.accuracy || "");
formData.append("altitude", userLocation.altitude || "");
formData.append("location_source", userLocation.source || "manual");
formData.append("address", userLocation.address || "");
// Debug: Log exactly what we're sending
console.log("📤 Form data being submitted:");
for (let [key, value] of formData.entries()) {
console.log(` ${key}: "${value}"`);
}
// Get the current URL for the check-in endpoint
const currentUrl = window.location.pathname;
const checkinUrl = `${currentUrl}/checkin`;
console.log("🎯 Submitting to URL:", checkinUrl);
fetch(checkinUrl, {
method: "POST",
body: formData,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
})
.then((response) => {
console.log("📡 Server response status:", response.status);
console.log("📡 Server response headers:", response.headers);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
isSubmitting = false;
updateSubmitButton(false);
console.log(
"📥 Complete server response:",
JSON.stringify(data, null, 2)
);
if (data.success) {
showSuccessPage(data);
console.log(
"✅ Check-in successful with location:",
data.data?.has_location || false
);
// Stop location watching after successful check-in
stopLocationWatching();
} else {
const errorMessage = data.message || data.error || "Check-in failed";
showStatusMessage(errorMessage, "error");
console.log("❌ Check-in failed:", errorMessage);
console.log("❌ Full error response:", data);
}
})
.catch((error) => {
isSubmitting = false;
updateSubmitButton(false);
console.error("❌ Network/Parse error details:", error);
console.error("❌ Error name:", error.name);
console.error("❌ Error message:", error.message);
console.error("❌ Error stack:", error.stack);
let errorMessage =
"Network error. Please check your connection and try again.";
if (error.message.includes("JSON")) {
errorMessage = "Server response error. Please try again.";
} else if (error.message.includes("HTTP error")) {
errorMessage =
"Server error. Please contact support if this continues.";
}
showStatusMessage(errorMessage, "error");
});
}
// UTILITY FUNCTIONS
// Update submit button state
function updateSubmitButton(isLoading) {
const submitBtn = document.querySelector(
'#checkinForm button[type="submit"]'
);
if (submitBtn) {
if (isLoading) {
submitBtn.disabled = true;
submitBtn.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Processing...';
} else {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> Check In';
}
}
}
// Show status messages
function showStatusMessage(message, type = "info") {
console.log(`Status: ${type} - ${message}`);
// Try to find existing status display element
let statusEl = document.getElementById("statusMessage");
if (!statusEl) {
statusEl = document.createElement("div");
statusEl.id = "statusMessage";
statusEl.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
border-radius: 8px;
z-index: 1000;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
document.body.appendChild(statusEl);
}
statusEl.textContent = message;
statusEl.className = `status-message ${type}`;
// Style based on type
if (type === "error") {
statusEl.style.backgroundColor = "#fee2e2";
statusEl.style.color = "#dc2626";
statusEl.style.border = "1px solid #fecaca";
} else if (type === "success") {
statusEl.style.backgroundColor = "#dcfce7";
statusEl.style.color = "#16a34a";
statusEl.style.border = "1px solid #bbf7d0";
} else {
statusEl.style.backgroundColor = "#dbeafe";
statusEl.style.color = "#2563eb";
statusEl.style.border = "1px solid #bfdbfe";
}
statusEl.style.display = "block";
// Auto-hide after 5 seconds
setTimeout(() => {
statusEl.style.display = "none";
}, 5000);
}
// Hide status messages
function hideStatusMessage() {
const statusEl = document.getElementById("statusMessage");
if (statusEl) {
statusEl.style.display = "none";
}
}
// Enhanced location status display
function showLocationStatus(type, message) {
const statusElement = document.getElementById("locationStatus");
const messageElement = document.getElementById("locationMessage");
if (statusElement && messageElement) {
statusElement.className = `location-status ${type}`;
messageElement.textContent = message;
// Auto-hide success messages after 3 seconds
if (type === "success") {
setTimeout(() => {
statusElement.style.display = "none";
}, 3000);
} else {
statusElement.style.display = "block";
}
}
console.log(`📍 Location status: ${type} - ${message}`);
}
// Show success page with safe data handling
function showSuccessPage(data) {
console.log("🎉 Showing success page with data:", data);
// Hide the form
const form = document.getElementById("checkinForm");
if (form) {
form.style.display = "none";
}
// Safely extract data with fallbacks
const responseData = data.data || data || {};
const employeeId = responseData.employee_id || "Unknown";
const location = responseData.location || "Unknown Location";
const event = responseData.event || responseData.location_event || "Check-in";
const checkInTime =
responseData.check_in_time || new Date().toLocaleTimeString();
const checkInDate =
responseData.check_in_date || new Date().toLocaleDateString();
const hasLocation = responseData.has_location || false;
const locationInfo = responseData.location_info || null;
console.log("📊 Processed success data:", {
employeeId,
location,
event,
checkInTime,
checkInDate,
hasLocation,
locationInfo,
});
// Show success message
showStatusMessage(`Check-in successful for ${employeeId}!`, "success");
// Update success card if it exists
const successCard = document.getElementById("successCard");
if (successCard) {
successCard.style.display = "block";
// Update success details safely
const updateElement = (id, value) => {
const el = document.getElementById(id);
if (el) {
el.textContent = value || "N/A";
console.log(`✅ Updated ${id}: ${value}`);
} else {
console.log(`⚠️ Element not found: ${id}`);
}
};
updateElement("successEmployeeId", employeeId);
updateElement("successLocation", location);
updateElement("successEvent", event);
updateElement("successTime", checkInTime);
updateElement("successDate", checkInDate);
// Show location info if available
if (hasLocation && locationInfo) {
const locationInfoEl = document.getElementById("successLocationInfo");
const gpsInfo = document.getElementById("successGpsInfo");
if (locationInfoEl && gpsInfo) {
const coordinates = locationInfo.coordinates || "Unknown coordinates";
const accuracy = locationInfo.accuracy || "Unknown accuracy";
gpsInfo.textContent = `${coordinates} (${accuracy})`;
locationInfoEl.style.display = "block";
console.log("✅ Updated GPS info display");
}
} else {
console.log("📍 No location data to display");
}
} else {
console.log("⚠️ Success card element not found, using fallback");
// Create a simple success display
const successMessage = document.createElement("div");
successMessage.innerHTML = `
<div style="
background: #dcfce7;
border: 1px solid #bbf7d0;
color: #16a34a;
padding: 20px;
border-radius: 8px;
margin: 20px;
text-align: center;
">
<h3>✅ Check-in Successful!</h3>
<p><strong>Employee:</strong> ${employeeId}</p>
<p><strong>Location:</strong> ${location}</p>
<p><strong>Time:</strong> ${checkInTime}</p>
${hasLocation ? "<p>📍 Location data captured</p>" : ""}
<button onclick="checkInAnother()" style="
background: #16a34a;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
">Check In Another Employee</button>
</div>
`;
// Insert after the form
if (form && form.parentNode) {
form.parentNode.insertBefore(successMessage, form.nextSibling);
} else {
document.body.appendChild(successMessage);
}
// Auto-reload after 10 seconds as fallback
setTimeout(() => {
location.reload();
}, 10000);
}
}
// INTERACTIVE FUNCTIONS (called from HTML)
// Retry location request
function retryLocationRequest() {
console.log("🔄 Retrying location request...");
// Stop any existing watch
stopLocationWatching();
// Reset location data
userLocation = {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
timestamp: null,
source: "manual",
address: null,
};
// Clear form fields
updateLocationFormFields();
// Request location again
requestUserLocation();
}
// Toggle location info display
function toggleLocationInfo() {
const locationInfo = document.getElementById("locationInfo");
if (!locationInfo) return;
if (locationInfo.style.display === "none" || !locationInfo.style.display) {
updateLocationDisplay();
locationInfo.style.display = "block";
} else {
locationInfo.style.display = "none";
}
}
// Check in another employee (reset form)
function checkInAnother() {
// Reset form
const form = document.getElementById("checkinForm");
const successCard = document.getElementById("successCard");
if (form) {
form.style.display = "block";
form.reset();
}
if (successCard) {
successCard.style.display = "none";
}
// Reset location data and restart tracking
userLocation = {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
timestamp: null,
source: "manual",
address: null,
};
// Restart location tracking
initializeGeolocation();
// Focus on employee input
const employeeInput = document.getElementById("employee_id");
if (employeeInput) {
employeeInput.focus();
}
hideStatusMessage();
}
// Get current location data (for external use)
function getCurrentLocationData() {
return {
hasLocation: !!(userLocation.latitude && userLocation.longitude),
latitude: userLocation.latitude,
longitude: userLocation.longitude,
accuracy: userLocation.accuracy,
altitude: userLocation.altitude,
source: userLocation.source,
timestamp: userLocation.timestamp,
address: userLocation.address,
};
}
console.log(
"📍 QR Destination JavaScript loaded successfully with location tracking!"
);