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();
});
// Load transit stops - search for common Stockholm areas
const loadSitesOnMap = async () => {
try {
// Start with focused search to avoid too many markers
const searchTerms = ['Ambassaderna'];
const allSites = new Map();
// Load nearby transit stops based on map center
const markersLayer = L.layerGroup().addTo(map);
const loadedSiteIds = new Set();
const loadNearbySites = async () => {
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 {
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();
console.log(`Search "${term}" returned:`, data);
if (data.sites) {
data.sites.forEach(site => {
// Only add sites with valid coordinates from API
const lat = site.lat || site.latitude;
const lon = site.lon || site.longitude;
if (loadedSiteIds.has(site.id)) return;
const lat = parseFloat(site.lat);
const lon = parseFloat(site.lon);
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
if (lat && lon && !isNaN(parseFloat(lat)) && !isNaN(parseFloat(lon))) {
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);
}
}
loadedSiteIds.add(site.id);
// 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({
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],
iconAnchor: [15, 15]
});
const marker = L.marker([lat, lon], { icon: customIcon }).addTo(map);
const marker = L.marker([lat, lon], { icon: customIcon });
const popupContent = `
<div style="min-width: 200px;">
@@ -793,36 +753,18 @@ class ConfigManager {
`;
marker.bindPopup(popupContent);
markers.push(marker);
// Make marker clickable to open popup
marker.on('click', function() {
this.openPopup();
markersLayer.addLayer(marker);
});
});
// 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');
}
console.log(`Loaded ${data.sites.length} nearby sites (${loadedSiteIds.size} total on map)`);
}
} catch (error) {
console.error('Error loading sites on map:', error);
console.error('Error loading nearby sites:', error);
}
};
// Load sites after map is initialized
setTimeout(() => {
loadSitesOnMap();
}, 500);
// Load sites on init and when map is panned/zoomed
setTimeout(() => loadNearbySites(), 300);
map.on('moveend', loadNearbySites);
// Handle site selection from map popup - use event delegation on the modal
mapModal.addEventListener('click', (e) => {
@@ -874,73 +816,42 @@ class ConfigManager {
const data = await response.json();
if (data.sites && data.sites.length > 0) {
// Clear existing markers
map.eachLayer((layer) => {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
// Clear existing markers and reset tracking
markersLayer.clearLayers();
loadedSiteIds.clear();
// Clear existing markers
map.eachLayer((layer) => {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
// Add markers for search results
const markers = [];
const searchMarkers = [];
data.sites.forEach(site => {
let lat = site.lat || site.latitude;
let lon = site.lon || site.longitude;
const lat = parseFloat(site.lat);
const lon = parseFloat(site.lon);
if (!lat || !lon || isNaN(lat) || isNaN(lon)) return;
// If no coordinates, use approximate location based on map center
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;
}
loadedSiteIds.add(site.id);
// Create custom icon
const customIcon = L.divIcon({
className: 'custom-transit-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>`,
className: 'custom-marker',
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],
iconAnchor: [16, 16],
popupAnchor: [0, -16]
iconAnchor: [16, 16]
});
const marker = L.marker([lat, lon], {
icon: customIcon,
title: site.name
}).addTo(map);
const popupContent = document.createElement('div');
popupContent.style.minWidth = '220px';
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;">
const marker = L.marker([lat, lon], { icon: customIcon });
marker.bindPopup(`
<div style="min-width: 200px;">
<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>
<button class="select-site-from-map" data-site-id="${site.id}" data-site-name="${site.name}"
style="margin-top: 8px; padding: 8px 15px; width: 100%; background-color: #0061a1; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Select This Stop
</button>
`;
marker.bindPopup(popupContent);
markers.push(marker);
marker.on('click', function() {
this.openPopup();
});
</div>
`);
markersLayer.addLayer(marker);
searchMarkers.push(marker);
});
// Fit map to show results
if (markers.length > 0) {
const group = new L.featureGroup(markers);
if (searchMarkers.length > 0) {
const group = new L.featureGroup(searchMarkers);
map.fitBounds(group.getBounds().pad(0.1));
}
}