diff --git a/backend/routes/movies.py b/backend/routes/movies.py
index e4f306e..da6c682 100644
--- a/backend/routes/movies.py
+++ b/backend/routes/movies.py
@@ -386,6 +386,34 @@ async def get_hund_des_monats(user=Depends(get_current_user_optional)):
return {"monat": monat, "top": [dict(r) for r in rows], "user_vote": user_vote}
+@router.get("/hund-des-monats/kandidaten")
+async def get_hdm_kandidaten(user=Depends(get_current_user)):
+ """Alle öffentlichen Hunde anderer User, mit aktuellem Stimmenstand."""
+ monat = datetime.now().strftime("%Y-%m")
+ with db() as conn:
+ rows = conn.execute("""
+ SELECT d.id, d.name, d.rasse, d.foto_url,
+ u.name AS besitzer_name,
+ COALESCE(v.stimmen, 0) AS stimmen
+ FROM dogs d
+ JOIN users u ON u.id = d.user_id
+ LEFT JOIN (
+ SELECT dog_id, COUNT(*) AS stimmen
+ FROM hund_des_monats_votes
+ WHERE monat = ?
+ GROUP BY dog_id
+ ) v ON v.dog_id = d.id
+ WHERE d.is_public = 1
+ AND d.user_id != ?
+ ORDER BY
+ CASE WHEN d.foto_url IS NOT NULL THEN 0 ELSE 1 END,
+ stimmen DESC,
+ d.name ASC
+ LIMIT 60
+ """, (monat, user["id"])).fetchall()
+ return [dict(r) for r in rows]
+
+
@router.post("/hund-des-monats/vote")
async def vote_hund_des_monats(data: HundDesMonatsVoteRequest, user=Depends(get_current_user)):
monat = datetime.now().strftime("%Y-%m")
@@ -393,7 +421,9 @@ async def vote_hund_des_monats(data: HundDesMonatsVoteRequest, user=Depends(get_
dog = conn.execute("SELECT id, user_id, is_public FROM dogs WHERE id=?", (data.dog_id,)).fetchone()
if not dog:
raise HTTPException(404, "Hund nicht gefunden.")
- if dog["user_id"] != user["id"] and not dog["is_public"]:
+ if dog["user_id"] == user["id"]:
+ raise HTTPException(403, "Du kannst nicht für deinen eigenen Hund abstimmen.")
+ if not dog["is_public"]:
raise HTTPException(403, "Dieser Hund ist nicht öffentlich.")
conn.execute("""
INSERT INTO hund_des_monats_votes (user_id, dog_id, monat)
diff --git a/backend/static/css/components.css b/backend/static/css/components.css
index 5264274..3582760 100644
--- a/backend/static/css/components.css
+++ b/backend/static/css/components.css
@@ -5241,11 +5241,19 @@ html.modal-open {
margin-bottom: var(--space-3);
}
+/* Kandidaten-Suche */
+.hdm-kandidaten-search {
+ margin-bottom: var(--space-3);
+}
+
/* Vote-Grid */
.hdm-vote-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-3);
+ max-height: 340px;
+ overflow-y: auto;
+ padding-right: var(--space-1);
}
.hdm-vote-card {
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 18e94c3..55a80eb 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 = '596'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '597'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
diff --git a/backend/static/js/pages/forum.js b/backend/static/js/pages/forum.js
index 1b5abd7..8c28a1b 100644
--- a/backend/static/js/pages/forum.js
+++ b/backend/static/js/pages/forum.js
@@ -232,45 +232,12 @@ window.Page_forum = (() => {
}
async function _openHdmModal(data) {
- // Immer frische Daten laden
- try { data = await API.get('/movies/hund-des-monats'); } catch { /* nutze gecachte */ }
+ try { data = await API.get('/movies/hund-des-monats'); } catch { /* gecachte Daten */ }
const [year, month] = data.monat.split('-');
const monthName = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' })
.format(new Date(+year, +month - 1, 1));
- let voteSection = '';
- if (_appState.user && _appState.dogs?.length > 0) {
- const cards = _appState.dogs.map(dog => {
- const isVoted = data.user_vote === dog.id;
- const av = dog.foto_url
- ? ``
- : `${_esc(dog.name.charAt(0).toUpperCase())}`;
- return `
-
- Anmelden - um für deinen Hund abzustimmen. -
-Noch keine Stimmen diesen Monat. Sei der Erste!
`; + : `Noch keine Stimmen. Sei der Erste!
`; + + const voteHint = !_appState.user + ? `+ Anmelden + um abstimmen zu können. +
+Keine Hunde gefunden.
`; + return; + } + grid.innerHTML = list.map(dog => { + const isVoted = data.user_vote === dog.id; + const av = dog.foto_url + ? `Kandidaten konnten nicht geladen werden.
`; + return; + } + _renderKandidaten(_kandidaten); + + document.getElementById('hdm-search')?.addEventListener('input', e => { + const q = e.target.value.trim().toLowerCase(); + _renderKandidaten(q + ? _kandidaten.filter(d => + (d.name || '').toLowerCase().includes(q) || + (d.rasse || '').toLowerCase().includes(q)) + : _kandidaten + ); }); } diff --git a/backend/static/sw.js b/backend/static/sw.js index 1a56aac..bc46029 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-v596'; +const CACHE_VERSION = 'by-v597'; 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