UI overhaul: kiosk-optimized landscape mode #23

Merged
kyle merged 4 commits from ui-overhaul into main 2026-02-15 10:15:17 -08:00
Showing only changes of commit 6565661740 - Show all commits

View File

@@ -709,77 +709,37 @@ class ConfigManager {
if (e.target === mapModal) closeMap(); if (e.target === mapModal) closeMap();
}); });
// Load transit stops - search for common Stockholm areas // Load nearby transit stops based on map center
const loadSitesOnMap = async () => { const markersLayer = L.layerGroup().addTo(map);
try { const loadedSiteIds = new Set();
// Start with focused search to avoid too many markers
const searchTerms = ['Ambassaderna']; const loadNearbySites = async () => {
const allSites = new Map(); const center = map.getCenter();
const zoom = map.getZoom();
// Scale radius based on zoom: wider view = larger radius
const radius = zoom >= 15 ? 500 : zoom >= 13 ? 1500 : zoom >= 11 ? 4000 : 8000;
for (const term of searchTerms) {
try { try {
const response = await fetch(`/api/sites/search?q=${encodeURIComponent(term)}`); const response = await fetch(`/api/sites/nearby?lat=${center.lat}&lon=${center.lng}&radius=${radius}`);
const data = await response.json(); const data = await response.json();
console.log(`Search "${term}" returned:`, data);
if (data.sites) { if (data.sites) {
data.sites.forEach(site => { data.sites.forEach(site => {
// Only add sites with valid coordinates from API if (loadedSiteIds.has(site.id)) return;
const lat = site.lat || site.latitude; const lat = parseFloat(site.lat);
const lon = site.lon || site.longitude; const lon = parseFloat(site.lon);
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
if (lat && lon && !isNaN(parseFloat(lat)) && !isNaN(parseFloat(lon))) { loadedSiteIds.add(site.id);
if (!allSites.has(site.id)) {
allSites.set(site.id, {
id: site.id,
name: site.name,
lat: parseFloat(lat),
lon: parseFloat(lon)
});
}
} else {
console.log(`Site ${site.id} (${site.name}) missing coordinates, skipping`);
}
});
}
} catch (err) {
console.error(`Error searching for ${term}:`, err);
}
}
// Add known site with coordinates as fallback
const knownSites = [
{ id: '1411', name: 'Ambassaderna', lat: 59.3293, lon: 18.0686 }
];
knownSites.forEach(site => {
if (!allSites.has(site.id)) {
allSites.set(site.id, site);
}
});
const sitesArray = Array.from(allSites.values());
console.log(`Loading ${sitesArray.length} sites on map with coordinates:`, sitesArray);
if (sitesArray.length > 0) {
const markers = [];
sitesArray.forEach(site => {
const lat = site.lat;
const lon = site.lon;
if (!lat || !lon || isNaN(lat) || isNaN(lon)) {
console.warn(`Invalid coordinates for site ${site.id}, skipping`);
return;
}
// Create custom icon
const customIcon = L.divIcon({ const customIcon = L.divIcon({
className: 'custom-marker', className: 'custom-marker',
html: `<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">🚌</div>`, html: '<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">&#x1F68C;</div>',
iconSize: [30, 30], iconSize: [30, 30],
iconAnchor: [15, 15] iconAnchor: [15, 15]
}); });
const marker = L.marker([lat, lon], { icon: customIcon }).addTo(map); const marker = L.marker([lat, lon], { icon: customIcon });
const popupContent = ` const popupContent = `
<div style="min-width: 200px;"> <div style="min-width: 200px;">
@@ -793,36 +753,18 @@ class ConfigManager {
`; `;
marker.bindPopup(popupContent); marker.bindPopup(popupContent);
markers.push(marker); markersLayer.addLayer(marker);
// Make marker clickable to open popup
marker.on('click', function() {
this.openPopup();
}); });
}); console.log(`Loaded ${data.sites.length} nearby sites (${loadedSiteIds.size} total on map)`);
// Fit map to show all markers, or center on first marker
if (markers.length > 0) {
if (markers.length === 1) {
map.setView(markers[0].getLatLng(), 15);
} else {
const group = new L.featureGroup(markers);
map.fitBounds(group.getBounds().pad(0.1));
}
} else {
// If no markers, show message
console.log('No sites with coordinates found');
}
} }
} catch (error) { } catch (error) {
console.error('Error loading sites on map:', error); console.error('Error loading nearby sites:', error);
} }
}; };
// Load sites after map is initialized // Load sites on init and when map is panned/zoomed
setTimeout(() => { setTimeout(() => loadNearbySites(), 300);
loadSitesOnMap(); map.on('moveend', loadNearbySites);
}, 500);
// Handle site selection from map popup - use event delegation on the modal // Handle site selection from map popup - use event delegation on the modal
mapModal.addEventListener('click', (e) => { mapModal.addEventListener('click', (e) => {
@@ -874,73 +816,42 @@ class ConfigManager {
const data = await response.json(); const data = await response.json();
if (data.sites && data.sites.length > 0) { if (data.sites && data.sites.length > 0) {
// Clear existing markers // Clear existing markers and reset tracking
map.eachLayer((layer) => { markersLayer.clearLayers();
if (layer instanceof L.Marker) { loadedSiteIds.clear();
map.removeLayer(layer);
}
});
// Clear existing markers const searchMarkers = [];
map.eachLayer((layer) => {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
// Add markers for search results
const markers = [];
data.sites.forEach(site => { data.sites.forEach(site => {
let lat = site.lat || site.latitude; const lat = parseFloat(site.lat);
let lon = site.lon || site.longitude; const lon = parseFloat(site.lon);
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
// If no coordinates, use approximate location based on map center loadedSiteIds.add(site.id);
if (!lat || !lon) {
const center = map.getCenter();
lat = center.lat + (Math.random() - 0.5) * 0.05;
lon = center.lon + (Math.random() - 0.5) * 0.05;
}
// Create custom icon
const customIcon = L.divIcon({ const customIcon = L.divIcon({
className: 'custom-transit-marker', className: 'custom-marker',
html: `<div style="background-color: #0061a1; color: white; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.4); font-size: 18px;">🚌</div>`, html: '<div style="background-color: #28a745; color: white; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; font-weight: bold; border: 3px solid white; box-shadow: 0 2px 8px rgba(0,0,0,0.4); font-size: 18px;">&#x1F68C;</div>',
iconSize: [32, 32], iconSize: [32, 32],
iconAnchor: [16, 16], iconAnchor: [16, 16]
popupAnchor: [0, -16]
}); });
const marker = L.marker([lat, lon], { const marker = L.marker([lat, lon], { icon: customIcon });
icon: customIcon, marker.bindPopup(`
title: site.name <div style="min-width: 200px;">
}).addTo(map); <strong style="font-size: 1.1em; color: #0061a1;">${site.name}</strong><br>
<span style="color: #666; font-size: 0.9em;">ID: ${site.id}</span><br>
const popupContent = document.createElement('div'); <button class="select-site-from-map" data-site-id="${site.id}" data-site-name="${site.name}"
popupContent.style.minWidth = '220px'; style="margin-top: 8px; padding: 8px 15px; width: 100%; background-color: #0061a1; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
popupContent.innerHTML = `
<div style="margin-bottom: 8px;">
<strong style="font-size: 1.1em; color: #0061a1; display: block; margin-bottom: 4px;">${site.name}</strong>
<span style="color: #666; font-size: 0.9em;">ID: ${site.id}</span>
</div>
<button class="select-site-from-map-btn"
data-site-id="${site.id}"
data-site-name="${site.name}"
style="margin-top: 8px; padding: 10px 15px; width: 100%; background-color: #0061a1; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 1em;">
Select This Stop Select This Stop
</button> </button>
`; </div>
`);
marker.bindPopup(popupContent); markersLayer.addLayer(marker);
markers.push(marker); searchMarkers.push(marker);
marker.on('click', function() {
this.openPopup();
});
}); });
// Fit map to show results if (searchMarkers.length > 0) {
if (markers.length > 0) { const group = new L.featureGroup(searchMarkers);
const group = new L.featureGroup(markers);
map.fitBounds(group.getBounds().pad(0.1)); map.fitBounds(group.getBounds().pad(0.1));
} }
} }