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"),
# Fell-Typ für personalisierte Wetter-Hinweise
("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
("tieraerzte", "avg_rating", "REAL DEFAULT 0"),
("tieraerzte", "anz_bewertungen", "INTEGER DEFAULT 0"),

View file

@ -376,7 +376,7 @@ if STAGING and os.path.isdir(PROD_MEDIA_DIR):
else:
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")
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">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>
{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>
</div>

View file

@ -15,26 +15,28 @@ MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
class DogCreate(BaseModel):
name: str
rasse: Optional[str] = None
geburtstag: Optional[str] = None
geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None
chip_nr: Optional[str] = None
bio: Optional[str] = None
is_public: bool = False
name: str
rasse: Optional[str] = None
geburtstag: Optional[str] = None
geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None
widerrist_cm: Optional[float] = None
chip_nr: Optional[str] = None
bio: Optional[str] = None
is_public: bool = False
class DogUpdate(BaseModel):
name: Optional[str] = None
rasse: Optional[str] = None
rasse_id: Optional[int] = None
geburtstag: Optional[str] = None
geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None
chip_nr: Optional[str] = None
bio: Optional[str] = None
is_public: Optional[bool] = None
name: Optional[str] = None
rasse: Optional[str] = None
rasse_id: Optional[int] = None
geburtstag: Optional[str] = None
geschlecht: Optional[str] = None
gewicht_kg: Optional[float] = None
widerrist_cm: Optional[float] = None
chip_nr: Optional[str] = None
bio: Optional[str] = None
is_public: Optional[bool] = None
@router.get("")
@ -141,11 +143,11 @@ async def create_dog(data: DogCreate, user=Depends(get_current_user)):
)
conn.execute(
"""INSERT INTO dogs (user_id, name, rasse, geburtstag, geschlecht,
gewicht_kg, chip_nr, bio, is_public)
VALUES (?,?,?,?,?,?,?,?,?)""",
gewicht_kg, widerrist_cm, chip_nr, bio, is_public)
VALUES (?,?,?,?,?,?,?,?,?,?)""",
(user["id"], data.name, data.rasse, data.geburtstag,
data.geschlecht, data.gewicht_kg, data.chip_nr,
data.bio, int(data.is_public))
data.geschlecht, data.gewicht_kg, data.widerrist_cm,
data.chip_nr, data.bio, int(data.is_public))
)
dog = conn.execute(
"SELECT * FROM dogs WHERE user_id=? ORDER BY id DESC LIMIT 1",

View file

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

View file

@ -3,7 +3,7 @@
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 IS_STAGING = location.hostname === 'staging.banyaro.app';
// 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>
` : ''}
${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 style="font-size:var(--text-xs);color:var(--c-text-secondary);
margin-bottom:2px">
@ -1133,6 +1139,18 @@ window.Page_dog_profile = (() => {
value="${dog?.gewicht_kg || ''}"
min="0.1" max="120" step="0.1" placeholder="z. B. 28.5">
</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">
<label class="form-label">
Chip-Nummer
@ -1141,6 +1159,7 @@ window.Page_dog_profile = (() => {
<input class="form-control" type="text" name="chip_nr"
value="${_esc(dog?.chip_nr || '')}" placeholder="15-stellig">
</div>
<div></div>
</div>
<div class="form-group">
@ -1327,8 +1346,9 @@ window.Page_dog_profile = (() => {
rasse_id: fd.rasse_id ? parseInt(fd.rasse_id) : null,
geburtstag: fd.geburtstag || null,
geschlecht: fd.geschlecht || null,
gewicht_kg: fd.gewicht_kg ? parseFloat(fd.gewicht_kg) : null,
chip_nr: fd.chip_nr || 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,
bio: fd.bio || null,
is_public: 'is_public' in fd,
fell_typ: fd.fell_typ || null,

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v872';
const CACHE_VERSION = 'by-v873';
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