Feature: Welten-Onboarding, Wetter-Motivation, UX-Fixes (SW by-v715)

Welten (worlds.js):
- Swipe-Hints beim ersten Öffnen (JETZT ← → WELT animiert, einmalig)
- Kein-Hund-Onboarding: Feature-Preview-Grid statt leerer Karte
- Hintergrund-Foto-Hint: Kamera-Karte wenn noch kein Tagebuchfoto
- worlds-back: navigiert zu Welcome wenn kein User eingeloggt
- Nach Logout: worlds-back Button sofort ausgeblendet

Wetter (wetter.js):
- Standort-Fehlerseite zu Motivations-Seite umgebaut
- Feature-Preview: Gassi-Score, 7-Tage, Regenradar, Rekorde
- CTA: Standort freigeben + Registrieren (nur für Gäste)

Settings (settings.js):
- Logo in Auth-Form: display:block + margin:0 auto zentriert
- Header bleibt sichtbar (FAB/Zurück-Navigation funktioniert)

Jobs (jobs.js):
- 2-Spalten-Grid auf Mobile: auto-fit statt festes 1fr 1fr
- Kein doppeltes Padding im Wrapper

Backend:
- weather.py, achievements.py: diary JOIN fix (d.user_id → dogs JOIN)
- Neue Wetter-Badges: wetter_tapfer, jahreszeiten, schnee
- Ernährungs-, Reise-, Ausgaben-Seite: diverse UX-Verbesserungen
- Presse-Seite erweitert
- Ban Yaro Foto-Assets (WebP + HIRES JPG)
This commit is contained in:
rene 2026-05-05 17:32:03 +02:00
parent aa4849d947
commit 55069d246b
28 changed files with 719 additions and 198 deletions

View file

@ -203,11 +203,11 @@ def check_and_award(user_id: int, conn):
"SELECT current_streak FROM users WHERE id=?", (user_id,)
).fetchone()
# Wetter-Tapferkeit: Diary-Einträge bei schlechtem Wetter
# Wetter-Tapferkeit: Diary-Einträge bei schlechtem Wetter (über Dog-Join)
wetter_row = conn.execute("""
SELECT COUNT(*) AS cnt FROM diary d
LEFT JOIN diary_dogs dd ON dd.diary_id = d.id
WHERE d.user_id = ?
JOIN dogs dog ON dog.id = d.dog_id
WHERE dog.user_id = ?
AND d.weather_json IS NOT NULL
AND (
CAST(json_extract(d.weather_json, '$.precip_prob') AS INTEGER) > 60
@ -216,23 +216,28 @@ def check_and_award(user_id: int, conn):
)
""", (user_id,)).fetchone()
# Jahreszeiten: Anzahl Jahreszeiten mit mind. 5 Diary-Einträgen
# Jahreszeiten: Anzahl Jahreszeiten mit mind. 5 Diary-Einträgen (über Dog-Join)
jahreszeiten_row = conn.execute("""
SELECT
(CASE WHEN (SELECT COUNT(*) FROM diary WHERE user_id=? AND CAST(strftime('%m', datum) AS INTEGER) IN (3,4,5)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary WHERE user_id=? AND CAST(strftime('%m', datum) AS INTEGER) IN (6,7,8)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary WHERE user_id=? AND CAST(strftime('%m', datum) AS INTEGER) IN (9,10,11)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary WHERE user_id=? AND CAST(strftime('%m', datum) AS INTEGER) IN (12,1,2)) >= 5 THEN 1 ELSE 0 END)
(CASE WHEN (SELECT COUNT(*) FROM diary d2 JOIN dogs g2 ON g2.id=d2.dog_id
WHERE g2.user_id=? AND CAST(strftime('%m', d2.datum) AS INTEGER) IN (3,4,5)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary d2 JOIN dogs g2 ON g2.id=d2.dog_id
WHERE g2.user_id=? AND CAST(strftime('%m', d2.datum) AS INTEGER) IN (6,7,8)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary d2 JOIN dogs g2 ON g2.id=d2.dog_id
WHERE g2.user_id=? AND CAST(strftime('%m', d2.datum) AS INTEGER) IN (9,10,11)) >= 5 THEN 1 ELSE 0 END) +
(CASE WHEN (SELECT COUNT(*) FROM diary d2 JOIN dogs g2 ON g2.id=d2.dog_id
WHERE g2.user_id=? AND CAST(strftime('%m', d2.datum) AS INTEGER) IN (12,1,2)) >= 5 THEN 1 ELSE 0 END)
AS jahreszeiten_score
FROM (SELECT 1)
""", (user_id, user_id, user_id, user_id)).fetchone()
# Schnee: Diary-Einträge bei Schnee (weathercode 71-77)
# Schnee: Diary-Einträge bei Schnee (weathercode 71-77, über Dog-Join)
schnee_row = conn.execute("""
SELECT COUNT(*) AS cnt FROM diary
WHERE user_id = ?
AND weather_json IS NOT NULL
AND CAST(json_extract(weather_json, '$.weathercode') AS INTEGER) BETWEEN 71 AND 77
SELECT COUNT(*) AS cnt FROM diary d
JOIN dogs dog ON dog.id = d.dog_id
WHERE dog.user_id = ?
AND d.weather_json IS NOT NULL
AND CAST(json_extract(d.weather_json, '$.weathercode') AS INTEGER) BETWEEN 71 AND 77
""", (user_id,)).fetchone()
metrics = {

View file

@ -280,9 +280,14 @@ class UserPoiIn(BaseModel):
ALLOWED_TYPES = {
'waste_basket', 'drinking_water', 'dog_park',
'giftkoeder', # Giftköder (exklusiv, kein Kombi)
'gefahr', # Allgemeine Gefahr / Hinweis
'freilauf', # Freilauffläche
'restaurant', # Hundefreundliches Restaurant / Café
'shop', # Hundefreundlicher Shop
'tierarzt', # Tierarzt / Tierklinik
'hundeschule', # Hundeschule / Trainer
'kotbeutel', # Kotbeutelspender
'bank', # Sitzbank
'gefahr', # Allgemeine Gefahr / Hinweis
'parkplatz', # Hundefreundlicher Parkplatz
'treffpunkt', # Treffpunkt für Hundehalter
'sonstiges',

View file

@ -43,7 +43,8 @@ async def weather_records(user=Depends(get_current_user)):
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
JOIN dogs dog ON dog.id = d.dog_id
WHERE dog.user_id = ? AND d.weather_json IS NOT NULL
ORDER BY d.datum ASC
""", (uid,)).fetchall()

View file

@ -1,6 +1,6 @@
"""BAN YARO — Widget-Snapshot + Tagesspruch Endpoints"""
import json, random
import json
from datetime import date
from fastapi import APIRouter, Depends, Query
from typing import Optional
@ -59,7 +59,8 @@ async def widget_snapshot(user=Depends(get_current_user)):
(dog_id,)
).fetchall()
random_photo = dict(random.choice(photos)) if photos else None
day_num = (date.today() - date(2024, 1, 1)).days
random_photo = dict(photos[day_num % len(photos)]) if photos else None
# Anzahl überfälliger Erinnerungen
overdue = conn.execute(