Feature: Bottom-Nav umgebaut, Dog-Avatar→Welcome, Routen-Filter-Panel, Recording-Fix

- Bottom-Nav: Tagebuch | Routen | [+] | Forum | Benachrichtigungen (mit Badge)
- Benachrichtigungs-Badge auch in Bottom-Nav (notif-nav-badge)
- Dog-Avatar-Klick → Welcome-Seite (Name bleibt → Hund-Profil)
- Routen: Filter in aufklappbarem Panel, aktive Filter zeigen roten Punkt
- Routen: Start/Stop-Button fragt Page_map.isRecording() ab, kein veralteter lokaler State
- SW by-v232, APP_VER 209
This commit is contained in:
rene 2026-04-19 10:09:02 +02:00
parent 0113ee2fbb
commit b6d2606a23
5 changed files with 137 additions and 57 deletions

View file

@ -2054,19 +2054,58 @@ html.modal-open {
.rk-rec-btn--active {
animation: rec-pulse 1.2s ease-in-out infinite;
}
/* Filter-Panel (aufklappbar) */
.rk-filter-toggle-btn {
position: relative;
flex-shrink: 0;
}
.rk-filter-toggle-btn.active {
background: var(--c-primary);
border-color: var(--c-primary);
color: #fff;
}
.rk-filter-badge {
position: absolute;
top: 2px;
right: 2px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--c-danger, #EF4444);
border: 1.5px solid var(--c-surface);
pointer-events: none;
}
.rk-filter-panel {
border-top: 1px solid var(--c-border-light);
padding: var(--space-3) var(--space-3) var(--space-2);
background: var(--c-surface);
}
.rk-filters {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.rk-filter-group {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.rk-filter-label {
font-size: var(--text-xs);
font-weight: 600;
color: var(--c-text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
padding-left: 2px;
}
.rk-chips-row {
display: flex;
gap: var(--space-1);
overflow-x: auto;
scrollbar-width: none;
flex-wrap: nowrap;
}
.rk-filter-group::-webkit-scrollbar { display: none; }
.rk-chips-row::-webkit-scrollbar { display: none; }
.rk-chip {
padding: 4px 10px;
border-radius: var(--radius-full);

View file

@ -284,24 +284,24 @@
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg>
<span class="nav-item-label">Tagebuch</span>
</div>
<div class="nav-item" data-page="health">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>
<span class="nav-item-label">Gesundheit</span>
<div class="nav-item" data-page="routes">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>
<span class="nav-item-label">Routen</span>
</div>
<!-- Mittlerer + Button -->
<div class="nav-item nav-item-center" id="nav-add">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#plus"></use></svg>
</div>
<div class="nav-item" data-page="poison">
<span style="position:relative;display:inline-flex">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning-octagon"></use></svg>
<span class="nav-badge hidden" id="poison-nav-badge">0</span>
</span>
<span class="nav-item-label">Alarm</span>
<div class="nav-item" data-page="forum">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>
<span class="nav-item-label">Forum</span>
</div>
<div class="nav-item" data-page="settings">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#gear"></use></svg>
<span class="nav-item-label">Ich</span>
<div class="nav-item" data-page="notifications">
<span style="position:relative;display:inline-flex">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bell"></use></svg>
<span class="nav-badge hidden" id="notif-nav-badge">0</span>
</span>
<span class="nav-item-label">Benachrichtigungen</span>
</div>
</nav>

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '208'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '209'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
@ -440,9 +440,15 @@ const App = (() => {
try {
const { count } = await API.notifications.unreadCount();
const badge = document.getElementById('notif-badge');
if (!badge) return;
badge.textContent = count;
badge.style.display = count > 0 ? '' : 'none';
if (badge) {
badge.textContent = count;
badge.style.display = count > 0 ? '' : 'none';
}
const navBadge = document.getElementById('notif-nav-badge');
if (navBadge) {
navBadge.textContent = count;
navBadge.classList.toggle('hidden', count === 0);
}
} catch { /* ignorieren */ }
}
@ -611,9 +617,9 @@ const App = (() => {
<span class="${titleClass} dog-sw-title" style="cursor:pointer" title="${UI.escape(dog.name)} bearbeiten">${UI.escape(dog.name)}</span>
${othersHtml}`;
// Klick aktiver Avatar oder Name → Hund-Profil
// Klick aktiver Avatar → Welcome-Seite; Klick Name → Hund-Profil
el.querySelector(`#dog-sw-active-${ctxId}`)?.addEventListener('click', () => {
navigate('dog-profile');
navigate('welcome');
});
el.querySelector(`.dog-sw-title`)?.addEventListener('click', () => {
navigate('dog-profile');

View file

@ -18,6 +18,7 @@ window.Page_routes = (() => {
let _onlyMine = false;
let _isRecording = false;
let _filterOpen = false;
// 'mine' | 'discover'
let _browseMode = 'mine';
@ -83,37 +84,58 @@ window.Page_routes = (() => {
<button class="rk-view-btn${_viewMode==='list'?' active':''}" id="rk-view-list" title="Liste">${UI.icon('list')}</button>
<button class="rk-view-btn${_viewMode==='map'?' active':''}" id="rk-view-map" title="Karte">${UI.icon('map-trifold')}</button>
</div>
<button class="btn btn-secondary btn-sm rk-filter-toggle-btn" id="rk-filter-btn" title="Filter">
${UI.icon('funnel')} Filter
<span class="rk-filter-badge" id="rk-filter-badge" style="display:none"></span>
</button>
<label class="btn btn-secondary btn-sm rk-imp-btn" id="rk-imp-wrap" title="GPX / KML / TCX importieren">
${UI.icon('download-simple')} Import
<input type="file" id="rk-import-input" accept=".gpx,.kml,.tcx" style="display:none">
</label>
<button class="btn btn-primary btn-sm rk-rec-btn" id="rk-rec-btn">${UI.icon('path')} Aufzeichnen</button>
</div>
<div class="rk-filters" id="rk-filters">
<div class="rk-filter-group">
<button class="rk-chip active" data-filter="difficulty" data-val="">Alle</button>
<button class="rk-chip" data-filter="difficulty" data-val="leicht">🟢 Leicht</button>
<button class="rk-chip" data-filter="difficulty" data-val="mittel">🟡 Mittel</button>
<button class="rk-chip" data-filter="difficulty" data-val="anspruchsvoll">🔴 Anspruchsvoll</button>
</div>
<div class="rk-filter-group">
<button class="rk-chip active" data-filter="terrain" data-val="">Alle Wege</button>
<button class="rk-chip" data-filter="terrain" data-val="wald">🌲 Wald</button>
<button class="rk-chip" data-filter="terrain" data-val="asphalt">🛣 Asphalt</button>
<button class="rk-chip" data-filter="terrain" data-val="wiese">🌿 Wiese</button>
<button class="rk-chip" data-filter="terrain" data-val="mix">🔀 Mix</button>
</div>
<div class="rk-filter-group">
<button class="rk-chip active" data-filter="sort" data-val="newest">Neueste</button>
<button class="rk-chip" data-filter="sort" data-val="distance">Längste</button>
<button class="rk-chip" data-filter="sort" data-val="rating">Beste</button>
<button class="rk-chip" data-filter="sort" data-val="dog">Hundefreundlich</button>
</div>
<div class="rk-filter-group" id="rk-mine-group" style="display:none">
<button class="rk-chip" data-filter="mine" data-val="mine">🔒 Nur meine</button>
</div>
<div class="rk-filter-group" id="rk-nearby-group" style="display:none">
<button class="rk-chip" id="rk-nearby-btn" data-filter="nearby" data-val="">${UI.icon('map-pin')} In meiner Nähe</button>
<div class="rk-filter-panel" id="rk-filter-panel" style="display:none">
<div class="rk-filters" id="rk-filters">
<div class="rk-filter-group">
<div class="rk-filter-label">Schwierigkeit</div>
<div class="rk-chips-row">
<button class="rk-chip active" data-filter="difficulty" data-val="">Alle</button>
<button class="rk-chip" data-filter="difficulty" data-val="leicht">🟢 Leicht</button>
<button class="rk-chip" data-filter="difficulty" data-val="mittel">🟡 Mittel</button>
<button class="rk-chip" data-filter="difficulty" data-val="anspruchsvoll">🔴 Anspruchsvoll</button>
</div>
</div>
<div class="rk-filter-group">
<div class="rk-filter-label">Untergrund</div>
<div class="rk-chips-row">
<button class="rk-chip active" data-filter="terrain" data-val="">Alle Wege</button>
<button class="rk-chip" data-filter="terrain" data-val="wald">🌲 Wald</button>
<button class="rk-chip" data-filter="terrain" data-val="asphalt">🛣 Asphalt</button>
<button class="rk-chip" data-filter="terrain" data-val="wiese">🌿 Wiese</button>
<button class="rk-chip" data-filter="terrain" data-val="mix">🔀 Mix</button>
</div>
</div>
<div class="rk-filter-group">
<div class="rk-filter-label">Sortierung</div>
<div class="rk-chips-row">
<button class="rk-chip active" data-filter="sort" data-val="newest">Neueste</button>
<button class="rk-chip" data-filter="sort" data-val="distance">Längste</button>
<button class="rk-chip" data-filter="sort" data-val="rating">Beste</button>
<button class="rk-chip" data-filter="sort" data-val="dog">Hundefreundlich</button>
</div>
</div>
<div class="rk-filter-group" id="rk-mine-group" style="display:none">
<div class="rk-filter-label">Eigene</div>
<div class="rk-chips-row">
<button class="rk-chip" data-filter="mine" data-val="mine">🔒 Nur meine</button>
</div>
</div>
<div class="rk-filter-group" id="rk-nearby-group" style="display:none">
<div class="rk-filter-label">Umgebung</div>
<div class="rk-chips-row">
<button class="rk-chip" id="rk-nearby-btn" data-filter="nearby" data-val="">${UI.icon('map-pin')} In meiner Nähe</button>
</div>
</div>
</div>
</div>
</div>
@ -133,14 +155,12 @@ window.Page_routes = (() => {
});
document.getElementById('rk-view-list').addEventListener('click', () => _switchView('list'));
document.getElementById('rk-view-map').addEventListener('click', () => _switchView('map'));
document.getElementById('rk-filter-btn').addEventListener('click', _toggleFilterPanel);
document.getElementById('rk-rec-btn').addEventListener('click', () => {
if (_isRecording) {
_isRecording = false;
if (window.Page_map?.isRecording?.()) {
window.Page_map.stopRecording();
_syncRecBtn();
window.Page_map?.stopRecording?.();
} else {
_isRecording = true;
_syncRecBtn();
App.navigate('map');
setTimeout(() => window.Page_map?.startRecording?.(), 600);
}
@ -154,7 +174,7 @@ window.Page_routes = (() => {
const chip = e.target.closest('.rk-chip');
if (!chip) return;
const { filter, val } = chip.dataset;
chip.closest('.rk-filter-group').querySelectorAll('.rk-chip')
chip.closest('.rk-chips-row').querySelectorAll('.rk-chip')
.forEach(c => c.classList.remove('active'));
chip.classList.add('active');
if (filter === 'difficulty') _difficulty = val;
@ -162,6 +182,7 @@ window.Page_routes = (() => {
if (filter === 'sort') _sortBy = val;
if (filter === 'mine') _onlyMine = chip.classList.contains('active') && val === 'mine';
if (filter === 'nearby') { _loadDataNearby(); return; } // async, calls _applyFilter itself
_updateFilterBadge();
_applyFilter();
});
// Mode toggle
@ -170,21 +191,34 @@ window.Page_routes = (() => {
}
function _syncRecBtn() {
// Falls Page_map bereits initialisiert ist, echten State abfragen
if (window.Page_map?.isRecording) {
_isRecording = window.Page_map.isRecording();
}
const recording = window.Page_map?.isRecording?.() ?? false;
_isRecording = recording;
const btn = document.getElementById('rk-rec-btn');
if (!btn) return;
if (_isRecording) {
if (recording) {
btn.className = 'btn btn-danger btn-sm rk-rec-btn rk-rec-btn--active';
btn.innerHTML = UI.icon('stop-circle') + ' Stopp';
btn.innerHTML = UI.icon('path') + ' Stopp aufnehmen';
} else {
btn.className = 'btn btn-primary btn-sm rk-rec-btn';
btn.innerHTML = UI.icon('path') + ' Aufzeichnen';
}
}
function _toggleFilterPanel() {
_filterOpen = !_filterOpen;
const panel = document.getElementById('rk-filter-panel');
const btn = document.getElementById('rk-filter-btn');
if (panel) panel.style.display = _filterOpen ? '' : 'none';
if (btn) btn.classList.toggle('active', _filterOpen);
}
function _updateFilterBadge() {
const badge = document.getElementById('rk-filter-badge');
if (!badge) return;
const hasFilter = _difficulty !== '' || _terrain !== '' || _sortBy !== 'newest' || _onlyMine;
badge.style.display = hasFilter ? '' : 'none';
}
function _setBrowseMode(mode) {
_browseMode = mode;
document.getElementById('rk-mode-mine')?.classList.toggle('active', mode === 'mine');
@ -474,6 +508,7 @@ window.Page_routes = (() => {
document.querySelectorAll('.rk-chip').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.rk-chip[data-val=""]').forEach(c => c.classList.add('active'));
document.querySelector('.rk-chip[data-val="newest"]')?.classList.add('active');
_updateFilterBadge();
_applyFilter();
});
} else if (_browseMode === 'discover') {

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v231';
const CACHE_VERSION = 'by-v232';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten