/* ============================================================ BAN YARO — Zentrale Karte Alle Layer auf einer Karte: Orte + Giftköder ============================================================ */ window.Page_map = (() => { let _container = null; let _appState = null; let _map = null; let _leafletLoaded = false; let _userPos = null; // Layer-Marker let _layers = { restaurant: [], freilauf: [], shop: [], kotbeutel: [], tierarzt: [], hundeschule: [], poison: [], }; // Layer-Sichtbarkeit let _visible = { restaurant: true, freilauf: true, shop: true, kotbeutel: true, tierarzt: true, hundeschule: true, poison: true, }; const TYPEN = { restaurant: { icon: '🍽️', label: 'Restaurant', color: '#F97316' }, freilauf: { icon: '🐕', label: 'Freilauf', color: '#22C55E' }, shop: { icon: '🛒', label: 'Shop', color: '#3B82F6' }, kotbeutel: { icon: '🧻', label: 'Kotbeutel', color: '#6B7280' }, tierarzt: { icon: '🩺', label: 'Tierarzt', color: '#EF4444' }, hundeschule: { icon: '🎓', label: 'Hundeschule', color: '#8B5CF6' }, poison: { icon: '⚠️', label: 'Giftköder', color: '#DC2626' }, }; // ---------------------------------------------------------- // INIT // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; _render(); try { _userPos = await API.getLocation(); } catch {} await _loadLeaflet(); _initMap(); _loadAll(); } function refresh() { _loadAll(); } function onDogChange() {} // ---------------------------------------------------------- // RENDER // ---------------------------------------------------------- function _render() { _container.innerHTML = `
${Object.entries(TYPEN).map(([k, t]) => ` `).join('')}
`; // Layer-Toggle document.getElementById('map-legend').addEventListener('click', e => { const btn = e.target.closest('.map-legend-btn'); if (!btn) return; const layer = btn.dataset.layer; _visible[layer] = !_visible[layer]; btn.classList.toggle('active', _visible[layer]); _applyVisibility(layer); }); // GPS-Locate document.getElementById('map-locate-btn').addEventListener('click', async () => { try { _userPos = await API.getLocation({ enableHighAccuracy: true }); _map?.setView([_userPos.lat, _userPos.lon], 14); } catch { UI.toast.error('Standort konnte nicht ermittelt werden.'); } }); } // ---------------------------------------------------------- // Leaflet laden // ---------------------------------------------------------- async function _loadLeaflet() { if (_leafletLoaded || window.L) { _leafletLoaded = true; return; } const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = '/css/leaflet.css'; document.head.appendChild(link); await new Promise(resolve => { const s = document.createElement('script'); s.src = '/js/leaflet.js'; s.onload = resolve; document.head.appendChild(s); }); _leafletLoaded = true; } // ---------------------------------------------------------- // Karte initialisieren // ---------------------------------------------------------- function _initMap() { const el = document.getElementById('central-map'); if (!el || !window.L || _map) return; const center = _userPos ? [_userPos.lat, _userPos.lon] : [51.1657, 10.4515]; const zoom = _userPos ? 13 : 6; _map = L.map('central-map', { zoomControl: true, attributionControl: false }) .setView(center, zoom); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }) .addTo(_map); } // ---------------------------------------------------------- // Alle Layer laden // ---------------------------------------------------------- async function _loadAll() { // Alles zurücksetzen Object.values(_layers).flat().forEach(m => m.remove?.()); _layers = { restaurant: [], freilauf: [], shop: [], kotbeutel: [], tierarzt: [], hundeschule: [], poison: [] }; // Parallel laden const [places, poisonList] = await Promise.allSettled([ API.places.list(), _userPos ? API.poison.listNearby(_userPos.lat, _userPos.lon, 10000) : Promise.resolve([]), ]); if (places.status === 'fulfilled') _addPlaces(places.value); if (poisonList.status === 'fulfilled') _addPoison(poisonList.value); } // ---------------------------------------------------------- // Orte-Marker // ---------------------------------------------------------- function _addPlaces(places) { if (!_map || !window.L) return; places.forEach(place => { const t = TYPEN[place.typ]; if (!t) return; const m = _createMarker(place.lat, place.lon, t, place.name, () => _showPlacePopup(place)); _layers[place.typ]?.push(m); if (!_visible[place.typ]) m.setOpacity(0); }); } function _showPlacePopup(place) { const t = TYPEN[place.typ] || { icon: '📍', label: place.typ }; UI.toast.info(`${t.icon} ${place.name}${place.adresse ? ' · ' + place.adresse : ''}`); } // ---------------------------------------------------------- // Giftköder-Marker // ---------------------------------------------------------- function _addPoison(items) { if (!_map || !window.L) return; items.forEach(p => { const t = TYPEN.poison; const m = _createMarker(p.lat, p.lon, t, `Giftköder-Alarm${p.beschreibung ? ': ' + p.beschreibung : ''}`, () => { App.navigate('poison'); }); _layers.poison.push(m); if (!_visible.poison) m.setOpacity(0); }); } // ---------------------------------------------------------- // Marker-Hilfsfunktion // ---------------------------------------------------------- function _createMarker(lat, lon, t, tooltip, onClick) { const icon = L.divIcon({ className: '', html: `
${t.icon}
`, iconSize: [32, 32], iconAnchor: [16, 16], }); return L.marker([lat, lon], { icon }) .addTo(_map) .bindTooltip(tooltip, { direction: 'top', offset: [0, -16] }) .on('click', onClick); } // ---------------------------------------------------------- // Layer ein/ausblenden // ---------------------------------------------------------- function _applyVisibility(layer) { (_layers[layer] || []).forEach(m => { // Leaflet hat keine native hide — Opacity-Trick if (m.setOpacity) m.setOpacity(_visible[layer] ? 1 : 0); }); } return { init, refresh, onDogChange }; })();