diff --git a/static/index.html b/static/index.html index fc4cbe3..5699739 100644 --- a/static/index.html +++ b/static/index.html @@ -1103,6 +1103,9 @@ βš™οΈ Settings
+ @@ -1465,6 +1468,42 @@ + +
+
+ +
+ + +
+
+ + + + +
+ @@ -1824,6 +1863,9 @@ + @@ -2297,6 +2339,14 @@ // TAB SWITCHING // ========================================================================= function switchTab(tabId) { + // Track previous tab for back navigation + if (AppState.currentTab !== tabId && AppState.currentTab !== 'tabNavigate') { + AppState._prevTab = AppState.currentTab; + } + // Cleanup nav page when leaving + if (AppState.currentTab === 'tabNavigate' && tabId !== 'tabNavigate') { + closeNavigate(); + } AppState.currentTab = tabId; // Update tab panels @@ -2309,6 +2359,7 @@ tabAddAsset: 'tabAddAsset', tabAssets: 'tabAssets', tabMap: 'tabMap', + tabNavigate: 'tabNavigate', tabDashboard: 'tabDashboard', }; const bottomTab = tabMap[tabId] || 'tabMore'; @@ -3528,94 +3579,112 @@ const nav = await api('/api/assets/' + AppState.currentAssetId + '/navigation?lat=' + AppState.gpsLat + '&lng=' + AppState.gpsLng); - // Switch to map tab - switchTab('tabMap'); - - // Initialize map if needed - if (!map) { initMap(); } - - // Wait for map to be ready - setTimeout(() => { - if (!map) return; - - // Clear previous route line - clearRouteLine(); - - // Draw route line from origin (user GPS) to destination (asset) - const routeCoords = [ - [nav.origin_lat, nav.origin_lng], - [nav.asset_lat, nav.asset_lng] - ]; - - map._routeLine = L.polyline(routeCoords, { - color: '#5b6ef7', - weight: 4, - opacity: 0.8, - dashArray: '10, 6', - }).addTo(map); - - // Add arrow markers at both ends - const originIcon = L.divIcon({ - html: '
', - className: '', - iconSize: [16, 16], - iconAnchor: [8, 8], - }); - - const destIcon = L.divIcon({ - html: '
πŸ“
', - className: '', - iconSize: [20, 20], - iconAnchor: [10, 10], - }); - - if (map._routeOriginMarker) map.removeLayer(map._routeOriginMarker); - map._routeOriginMarker = L.marker([nav.origin_lat, nav.origin_lng], { icon: originIcon }).addTo(map); - - if (map._routeDestMarker) map.removeLayer(map._routeDestMarker); - map._routeDestMarker = L.marker([nav.asset_lat, nav.asset_lng], { icon: destIcon }).addTo(map); - - // Distance label at midpoint - const midLat = (nav.origin_lat + nav.asset_lat) / 2; - const midLng = (nav.origin_lng + nav.asset_lng) / 2; - const distText = nav.distance_km >= 1 - ? nav.distance_km + ' km' - : nav.distance_meters + ' m'; - - if (map._routeDistLabel) map.removeLayer(map._routeDistLabel); - map._routeDistLabel = L.marker([midLat, midLng], { - icon: L.divIcon({ - html: '
' + distText + ' ' + nav.cardinal + '
', - className: '', - iconSize: null, - iconAnchor: null, - }), - }).addTo(map); - - // Fit bounds to show the whole route - const bounds = L.latLngBounds(routeCoords); - map.fitBounds(bounds, { padding: [60, 60], maxZoom: 16 }); - - // Show info toast - showToast('πŸ“ ' + distText + ' ' + nav.cardinal + ' to ' + nav.asset_name); - - // If asset has address, add popup to destination marker - if (nav.asset_address) { - map._routeDestMarker.bindPopup( - '
' + - '
' + esc(nav.asset_name) + '
' + - '
πŸ“ ' + esc(nav.asset_address) + '
' + - '🧭 Open in Google Maps' + - '
' - ).openPopup(); - } - }, 300); + // Store nav data for dedicated page + AppState._navData = nav; + // Switch to dedicated navigate tab + switchTab('tabNavigate'); + renderNavPage(); } catch (e) { showToast(e.message, true); } } + let navMap = null; + + function initNavMap(lat, lng) { + if (navMap) { navMap.remove(); navMap = null; } + const container = document.getElementById('navMapContainer'); + navMap = L.map(container, { + attributionControl: false, + zoomControl: false, + dragging: true, + tap: true, + }).setView([lat, lng], 15); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + }).addTo(navMap); + // Invalidate size after container is visible + setTimeout(() => { if (navMap) navMap.invalidateSize(); }, 100); + } + + function renderNavPage() { + const nav = AppState._navData; + if (!nav) return; + + // Set header info + document.getElementById('navDestName').textContent = nav.asset_name || 'Navigate'; + document.getElementById('navDestAddr').textContent = nav.asset_address || ''; + + // Init dedicated map centered on route midpoint + const midLat = (nav.origin_lat + nav.asset_lat) / 2; + const midLng = (nav.origin_lng + nav.asset_lng) / 2; + initNavMap(midLat, midLng); + + // Give map time to init, then draw + setTimeout(() => { + if (!navMap) return; + + // Origin (user) marker β€” blue + const userIcon = L.divIcon({ + html: '
', + className: '', iconSize: [14, 14], iconAnchor: [7, 7], + }); + L.marker([nav.origin_lat, nav.origin_lng], { icon: userIcon }) + .addTo(navMap).bindPopup('You are here'); + + // Destination marker β€” green pin + const destIcon = L.divIcon({ + html: '
πŸ“
', + className: '', iconSize: [20, 20], iconAnchor: [10, 10], + }); + const destMarker = L.marker([nav.asset_lat, nav.asset_lng], { icon: destIcon }).addTo(navMap); + if (nav.asset_address) { + destMarker.bindPopup('
' + esc(nav.asset_name) + '
πŸ“ ' + esc(nav.asset_address) + '
'); + } + + // Route line + L.polyline([[nav.origin_lat, nav.origin_lng], [nav.asset_lat, nav.asset_lng]], { + color: '#5b6ef7', weight: 4, opacity: 0.8, dashArray: '10, 6', + }).addTo(navMap); + + // Fit bounds + navMap.fitBounds([[nav.origin_lat, nav.origin_lng], [nav.asset_lat, nav.asset_lng]], { padding: [40, 40], maxZoom: 16 }); + }, 200); + + // Populate info card + const distText = nav.distance_km >= 1 + ? nav.distance_km + ' km' : nav.distance_meters + ' m'; + document.getElementById('navDistance').textContent = distText; + document.getElementById('navCardinal').textContent = nav.cardinal || 'β€”'; + document.getElementById('navBearing').textContent = nav.bearing ? nav.bearing + 'Β°' : 'β€”'; + document.getElementById('navInfoCard').style.display = ''; + + // Walking directions + if (nav.walking_directions) { + document.getElementById('navDirections').textContent = nav.walking_directions; + document.getElementById('navDirectionsCard').style.display = ''; + } else { + document.getElementById('navDirectionsCard').style.display = 'none'; + } + + // Google Maps link + if (nav.google_maps_url) { + const link = document.getElementById('navGoogleMapsLink'); + link.href = nav.google_maps_url; + link.style.display = ''; + } else { + document.getElementById('navGoogleMapsLink').style.display = 'none'; + } + } + + function closeNavigate() { + if (navMap) { navMap.remove(); navMap = null; } + AppState._navData = null; + // Go back to previous tab or asset list + switchTab(AppState._prevTab || 'tabAssets'); + } + function clearRouteLine() { if (!map) return; if (map._routeLine) { map.removeLayer(map._routeLine); map._routeLine = null; }