558 lines
22 KiB
JavaScript
558 lines
22 KiB
JavaScript
/* ============================================================
|
||
BAN YARO — Events (Hundeveranstaltungen)
|
||
Liste/Karte · Filter · Erstellen/Bearbeiten
|
||
============================================================ */
|
||
|
||
window.Page_events = (() => {
|
||
|
||
// ----------------------------------------------------------
|
||
// Konstanten
|
||
// ----------------------------------------------------------
|
||
const TYPEN = [
|
||
{ id: 'alle', label: 'Alle', icon: '🎪' },
|
||
{ id: 'ausstellung', label: 'Ausstellung', icon: '🏆' },
|
||
{ id: 'training', label: 'Training', icon: '🎓' },
|
||
{ id: 'treffen', label: 'Treffen', icon: '🐕' },
|
||
{ id: 'markt', label: 'Markt', icon: '🛍️' },
|
||
{ id: 'wettkampf', label: 'Wettkampf', icon: '🥇' },
|
||
{ id: 'sonstiges', label: 'Sonstiges', icon: '📌' },
|
||
];
|
||
|
||
const TYP_COLOR = {
|
||
ausstellung: '#8b5cf6',
|
||
training: '#3b82f6',
|
||
treffen: '#10b981',
|
||
markt: '#f59e0b',
|
||
wettkampf: '#ef4444',
|
||
sonstiges: '#6b7280',
|
||
};
|
||
|
||
// ----------------------------------------------------------
|
||
// 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 _clusterGroup = null;
|
||
|
||
// ----------------------------------------------------------
|
||
// Phosphor-Icon-Helper
|
||
// ----------------------------------------------------------
|
||
function _icon(name) {
|
||
return `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// init
|
||
// ----------------------------------------------------------
|
||
async function init(container, appState) {
|
||
_container = container;
|
||
_state = appState;
|
||
_render();
|
||
await _load();
|
||
}
|
||
|
||
async function refresh() {
|
||
await _load();
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Render Grundstruktur
|
||
// ----------------------------------------------------------
|
||
function _render() {
|
||
_container.innerHTML = `
|
||
<div class="by-toolbar">
|
||
<div class="events-view-toggle">
|
||
<button class="events-view-btn active" data-ev-view="liste">${UI.icon('list')} Liste</button>
|
||
<button class="events-view-btn" data-ev-view="karte">${UI.icon('map-trifold')} Karte</button>
|
||
</div>
|
||
<div style="flex:1"></div>
|
||
${_state.user ? `<button class="btn btn-primary btn-sm" id="ev-new-btn">${UI.icon('plus')} Event</button>` : ''}
|
||
</div>
|
||
|
||
<div class="events-filter-bar by-tabs" id="ev-filter-bar">
|
||
${TYPEN.map(t => `
|
||
<button class="by-tab ${t.id === 'alle' ? 'active' : ''}" data-ev-typ="${t.id}">
|
||
${t.icon} ${t.label}
|
||
</button>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="events-source-bar by-tabs" id="ev-source-bar">
|
||
<button class="by-tab active" data-ev-quelle="alle">Alle Quellen</button>
|
||
<button class="by-tab" data-ev-quelle="vdh">
|
||
<span class="ev-vdh-badge">VDH</span> VDH-Events
|
||
</button>
|
||
<button class="by-tab" data-ev-quelle="nutzer">Von Nutzern</button>
|
||
</div>
|
||
|
||
<div class="events-list" id="ev-list"></div>
|
||
<div class="events-map" id="ev-map" style="display:none"></div>
|
||
`;
|
||
|
||
_container.addEventListener('click', _onClick);
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Daten laden
|
||
// ----------------------------------------------------------
|
||
async function _load() {
|
||
const listEl = document.getElementById('ev-list');
|
||
if (!listEl) return;
|
||
listEl.innerHTML = UI.skeleton(3);
|
||
try {
|
||
_events = await API.events.list();
|
||
_renderList();
|
||
} catch (e) {
|
||
UI.toast(e.message, 'error');
|
||
}
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Gefilterte Events ermitteln
|
||
// ----------------------------------------------------------
|
||
function _filtered() {
|
||
let evs = _filter === 'alle' ? _events : _events.filter(e => e.typ === _filter);
|
||
if (_quellFilter !== 'alle') {
|
||
evs = evs.filter(e => (e.quelle || 'nutzer') === _quellFilter);
|
||
}
|
||
return evs;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Liste rendern
|
||
// ----------------------------------------------------------
|
||
function _renderList() {
|
||
const listEl = document.getElementById('ev-list');
|
||
if (!listEl) return;
|
||
|
||
const filtered = _filtered();
|
||
if (!filtered.length) {
|
||
listEl.innerHTML = UI.emptyState({ icon: '🎪', title: 'Keine Events', text: 'Noch keine Veranstaltungen geplant.' });
|
||
return;
|
||
}
|
||
|
||
// Monats-Gruppierung
|
||
const groups = {};
|
||
for (const ev of filtered) {
|
||
const d = new Date(ev.datum + 'T00:00:00');
|
||
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
||
const label = d.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
|
||
if (!groups[key]) groups[key] = { label, items: [] };
|
||
groups[key].items.push(ev);
|
||
}
|
||
|
||
let html = '';
|
||
for (const key of Object.keys(groups).sort()) {
|
||
const g = groups[key];
|
||
html += `<div class="events-month-label">${g.label}</div>`;
|
||
for (const ev of g.items) {
|
||
html += _cardHTML(ev);
|
||
}
|
||
}
|
||
listEl.innerHTML = html;
|
||
}
|
||
|
||
function _cardHTML(ev) {
|
||
const d = new Date(ev.datum + 'T00:00:00');
|
||
const dow = d.toLocaleDateString('de-DE', { weekday: 'short' });
|
||
const day = d.getDate();
|
||
const mon = d.toLocaleDateString('de-DE', { month: 'short' });
|
||
const typ = TYPEN.find(t => t.id === ev.typ) || TYPEN[TYPEN.length - 1];
|
||
const color = TYP_COLOR[ev.typ] || '#6b7280';
|
||
const isOwn = _state.user?.id === ev.user_id;
|
||
const isVdh = ev.quelle === 'vdh';
|
||
|
||
return `
|
||
<div class="events-card" data-ev-id="${ev.id}" style="border-left-color:${color}">
|
||
<div class="events-date-badge">
|
||
<span class="day">${dow}</span>
|
||
<span class="num">${day}</span>
|
||
<span class="month">${mon}</span>
|
||
</div>
|
||
<div class="events-card-body">
|
||
<div class="events-card-title">
|
||
${UI.escHtml(ev.titel)}
|
||
${isVdh ? `<span class="ev-vdh-badge" title="Vom VDH importiert">VDH</span>` : ''}
|
||
</div>
|
||
<div class="events-card-meta">
|
||
<span class="events-badge" style="background:${color}20;color:${color}">${typ.icon} ${typ.label}</span>
|
||
${ev.uhrzeit ? `· ${_icon('clock')} ${ev.uhrzeit} Uhr` : ''}
|
||
${ev.ort_name ? `· ${_icon('map-pin')} ${UI.escHtml(ev.ort_name)}` : ''}
|
||
</div>
|
||
${ev.link ? `<div class="events-card-actions">
|
||
<a class="btn btn-ghost btn-xs ev-ext-link" href="${UI.escHtml(ev.link)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">
|
||
${_icon('arrow-square-out')} Details
|
||
</a>
|
||
</div>` : ''}
|
||
</div>
|
||
${isOwn ? `<button class="btn-icon" data-ev-edit="${ev.id}" title="Bearbeiten">${_icon('pencil-simple')}</button>` : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Karte
|
||
// ----------------------------------------------------------
|
||
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);
|
||
}
|
||
|
||
// 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 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]);
|
||
}
|
||
|
||
_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 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);
|
||
});
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Detail-Modal
|
||
// ----------------------------------------------------------
|
||
async function _showDetail(id) {
|
||
let ev;
|
||
try { ev = await API.events.get(id); } catch { return; }
|
||
|
||
const typ = TYPEN.find(t => t.id === ev.typ) || TYPEN[TYPEN.length - 1];
|
||
const color = TYP_COLOR[ev.typ] || '#6b7280';
|
||
const d = new Date(ev.datum + 'T00:00:00');
|
||
const datum = d.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
|
||
const isOwn = _state.user?.id === ev.user_id;
|
||
const isVdh = ev.quelle === 'vdh';
|
||
|
||
const body = `
|
||
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)">
|
||
<span class="events-badge" style="background:${color}20;color:${color};font-size:var(--text-sm)">${typ.icon} ${typ.label}</span>
|
||
${isVdh ? `<span class="ev-vdh-badge">VDH</span>` : ''}
|
||
</div>
|
||
<div class="events-detail-row">${_icon('calendar-dots')} ${datum}${ev.uhrzeit ? ' · ' + ev.uhrzeit + ' Uhr' : ''}</div>
|
||
${ev.ort_name ? `<div class="events-detail-row">${_icon('map-pin')} ${UI.escHtml(ev.ort_name)}</div>` : ''}
|
||
${ev.beschreibung ? `<div class="events-detail-desc">${UI.escHtml(ev.beschreibung)}</div>` : ''}
|
||
${ev.link ? `<div class="events-detail-row">
|
||
${_icon('arrow-square-out')}
|
||
<a href="${UI.escHtml(ev.link)}" target="_blank" rel="noopener">Mehr Infos</a>
|
||
</div>` : ''}
|
||
<div class="events-detail-row" style="color:var(--c-text-muted);font-size:var(--text-xs)">
|
||
${_icon('user')} Veranstalter: ${UI.escHtml(ev.veranstalter_name || '–')}
|
||
</div>
|
||
`;
|
||
|
||
const footer = isOwn ? `
|
||
<button class="btn btn-secondary" id="ev-detail-edit">${_icon('pencil-simple')} Bearbeiten</button>
|
||
<button class="btn btn-danger" id="ev-detail-del">${_icon('trash')} Löschen</button>
|
||
` : (ev.link ? `
|
||
<a class="btn btn-primary" href="${UI.escHtml(ev.link)}" target="_blank" rel="noopener">
|
||
${_icon('arrow-square-out')} Zur Veranstaltung
|
||
</a>
|
||
` : '');
|
||
|
||
UI.modal.open({ title: UI.escHtml(ev.titel), body, footer });
|
||
|
||
document.getElementById('ev-detail-edit')?.addEventListener('click', () => {
|
||
UI.modal.close(); setTimeout(() => _openForm(ev), 50);
|
||
});
|
||
document.getElementById('ev-detail-del')?.addEventListener('click', () => _deleteEvent(ev));
|
||
}
|
||
|
||
async function _deleteEvent(ev) {
|
||
if (!confirm(`"${ev.titel}" wirklich löschen?`)) return;
|
||
try {
|
||
await API.events.delete(ev.id);
|
||
UI.modal.close();
|
||
UI.toast('Event gelöscht.');
|
||
await _load();
|
||
} catch (e) { UI.toast(e.message, 'error'); }
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Erstellen / Bearbeiten
|
||
// ----------------------------------------------------------
|
||
function openNew() { _openForm(null); }
|
||
|
||
function _openForm(ev) {
|
||
const isEdit = !!ev;
|
||
const id = 'ev-form';
|
||
const body = `
|
||
<form id="${id}">
|
||
<div class="form-group">
|
||
<label class="form-label">Titel *</label>
|
||
<input class="form-control" name="titel" required value="${ev ? UI.escHtml(ev.titel) : ''}">
|
||
</div>
|
||
<div class="form-row-2">
|
||
<div class="form-group">
|
||
<label class="form-label">Datum *</label>
|
||
<input class="form-control" type="date" name="datum" required value="${ev?.datum || ''}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Uhrzeit</label>
|
||
<input class="form-control" type="time" name="uhrzeit" value="${ev?.uhrzeit || ''}">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Typ *</label>
|
||
<select class="form-control" name="typ">
|
||
${TYPEN.filter(t => t.id !== 'alle').map(t =>
|
||
`<option value="${t.id}" ${ev?.typ === t.id ? 'selected' : ''}>${t.icon} ${t.label}</option>`
|
||
).join('')}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Ort / Veranstaltungsort</label>
|
||
<input class="form-control" name="ort_name" placeholder="z.B. Stadtpark München" value="${ev?.ort_name || ''}">
|
||
</div>
|
||
<div class="form-row-2">
|
||
<div class="form-group">
|
||
<label class="form-label">Breitengrad</label>
|
||
<input class="form-control" type="number" step="any" name="lat" id="ev-lat" placeholder="48.1234" value="${ev?.lat || ''}">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Längengrad</label>
|
||
<input class="form-control" type="number" step="any" name="lon" id="ev-lon" placeholder="11.5678" value="${ev?.lon || ''}">
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-secondary btn-sm" id="ev-gps-btn">${_icon('map-pin')} GPS-Position</button>
|
||
<div class="form-group" style="margin-top:var(--space-3)">
|
||
<label class="form-label">Beschreibung</label>
|
||
<textarea class="form-control" name="beschreibung" rows="3">${ev?.beschreibung || ''}</textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Link / Website</label>
|
||
<input class="form-control" type="url" name="link" placeholder="https://..." value="${ev?.link || ''}">
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
const footer = `
|
||
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
|
||
<button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn">
|
||
${isEdit ? 'Speichern' : 'Event erstellen'}
|
||
</button>
|
||
`;
|
||
|
||
UI.modal.open({ title: isEdit ? 'Event bearbeiten' : 'Neues Event', body, footer });
|
||
|
||
document.getElementById('ev-gps-btn')?.addEventListener('click', async () => {
|
||
try {
|
||
const pos = await API.getLocation();
|
||
document.getElementById('ev-lat').value = pos.lat.toFixed(6);
|
||
document.getElementById('ev-lon').value = pos.lon.toFixed(6);
|
||
} catch { UI.toast('GPS nicht verfügbar.', 'error'); }
|
||
});
|
||
|
||
const form = document.getElementById(id);
|
||
const submitBtn = document.querySelector(`[form="${id}"][type="submit"]`) || form.querySelector('[type="submit"]');
|
||
|
||
form.addEventListener('submit', async e => {
|
||
e.preventDefault();
|
||
const fd = new FormData(form);
|
||
const data = {
|
||
titel: fd.get('titel'),
|
||
datum: fd.get('datum'),
|
||
uhrzeit: fd.get('uhrzeit') || null,
|
||
typ: fd.get('typ'),
|
||
ort_name: fd.get('ort_name') || null,
|
||
lat: fd.get('lat') ? parseFloat(fd.get('lat')) : null,
|
||
lon: fd.get('lon') ? parseFloat(fd.get('lon')) : null,
|
||
beschreibung: fd.get('beschreibung') || null,
|
||
link: fd.get('link') || null,
|
||
};
|
||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '…'; }
|
||
try {
|
||
isEdit ? await API.events.update(ev.id, data) : await API.events.create(data);
|
||
UI.modal.close();
|
||
UI.toast(isEdit ? 'Event aktualisiert.' : 'Event erstellt!');
|
||
await _load();
|
||
} catch (err) {
|
||
UI.toast(err.message, 'error');
|
||
} finally {
|
||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = isEdit ? 'Speichern' : 'Event erstellen'; }
|
||
}
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Click-Handler
|
||
// ----------------------------------------------------------
|
||
function _onClick(e) {
|
||
// Quelle-Filter
|
||
const sourceBtn = e.target.closest('[data-ev-quelle]');
|
||
if (sourceBtn) {
|
||
_quellFilter = sourceBtn.dataset.evQuelle;
|
||
document.querySelectorAll('[data-ev-quelle]').forEach(b => b.classList.toggle('active', b.dataset.evQuelle === _quellFilter));
|
||
if (_view === 'karte') { _renderMap(_filtered()); } else { _renderList(); }
|
||
return;
|
||
}
|
||
|
||
// Typ-Filter
|
||
const filterBtn = e.target.closest('[data-ev-typ]');
|
||
if (filterBtn) {
|
||
_filter = filterBtn.dataset.evTyp;
|
||
document.querySelectorAll('[data-ev-typ]').forEach(b => b.classList.toggle('active', b.dataset.evTyp === _filter));
|
||
if (_view === 'karte') { _renderMap(_filtered()); } else { _renderList(); }
|
||
return;
|
||
}
|
||
|
||
// View-Toggle
|
||
const viewBtn = e.target.closest('[data-ev-view]');
|
||
if (viewBtn) {
|
||
_view = viewBtn.dataset.evView;
|
||
document.querySelectorAll('[data-ev-view]').forEach(b => b.classList.toggle('active', b.dataset.evView === _view));
|
||
const listEl = document.getElementById('ev-list');
|
||
const mapEl = document.getElementById('ev-map');
|
||
if (_view === 'karte') {
|
||
listEl.style.display = 'none';
|
||
mapEl.style.display = 'block';
|
||
// Erst div sichtbar machen, dann Karte initialisieren
|
||
_renderMap(_filtered());
|
||
} else {
|
||
// Karte sauber entfernen
|
||
if (_map) {
|
||
_map.remove();
|
||
_map = null;
|
||
_clusterGroup = null;
|
||
_markers = [];
|
||
}
|
||
mapEl.style.display = 'none';
|
||
listEl.style.display = '';
|
||
_renderList();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Neu-Button
|
||
if (e.target.closest('#ev-new-btn')) { openNew(); return; }
|
||
|
||
// Externer Link — nicht als Karten-Klick behandeln
|
||
if (e.target.closest('.ev-ext-link')) return;
|
||
|
||
// Bearbeiten-Icon auf Karte
|
||
const editBtn = e.target.closest('[data-ev-edit]');
|
||
if (editBtn) {
|
||
e.stopPropagation();
|
||
const id = parseInt(editBtn.dataset.evEdit);
|
||
const ev = _events.find(x => x.id === id);
|
||
if (ev) _openForm(ev);
|
||
return;
|
||
}
|
||
|
||
// Karten-Klick → Detail
|
||
const card = e.target.closest('[data-ev-id]');
|
||
if (card) { _showDetail(parseInt(card.dataset.evId)); }
|
||
}
|
||
|
||
return { init, refresh, openNew, _openDetail: _showDetail };
|
||
|
||
})();
|