Feature: Widerristhöhe im Hundeprofil + Ausweis-Fix (SW by-v873)

This commit is contained in:
rene 2026-05-11 21:30:07 +02:00
parent b12467286c
commit 83034c0db0
7 changed files with 58 additions and 33 deletions

View file

@ -580,6 +580,8 @@ def _migrate(conn_factory):
("users", "password_reset_expires", "TEXT"), ("users", "password_reset_expires", "TEXT"),
# Fell-Typ für personalisierte Wetter-Hinweise # Fell-Typ für personalisierte Wetter-Hinweise
("dogs", "fell_typ", "TEXT"), # kurz|mittel|lang|drahtaar|doppel|nackt ("dogs", "fell_typ", "TEXT"), # kurz|mittel|lang|drahtaar|doppel|nackt
# Widerristhöhe in cm (höchster Punkt Schulterblatt → Boden)
("dogs", "widerrist_cm", "REAL"),
# Tierarzt-Bewertungen: Durchschnitt + Anzahl am Tierarzt-Datensatz # Tierarzt-Bewertungen: Durchschnitt + Anzahl am Tierarzt-Datensatz
("tieraerzte", "avg_rating", "REAL DEFAULT 0"), ("tieraerzte", "avg_rating", "REAL DEFAULT 0"),
("tieraerzte", "anz_bewertungen", "INTEGER DEFAULT 0"), ("tieraerzte", "anz_bewertungen", "INTEGER DEFAULT 0"),

View file

@ -376,7 +376,7 @@ if STAGING and os.path.isdir(PROD_MEDIA_DIR):
else: else:
app.mount("/media", StaticFiles(directory=MEDIA_DIR), name="media") app.mount("/media", StaticFiles(directory=MEDIA_DIR), name="media")
APP_VER = "872" # muss mit APP_VER in app.js übereinstimmen APP_VER = "873" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json") @app.get("/.well-known/assetlinks.json")
async def assetlinks(): async def assetlinks():
@ -1407,6 +1407,7 @@ async def ausweis_page(dog_id: int, request: Request):
<div class="meta-item"><div class="label">Geschlecht</div><div class="value">{geschlecht}</div></div> <div class="meta-item"><div class="label">Geschlecht</div><div class="value">{geschlecht}</div></div>
<div class="meta-item"><div class="label">Gewicht</div><div class="value">{f'{dog["gewicht_kg"]} kg' if dog.get("gewicht_kg") else ""}</div></div> <div class="meta-item"><div class="label">Gewicht</div><div class="value">{f'{dog["gewicht_kg"]} kg' if dog.get("gewicht_kg") else ""}</div></div>
<div class="meta-item"><div class="label">Transponder</div><div class="value">{esc(dog.get("chip_nr")) or ""}</div></div> <div class="meta-item"><div class="label">Transponder</div><div class="value">{esc(dog.get("chip_nr")) or ""}</div></div>
{f'<div class="meta-item"><div class="label">Widerrist</div><div class="value">{dog["widerrist_cm"]} cm</div></div>' if dog.get("widerrist_cm") else ''}
<div class="meta-item"><div class="label">Besitzer</div><div class="value">{esc(owner["name"]) if owner else ""}</div></div> <div class="meta-item"><div class="label">Besitzer</div><div class="value">{esc(owner["name"]) if owner else ""}</div></div>
</div> </div>
</div> </div>

View file

@ -20,6 +20,7 @@ class DogCreate(BaseModel):
geburtstag: Optional[str] = None geburtstag: Optional[str] = None
geschlecht: Optional[str] = None geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None gewicht_kg: Optional[float] = None
widerrist_cm: Optional[float] = None
chip_nr: Optional[str] = None chip_nr: Optional[str] = None
bio: Optional[str] = None bio: Optional[str] = None
is_public: bool = False is_public: bool = False
@ -32,6 +33,7 @@ class DogUpdate(BaseModel):
geburtstag: Optional[str] = None geburtstag: Optional[str] = None
geschlecht: Optional[str] = None geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None gewicht_kg: Optional[float] = None
widerrist_cm: Optional[float] = None
chip_nr: Optional[str] = None chip_nr: Optional[str] = None
bio: Optional[str] = None bio: Optional[str] = None
is_public: Optional[bool] = None is_public: Optional[bool] = None
@ -141,11 +143,11 @@ async def create_dog(data: DogCreate, user=Depends(get_current_user)):
) )
conn.execute( conn.execute(
"""INSERT INTO dogs (user_id, name, rasse, geburtstag, geschlecht, """INSERT INTO dogs (user_id, name, rasse, geburtstag, geschlecht,
gewicht_kg, chip_nr, bio, is_public) gewicht_kg, widerrist_cm, chip_nr, bio, is_public)
VALUES (?,?,?,?,?,?,?,?,?)""", VALUES (?,?,?,?,?,?,?,?,?,?)""",
(user["id"], data.name, data.rasse, data.geburtstag, (user["id"], data.name, data.rasse, data.geburtstag,
data.geschlecht, data.gewicht_kg, data.chip_nr, data.geschlecht, data.gewicht_kg, data.widerrist_cm,
data.bio, int(data.is_public)) data.chip_nr, data.bio, int(data.is_public))
) )
dog = conn.execute( dog = conn.execute(
"SELECT * FROM dogs WHERE user_id=? ORDER BY id DESC LIMIT 1", "SELECT * FROM dogs WHERE user_id=? ORDER BY id DESC LIMIT 1",

View file

@ -101,9 +101,9 @@
</script> </script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung --> <!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=872"> <link rel="stylesheet" href="/css/design-system.css?v=873">
<link rel="stylesheet" href="/css/layout.css?v=872"> <link rel="stylesheet" href="/css/layout.css?v=873">
<link rel="stylesheet" href="/css/components.css?v=872"> <link rel="stylesheet" href="/css/components.css?v=873">
</head> </head>
<body> <body>
@ -583,10 +583,10 @@
<div id="modal-container"></div> <div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features --> <!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=872"></script> <script src="/js/api.js?v=873"></script>
<script src="/js/ui.js?v=872"></script> <script src="/js/ui.js?v=873"></script>
<script src="/js/app.js?v=872"></script> <script src="/js/app.js?v=873"></script>
<script src="/js/worlds.js?v=872"></script> <script src="/js/worlds.js?v=873"></script>
<!-- Feature-Seiten werden lazy geladen --> <!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '872'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '873'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app'; const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen // Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -129,6 +129,12 @@ window.Page_dog_profile = (() => {
<div style="font-weight:500;font-size:var(--text-sm)">${dog.gewicht_kg} kg</div> <div style="font-weight:500;font-size:var(--text-sm)">${dog.gewicht_kg} kg</div>
</div> </div>
` : ''} ` : ''}
${dog.widerrist_cm ? `
<div class="card" style="padding:var(--space-3)">
<div class="dp-info-label"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#ruler"></use></svg> Widerrist</div>
<div style="font-weight:500;font-size:var(--text-sm)">${dog.widerrist_cm} cm</div>
</div>
` : ''}
<div class="card" style="padding:var(--space-3)"> <div class="card" style="padding:var(--space-3)">
<div style="font-size:var(--text-xs);color:var(--c-text-secondary); <div style="font-size:var(--text-xs);color:var(--c-text-secondary);
margin-bottom:2px"> margin-bottom:2px">
@ -1133,6 +1139,18 @@ window.Page_dog_profile = (() => {
value="${dog?.gewicht_kg || ''}" value="${dog?.gewicht_kg || ''}"
min="0.1" max="120" step="0.1" placeholder="z. B. 28.5"> min="0.1" max="120" step="0.1" placeholder="z. B. 28.5">
</div> </div>
<div class="form-group">
<label class="form-label">
Widerristhöhe (cm)
${UI.help('Der Widerrist ist der höchste Punkt zwischen den Schulterblättern. Hund gerade hinstellen, senkrecht von diesem Punkt zum Boden messen. Ab 40 cm gilt der Hund in NRW als „großer Hund" (Anleinpflicht + Versicherungspflicht). In anderen Bundesländern gelten teils andere Regeln — im Zweifel bei der Gemeinde nachfragen.')}
</label>
<input class="form-control" type="number" name="widerrist_cm"
value="${dog?.widerrist_cm || ''}"
min="10" max="120" step="1" placeholder="z. B. 58">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
Chip-Nummer Chip-Nummer
@ -1141,6 +1159,7 @@ window.Page_dog_profile = (() => {
<input class="form-control" type="text" name="chip_nr" <input class="form-control" type="text" name="chip_nr"
value="${_esc(dog?.chip_nr || '')}" placeholder="15-stellig"> value="${_esc(dog?.chip_nr || '')}" placeholder="15-stellig">
</div> </div>
<div></div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -1328,6 +1347,7 @@ window.Page_dog_profile = (() => {
geburtstag: fd.geburtstag || null, geburtstag: fd.geburtstag || null,
geschlecht: fd.geschlecht || null, geschlecht: fd.geschlecht || null,
gewicht_kg: fd.gewicht_kg ? parseFloat(fd.gewicht_kg) : null, gewicht_kg: fd.gewicht_kg ? parseFloat(fd.gewicht_kg) : null,
widerrist_cm: fd.widerrist_cm ? parseFloat(fd.widerrist_cm) : null,
chip_nr: fd.chip_nr || null, chip_nr: fd.chip_nr || null,
bio: fd.bio || null, bio: fd.bio || null,
is_public: 'is_public' in fd, is_public: 'is_public' in fd,

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v872'; const CACHE_VERSION = 'by-v873';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache