Sprint 18: Lost-Dog CSS, Freunde-Aktivitäts-Feed, Events-Karte
This commit is contained in:
parent
cfdb3fbc19
commit
10d30bf565
8 changed files with 595 additions and 41 deletions
|
|
@ -30,14 +30,15 @@ window.Page_events = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// State
|
||||
// ----------------------------------------------------------
|
||||
let _container = null;
|
||||
let _state = null;
|
||||
let _events = [];
|
||||
let _filter = 'alle';
|
||||
let _quellFilter = 'alle'; // 'alle' | 'vdh' | 'nutzer'
|
||||
let _view = 'liste'; // liste | karte
|
||||
let _map = null;
|
||||
let _markers = [];
|
||||
let _container = null;
|
||||
let _state = null;
|
||||
let _events = [];
|
||||
let _filter = 'alle';
|
||||
let _quellFilter = 'alle'; // 'alle' | 'vdh' | 'nutzer'
|
||||
let _view = 'liste'; // liste | karte
|
||||
let _map = null;
|
||||
let _markers = [];
|
||||
let _clusterGroup = null;
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Phosphor-Icon-Helper
|
||||
|
|
@ -155,8 +156,6 @@ window.Page_events = (() => {
|
|||
}
|
||||
}
|
||||
listEl.innerHTML = html;
|
||||
|
||||
if (_view === 'karte') _renderMap(filtered);
|
||||
}
|
||||
|
||||
function _cardHTML(ev) {
|
||||
|
|
@ -203,48 +202,120 @@ window.Page_events = (() => {
|
|||
async function _renderMap(filtered) {
|
||||
const mapEl = document.getElementById('ev-map');
|
||||
if (!mapEl) return;
|
||||
|
||||
await _loadLeaflet();
|
||||
await _loadMarkerCluster();
|
||||
|
||||
if (!_map) {
|
||||
_map = L.map('ev-map', { zoomControl: true }).setView([51.1657, 10.4515], 6);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_map);
|
||||
} else {
|
||||
_markers.forEach(m => m.remove());
|
||||
_markers = [];
|
||||
}
|
||||
|
||||
// Cluster-Gruppe aufräumen und neu befüllen
|
||||
if (_clusterGroup) {
|
||||
_map.removeLayer(_clusterGroup);
|
||||
}
|
||||
_clusterGroup = L.markerClusterGroup();
|
||||
_markers = [];
|
||||
|
||||
const bounds = [];
|
||||
for (const ev of filtered) {
|
||||
if (!ev.lat || !ev.lon) continue;
|
||||
const color = TYP_COLOR[ev.typ] || '#6b7280';
|
||||
const typ = TYPEN.find(t => t.id === ev.typ) || TYPEN[TYPEN.length - 1];
|
||||
const d = new Date(ev.datum + 'T00:00:00');
|
||||
const datum = d.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="width:32px;height:32px;border-radius:50% 50% 50% 0;background:${color};border:2px solid #fff;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transform:rotate(-45deg)"><span style="transform:rotate(45deg)">${typ.icon}</span></div>`,
|
||||
iconSize: [32, 32], iconAnchor: [16, 32],
|
||||
});
|
||||
const m = L.marker([ev.lat, ev.lon], { icon })
|
||||
.addTo(_map)
|
||||
.on('click', () => _showDetail(ev.id));
|
||||
const popup = `
|
||||
<div style="min-width:180px">
|
||||
<strong>${UI.escHtml(ev.titel)}</strong><br>
|
||||
<span style="color:#666;font-size:12px">${datum}</span><br>
|
||||
${ev.ort_name ? `<span style="font-size:12px">📍 ${UI.escHtml(ev.ort_name)}</span><br>` : ''}
|
||||
${ev.beschreibung ? `<span style="font-size:12px">${UI.escHtml(ev.beschreibung.slice(0, 80))}${ev.beschreibung.length > 80 ? '…' : ''}</span><br>` : ''}
|
||||
<a href="#" onclick="event.preventDefault();Page_events._openDetail(${ev.id})"
|
||||
style="font-size:12px;color:var(--c-primary,#2563eb)">Details</a>
|
||||
</div>
|
||||
`;
|
||||
const m = L.marker([ev.lat, ev.lon], { icon }).bindPopup(popup);
|
||||
_clusterGroup.addLayer(m);
|
||||
_markers.push(m);
|
||||
bounds.push([ev.lat, ev.lon]);
|
||||
}
|
||||
|
||||
if (bounds.length) _map.fitBounds(bounds, { padding: [40, 40], maxZoom: 12 });
|
||||
setTimeout(() => _map.invalidateSize(), 50);
|
||||
_map.addLayer(_clusterGroup);
|
||||
|
||||
if (bounds.length) {
|
||||
_map.fitBounds(bounds, { padding: [40, 40], maxZoom: 12 });
|
||||
} else {
|
||||
// Versuche Nutzerstandort, sonst Deutschland-Übersicht
|
||||
try {
|
||||
const pos = await API.getLocation({ timeout: 5000 });
|
||||
_map.setView([pos.lat, pos.lon], 10);
|
||||
} catch {
|
||||
_map.setView([51.1657, 10.4515], 6);
|
||||
}
|
||||
}
|
||||
|
||||
_map.invalidateSize();
|
||||
setTimeout(() => _map.invalidateSize(), 100);
|
||||
}
|
||||
|
||||
function _loadLeaflet() {
|
||||
if (window.L) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = '/css/leaflet.css';
|
||||
document.head.appendChild(link);
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.js';
|
||||
s.onload = resolve;
|
||||
s.onerror = reject;
|
||||
document.head.appendChild(s);
|
||||
const cssLoaded = document.querySelector('link[href*="leaflet"]')
|
||||
? Promise.resolve()
|
||||
: new Promise(res => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = '/css/leaflet.css';
|
||||
link.onload = res;
|
||||
link.onerror = res;
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
cssLoaded.then(() => {
|
||||
if (document.querySelector('script[src*="leaflet.js"]')) { resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.js';
|
||||
s.onload = resolve;
|
||||
s.onerror = reject;
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _loadMarkerCluster() {
|
||||
if (window.L && L.markerClusterGroup) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
const cssLoaded = document.querySelector('link[href*="MarkerCluster"]')
|
||||
? Promise.resolve()
|
||||
: new Promise(res => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = '/css/MarkerCluster.css';
|
||||
link.onload = res;
|
||||
link.onerror = res;
|
||||
document.head.appendChild(link);
|
||||
const link2 = document.createElement('link');
|
||||
link2.rel = 'stylesheet';
|
||||
link2.href = '/css/MarkerCluster.Default.css';
|
||||
link2.onload = res;
|
||||
link2.onerror = res;
|
||||
document.head.appendChild(link2);
|
||||
});
|
||||
cssLoaded.then(() => {
|
||||
if (document.querySelector('script[src*="markercluster"]') ||
|
||||
document.querySelector('script[src*="MarkerCluster"]')) { resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.markercluster.js';
|
||||
s.onload = resolve;
|
||||
s.onerror = resolve; // Cluster ist optional — graceful degradation
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -421,7 +492,7 @@ window.Page_events = (() => {
|
|||
if (sourceBtn) {
|
||||
_quellFilter = sourceBtn.dataset.evQuelle;
|
||||
document.querySelectorAll('[data-ev-quelle]').forEach(b => b.classList.toggle('active', b.dataset.evQuelle === _quellFilter));
|
||||
_renderList();
|
||||
if (_view === 'karte') { _renderMap(_filtered()); } else { _renderList(); }
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +501,7 @@ window.Page_events = (() => {
|
|||
if (filterBtn) {
|
||||
_filter = filterBtn.dataset.evTyp;
|
||||
document.querySelectorAll('[data-ev-typ]').forEach(b => b.classList.toggle('active', b.dataset.evTyp === _filter));
|
||||
_renderList();
|
||||
if (_view === 'karte') { _renderMap(_filtered()); } else { _renderList(); }
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -444,10 +515,19 @@ window.Page_events = (() => {
|
|||
if (_view === 'karte') {
|
||||
listEl.style.display = 'none';
|
||||
mapEl.style.display = 'block';
|
||||
// Erst div sichtbar machen, dann Karte initialisieren
|
||||
_renderMap(_filtered());
|
||||
} else {
|
||||
listEl.style.display = '';
|
||||
// Karte sauber entfernen
|
||||
if (_map) {
|
||||
_map.remove();
|
||||
_map = null;
|
||||
_clusterGroup = null;
|
||||
_markers = [];
|
||||
}
|
||||
mapEl.style.display = 'none';
|
||||
listEl.style.display = '';
|
||||
_renderList();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -473,6 +553,6 @@ window.Page_events = (() => {
|
|||
if (card) { _showDetail(parseInt(card.dataset.evId)); }
|
||||
}
|
||||
|
||||
return { init, refresh, openNew };
|
||||
return { init, refresh, openNew, _openDetail: _showDetail };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue