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
|
|
@ -11,21 +11,20 @@ window.Page_walks = (() => {
|
|||
let _view = 'liste'; // 'liste' | 'karte'
|
||||
let _map = null;
|
||||
let _markers = [];
|
||||
let _leafletLoaded = false;
|
||||
let _userPos = null;
|
||||
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
// _esc ersetzt durch UI.escape()
|
||||
|
||||
// Datum deutsch formatieren: "2026-04-20" → "Sonntag, 20. April 2026"
|
||||
// Hinweis: UI.time.format() liefert kein weekday — daher lokale Funktion beibehalten
|
||||
function _fmtDate(iso) {
|
||||
if (!iso) return '—';
|
||||
const d = new Date(iso + 'T12:00:00');
|
||||
return d.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
|
||||
}
|
||||
|
||||
// Datum kurz: "So, 20.04."
|
||||
// Datum kurz: "So, 20.04." — UI.time.formatShort() gibt "20. Apr." ohne Wochentag
|
||||
// Hinweis: Format nicht äquivalent zu UI.time.formatShort() — daher lokal beibehalten
|
||||
function _fmtDateShort(iso) {
|
||||
if (!iso) return '—';
|
||||
const d = new Date(iso + 'T12:00:00');
|
||||
|
|
@ -116,7 +115,7 @@ window.Page_walks = (() => {
|
|||
document.getElementById('walks-map-view').style.display = view === 'karte' ? '' : 'none';
|
||||
|
||||
if (view === 'karte') {
|
||||
_loadLeaflet().then(() => {
|
||||
UI.loadLeaflet().then(() => {
|
||||
_initMap();
|
||||
setTimeout(() => _map?.invalidateSize(), 150);
|
||||
setTimeout(() => _map?.invalidateSize(), 400);
|
||||
|
|
@ -196,8 +195,8 @@ window.Page_walks = (() => {
|
|||
<div class="walks-card-time">${w.uhrzeit}</div>
|
||||
</div>
|
||||
<div class="walks-card-body">
|
||||
<div class="walks-card-title">${_esc(w.titel)}</div>
|
||||
${w.ort_name ? `<div class="walks-card-ort">${UI.icon('map-pin')} ${_esc(w.ort_name)}</div>` : ''}
|
||||
<div class="walks-card-title">${UI.escape(w.titel)}</div>
|
||||
${w.ort_name ? `<div class="walks-card-ort">${UI.icon('map-pin')} ${UI.escape(w.ort_name)}</div>` : ''}
|
||||
<div class="walks-card-meta">
|
||||
<span class="walks-badge ${isFull ? 'walks-badge--full' : 'walks-badge--open'}">
|
||||
${isFull ? '🔴 Voll' : `🟢 ${spots} Platz${spots !== 1 ? 'e' : ''} frei`}
|
||||
|
|
@ -213,28 +212,6 @@ window.Page_walks = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Leaflet + Karte
|
||||
// ----------------------------------------------------------
|
||||
function _loadLeaflet() {
|
||||
if (window.L) { _leafletLoaded = true; 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"]')) { _leafletLoaded = true; resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.js';
|
||||
s.onload = () => { _leafletLoaded = true; resolve(); };
|
||||
s.onerror = reject;
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _initMap() {
|
||||
const el = document.getElementById('walks-map');
|
||||
if (!el || !window.L || _map) return;
|
||||
|
|
@ -253,15 +230,7 @@ window.Page_walks = (() => {
|
|||
if (!w.lat || !w.lon) return;
|
||||
const isFull = w.status === 'voll' || w.teilnehmer_count >= w.max_teilnehmer;
|
||||
const color = _isToday(w.datum) ? 'var(--c-primary)' : (isFull ? '#6B7280' : '#22C55E');
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="background:${color};color:#fff;font-size:14px;font-weight:700;
|
||||
width:32px;height:32px;border-radius:50%;display:flex;align-items:center;
|
||||
justify-content:center;box-shadow:0 2px 5px rgba(0,0,0,0.3);
|
||||
border:2px solid rgba(255,255,255,0.8)">${UI.icon('dog')}</div>`,
|
||||
iconSize: [32, 32], iconAnchor: [16, 16],
|
||||
});
|
||||
const m = L.marker([w.lat, w.lon], { icon })
|
||||
const m = UI.leafletMarker({ lat: w.lat, lon: w.lon, color, icon: UI.icon('dog') })
|
||||
.addTo(_map)
|
||||
.bindTooltip(`${w.titel} · ${_fmtDateShort(w.datum)} ${w.uhrzeit}`, { direction: 'top', offset: [0,-16] })
|
||||
.on('click', () => _openDetail(w.id));
|
||||
|
|
@ -292,8 +261,8 @@ window.Page_walks = (() => {
|
|||
<div class="walks-invitation-row">
|
||||
<div class="walks-inv-avatar">${_avatarInitials(inv.user_name)}</div>
|
||||
<div class="walks-inv-info">
|
||||
<div class="walks-inv-name">${_esc(inv.user_name)}</div>
|
||||
${inv.hunde ? `<div class="walks-inv-hunde">${UI.icon('dog')} ${_esc(inv.hunde)}</div>` : ''}
|
||||
<div class="walks-inv-name">${UI.escape(inv.user_name)}</div>
|
||||
${inv.hunde ? `<div class="walks-inv-hunde">${UI.icon('dog')} ${UI.escape(inv.hunde)}</div>` : ''}
|
||||
</div>
|
||||
<div class="walks-inv-badge">${_rsvpBadge(inv.status)}</div>
|
||||
</div>`;
|
||||
|
|
@ -328,8 +297,8 @@ window.Page_walks = (() => {
|
|||
? walk.teilnehmer.map(t => `
|
||||
<div class="walks-participant">
|
||||
<div class="walks-inv-avatar walks-inv-avatar--sm">${_avatarInitials(t.user_name)}</div>
|
||||
<span class="walks-participant-name">${_esc(t.user_name)}</span>
|
||||
${t.hunde ? `<span class="walks-participant-hunde">${UI.icon('dog')} ${_esc(t.hunde)}</span>` : ''}
|
||||
<span class="walks-participant-name">${UI.escape(t.user_name)}</span>
|
||||
${t.hunde ? `<span class="walks-participant-hunde">${UI.icon('dog')} ${UI.escape(t.hunde)}</span>` : ''}
|
||||
</div>`).join('')
|
||||
: '';
|
||||
|
||||
|
|
@ -362,7 +331,7 @@ window.Page_walks = (() => {
|
|||
${_fmtDate(walk.datum)}<br>
|
||||
<strong>um ${walk.uhrzeit} Uhr</strong>
|
||||
</div>
|
||||
${walk.ort_name ? `<div style="margin-top:var(--space-2);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${_esc(walk.ort_name)}</div>` : ''}
|
||||
${walk.ort_name ? `<div style="margin-top:var(--space-2);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${UI.escape(walk.ort_name)}</div>` : ''}
|
||||
<div style="margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap">
|
||||
<span class="walks-badge ${isFull ? 'walks-badge--full' : 'walks-badge--open'}">
|
||||
${isFull ? '🔴 Voll' : `🟢 ${spots} Platz${spots !== 1 ? 'e' : ''} frei`}
|
||||
|
|
@ -373,7 +342,7 @@ window.Page_walks = (() => {
|
|||
</div>
|
||||
|
||||
${walk.beschreibung ? `
|
||||
<p style="margin:var(--space-4) 0;color:var(--c-text-secondary)">${_esc(walk.beschreibung)}</p>
|
||||
<p style="margin:var(--space-4) 0;color:var(--c-text-secondary)">${UI.escape(walk.beschreibung)}</p>
|
||||
` : ''}
|
||||
|
||||
${rsvpSectionHTML}
|
||||
|
|
@ -393,8 +362,13 @@ window.Page_walks = (() => {
|
|||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="walks-detail-section">
|
||||
<div class="walks-detail-section-label">${UI.icon('star')} Bewertung</div>
|
||||
<div id="wd-rating-${walk.id}"></div>
|
||||
</div>
|
||||
|
||||
<p style="color:var(--c-text-muted);font-size:0.8rem;margin-top:var(--space-4)">
|
||||
Veranstaltet von ${_esc(walk.veranstalter_name || 'Unbekannt')}
|
||||
Veranstaltet von ${UI.escape(walk.veranstalter_name || 'Unbekannt')}
|
||||
</p>
|
||||
|
||||
${isOwn && !isPast ? `
|
||||
|
|
@ -440,6 +414,14 @@ window.Page_walks = (() => {
|
|||
|
||||
UI.modal.open({ title: `${UI.icon('dog')} ${walk.titel}`, body, footer });
|
||||
|
||||
// Bewertungskomponente initialisieren (nur nach abgelaufenem Treffen sinnvoll, aber immer anzeigen)
|
||||
UI.ratingStars({
|
||||
containerId: `wd-rating-${walk.id}`,
|
||||
targetType: 'walk',
|
||||
targetId: walk.id,
|
||||
isLoggedIn: !!_appState.user,
|
||||
});
|
||||
|
||||
document.getElementById('wd-close')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
document.getElementById('wd-login')?.addEventListener('click', () => {
|
||||
|
|
@ -553,9 +535,9 @@ window.Page_walks = (() => {
|
|||
|
||||
const listHTML = candidates.length
|
||||
? candidates.map(f => `
|
||||
<div class="walks-invite-row" data-friend-id="${f.friend_id}" data-friend-name="${_esc(f.friend_name)}">
|
||||
<div class="walks-invite-row" data-friend-id="${f.friend_id}" data-friend-name="${UI.escape(f.friend_name)}">
|
||||
<div class="walks-inv-avatar">${_avatarInitials(f.friend_name)}</div>
|
||||
<div class="walks-inv-name" style="flex:1">${_esc(f.friend_name)}</div>
|
||||
<div class="walks-inv-name" style="flex:1">${UI.escape(f.friend_name)}</div>
|
||||
<button type="button" class="btn btn-primary btn-sm walks-invite-send">
|
||||
${UI.icon('paper-plane-tilt')} Einladen
|
||||
</button>
|
||||
|
|
@ -565,7 +547,7 @@ window.Page_walks = (() => {
|
|||
const body = `
|
||||
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">
|
||||
${_fmtDate(walk.datum)} · ${walk.uhrzeit} Uhr
|
||||
${walk.ort_name ? `· ${_esc(walk.ort_name)}` : ''}
|
||||
${walk.ort_name ? `· ${UI.escape(walk.ort_name)}` : ''}
|
||||
</p>
|
||||
<div id="invite-list">${listHTML}</div>
|
||||
`;
|
||||
|
|
@ -590,7 +572,7 @@ window.Page_walks = (() => {
|
|||
await API.walks.invite(walk.id, friendId);
|
||||
row.innerHTML = `
|
||||
<div class="walks-inv-avatar">${_avatarInitials(name)}</div>
|
||||
<div class="walks-inv-name" style="flex:1">${_esc(name)}</div>
|
||||
<div class="walks-inv-name" style="flex:1">${UI.escape(name)}</div>
|
||||
<span class="walks-rsvp-badge walks-rsvp--invited">Eingeladen</span>
|
||||
`;
|
||||
UI.toast.success(`${name} eingeladen.`);
|
||||
|
|
@ -609,14 +591,14 @@ window.Page_walks = (() => {
|
|||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer;
|
||||
padding:var(--space-2) 0">
|
||||
<input type="checkbox" name="dog" value="${d.id}" checked>
|
||||
${UI.icon('dog')} ${_esc(d.name)}
|
||||
${UI.icon('dog')} ${UI.escape(d.name)}
|
||||
</label>`).join('')
|
||||
: `<p style="color:var(--c-text-muted)">Keine Hunde im Profil — du kannst trotzdem mitmachen.</p>`;
|
||||
|
||||
const body = `
|
||||
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
${_fmtDate(walk.datum)} um ${walk.uhrzeit} Uhr<br>
|
||||
${walk.ort_name ? `${UI.icon('map-pin')} ${_esc(walk.ort_name)}` : ''}
|
||||
${walk.ort_name ? `${UI.icon('map-pin')} ${UI.escape(walk.ort_name)}` : ''}
|
||||
</p>
|
||||
<form id="join-form" autocomplete="off">
|
||||
<div class="form-group">
|
||||
|
|
@ -682,7 +664,7 @@ window.Page_walks = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Titel *</label>
|
||||
<input class="form-control" type="text" name="titel"
|
||||
value="${_esc(v.titel || '')}"
|
||||
value="${UI.escape(v.titel || '')}"
|
||||
placeholder="z. B. Sonntagsspaziergang im Stadtpark" required>
|
||||
</div>
|
||||
|
||||
|
|
@ -690,12 +672,12 @@ window.Page_walks = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Datum *</label>
|
||||
<input class="form-control" type="date" name="datum"
|
||||
value="${_esc(v.datum || '')}" required>
|
||||
value="${UI.escape(v.datum || '')}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Uhrzeit *</label>
|
||||
<input class="form-control" type="time" name="uhrzeit"
|
||||
value="${_esc(v.uhrzeit || '10:00')}" required>
|
||||
value="${UI.escape(v.uhrzeit || '10:00')}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -720,7 +702,7 @@ window.Page_walks = (() => {
|
|||
<div id="wf-location-chip-wrap" style="${_locName ? '' : 'display:none'}">
|
||||
<div class="diary-location-chip">
|
||||
${UI.icon('map-pin')}
|
||||
<span id="wf-location-label">${_esc(_locName || '')}</span>
|
||||
<span id="wf-location-label">${UI.escape(_locName || '')}</span>
|
||||
<button type="button" id="wf-location-clear" aria-label="Name entfernen">
|
||||
${UI.icon('x')}
|
||||
</button>
|
||||
|
|
@ -742,7 +724,7 @@ window.Page_walks = (() => {
|
|||
<!-- Versteckte Koordinaten-Felder -->
|
||||
<input type="hidden" name="lat" id="wf-lat" value="${_locLat || ''}">
|
||||
<input type="hidden" name="lon" id="wf-lon" value="${_locLon || ''}">
|
||||
<input type="hidden" name="ort_name" id="wf-ort-name" value="${_esc(_locName || '')}">
|
||||
<input type="hidden" name="ort_name" id="wf-ort-name" value="${UI.escape(_locName || '')}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -754,7 +736,7 @@ window.Page_walks = (() => {
|
|||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
|
||||
<textarea class="form-control" name="beschreibung" rows="3"
|
||||
placeholder="Treffpunkt-Details, Streckenlänge, Hundefreundlichkeit…">${_esc(v.beschreibung || '')}</textarea>
|
||||
placeholder="Treffpunkt-Details, Streckenlänge, Hundefreundlichkeit…">${UI.escape(v.beschreibung || '')}</textarea>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -804,7 +786,7 @@ window.Page_walks = (() => {
|
|||
document.getElementById('wf-location-suggestions').style.display = 'none';
|
||||
}
|
||||
|
||||
_loadLeaflet().then(() => {
|
||||
UI.loadLeaflet().then(() => {
|
||||
setTimeout(() => {
|
||||
const lat = _locLat || 48.0, lon = _locLon || 11.9, zoom = _locLat ? 15 : 7;
|
||||
_miniMap = L.map('wf-map-wrap', {
|
||||
|
|
@ -895,9 +877,9 @@ window.Page_walks = (() => {
|
|||
} else {
|
||||
sugEl.innerHTML = suggestions.map(s => `
|
||||
<button type="button" class="diary-location-suggestion"
|
||||
data-name="${_esc(s.name)}" data-lat="${s.lat}" data-lon="${s.lon}">
|
||||
data-name="${UI.escape(s.name)}" data-lat="${s.lat}" data-lon="${s.lon}">
|
||||
${UI.icon(_sourceIcon(s.source))}
|
||||
<span>${_esc(s.name)}</span>
|
||||
<span>${UI.escape(s.name)}</span>
|
||||
<small>${s.distance_m < 1000 ? s.distance_m + ' m' : (s.distance_m / 1000).toFixed(1) + ' km'}</small>
|
||||
</button>`).join('');
|
||||
sugEl.querySelectorAll('.diary-location-suggestion').forEach(el => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue