From 6152d6bf0e3f1b34decfc31fcff4cc7996be87c0 Mon Sep 17 00:00:00 2001 From: rene Date: Mon, 4 May 2026 20:28:06 +0200 Subject: [PATCH] Feature: Meine Wetterrekorde Sektion auf Wetter-Seite (SW by-v694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: GET /api/weather/records — liest diary-Einträge mit weather_json und berechnet Kältester/Heißester Gassi, Stürmischster Tag, Regentage - Frontend: #wttr-records 2×2 Grid-Karten unterhalb Hunde-Wetter (nur für eingeloggte User mit ≥3 Tagebucheinträgen mit Wetterdaten) - SW-Version auf by-v694 erhöht, APP_VER auf 694 --- backend/routes/weather.py | 56 ++++++++++++++ backend/static/index.html | 8 +- backend/static/js/app.js | 2 +- backend/static/js/pages/wetter.js | 117 ++++++++++++++++++++++++++++-- backend/static/sw.js | 2 +- 5 files changed, 173 insertions(+), 12 deletions(-) diff --git a/backend/routes/weather.py b/backend/routes/weather.py index fced719..ba45306 100644 --- a/backend/routes/weather.py +++ b/backend/routes/weather.py @@ -3,9 +3,11 @@ BAN YARO — Wetter-API GET /api/weather?lat=&lon= → aktuelles Wetter + Zecken-Warnung für Nutzerstandort """ +import json from fastapi import APIRouter, Query, HTTPException, Depends import weather as weather_module from auth import get_current_user +from database import db router = APIRouter() @@ -31,3 +33,57 @@ async def get_weather_forecast( return await weather_module.get_forecast(lat, lon) except Exception as exc: raise HTTPException(503, f'Wettervorhersage nicht verfügbar: {exc}') + + +@router.get('/records') +async def weather_records(user=Depends(get_current_user)): + """Persönliche Wetterrekorde aus diary-Einträgen mit weather_json.""" + uid = user["id"] + with db() as conn: + rows = conn.execute(""" + SELECT d.datum, d.weather_json, d.titel + FROM diary d + WHERE d.user_id = ? AND d.weather_json IS NOT NULL + ORDER BY d.datum ASC + """, (uid,)).fetchall() + + if not rows: + return {"records": None} + + entries = [] + for r in rows: + try: + w = json.loads(r["weather_json"]) + entries.append({ + "datum": r["datum"], + "titel": r["titel"], + "temp_c": w.get("temp_c"), + "wind_kmh": w.get("wind_kmh"), + "precip_prob": w.get("precip_prob"), + "desc": w.get("desc", ""), + "weathercode": w.get("weathercode"), + }) + except Exception: + pass + + if not entries: + return {"records": None} + + temps = [e for e in entries if e["temp_c"] is not None] + winds = [e for e in entries if e["wind_kmh"] is not None] + + records = {} + if temps: + kaeltester = min(temps, key=lambda e: e["temp_c"]) + heissester = max(temps, key=lambda e: e["temp_c"]) + records["kaeltester"] = kaeltester + records["heissester"] = heissester + if winds: + stuermischster = max(winds, key=lambda e: e["wind_kmh"]) + records["stuermischster"] = stuermischster + + regen_count = sum(1 for e in entries if (e.get("precip_prob") or 0) > 60) + records["regen_eintraege"] = regen_count + records["gesamt_eintraege"] = len(entries) + + return {"records": records} diff --git a/backend/static/index.html b/backend/static/index.html index 5ce7819..837f2eb 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -93,9 +93,9 @@ - - - + + + @@ -562,7 +562,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index c235900..393bf53 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 = '693'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '694'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; diff --git a/backend/static/js/pages/wetter.js b/backend/static/js/pages/wetter.js index a06ac23..755b825 100644 --- a/backend/static/js/pages/wetter.js +++ b/backend/static/js/pages/wetter.js @@ -55,11 +55,12 @@ window.Page_wetter = (() => { // ---------------------------------------------------------- // MODUL-STATE // ---------------------------------------------------------- - let _container = null; - let _appState = null; - let _data = null; - let _selDay = 0; - let _loading = false; + let _container = null; + let _appState = null; + let _data = null; + let _selDay = 0; + let _loading = false; + let _recordsLoaded = false; // ---------------------------------------------------------- // INIT @@ -76,7 +77,8 @@ window.Page_wetter = (() => { // REFRESH // ---------------------------------------------------------- async function refresh() { - _selDay = 0; + _selDay = 0; + _recordsLoaded = false; _renderShell(); _tryAutoLocate(); } @@ -195,6 +197,10 @@ window.Page_wetter = (() => {
+ + +
+
`; // Strip-Klick-Events @@ -211,6 +217,7 @@ window.Page_wetter = (() => { _renderDetail(); _renderRainTimeline(); _renderDog(); + _loadRecords(); } // ---------------------------------------------------------- @@ -972,6 +979,104 @@ window.Page_wetter = (() => { .replace(/"/g, '"'); } + // ---------------------------------------------------------- + // MEINE WETTERREKORDE + // ---------------------------------------------------------- + async function _loadRecords() { + // Nur wenn User eingeloggt + if (!_appState?.user) return; + // Nur einmal pro Seitenaufruf laden + if (_recordsLoaded) return; + _recordsLoaded = true; + try { + const res = await API.get('/weather/records'); + _renderRecords(res?.records || null); + } catch { + // Stumm scheitern — Rekorde sind ein Nice-to-have + } + } + + function _fmtDate(datum) { + if (!datum) return ''; + try { + return new Date(datum + 'T12:00').toLocaleDateString('de', { + day: 'numeric', month: 'short', year: 'numeric' + }); + } catch { return datum; } + } + + function _recordCard(emoji, title, value, subtitle, color) { + return ` +
+
+ ${emoji} + ${_esc(title)} +
+
+ ${_esc(value)} +
+
+ ${_esc(subtitle)} +
+
+ `; + } + + function _renderRecords(records) { + const el = _container.querySelector('#wttr-records'); + if (!el) return; + + // Mindestens 3 Einträge nötig + if (!records || (records.gesamt_eintraege || 0) < 3) { + el.innerHTML = ''; + return; + } + + const cards = []; + + if (records.kaeltester) { + const e = records.kaeltester; + const sub = e.titel ? `${e.titel} · ${_fmtDate(e.datum)}` : _fmtDate(e.datum); + cards.push(_recordCard('🥶', 'Kältester Gassi', `${Math.round(e.temp_c)}°C`, sub, '#60A5FA')); + } + + if (records.heissester) { + const e = records.heissester; + const sub = e.titel ? `${e.titel} · ${_fmtDate(e.datum)}` : _fmtDate(e.datum); + cards.push(_recordCard('🔥', 'Heißester Gassi', `${Math.round(e.temp_c)}°C`, sub, '#EF4444')); + } + + if (records.stuermischster) { + const e = records.stuermischster; + const sub = e.titel ? `${e.titel} · ${_fmtDate(e.datum)}` : _fmtDate(e.datum); + cards.push(_recordCard('🌬️', 'Stürmischster Tag', `${Math.round(e.wind_kmh)} km/h`, sub, '#A78BFA')); + } + + const regenCount = records.regen_eintraege || 0; + const gesamt = records.gesamt_eintraege || 0; + cards.push(_recordCard('💧', 'Regentage', `${regenCount} Einträge`, `von ${gesamt} Tagebucheinträgen`, '#3B82F6')); + + el.innerHTML = ` +
+

+ + + + Meine Wetterrekorde +

+
+ ${cards.join('')} +
+
+ `; + } + // ---------------------------------------------------------- // PUBLIC API // ---------------------------------------------------------- diff --git a/backend/static/sw.js b/backend/static/sw.js index 926497b..7ce0992 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-v693'; +const CACHE_VERSION = 'by-v694'; 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