diff --git a/backend/main.py b/backend/main.py index 33bf56d..9d39d5e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "894" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "895" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/static/index.html b/backend/static/index.html index fb4cbe7..1da1a58 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -591,10 +591,10 @@ - - - - + + + + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index be700ea..4c2987e 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '894'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '895'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/litters.js b/backend/static/js/pages/litters.js index 12ced25..eda8672 100644 --- a/backend/static/js/pages/litters.js +++ b/backend/static/js/pages/litters.js @@ -5,10 +5,11 @@ window.Page_litters = (() => { - let _container = null; - let _appState = null; - let _litters = []; // geladene Würfe - let _openId = null; // aufgeklappter Wurf + let _container = null; + let _appState = null; + let _litters = []; // geladene Würfe + let _openId = null; // aufgeklappter Wurf + let _filterStatus = null; // aktiver Status-Filter // ---------------------------------------------------------- // Hilfsfunktionen @@ -142,22 +143,59 @@ window.Page_litters = (() => { const welpen = _litters.reduce((s, l) => s + (l.welpen_gesamt || 0), 0); const verfuegb = _litters.reduce((s, l) => s + (l.welpen_verfuegbar || 0), 0); const statItems = [ - { icon: 'list-bullets', label: 'Würfe gesamt', val: total }, - { icon: 'baby', label: 'Aktiv', val: aktiv, color: 'var(--c-success)' }, - { icon: 'calendar-dots',label: 'Geplant', val: geplant }, - { icon: 'dog', label: 'Welpen', val: welpen }, - { icon: 'tag', label: 'Verfügbar', val: verfuegb, color: verfuegb > 0 ? 'var(--c-primary)' : undefined }, + { icon: 'list-bullets', label: 'Alle Würfe', val: total, filter: null }, + { icon: 'baby', label: 'Aktiv', val: aktiv, filter: ['verfuegbar','geboren'], color: 'var(--c-success)' }, + { icon: 'calendar-dots',label: 'Geplant', val: geplant, filter: ['geplant'] }, + { icon: 'dog', label: 'Welpen ges.', val: welpen, filter: null }, + { icon: 'tag', label: 'Verfügbar', val: verfuegb,filter: ['verfuegbar'], color: verfuegb > 0 ? 'var(--c-primary)' : undefined }, ]; bar.style.display = 'flex'; - bar.innerHTML = statItems.map(s => ` -
- ${UI.icon(s.icon)} -
-
${s.val}
-
${s.label}
-
-
`).join(''); + bar.innerHTML = statItems.map((s, i) => { + const isActive = JSON.stringify(_filterStatus) === JSON.stringify(s.filter); + const clickable = s.filter !== undefined && !(s.label === 'Welpen ges.'); + return ` +
+ ${UI.icon(s.icon)} +
+
${s.val}
+
${s.label}
+
+
`; + }).join(''); + + bar.querySelectorAll('[data-stat-idx]').forEach(chip => { + const s = statItems[parseInt(chip.dataset.statIdx)]; + if (!s.filter && s.label !== 'Alle Würfe') return; + chip.addEventListener('click', () => { + _filterStatus = JSON.stringify(_filterStatus) === JSON.stringify(s.filter) ? null : s.filter; + _renderStats(); + _renderFilteredList(); + }); + }); + } + + function _renderFilteredList() { + const el = document.getElementById('litters-list'); + if (!el) return; + const visible = _filterStatus + ? _litters.filter(l => _filterStatus.includes(l.status)) + : _litters; + if (!visible.length) { + el.innerHTML = ` +
+

Keine Würfe für diesen Filter.

+
`; + return; + } + el.innerHTML = visible.map(l => _litterCardHTML(l)).join(''); + _bindCardEvents(el, visible); } function _renderList() { @@ -178,73 +216,52 @@ window.Page_litters = (() => { } _renderStats(); + _filterStatus = null; el.innerHTML = _litters.map(l => _litterCardHTML(l)).join(''); + _bindCardEvents(el, _litters); + if (_openId) _togglePuppies(_openId, true); + } - // Events + function _bindCardEvents(el, litters) { el.querySelectorAll('.litters-card-toggle').forEach(btn => { - btn.addEventListener('click', () => { - const id = parseInt(btn.dataset.id); - _togglePuppies(id); - }); + btn.addEventListener('click', () => _togglePuppies(parseInt(btn.dataset.id))); }); - el.querySelectorAll('.litters-photos-btn').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.dataset.id); - const litter = _litters.find(l => l.id === id); - if (litter) _showPhotosModal('litter', litter.id, litter.zwingername || `Wurf #${litter.id}`); + const l = litters.find(x => x.id === id); + if (l) _showPhotosModal('litter', l.id, l.zwingername || `Wurf #${l.id}`); }); }); - el.querySelectorAll('.litters-parent-photos-btn').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.dataset.id); - const litter = _litters.find(l => l.id === id); - if (!litter) return; - const label = [litter.vater_name, litter.mutter_name].filter(Boolean).join(' × ') || `Eltern Wurf #${id}`; - _showPhotosModal('parent', litter.id, label); + const l = litters.find(x => x.id === id); + if (!l) return; + _showPhotosModal('parent', l.id, [l.vater_name, l.mutter_name].filter(Boolean).join(' × ') || `Eltern #${id}`); }); }); - el.querySelectorAll('.litters-edit-btn').forEach(btn => { btn.addEventListener('click', () => { - const id = parseInt(btn.dataset.id); - const litter = _litters.find(l => l.id === id); - if (litter) _showLitterForm(litter); + const l = litters.find(x => x.id === parseInt(btn.dataset.id)); + if (l) _showLitterForm(l); }); }); - el.querySelectorAll('.litters-delete-btn').forEach(btn => { - btn.addEventListener('click', () => { - const id = parseInt(btn.dataset.id); - _deleteLitter(id); - }); + btn.addEventListener('click', () => _deleteLitter(parseInt(btn.dataset.id))); }); - el.querySelectorAll('.litters-ki-announce-btn').forEach(btn => { - btn.addEventListener('click', () => { - const id = parseInt(btn.dataset.id); - _showKiAnnouncement(id); - }); + btn.addEventListener('click', () => _showKiAnnouncement(parseInt(btn.dataset.id))); }); - el.querySelectorAll('.litters-add-puppy-btn').forEach(btn => { - btn.addEventListener('click', () => { - const id = parseInt(btn.dataset.id); - _showPuppyForm(id, null); - }); + btn.addEventListener('click', () => _showPuppyForm(parseInt(btn.dataset.id), null)); }); - el.querySelectorAll('.litters-waitlist-btn').forEach(btn => { btn.addEventListener('click', () => _toggleWaitlist(parseInt(btn.dataset.id))); }); - el.querySelectorAll('.litters-add-waitlist-btn').forEach(btn => { btn.addEventListener('click', () => _showWaitlistForm(parseInt(btn.dataset.id), null)); }); - - // Aufgeklappten Wurf wiederherstellen - if (_openId) _togglePuppies(_openId, true); } function _daysUntil(dateStr) { diff --git a/backend/static/sw.js b/backend/static/sw.js index f39006d..6ee03d1 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v894'; +const CACHE_VERSION = 'by-v895'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache