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