Refactor: UI.loadLeaflet, leafletMarker, escape, emptyState, locationPicker zentralisiert
- Task 1: UI.loadLeaflet() in ui.js (mit Cluster-Option), lokale _loadLeaflet() in diary/walks/routes/places/poison/events.js entfernt - Task 2: UI.escape() ersetzt lokale _esc()/_escape() in allen 5 Seiten-Modulen - Task 3: UI.emptyState() ersetzt lokale _emptyState() in diary/routes/events.js - Task 4: _fmtDate/_fmtDateShort in walks/poison bewusst behalten (anderes Format), Kommentare ergänzt - Task 5: UI.locationPicker() eingebaut in places/poison/events (ersetzt manuelle GPS-Input-Blöcke) - Task 6: UI.leafletMarker() factory in ui.js, Kreis-divIcon-Blöcke in walks/places/ poison ersetzt; events.js behält Diamant-Marker (andere Form) - SW by-v207, APP_VER 175
This commit is contained in:
parent
066b722c5e
commit
e98ce0d232
9 changed files with 761 additions and 471 deletions
|
|
@ -48,16 +48,7 @@ window.Page_events = (() => {
|
|||
return `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
|
||||
}
|
||||
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
return `<div class="empty-state">
|
||||
<svg class="ph-icon empty-state-icon" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${icon}"></use>
|
||||
</svg>
|
||||
<div class="empty-state-title">${title}</div>
|
||||
${text ? `<p class="empty-state-text">${text}</p>` : ''}
|
||||
${cta ? `<div class="empty-state-cta">${cta}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
// _emptyState ersetzt durch UI.emptyState()
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// init
|
||||
|
|
@ -145,11 +136,11 @@ window.Page_events = (() => {
|
|||
|
||||
const filtered = _filtered();
|
||||
if (!filtered.length) {
|
||||
listEl.innerHTML = _emptyState(
|
||||
'calendar-blank',
|
||||
'Keine Events in der Nähe',
|
||||
'Hier erscheinen Hundeveranstaltungen, Treffen und Aktivitäten in deiner Umgebung.'
|
||||
);
|
||||
listEl.innerHTML = UI.emptyState({
|
||||
icon: UI.icon('calendar-blank'),
|
||||
title: 'Keine Events in der Nähe',
|
||||
text: 'Hier erscheinen Hundeveranstaltungen, Treffen und Aktivitäten in deiner Umgebung.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -220,8 +211,7 @@ window.Page_events = (() => {
|
|||
const mapEl = document.getElementById('ev-map');
|
||||
if (!mapEl) return;
|
||||
|
||||
await _loadLeaflet();
|
||||
await _loadMarkerCluster();
|
||||
await UI.loadLeaflet(true); // true = mit MarkerCluster
|
||||
|
||||
if (!_map) {
|
||||
_map = L.map('ev-map', { zoomControl: true }).setView([51.1657, 10.4515], 6);
|
||||
|
|
@ -242,6 +232,7 @@ window.Page_events = (() => {
|
|||
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' });
|
||||
// Events nutzen rotierten Diamant-Marker (nicht Kreis) — UI.leafletMarker() nicht anwendbar
|
||||
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>`,
|
||||
|
|
@ -281,60 +272,7 @@ window.Page_events = (() => {
|
|||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
// _loadLeaflet und _loadMarkerCluster ersetzt durch UI.loadLeaflet(true)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Detail-Modal
|
||||
|
|
@ -520,17 +458,10 @@ window.Page_events = (() => {
|
|||
<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 class="form-group">
|
||||
<label class="form-label">GPS-Position</label>
|
||||
<div id="ev-location-picker"></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>
|
||||
|
|
@ -563,30 +494,31 @@ window.Page_events = (() => {
|
|||
if (ok) await _deleteEvent(ev);
|
||||
});
|
||||
|
||||
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'); }
|
||||
// Location-Picker initialisieren
|
||||
const _picker = UI.locationPicker({
|
||||
containerId: 'ev-location-picker',
|
||||
});
|
||||
if (ev?.lat && ev?.lon) {
|
||||
_picker.setValue(ev.lat, ev.lon, ev.ort_name || null);
|
||||
}
|
||||
|
||||
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 fd = new FormData(form);
|
||||
const loc = _picker.getValue();
|
||||
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,
|
||||
titel: fd.get('titel'),
|
||||
datum: fd.get('datum'),
|
||||
uhrzeit: fd.get('uhrzeit') || null,
|
||||
typ: fd.get('typ'),
|
||||
ort_name: loc.name || fd.get('ort_name') || null,
|
||||
lat: loc.lat || null,
|
||||
lon: loc.lon || null,
|
||||
beschreibung: fd.get('beschreibung') || null,
|
||||
link: fd.get('link') || null,
|
||||
link: fd.get('link') || null,
|
||||
};
|
||||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '…'; }
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue