Файловый менеджер - Редактировать - /home/gqdcvggs/imators.systems/traffic/app.js
Назад
document.addEventListener('DOMContentLoaded', () => { const app = { map: null, mapAccessToken: null, markers: {}, userLocation: null, userMarker: null, tempMarker: null, directionsClient: null, directionsControl: null, selectedLightId: null, pendingLights: [], navigation: { active: false, isNavigating: false, route: null, mode: 'driving', lightsOnRoute: [], updateInterval: null }, measure: { active: false, mode: null, timer: 0, interval: null, lightId: null }, menuOpen: false, settings: { darkMode: false, notifications: true, autoRefresh: true, autoMeasure: true, mapStyle: 'navigation-day-v1', defaultTransport: 'driving' }, user: { isLoggedIn: false, id: null, firstName: '', lastName: '', email: '', vehicle: 'car' }, timers: { updateLights: null, notification: null } }; initApp(); function initApp() { loadSettings(); loadUserData(); loadMapboxKey(); addEventListeners(); } function loadSettings() { const savedSettings = localStorage.getItem('trafficSettings'); if (savedSettings) { try { const parsed = JSON.parse(savedSettings); Object.assign(app.settings, parsed); } catch (e) {} } applySettings(); } function applySettings() { const darkModeToggle = document.getElementById('darkModeToggle'); const notificationsToggle = document.getElementById('notificationsToggle'); const autoRefreshToggle = document.getElementById('autoRefreshToggle'); const autoMeasureToggle = document.getElementById('autoMeasureToggle'); const mapStyleSelect = document.getElementById('mapStyle'); const transportSelect = document.getElementById('defaultTransport'); if (darkModeToggle) darkModeToggle.checked = app.settings.darkMode; if (notificationsToggle) notificationsToggle.checked = app.settings.notifications; if (autoRefreshToggle) autoRefreshToggle.checked = app.settings.autoRefresh; if (autoMeasureToggle) autoMeasureToggle.checked = app.settings.autoMeasure; if (mapStyleSelect) mapStyleSelect.value = app.settings.mapStyle; if (transportSelect) transportSelect.value = app.settings.defaultTransport; if (app.settings.darkMode) { document.documentElement.classList.add('dark'); document.body.classList.add('dark-mode'); } } function saveSettings() { const darkModeToggle = document.getElementById('darkModeToggle'); const notificationsToggle = document.getElementById('notificationsToggle'); const autoRefreshToggle = document.getElementById('autoRefreshToggle'); const autoMeasureToggle = document.getElementById('autoMeasureToggle'); const mapStyleSelect = document.getElementById('mapStyle'); const transportSelect = document.getElementById('defaultTransport'); app.settings.darkMode = darkModeToggle ? darkModeToggle.checked : app.settings.darkMode; app.settings.notifications = notificationsToggle ? notificationsToggle.checked : app.settings.notifications; app.settings.autoRefresh = autoRefreshToggle ? autoRefreshToggle.checked : app.settings.autoRefresh; app.settings.autoMeasure = autoMeasureToggle ? autoMeasureToggle.checked : app.settings.autoMeasure; app.settings.mapStyle = mapStyleSelect ? mapStyleSelect.value : app.settings.mapStyle; app.settings.defaultTransport = transportSelect ? transportSelect.value : app.settings.defaultTransport; localStorage.setItem('trafficSettings', JSON.stringify(app.settings)); } function loadUserData() { const userId = localStorage.getItem('userId') || sessionStorage.getItem('userId'); if (userId) { app.user.id = userId; app.user.isLoggedIn = true; fetchUserData(userId).then(userData => { if (userData) { app.user.firstName = userData.first_name; app.user.lastName = userData.last_name; app.user.email = userData.email; app.user.vehicle = userData.preferred_vehicle || 'car'; updateUserDisplay(); } }); } } async function fetchUserData(userId) { try { const response = await fetch(`api/user.php?id=${userId}`); const data = await response.json(); if (data.success) { return data.user; } return null; } catch (error) { showNotification('Error', 'Failed to load user data', 'error'); return null; } } function updateUserDisplay() { const accountName = document.getElementById('accountName'); const accountEmail = document.getElementById('accountEmail'); const loginButtons = document.getElementById('loginButtons'); const logoutButton = document.getElementById('logoutButton'); if (app.user.isLoggedIn) { if (accountName) accountName.textContent = `${app.user.firstName} ${app.user.lastName}`; if (accountEmail) accountEmail.textContent = app.user.email; if (loginButtons) loginButtons.classList.add('hidden'); if (logoutButton) logoutButton.classList.remove('hidden'); } else { if (accountName) accountName.textContent = 'Guest User'; if (accountEmail) accountEmail.textContent = 'Not signed in'; if (loginButtons) loginButtons.classList.remove('hidden'); if (logoutButton) logoutButton.classList.add('hidden'); } } function loadMapboxKey() { fetch('get-mapbox-key.php') .then(response => response.json()) .then(data => { if (data.success) { app.mapAccessToken = data.key; initMap(); } else { showNotification('Error', 'Could not load map service', 'error'); } }) .catch(error => { showNotification('Error', 'Network error loading map service', 'error'); }); } function initMap() { if (!app.mapAccessToken) return; mapboxgl.accessToken = app.mapAccessToken; app.map = new mapboxgl.Map({ container: 'map', style: `mapbox://styles/mapbox/${app.settings.mapStyle}`, center: [0, 0], zoom: 2, attributionControl: false, pitchWithRotate: false, dragRotate: false, failIfMajorPerformanceCaveat: true, preserveDrawingBuffer: false, refreshExpiredTiles: false, renderWorldCopies: true, transformRequest: (url, resourceType) => { if (resourceType === 'Tile' || resourceType === 'Style') { return { url: url, headers: { 'Cache-Control': 'max-age=3600' } }; } } }); app.map.addControl(new mapboxgl.AttributionControl(), 'bottom-left'); app.map.on('load', onMapLoaded); app.map.on('error', event => { console.error('Map error:', event.error); }); } function onMapLoaded() { setupMapControls(); setupSearchBoxes(); getLocation(); loadTrafficLights(); app.map.on('click', handleMapClick); if (!localStorage.getItem('trafficLightOnboardingCompleted') && !app.user.isLoggedIn) { const onboardingModal = document.getElementById('onboardingModal'); if (onboardingModal) onboardingModal.style.display = 'flex'; } } function setupMapControls() { app.map.addControl( new mapboxgl.NavigationControl({ showCompass: false }), 'bottom-right' ); const geolocateControl = new mapboxgl.GeolocateControl({ positionOptions: { enableHighAccuracy: true }, trackUserLocation: true, showUserHeading: true }); app.map.addControl(geolocateControl, 'bottom-right'); geolocateControl.on('geolocate', (position) => { const { longitude, latitude } = position.coords; app.userLocation = [longitude, latitude]; updateUserMarker(); }); try { app.directionsControl = new MapboxDirections({ accessToken: mapboxgl.accessToken, unit: 'metric', profile: 'mapbox/driving', alternatives: false, congestion: true, steps: true, controls: { inputs: false, instructions: false, profileSwitcher: false }, interactive: false }); app.map.addControl(app.directionsControl, 'top-left'); document.querySelector('.mapboxgl-ctrl-directions').style.display = 'none'; app.directionsControl.on('route', (e) => { if (e.route && e.route[0]) { handleRouteCalculated(e.route[0]); } }); app.directionsControl.on('error', (e) => { console.error('Directions error:', e); showNotification('Directions Error', 'Could not calculate route', 'error'); }); } catch (e) { console.error('Error setting up directions:', e); } startLightUpdates(); } function setupSearchBoxes() { try { const searchBox = new MapboxGeocoder({ accessToken: mapboxgl.accessToken, mapboxgl: mapboxgl, marker: false, placeholder: 'Search locations...', proximity: 'ip', language: 'en-GB' }); document.getElementById('searchBox').appendChild(searchBox.onAdd(app.map)); const startLocationSearch = new MapboxGeocoder({ accessToken: mapboxgl.accessToken, mapboxgl: mapboxgl, marker: false, placeholder: 'Enter starting point', proximity: 'ip', language: 'en-GB' }); const endLocationSearch = new MapboxGeocoder({ accessToken: mapboxgl.accessToken, mapboxgl: mapboxgl, marker: false, placeholder: 'Enter destination', proximity: 'ip', language: 'en-GB' }); document.getElementById('startLocationSearch').appendChild(startLocationSearch.onAdd(app.map)); document.getElementById('endLocationSearch').appendChild(endLocationSearch.onAdd(app.map)); startLocationSearch.on('result', (e) => { if (e.result && e.result.center) { app.directionsControl.setOrigin(e.result.center); } }); endLocationSearch.on('result', (e) => { if (e.result && e.result.center) { app.directionsControl.setDestination(e.result.center); } }); } catch (e) { console.error('Error setting up search boxes:', e); } } function getLocation() { if ('geolocation' in navigator) { navigator.geolocation.getCurrentPosition( position => { const { longitude, latitude } = position.coords; app.userLocation = [longitude, latitude]; updateUserMarker(); centerMapOnUser(); }, error => { console.error('Geolocation error:', error); showNotification('Location Error', 'Could not get your location', 'error'); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); navigator.geolocation.watchPosition( position => { const { longitude, latitude } = position.coords; app.userLocation = [longitude, latitude]; updateUserMarker(); if (app.navigation.isNavigating) { updateNavigation(); } }, error => { console.error('Watch position error:', error); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } else { showNotification('Location Error', 'Geolocation is not supported by your browser', 'error'); } } function updateUserMarker() { if (!app.map || !app.userLocation) return; if (!app.userMarker) { const el = document.createElement('div'); el.className = 'user-marker pulse'; app.userMarker = new mapboxgl.Marker(el) .setLngLat(app.userLocation) .addTo(app.map); } else { app.userMarker.setLngLat(app.userLocation); } } function centerMapOnUser() { if (!app.map || !app.userLocation) return; app.map.flyTo({ center: app.userLocation, zoom: 15, speed: 1.5 }); } function loadTrafficLights() { fetch('api/get-traffic-lights.php') .then(response => response.json()) .then(data => { if (Array.isArray(data)) { clearTrafficLights(); data.forEach(light => addTrafficLight(light)); } }) .catch(error => { console.error('Error loading traffic lights:', error); showNotification('Error', 'Could not load traffic lights', 'error'); }); } function clearTrafficLights() { for (const id in app.markers) { if (app.markers[id].marker) { app.markers[id].marker.remove(); } } app.markers = {}; } function addTrafficLight(light) { if (!app.map) return; const status = getLightStatus(light); const el = document.createElement('div'); el.className = `w-8 h-8 rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white bg-traffic-${status.color}`; el.innerHTML = '<i class="fas fa-traffic-light"></i>'; const marker = new mapboxgl.Marker(el) .setLngLat([parseFloat(light.longitude), parseFloat(light.latitude)]) .addTo(app.map); marker.getElement().addEventListener('click', () => { showLightDetails(light); }); app.markers[light.id] = { marker: marker, element: el, data: light }; } function getLightStatus(light) { const totalCycle = parseInt(light.red_duration) + parseInt(light.green_duration); const currentTime = Math.floor(Date.now() / 1000); const timeInCycle = currentTime % totalCycle; if (timeInCycle < light.red_duration) { return { isRed: true, color: 'red', label: 'RED', timeLeft: light.red_duration - timeInCycle, percentage: Math.round(((light.red_duration - timeInCycle) / totalCycle) * 100) }; } else { return { isRed: false, color: 'green', label: 'GREEN', timeLeft: totalCycle - timeInCycle, percentage: Math.round(((totalCycle - timeInCycle) / totalCycle) * 100) }; } } function updateLightStatus(light) { if (!app.markers[light.id]) return; const status = getLightStatus(light); const element = app.markers[light.id].element; if (element) { element.className = `w-8 h-8 rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white bg-traffic-${status.color}`; } if (app.selectedLightId === light.id) { updateInfoPanel(light, status); } if (app.measure.lightId === light.id) { updateMeasureStatus(light, status); } } function updateAllLights() { for (const id in app.markers) { updateLightStatus(app.markers[id].data); } } function startLightUpdates() { if (app.timers.updateLights) { clearInterval(app.timers.updateLights); } if (app.settings.autoRefresh) { app.timers.updateLights = setInterval(updateAllLights, 1000); } } function showLightDetails(light) { app.selectedLightId = light.id; const status = getLightStatus(light); const infoPanelTitle = document.getElementById('infoPanelTitle'); if (infoPanelTitle) infoPanelTitle.textContent = light.name; updateInfoPanel(light, status); app.map.flyTo({ center: [parseFloat(light.longitude), parseFloat(light.latitude)], zoom: 17, duration: 800 }); const infoPanel = document.getElementById('infoPanel'); if (infoPanel) infoPanel.classList.add('active'); } function updateInfoPanel(light, status) { const lightStatusText = document.getElementById('lightStatusText'); const lightStatusTime = document.getElementById('lightStatusTime'); const lightStatusBar = document.getElementById('lightStatusBar'); if (lightStatusText) lightStatusText.textContent = status.label; if (lightStatusTime) lightStatusTime.textContent = `${status.timeLeft}s until change`; if (lightStatusBar) { lightStatusBar.className = `bg-traffic-${status.color} h-2 rounded-full`; lightStatusBar.style.width = `${status.percentage}%`; } } function updateMeasureStatus(light, status) { const measureStatus = document.getElementById('measureStatus'); if (!measureStatus) return; measureStatus.innerHTML = ` <span class="px-3 py-1.5 rounded-full text-sm font-medium bg-traffic-${status.color} text-white"> ${status.label} (${status.timeLeft}s) </span> `; } function handleMapClick(e) { if (app.tempMarker) { app.tempMarker.remove(); app.tempMarker = null; } if (app.selectedLightId) { const infoPanel = document.getElementById('infoPanel'); if (infoPanel) infoPanel.classList.remove('active'); app.selectedLightId = null; } const latitudeInput = document.getElementById('latitude'); const longitudeInput = document.getElementById('longitude'); const addLightModal = document.getElementById('addLightModal'); if (addLightModal && addLightModal.style.display === 'flex' && latitudeInput && longitudeInput) { const lng = e.lngLat.lng; const lat = e.lngLat.lat; latitudeInput.value = lat.toFixed(6); longitudeInput.value = lng.toFixed(6); const markerEl = document.createElement('div'); markerEl.className = 'w-8 h-8 bg-traffic-green rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white'; markerEl.innerHTML = '<i class="fas fa-map-pin"></i>'; app.tempMarker = new mapboxgl.Marker(markerEl, { draggable: true }) .setLngLat([lng, lat]) .addTo(app.map); app.tempMarker.on('dragend', () => { const newPos = app.tempMarker.getLngLat(); latitudeInput.value = newPos.lat.toFixed(6); longitudeInput.value = newPos.lng.toFixed(6); }); } } function handleRouteCalculated(route) { app.navigation.route = route; const routePanel = document.querySelector('.route-panel'); const searchContainer = document.querySelector('.search-container'); if (routePanel) routePanel.classList.add('active'); if (searchContainer) searchContainer.classList.add('route-active'); const estimatedDuration = document.getElementById('estimatedDuration'); const arrivalTime = document.getElementById('arrivalTime'); if (estimatedDuration) { const minutes = Math.floor(route.duration / 60); estimatedDuration.textContent = `${minutes} min`; } if (arrivalTime) { const now = new Date(); const arrival = new Date(now.getTime() + route.duration * 1000); const hours = arrival.getHours().toString().padStart(2, '0'); const mins = arrival.getMinutes().toString().padStart(2, '0'); arrivalTime.textContent = `Arrival at ${hours}:${mins}`; } findTrafficLightsOnRoute(route.geometry.coordinates); } function findTrafficLightsOnRoute(coordinates) { app.navigation.lightsOnRoute = []; for (const id in app.markers) { const light = app.markers[id].data; const lightPos = [parseFloat(light.longitude), parseFloat(light.latitude)]; for (let i = 0; i < coordinates.length; i++) { const distance = calculateDistance(coordinates[i], lightPos); if (distance < 30) { // Within 30 meters of route app.navigation.lightsOnRoute.push({ id: id, light: light, routeIndex: i, distance: distance }); break; } } } if (app.navigation.lightsOnRoute.length > 0) { showNotification('Route', `Found ${app.navigation.lightsOnRoute.length} traffic lights on your route`, 'info'); } } function calculateDistance(point1, point2) { const [lon1, lat1] = point1; const [lon2, lat2] = point2; const R = 6371e3; // Earth radius in meters const φ1 = lat1 * Math.PI / 180; const φ2 = lat2 * Math.PI / 180; const Δφ = (lat2 - lat1) * Math.PI / 180; const Δλ = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function calculatePathDistance(coordinates) { let total = 0; for (let i = 0; i < coordinates.length - 1; i++) { total += calculateDistance(coordinates[i], coordinates[i + 1]); } return total; } function updateNavigation() { if (!app.userLocation || !app.navigation.route || !app.navigation.isNavigating) return; const coordinates = app.navigation.route.geometry.coordinates; let closestPointIndex = 0; let minDistance = Infinity; for (let i = 0; i < coordinates.length; i++) { const distance = calculateDistance(app.userLocation, coordinates[i]); if (distance < minDistance) { minDistance = distance; closestPointIndex = i; } } const remainingCoords = coordinates.slice(closestPointIndex); const remainingDistance = calculatePathDistance(remainingCoords); const totalDistance = app.navigation.route.distance; const totalDuration = app.navigation.route.duration; const remainingDuration = (remainingDistance / totalDistance) * totalDuration; const estimatedDuration = document.getElementById('estimatedDuration'); const arrivalTime = document.getElementById('arrivalTime'); if (estimatedDuration) { const minutes = Math.round(remainingDuration / 60); estimatedDuration.textContent = `${minutes} min`; } if (arrivalTime) { const now = new Date(); const arrival = new Date(now.getTime() + remainingDuration * 1000); const hours = arrival.getHours().toString().padStart(2, '0'); const mins = arrival.getMinutes().toString().padStart(2, '0'); arrivalTime.textContent = `Arrival at ${hours}:${mins}`; } findUpcomingLights(closestPointIndex); if (app.settings.autoMeasure) { detectStop(); } } function findUpcomingLights(currentIndex) { if (!app.navigation.lightsOnRoute.length) return; const upcomingLights = app.navigation.lightsOnRoute .filter(item => item.routeIndex > currentIndex) .sort((a, b) => a.routeIndex - b.routeIndex); if (upcomingLights.length > 0) { const nextLight = upcomingLights[0]; const status = getLightStatus(nextLight.light); const route = app.navigation.route; const coordinates = route.geometry.coordinates; const distanceToLight = calculatePathDistance( coordinates.slice(currentIndex, nextLight.routeIndex + 1) ); // Rough estimate of time to reach light based on mode const speedKmh = app.navigation.mode === 'driving' ? 40 : app.navigation.mode === 'cycling' ? 15 : 5; const timeToReachSec = (distanceToLight / 1000) / speedKmh * 3600; // Only notify if within 100 seconds of reaching the light if (timeToReachSec < 100 && Date.now() - (app.lastLightNotification || 0) > 15000) { const willHitRed = status.isRed && status.timeLeft > timeToReachSec; const willHitGreen = !status.isRed && status.timeLeft > timeToReachSec; let message = ''; if (willHitRed) { message = `Red light ahead in ${Math.round(distanceToLight)}m. Will likely be red for ${Math.round(status.timeLeft - timeToReachSec)}s.`; } else if (willHitGreen) { message = `Green light ahead in ${Math.round(distanceToLight)}m. Will stay green for ${Math.round(status.timeLeft - timeToReachSec)}s.`; } else if (status.isRed) { message = `Red light ahead in ${Math.round(distanceToLight)}m. Will change to green in ${status.timeLeft}s.`; } else { message = `Green light ahead in ${Math.round(distanceToLight)}m. Will change to red in ${status.timeLeft}s.`; } app.lastLightNotification = Date.now(); showNotification('Traffic Light', message, willHitRed ? 'warning' : 'info'); } } } function detectStop() { if (!app.userLocation || !app.lastUserLocation) { app.lastUserLocation = [...app.userLocation]; app.lastMoveTime = Date.now(); return; } const distance = calculateDistance(app.userLocation, app.lastUserLocation); const now = Date.now(); if (distance < 5) { // Less than 5m movement if (!app.stopStartTime) { app.stopStartTime = now; } else if (now - app.stopStartTime > 3000 && !app.measuringStop) { // Stopped for 3+ seconds app.measuringStop = true; checkForTrafficLightAtStop(); } } else { if (app.measuringStop && app.measuringLightId) { const stopDuration = (now - app.stopStartTime) / 1000; if (stopDuration > 5 && stopDuration < 120) { autoSaveTrafficLightMeasurement(app.measuringLightId, stopDuration); } } app.lastUserLocation = [...app.userLocation]; app.lastMoveTime = now; app.stopStartTime = null; app.measuringStop = false; app.measuringLightId = null; } } function checkForTrafficLightAtStop() { if (!app.userLocation) return; let closestLightId = null; let minDistance = Infinity; for (const id in app.markers) { const light = app.markers[id].data; const lightPos = [parseFloat(light.longitude), parseFloat(light.latitude)]; const distance = calculateDistance(app.userLocation, lightPos); if (distance < 50 && distance < minDistance) { minDistance = distance; closestLightId = id; } } if (closestLightId) { const light = app.markers[closestLightId].data; const status = getLightStatus(light); if (status.isRed) { app.measuringLightId = closestLightId; showNotification('Auto Measure', `Measuring red light time for "${light.name}"`, 'info'); } } } function autoSaveTrafficLightMeasurement(lightId, duration) { const light = app.markers[lightId].data; fetch('api/update-traffic-light.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: lightId, duration_type: 'red', duration: Math.round(duration), auto_measured: true, user_id: app.user.id }) }) .then(response => response.json()) .then(data => { if (data.success) { app.markers[lightId].data = data.light; showNotification('Auto Measure', `Updated red duration for "${light.name}" to ${Math.round(duration)}s`, 'success'); } }) .catch(error => { console.error('Error saving measurement:', error); }); } function startNavigation() { if (app.navigation.isNavigating) { stopNavigation(); return; } if (!app.navigation.route) { showNotification('Navigation', 'No route calculated', 'error'); return; } app.navigation.isNavigating = true; const startButton = document.getElementById('startNavigationBtn'); if (startButton) startButton.innerHTML = '<i class="fas fa-stop"></i>'; showNotification('Navigation', 'Turn-by-turn navigation started', 'success'); app.lastUserLocation = null; app.stopStartTime = null; app.measuringStop = false; app.measuringLightId = null; if (app.navigation.updateInterval) clearInterval(app.navigation.updateInterval); app.navigation.updateInterval = setInterval(updateNavigation, 3000); } function stopNavigation() { app.navigation.isNavigating = false; const startButton = document.getElementById('startNavigationBtn'); if (startButton) startButton.innerHTML = '<i class="fas fa-play"></i>'; if (app.navigation.updateInterval) { clearInterval(app.navigation.updateInterval); app.navigation.updateInterval = null; } showNotification('Navigation', 'Navigation stopped', 'info'); } function cancelRoute() { const routePanel = document.querySelector('.route-panel'); const searchContainer = document.querySelector('.search-container'); const directionsElement = document.querySelector('.mapboxgl-ctrl-directions'); if (routePanel) routePanel.classList.remove('active'); if (searchContainer) searchContainer.classList.remove('route-active'); if (directionsElement) directionsElement.style.display = 'none'; if (app.directionsControl) app.directionsControl.removeRoutes(); stopNavigation(); app.navigation.route = null; app.navigation.lightsOnRoute = []; } function calculateRoute() { if (!app.directionsControl) return; const origin = app.directionsControl.getOrigin(); const destination = app.directionsControl.getDestination(); if (!origin || !destination) { showNotification('Route', 'Please select start and destination points', 'error'); return; } app.directionsControl.setProfile(`mapbox/${app.navigation.mode}`); const directionsElement = document.querySelector('.mapboxgl-ctrl-directions'); if (directionsElement) directionsElement.style.display = 'block'; const routeModal = document.getElementById('routeSearchModal'); if (routeModal) { closeModal(routeModal); } } function addEventListeners() { addMenuEventListeners(); addModalEventListeners(); addRouteEventListeners(); addSettingsEventListeners(); addMeasureEventListeners(); addFormEventListeners(); } function addMenuEventListeners() { const menuBtn = document.getElementById('fabMenuBtn'); if (menuBtn) { menuBtn.addEventListener('click', toggleMenu); } const menuItems = document.querySelectorAll('.menu-item'); menuItems.forEach(item => { const btn = item.querySelector('button'); if (btn) { btn.addEventListener('click', () => { handleMenuClick(item.id); }); } }); } function addModalEventListeners() { const closeButtons = document.querySelectorAll('.close-modal'); closeButtons.forEach(btn => { btn.addEventListener('click', () => { const modal = btn.closest('.modal'); if (modal) closeModal(modal); }); }); const overlays = document.querySelectorAll('.modal-overlay'); overlays.forEach(overlay => { overlay.addEventListener('click', () => { const modal = overlay.closest('.modal'); if (modal) closeModal(modal); }); }); const closeInfoBtn = document.getElementById('closeInfoPanel'); if (closeInfoBtn) { closeInfoBtn.addEventListener('click', () => { const infoPanel = document.getElementById('infoPanel'); if (infoPanel) infoPanel.classList.remove('active'); app.selectedLightId = null; }); } const infoPanelNavigateBtn = document.getElementById('infoPanelNavigate'); if (infoPanelNavigateBtn) { infoPanelNavigateBtn.addEventListener('click', () => { if (app.selectedLightId && app.markers[app.selectedLightId]) { navigateToLight(app.markers[app.selectedLightId].data); } }); } const infoPanelMeasureBtn = document.getElementById('infoPanelMeasure'); if (infoPanelMeasureBtn) { infoPanelMeasureBtn.addEventListener('click', () => { if (app.selectedLightId) { openMeasureModal(app.selectedLightId); } }); } const skipOnboardingBtn = document.getElementById('skipOnboarding'); if (skipOnboardingBtn) { skipOnboardingBtn.addEventListener('click', () => { localStorage.setItem('trafficLightOnboardingCompleted', 'true'); closeModal(document.getElementById('onboardingModal')); }); } } function addRouteEventListeners() { const cancelRouteBtn = document.getElementById('cancelRouteBtn'); if (cancelRouteBtn) { cancelRouteBtn.addEventListener('click', cancelRoute); } const startNavigationBtn = document.getElementById('startNavigationBtn'); if (startNavigationBtn) { startNavigationBtn.addEventListener('click', startNavigation); } const transportButtons = document.querySelectorAll('.transport-mode-btn'); transportButtons.forEach(btn => { btn.addEventListener('click', () => { transportButtons.forEach(b => { b.classList.remove('active', 'bg-white', 'text-primary-600'); b.classList.add('text-gray-600'); }); btn.classList.add('active', 'bg-white', 'text-primary-600'); btn.classList.remove('text-gray-600'); let mode = 'driving'; if (btn.id === 'bikingModeBtn') mode = 'cycling'; if (btn.id === 'walkingModeBtn') mode = 'walking'; app.navigation.mode = mode; if (app.navigation.route && app.directionsControl) { app.directionsControl.setProfile(`mapbox/${mode}`); } }); }); const transportModeChoices = document.querySelectorAll('.transport-mode-choice'); transportModeChoices.forEach(btn => { btn.addEventListener('click', () => { transportModeChoices.forEach(b => { b.classList.remove('active', 'bg-primary-50', 'border-primary-200', 'text-primary-600'); b.classList.add('bg-gray-50', 'border-gray-200', 'text-gray-600'); }); btn.classList.add('active', 'bg-primary-50', 'border-primary-200', 'text-primary-600'); btn.classList.remove('bg-gray-50', 'border-gray-200', 'text-gray-600'); app.navigation.mode = btn.getAttribute('data-mode'); }); }); const calculateRouteBtn = document.getElementById('calculateRouteBtn'); if (calculateRouteBtn) { calculateRouteBtn.addEventListener('click', calculateRoute); } } function addSettingsEventListeners() { const darkModeToggle = document.getElementById('darkModeToggle'); if (darkModeToggle) { darkModeToggle.addEventListener('change', () => { toggleDarkMode(); saveSettings(); }); } const notificationsToggle = document.getElementById('notificationsToggle'); if (notificationsToggle) { notificationsToggle.addEventListener('change', saveSettings); } const autoRefreshToggle = document.getElementById('autoRefreshToggle'); if (autoRefreshToggle) { autoRefreshToggle.addEventListener('change', () => { app.settings.autoRefresh = autoRefreshToggle.checked; if (app.settings.autoRefresh) { startLightUpdates(); } else if (app.timers.updateLights) { clearInterval(app.timers.updateLights); app.timers.updateLights = null; } saveSettings(); }); } const autoMeasureToggle = document.getElementById('autoMeasureToggle'); if (autoMeasureToggle) { autoMeasureToggle.addEventListener('change', saveSettings); } const mapStyleSelect = document.getElementById('mapStyle'); if (mapStyleSelect) { mapStyleSelect.addEventListener('change', () => { if (!app.map) return; app.settings.mapStyle = mapStyleSelect.value; app.map.setStyle(`mapbox://styles/mapbox/${app.settings.mapStyle}`); saveSettings(); }); } const defaultTransportSelect = document.getElementById('defaultTransport'); if (defaultTransportSelect) { defaultTransportSelect.addEventListener('change', saveSettings); } const resetAppBtn = document.getElementById('resetAppBtn'); if (resetAppBtn) { resetAppBtn.addEventListener('click', resetAppData); } } function addMeasureEventListeners() { const measureRedBtn = document.getElementById('measureRedBtn'); if (measureRedBtn) { measureRedBtn.addEventListener('click', () => startMeasure('red')); } const measureGreenBtn = document.getElementById('measureGreenBtn'); if (measureGreenBtn) { measureGreenBtn.addEventListener('click', () => startMeasure('green')); } const startTimerBtn = document.getElementById('startTimer'); if (startTimerBtn) { startTimerBtn.addEventListener('click', startTimer); } const stopTimerBtn = document.getElementById('stopTimer'); if (stopTimerBtn) { stopTimerBtn.addEventListener('click', stopTimer); } const saveTimerBtn = document.getElementById('saveTimer'); if (saveTimerBtn) { saveTimerBtn.addEventListener('click', saveTimer); } } function addFormEventListeners() { const addLightForm = document.getElementById('addLightForm'); if (addLightForm) { addLightForm.addEventListener('submit', handleAddLight); } const loginBtn = document.getElementById('loginBtn'); if (loginBtn) { loginBtn.addEventListener('click', () => { showLoginForm(); openModal(document.getElementById('accountModal')); }); } const signupBtn = document.getElementById('signupBtn'); if (signupBtn) { signupBtn.addEventListener('click', () => { showSignupForm(); openModal(document.getElementById('accountModal')); }); } const submitLoginBtn = document.getElementById('submitLoginBtn'); if (submitLoginBtn) { submitLoginBtn.addEventListener('click', login); } const submitSignupBtn = document.getElementById('submitSignupBtn'); if (submitSignupBtn) { submitSignupBtn.addEventListener('click', signup); } const switchToSignupBtn = document.getElementById('switchToSignupBtn'); if (switchToSignupBtn) { switchToSignupBtn.addEventListener('click', (e) => { e.preventDefault(); showSignupForm(); }); } const switchToLoginBtn = document.getElementById('switchToLoginBtn'); if (switchToLoginBtn) { switchToLoginBtn.addEventListener('click', (e) => { e.preventDefault(); showLoginForm(); }); } const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', logout); } const saveAccountBtn = document.getElementById('saveAccountBtn'); if (saveAccountBtn) { saveAccountBtn.addEventListener('click', updateAccount); } const onboardingForm = document.getElementById('onboardingForm'); if (onboardingForm) { onboardingForm.addEventListener('submit', (e) => { e.preventDefault(); onboarding(); }); } } function toggleMenu() { app.menuOpen = !app.menuOpen; const menuBtn = document.getElementById('fabMenuBtn'); if (!menuBtn) return; if (app.menuOpen) { menuBtn.innerHTML = '<i class="fas fa-times"></i>'; document.querySelectorAll('.menu-item').forEach(item => { const position = parseInt(item.getAttribute('data-position')); setTimeout(() => { item.classList.add('active'); item.style.transform = `translateY(-${position * 70}px)`; }, position * 50); }); } else { menuBtn.innerHTML = '<i class="fas fa-plus"></i>'; document.querySelectorAll('.menu-item').forEach(item => { item.classList.remove('active'); item.style.transform = ''; }); } } function handleMenuClick(id) { toggleMenu(); switch (id) { case 'accountMenuItem': if (app.user.isLoggedIn) { showAccountForm(); } else { showLoginForm(); } openModal(document.getElementById('accountModal')); break; case 'addLightMenuItem': openModal(document.getElementById('addLightModal')); if (app.userLocation) { const latInput = document.getElementById('latitude'); const lngInput = document.getElementById('longitude'); if (latInput && lngInput) { latInput.value = app.userLocation[1].toFixed(6); lngInput.value = app.userLocation[0].toFixed(6); if (app.tempMarker) app.tempMarker.remove(); const markerEl = document.createElement('div'); markerEl.className = 'w-8 h-8 bg-traffic-green rounded-full border-2 border-white shadow-lg flex items-center justify-center text-white'; markerEl.innerHTML = '<i class="fas fa-map-pin"></i>'; app.tempMarker = new mapboxgl.Marker(markerEl, { draggable: true }) .setLngLat(app.userLocation) .addTo(app.map); app.tempMarker.on('dragend', () => { const newPos = app.tempMarker.getLngLat(); latInput.value = newPos.lat.toFixed(6); lngInput.value = newPos.lng.toFixed(6); }); } } break; case 'searchMenuItem': openModal(document.getElementById('routeSearchModal')); break; case 'locateMenuItem': centerMapOnUser(); break; case 'settingsMenuItem': openModal(document.getElementById('settingsModal')); break; } } function openModal(modal) { if (!modal) return; modal.style.display = 'flex'; setTimeout(() => { const content = modal.querySelector('.modal-content'); if (content) content.classList.add('active'); }, 10); } function closeModal(modal) { if (!modal) return; const content = modal.querySelector('.modal-content'); if (content) content.classList.remove('active'); setTimeout(() => { modal.style.display = 'none'; if (modal.id === 'addLightModal' && app.tempMarker) { app.tempMarker.remove(); app.tempMarker = null; } }, 300); } function showLoginForm() { const accountForm = document.getElementById('accountForm'); const loginForm = document.getElementById('loginForm'); const signupForm = document.getElementById('signupForm'); if (accountForm) accountForm.classList.add('hidden'); if (loginForm) loginForm.classList.remove('hidden'); if (signupForm) signupForm.classList.add('hidden'); } function showSignupForm() { const accountForm = document.getElementById('accountForm'); const loginForm = document.getElementById('loginForm'); const signupForm = document.getElementById('signupForm'); if (accountForm) accountForm.classList.add('hidden'); if (loginForm) loginForm.classList.add('hidden'); if (signupForm) signupForm.classList.remove('hidden'); } function showAccountForm() { const accountForm = document.getElementById('accountForm'); const loginForm = document.getElementById('loginForm'); const signupForm = document.getElementById('signupForm'); if (accountForm) accountForm.classList.remove('hidden'); if (loginForm) loginForm.classList.add('hidden'); if (signupForm) signupForm.classList.add('hidden'); if (app.user.isLoggedIn) { const firstName = document.getElementById('firstName'); const lastName = document.getElementById('lastName'); const email = document.getElementById('email'); const vehicle = document.getElementById('preferredVehicle'); if (firstName) firstName.value = app.user.firstName; if (lastName) lastName.value = app.user.lastName; if (email) email.value = app.user.email; if (vehicle) vehicle.value = app.user.vehicle; } } function showNotification(title, message, type) { if (!app.settings.notifications && type !== 'error') return; const banner = document.getElementById('notificationBanner'); const titleEl = document.getElementById('notificationTitle'); const messageEl = document.getElementById('notificationMessage'); const iconEl = document.getElementById('notificationIcon'); if (!banner || !titleEl || !messageEl || !iconEl) return; titleEl.textContent = title; messageEl.textContent = message; let bgColor, iconClass; switch (type) { case 'success': bgColor = 'bg-traffic-green'; iconClass = 'fas fa-check'; break; case 'error': bgColor = 'bg-traffic-red'; iconClass = 'fas fa-exclamation-circle'; break; case 'warning': bgColor = 'bg-amber-500'; iconClass = 'fas fa-exclamation-triangle'; break; default: bgColor = 'bg-primary-500'; iconClass = 'fas fa-info-circle'; } iconEl.className = `w-8 h-8 ${bgColor} text-white rounded-full flex items-center justify-center mr-3`; iconEl.innerHTML = `<i class="${iconClass}"></i>`; if (app.timers.notification) { clearTimeout(app.timers.notification); } banner.style.display = 'block'; banner.style.opacity = '1'; app.timers.notification = setTimeout(() => { banner.style.opacity = '0'; setTimeout(() => { banner.style.display = 'none'; }, 300); }, 4000); } function toggleDarkMode() { app.settings.darkMode = !app.settings.darkMode; if (app.settings.darkMode) { document.documentElement.classList.add('dark'); document.body.classList.add('dark-mode'); if (app.map && app.settings.mapStyle.includes('day')) { app.settings.mapStyle = app.settings.mapStyle.replace('day', 'night'); const mapStyleSelect = document.getElementById('mapStyle'); if (mapStyleSelect) mapStyleSelect.value = app.settings.mapStyle; app.map.setStyle(`mapbox://styles/mapbox/${app.settings.mapStyle}`); } } else { document.documentElement.classList.remove('dark'); document.body.classList.remove('dark-mode'); if (app.map && app.settings.mapStyle.includes('night')) { app.settings.mapStyle = app.settings.mapStyle.replace('night', 'day'); const mapStyleSelect = document.getElementById('mapStyle'); if (mapStyleSelect) mapStyleSelect.value = app.settings.mapStyle; app.map.setStyle(`mapbox://styles/mapbox/${app.settings.mapStyle}`); } } const darkModeToggle = document.getElementById('darkModeToggle'); if (darkModeToggle) darkModeToggle.checked = app.settings.darkMode; } function resetAppData() { if (confirm('Are you sure you want to reset all app data? This will log you out and clear all your settings.')) { localStorage.removeItem('trafficSettings'); localStorage.removeItem('trafficLightOnboardingCompleted'); localStorage.removeItem('userId'); sessionStorage.removeItem('userId'); showNotification('Reset', 'All app data has been reset. Refreshing...', 'info'); setTimeout(() => { window.location.reload(); }, 1500); } } function login() { const email = document.getElementById('loginEmail')?.value; const password = document.getElementById('loginPassword')?.value; const rememberMe = document.getElementById('rememberMe')?.checked; if (!email || !password) { showNotification('Error', 'Please enter your email and password', 'error'); return; } fetch('api/login.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, remember_me: rememberMe }) }) .then(response => response.json()) .then(data => { if (data.success) { app.user.isLoggedIn = true; app.user.id = data.user.id; app.user.firstName = data.user.first_name; app.user.lastName = data.user.last_name; app.user.email = data.user.email; app.user.vehicle = data.user.preferred_vehicle; if (rememberMe) { localStorage.setItem('userId', data.user.id); } else { sessionStorage.setItem('userId', data.user.id); } updateUserDisplay(); closeModal(document.getElementById('accountModal')); showNotification('Success', 'Logged in successfully', 'success'); } else { showNotification('Error', data.message || 'Login failed', 'error'); } }) .catch(error => { console.error('Login error:', error); showNotification('Error', 'Connection error', 'error'); }); } function signup() { const firstName = document.getElementById('signupFirstName')?.value; const lastName = document.getElementById('signupLastName')?.value; const email = document.getElementById('signupEmail')?.value; const password = document.getElementById('signupPassword')?.value; const vehicle = document.getElementById('signupVehicle')?.value; const terms = document.getElementById('termsAgreement')?.checked; if (!firstName || !lastName || !email || !password || !vehicle || !terms) { showNotification('Error', 'Please fill in all fields and agree to the terms', 'error'); return; } fetch('api/signup.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ first_name: firstName, last_name: lastName, email, password, preferred_vehicle: vehicle }) }) .then(response => response.json()) .then(data => { if (data.success) { app.user.isLoggedIn = true; app.user.id = data.user.id; app.user.firstName = firstName; app.user.lastName = lastName; app.user.email = email; app.user.vehicle = vehicle; localStorage.setItem('userId', data.user.id); updateUserDisplay(); closeModal(document.getElementById('accountModal')); showNotification('Success', 'Account created successfully', 'success'); } else { showNotification('Error', data.message || 'Signup failed', 'error'); } }) .catch(error => { console.error('Signup error:', error); showNotification('Error', 'Connection error', 'error'); }); } function onboarding() { const firstName = document.getElementById('onboardingFirstName')?.value; const lastName = document.getElementById('onboardingLastName')?.value; const email = document.getElementById('onboardingEmail')?.value; const password = document.getElementById('onboardingPassword')?.value; const vehicle = document.getElementById('onboardingVehicle')?.value; const terms = document.getElementById('onboardingTerms')?.checked; if (!firstName || !lastName || !email || !password || !vehicle || !terms) { showNotification('Error', 'Please fill in all fields and agree to the terms', 'error'); return; } fetch('api/signup.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ first_name: firstName, last_name: lastName, email, password, preferred_vehicle: vehicle }) }) .then(response => response.json()) .then(data => { if (data.success) { app.user.isLoggedIn = true; app.user.id = data.user.id; app.user.firstName = firstName; app.user.lastName = lastName; app.user.email = email; app.user.vehicle = vehicle; localStorage.setItem('userId', data.user.id); localStorage.setItem('trafficLightOnboardingCompleted', 'true'); updateUserDisplay(); closeModal(document.getElementById('onboardingModal')); showNotification('Welcome', 'Your account has been created!', 'success'); } else { showNotification('Error', data.message || 'Registration failed', 'error'); } }) .catch(error => { console.error('Onboarding error:', error); showNotification('Error', 'Connection error', 'error'); }); } function logout() { fetch('api/logout.php') .then(() => { app.user.isLoggedIn = false; app.user.id = null; app.user.firstName = ''; app.user.lastName = ''; app.user.email = ''; app.user.vehicle = 'car'; localStorage.removeItem('userId'); sessionStorage.removeItem('userId'); updateUserDisplay(); closeModal(document.getElementById('accountModal')); showNotification('Logout', 'You have been logged out', 'info'); }) .catch(error => { console.error('Logout error:', error); showNotification('Error', 'Connection error', 'error'); }); } function updateAccount() { if (!app.user.isLoggedIn) return; const firstName = document.getElementById('firstName')?.value; const lastName = document.getElementById('lastName')?.value; const email = document.getElementById('email')?.value; const vehicle = document.getElementById('preferredVehicle')?.value; if (!firstName || !lastName || !email || !vehicle) { showNotification('Error', 'Please fill in all fields', 'error'); return; } fetch('api/update-user.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: app.user.id, first_name: firstName, last_name: lastName, email, preferred_vehicle: vehicle }) }) .then(response => response.json()) .then(data => { if (data.success) { app.user.firstName = firstName; app.user.lastName = lastName; app.user.email = email; app.user.vehicle = vehicle; updateUserDisplay(); closeModal(document.getElementById('accountModal')); showNotification('Success', 'Account updated successfully', 'success'); } else { showNotification('Error', data.message || 'Update failed', 'error'); } }) .catch(error => { console.error('Update account error:', error); showNotification('Error', 'Connection error', 'error'); }); } function handleAddLight(e) { e.preventDefault(); const lightName = document.getElementById('lightName')?.value; const latitude = document.getElementById('latitude')?.value; const longitude = document.getElementById('longitude')?.value; const direction = document.getElementById('direction')?.value; const redDuration = document.getElementById('redDuration')?.value; const greenDuration = document.getElementById('greenDuration')?.value; if (!lightName || !latitude || !longitude || !direction || !redDuration || !greenDuration) { showNotification('Error', 'Please fill in all fields', 'error'); return; } const submitBtn = document.querySelector('#addLightForm button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.innerHTML = '<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div> Adding...'; submitBtn.disabled = true; fetch('api/add-traffic-light.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: lightName, latitude, longitude, direction, red_duration: redDuration, green_duration: greenDuration, user_id: app.user.id }) }) .then(response => response.json()) .then(data => { if (data.success) { app.pendingLights.push(data.light.id); const certBanner = document.getElementById('certificationBanner'); if (certBanner) certBanner.classList.remove('hidden'); addTrafficLight(data.light); closeModal(document.getElementById('addLightModal')); document.getElementById('addLightForm').reset(); if (app.tempMarker) { app.tempMarker.remove(); app.tempMarker = null; } showNotification('Success', 'Traffic light added successfully! Pending certification.', 'success'); } else { showNotification('Error', data.message || 'Failed to add traffic light', 'error'); } submitBtn.innerHTML = originalText; submitBtn.disabled = false; }) .catch(error => { console.error('Add light error:', error); showNotification('Error', 'Connection error', 'error'); submitBtn.innerHTML = originalText; submitBtn.disabled = false; }); } function openMeasureModal(lightId) { const light = app.markers[lightId]?.data; if (!light) return; app.measure.lightId = lightId; const measureTitle = document.getElementById('measureTitle'); if (measureTitle) { measureTitle.textContent = light.name; measureTitle.setAttribute('data-id', lightId); } const status = getLightStatus(light); const measureStatus = document.getElementById('measureStatus'); if (measureStatus) { measureStatus.innerHTML = ` <span class="px-3 py-1.5 rounded-full text-sm font-medium bg-traffic-${status.color} text-white"> ${status.label} (${status.timeLeft}s) </span> `; } const timerContainer = document.getElementById('timerContainer'); if (timerContainer) timerContainer.classList.add('hidden'); app.measure.mode = null; app.measure.timer = 0; if (app.measure.interval) { clearInterval(app.measure.interval); app.measure.interval = null; } const measureModal = document.getElementById('measureModal'); if (measureModal) openModal(measureModal); } function startMeasure(mode) { app.measure.mode = mode; const timerContainer = document.getElementById('timerContainer'); if (timerContainer) timerContainer.classList.remove('hidden'); const timerInstructions = document.getElementById('timerInstructions'); const startTimer = document.getElementById('startTimer'); const stopTimer = document.getElementById('stopTimer'); if (timerInstructions) { timerInstructions.textContent = mode === 'red' ? 'Press "Start" when the light turns red, then "Stop" when it turns green.' : 'Press "Start" when the light turns green, then "Stop" when it turns red.'; } if (startTimer) { startTimer.className = mode === 'red' ? 'bg-traffic-red text-white py-2.5 rounded-lg hover:bg-red-600 transition-colors' : 'bg-traffic-green text-white py-2.5 rounded-lg hover:bg-green-600 transition-colors'; startTimer.disabled = false; startTimer.classList.remove('opacity-50'); } if (stopTimer) { stopTimer.className = mode === 'red' ? 'bg-traffic-green text-white py-2.5 rounded-lg hover:bg-green-600 transition-colors opacity-50' : 'bg-traffic-red text-white py-2.5 rounded-lg hover:bg-red-600 transition-colors opacity-50'; stopTimer.disabled = true; stopTimer.classList.add('opacity-50'); } const saveTimer = document.getElementById('saveTimer'); if (saveTimer) { saveTimer.disabled = true; saveTimer.classList.add('opacity-50'); } const timerDisplay = document.getElementById('timerDisplay'); if (timerDisplay) timerDisplay.textContent = '00:00'; const measureResult = document.getElementById('measureResult'); if (measureResult) measureResult.textContent = ''; app.measure.timer = 0; if (app.measure.interval) { clearInterval(app.measure.interval); app.measure.interval = null; } } function startTimer() { app.measure.timer = 0; const timerDisplay = document.getElementById('timerDisplay'); const measureResult = document.getElementById('measureResult'); const startBtn = document.getElementById('startTimer'); const stopBtn = document.getElementById('stopTimer'); const saveBtn = document.getElementById('saveTimer'); if (timerDisplay) timerDisplay.textContent = '00:00'; if (measureResult) measureResult.textContent = 'Measuring...'; if (startBtn) { startBtn.disabled = true; startBtn.classList.add('opacity-50'); } if (stopBtn) { stopBtn.disabled = false; stopBtn.classList.remove('opacity-50'); } if (saveBtn) { saveBtn.disabled = true; saveBtn.classList.add('opacity-50'); } const startTime = Date.now(); if (app.measure.interval) { clearInterval(app.measure.interval); } app.measure.interval = setInterval(() => { const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000); app.measure.timer = elapsedSeconds; if (timerDisplay) { const minutes = Math.floor(elapsedSeconds / 60); const seconds = elapsedSeconds % 60; timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } }, 1000); } function stopTimer() { if (app.measure.interval) { clearInterval(app.measure.interval); app.measure.interval = null; } const measureResult = document.getElementById('measureResult'); const stopBtn = document.getElementById('stopTimer'); const saveBtn = document.getElementById('saveTimer'); if (measureResult) { measureResult.textContent = `Measured duration: ${app.measure.timer} seconds. Click Save to confirm.`; } if (stopBtn) { stopBtn.disabled = true; stopBtn.classList.add('opacity-50'); } if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.remove('opacity-50'); } } function saveTimer() { const lightId = document.getElementById('measureTitle')?.getAttribute('data-id'); if (!lightId || !app.measure.mode || app.measure.timer <= 0) { showNotification('Error', 'Invalid measurement data', 'error'); return; } const measureResult = document.getElementById('measureResult'); const saveBtn = document.getElementById('saveTimer'); if (measureResult) measureResult.textContent = 'Saving...'; if (saveBtn) { saveBtn.disabled = true; saveBtn.classList.add('opacity-50'); } fetch('api/update-traffic-light.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: lightId, duration_type: app.measure.mode, duration: app.measure.timer, user_id: app.user.id }) }) .then(response => response.json()) .then(data => { if (data.success) { if (measureResult) { measureResult.innerHTML = '<i class="fas fa-check text-green-500 mr-1"></i> Timing updated!'; } if (app.markers[lightId]) { app.markers[lightId].data = data.light; updateLightStatus(data.light); } showNotification('Success', 'Traffic light timing updated', 'success'); setTimeout(() => { const timerContainer = document.getElementById('timerContainer'); if (timerContainer) timerContainer.classList.add('hidden'); app.measure.mode = null; }, 1500); } else { if (measureResult) { measureResult.innerHTML = '<i class="fas fa-times text-red-500 mr-1"></i> Error: ' + (data.message || 'Failed to update'); } showNotification('Error', data.message || 'Failed to update timing', 'error'); if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.remove('opacity-50'); } } }) .catch(error => { console.error('Save timer error:', error); if (measureResult) { measureResult.innerHTML = '<i class="fas fa-times text-red-500 mr-1"></i> Connection error'; } showNotification('Error', 'Connection error', 'error'); if (saveBtn) { saveBtn.disabled = false; saveBtn.classList.remove('opacity-50'); } }); } function navigateToLight(light) { if (!app.userLocation) { showNotification('Error', 'Your location is not available', 'error'); return; } if (!light) return; const lightPos = [parseFloat(light.longitude), parseFloat(light.latitude)]; if (app.directionsControl) { app.directionsControl.setOrigin(app.userLocation); app.directionsControl.setDestination(lightPos); app.directionsControl.setProfile(`mapbox/${app.navigation.mode}`); document.querySelector('.mapboxgl-ctrl-directions').style.display = 'block'; const infoPanel = document.getElementById('infoPanel'); if (infoPanel) infoPanel.classList.remove('active'); } } window.addEventListener('error', (e) => { if (e.message.includes('FrameDoesNotExistError')) { console.warn('Ignoring frame error:', e.message); e.preventDefault(); return true; } }); });
| ver. 1.6 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка