Sprint 12: UI-Vereinheitlichung + Läufigkeits-Tracker
- by-tabs/by-tab: einheitliche Tab/Pill-Navigation in allen Seiten - by-section-label, by-toolbar: einheitliche Section-Labels und Toolbars - Design-Tokens: fehlende --c-amber, --c-primary-soft ergänzt, Fallback-Werte entfernt - sitting.js: sitting-layout für konsistentes flush-Layout (wie walks) - Läufigkeits-Tracker: neuer Health-Tab für Hündinnen mit Zyklusvorhersage, Timeline vergangener Läufigkeiten, Erinnerungen und auto-berechnetem Nächst-Datum - emptyState-Bug: icon-Parameter muss SVG sein, nicht Icon-Name (dog/bell/warning gefixt) - SW-Cache: by-v103, APP_VER: 79
This commit is contained in:
parent
32d630d5a1
commit
b58789373c
30 changed files with 4344 additions and 523 deletions
|
|
@ -174,14 +174,16 @@ window.Page_routes = (() => {
|
|||
sec.className = 'rk-map-section';
|
||||
sec.innerHTML = `
|
||||
<div class="rk-map-bar">
|
||||
<button class="btn btn-secondary btn-sm" id="rk-map-back" title="Zurück zur Liste">${UI.icon('arrow-left')}</button>
|
||||
<input class="rk-map-loc-input" id="rk-map-loc" type="search"
|
||||
placeholder="🔍 Ort suchen…" autocomplete="off">
|
||||
<button class="btn btn-secondary btn-sm" id="rk-map-gps" title="Mein Standort">📍</button>
|
||||
<button class="btn btn-secondary btn-sm" id="rk-map-gps" title="Mein Standort">${UI.icon('map-pin')}</button>
|
||||
</div>
|
||||
<div id="rk-search-map" style="height:${mapH}px;width:100%"></div>
|
||||
<div id="rk-search-map" style="flex:1;width:100%"></div>
|
||||
<div id="rk-map-hint" class="rk-map-hint">Route antippen um Details zu sehen</div>
|
||||
`;
|
||||
document.body.appendChild(sec);
|
||||
document.getElementById('rk-map-back')?.addEventListener('click', () => _switchView('list'));
|
||||
|
||||
// Wie _initMiniMaps: pollen bis window.L bereit ist
|
||||
_pollAndInitSearchMap();
|
||||
|
|
@ -392,14 +394,14 @@ window.Page_routes = (() => {
|
|||
<h3 class="rk-empty-title">Deine erste Gassi-Route</h3>
|
||||
<p class="rk-empty-text">Zeichne deine Lieblingsstrecken auf — mit Streckendaten, Fotos und Hundetauglichkeit.</p>
|
||||
<div class="rk-empty-features">
|
||||
<div class="rk-empty-feature"><span>🗺️</span><span>GPS-Aufzeichnung</span></div>
|
||||
<div class="rk-empty-feature"><span>📷</span><span>Fotos entlang der Strecke</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('map-trifold')}<span>GPS-Aufzeichnung</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('camera')}<span>Fotos entlang der Strecke</span></div>
|
||||
<div class="rk-empty-feature"><span>🐾</span><span>Hundetauglichkeit bewerten</span></div>
|
||||
<div class="rk-empty-feature"><span>⬇️</span><span>GPX-Download für Navi</span></div>
|
||||
<div class="rk-empty-feature"><span>📍</span><span>Restaurants & Parkplätze</span></div>
|
||||
<div class="rk-empty-feature"><span>🔒</span><span>Privat oder öffentlich</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('download-simple')}<span>GPX-Download für Navi</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('map-pin')}<span>Restaurants & Parkplätze</span></div>
|
||||
<div class="rk-empty-feature">${UI.icon('lock')}<span>Privat oder öffentlich</span></div>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg" id="rk-empty-rec">🔴 Erste Route aufzeichnen</button>
|
||||
<button class="btn btn-primary btn-lg" id="rk-empty-rec">${UI.icon('path')} Erste Route aufzeichnen</button>
|
||||
</div>`;
|
||||
document.getElementById('rk-empty-rec')?.addEventListener('click', () => {
|
||||
App.navigate('map');
|
||||
|
|
@ -438,7 +440,7 @@ window.Page_routes = (() => {
|
|||
// Karte HTML
|
||||
// ----------------------------------------------------------
|
||||
function _cardHTML(r) {
|
||||
const privBadge = !r.is_public ? '<span class="rk-badge rk-badge--private">🔒 Privat</span>' : '';
|
||||
const privBadge = !r.is_public ? `<span class="rk-badge rk-badge--private">${UI.icon('lock')} Privat</span>` : '';
|
||||
const diffLabel = DIFFICULTY_LABEL[r.schwierigkeit] || '';
|
||||
const terrain = TERRAIN_LABEL[r.untergrund] || '';
|
||||
const paws = HUNDE_LABEL[r.hunde_tauglichkeit] || '';
|
||||
|
|
@ -457,22 +459,22 @@ window.Page_routes = (() => {
|
|||
<div class="rk-card-body">
|
||||
<div class="rk-card-name">${_esc(r.name)}</div>
|
||||
<div class="rk-card-stats">
|
||||
${dist ? `<span>🗺️ ${dist}</span>` : ''}
|
||||
${dur ? `<span>⏱ ${dur}</span>` : ''}
|
||||
${dist ? `<span>${UI.icon('map-trifold')} ${dist}</span>` : ''}
|
||||
${dur ? `<span>${UI.icon('timer')} ${dur}</span>` : ''}
|
||||
${terrain ? `<span>${terrain}</span>` : ''}
|
||||
${paws ? `<span title="Hundetauglichkeit">${paws}</span>` : ''}
|
||||
</div>
|
||||
<div class="rk-card-tags">
|
||||
${privBadge}
|
||||
${diffLabel ? `<span class="rk-badge rk-badge--${r.schwierigkeit}">${diffLabel}</span>` : ''}
|
||||
${r.schatten ? '<span class="rk-badge">🌳 Schatten</span>' : ''}
|
||||
${r.leine_empfohlen ? '<span class="rk-badge">🔗 Leine</span>' : ''}
|
||||
${r.schatten ? `<span class="rk-badge">${UI.icon('tree')} Schatten</span>` : ''}
|
||||
${r.leine_empfohlen ? `<span class="rk-badge">${UI.icon('link')} Leine</span>` : ''}
|
||||
</div>
|
||||
<div class="rk-card-footer">
|
||||
<div class="rk-stars">${_starsHTML(r.id, r.bewertung||0, r.anz_bewertungen||0)}</div>
|
||||
<div class="rk-card-actions">
|
||||
<span class="rk-card-author">${_esc(r.user_name||'Anonym')}</span>
|
||||
<button class="rk-dl-btn" data-id="${r.id}">⬇ GPX</button>
|
||||
<button class="rk-dl-btn" data-id="${r.id}">${UI.icon('download-simple')} GPX</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -579,7 +581,7 @@ window.Page_routes = (() => {
|
|||
</label>` : ''}
|
||||
</div>` :
|
||||
isOwn ? `<label class="rk-photo-add-empty">
|
||||
📷 Foto hinzufügen
|
||||
${UI.icon('camera')} Foto hinzufügen
|
||||
<input type="file" id="rk-photo-input" accept="image/*" style="display:none">
|
||||
</label>` : '';
|
||||
|
||||
|
|
@ -588,14 +590,14 @@ window.Page_routes = (() => {
|
|||
margin-bottom:var(--space-3);background:var(--c-surface-2)"></div>
|
||||
${photoGallery}
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin:var(--space-3) 0">
|
||||
${route.distanz_km ? `<span class="rk-badge rk-badge--info">🗺️ ${route.distanz_km.toFixed(2)} km</span>` : ''}
|
||||
${route.dauer_min ? `<span class="rk-badge rk-badge--info">⏱ ${_fmtDur(route.dauer_min)}</span>` : ''}
|
||||
${route.distanz_km ? `<span class="rk-badge rk-badge--info">${UI.icon('map-trifold')} ${route.distanz_km.toFixed(2)} km</span>` : ''}
|
||||
${route.dauer_min ? `<span class="rk-badge rk-badge--info">${UI.icon('timer')} ${_fmtDur(route.dauer_min)}</span>` : ''}
|
||||
${route.schwierigkeit ? `<span class="rk-badge rk-badge--${route.schwierigkeit}">${DIFFICULTY_LABEL[route.schwierigkeit]||route.schwierigkeit}</span>` : ''}
|
||||
${route.untergrund ? `<span class="rk-badge">${TERRAIN_LABEL[route.untergrund]||route.untergrund}</span>` : ''}
|
||||
${paws ? `<span class="rk-badge rk-badge--dog" title="Hundetauglichkeit">${paws}</span>` : ''}
|
||||
${route.schatten ? '<span class="rk-badge">🌳 Schatten</span>' : ''}
|
||||
${route.leine_empfohlen ? '<span class="rk-badge">🔗 Leine empfohlen</span>' : ''}
|
||||
${!route.is_public ? '<span class="rk-badge rk-badge--private">🔒 Privat</span>' : ''}
|
||||
${route.schatten ? `<span class="rk-badge">${UI.icon('tree')} Schatten</span>` : ''}
|
||||
${route.leine_empfohlen ? `<span class="rk-badge">${UI.icon('link')} Leine empfohlen</span>` : ''}
|
||||
${!route.is_public ? `<span class="rk-badge rk-badge--private">${UI.icon('lock')} Privat</span>` : ''}
|
||||
</div>
|
||||
${route.beschreibung ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">${_esc(route.beschreibung)}</p>` : ''}
|
||||
<div id="rk-nearby" class="rk-nearby-section">
|
||||
|
|
@ -603,16 +605,16 @@ window.Page_routes = (() => {
|
|||
</div>
|
||||
<p style="color:var(--c-text-muted);font-size:0.75rem;margin-top:var(--space-2)">
|
||||
${track.length} GPS-Punkte · von ${_esc(route.user_name||'Anonym')}
|
||||
${route.bewertung ? ` · ⭐ ${route.bewertung.toFixed(1)} (${route.anz_bewertungen})` : ''}
|
||||
${route.bewertung ? ` · ${UI.icon('star')} ${route.bewertung.toFixed(1)} (${route.anz_bewertungen})` : ''}
|
||||
</p>
|
||||
`;
|
||||
|
||||
const footer = `
|
||||
<button type="button" class="btn btn-secondary" id="rd-gpx">⬇ GPX</button>
|
||||
<button type="button" class="btn btn-secondary" id="rd-gpx">${UI.icon('download-simple')} GPX</button>
|
||||
${isOwn ? `<button type="button" class="btn btn-ghost" id="rd-vis" title="${route.is_public?'Privat machen':'Öffentlich machen'}">
|
||||
${route.is_public?'🔒 Privat':'🌍 Öffentlich'}
|
||||
${route.is_public ? UI.icon('lock')+' Privat' : UI.icon('globe')+' Öffentlich'}
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost" id="rd-del" style="color:var(--c-danger)">🗑</button>` : ''}
|
||||
<button type="button" class="btn btn-ghost" id="rd-del" style="color:var(--c-danger)">${UI.icon('trash')}</button>` : ''}
|
||||
<button type="button" class="btn btn-primary flex-1" id="rd-close">Schließen</button>
|
||||
`;
|
||||
|
||||
|
|
@ -627,7 +629,7 @@ window.Page_routes = (() => {
|
|||
await API.routes.update(route.id, { is_public: !route.is_public });
|
||||
route.is_public = !route.is_public;
|
||||
const btn = document.getElementById('rd-vis');
|
||||
if (btn) btn.textContent = route.is_public ? '🔒 Privat' : '🌍 Öffentlich';
|
||||
if (btn) btn.innerHTML = route.is_public ? UI.icon('lock')+' Privat' : UI.icon('globe')+' Öffentlich';
|
||||
const r = _data.find(x => x.id === route.id);
|
||||
if (r) r.is_public = route.is_public;
|
||||
_applyFilter();
|
||||
|
|
@ -744,15 +746,15 @@ window.Page_routes = (() => {
|
|||
});
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="rk-nearby-title">📍 Entlang der Route</div>
|
||||
<div class="rk-nearby-title">${UI.icon('map-pin')} Entlang der Route</div>
|
||||
${Object.values(byType).map(group => `
|
||||
<div class="rk-nearby-group">
|
||||
<div class="rk-nearby-group-label">${group.icon} ${_esc(group.label)} (${group.items.length})</div>
|
||||
${group.items.slice(0, 5).map(p => `
|
||||
<div class="rk-nearby-item">
|
||||
<span class="rk-nearby-name">${_esc(p.name || group.label)}</span>
|
||||
${p.opening_hours ? `<span class="rk-nearby-detail">🕐 ${_esc(p.opening_hours)}</span>` : ''}
|
||||
${p.phone ? `<a href="tel:${_esc(p.phone)}" class="rk-nearby-detail rk-nearby-phone">📞 ${_esc(p.phone)}</a>` : ''}
|
||||
${p.opening_hours ? `<span class="rk-nearby-detail">${UI.icon('clock')} ${_esc(p.opening_hours)}</span>` : ''}
|
||||
${p.phone ? `<a href="tel:${_esc(p.phone)}" class="rk-nearby-detail rk-nearby-phone">${UI.icon('phone')} ${_esc(p.phone)}</a>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
${group.items.length > 5 ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 0">+${group.items.length-5} weitere</div>` : ''}
|
||||
|
|
@ -977,9 +979,9 @@ window.Page_routes = (() => {
|
|||
const body = `
|
||||
<div class="rk-import-preview">${preview}</div>
|
||||
<div class="rk-import-stats">
|
||||
<span>📍 ${track.length} Punkte</span>
|
||||
<span>🗺️ ${distanz_km.toFixed(2)} km</span>
|
||||
${dauer_min ? `<span>⏱ ${_fmtDur(dauer_min)}</span>` : ''}
|
||||
<span>${UI.icon('map-pin')} ${track.length} Punkte</span>
|
||||
<span>${UI.icon('map-trifold')} ${distanz_km.toFixed(2)} km</span>
|
||||
${dauer_min ? `<span>${UI.icon('timer')} ${_fmtDur(dauer_min)}</span>` : ''}
|
||||
<span class="rk-badge rk-badge--info">${source}</span>
|
||||
</div>
|
||||
<form id="rk-import-form" style="display:flex;flex-direction:column;gap:var(--space-3);margin-top:var(--space-4)">
|
||||
|
|
@ -1036,7 +1038,7 @@ window.Page_routes = (() => {
|
|||
|
||||
const footer = `
|
||||
<button type="button" class="btn btn-ghost" id="ri-cancel">Abbrechen</button>
|
||||
<button type="button" class="btn btn-primary flex-1" id="ri-save">💾 Route speichern</button>
|
||||
<button type="button" class="btn btn-primary flex-1" id="ri-save">${UI.icon('floppy-disk')} Route speichern</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: '📥 Route importieren', body, footer });
|
||||
|
|
@ -1083,7 +1085,7 @@ window.Page_routes = (() => {
|
|||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Speichern.');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = '💾 Route speichern';
|
||||
saveBtn.innerHTML = UI.icon('floppy-disk') + ' Route speichern';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue