Feature: Wurf-Buchstabe (A–Z) + Wurf-Name — DB, Backend, Formular, Kartenanzeige (SW by-v912)

This commit is contained in:
rene 2026-05-13 19:58:50 +02:00
parent ca9d9a05c5
commit 53f0cb37d6
7 changed files with 56 additions and 15 deletions

View file

@ -1353,6 +1353,13 @@ def _migrate(conn_factory):
);
CREATE INDEX IF NOT EXISTS idx_litters_breeder ON litters(breeder_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_litters_status ON litters(status, sichtbar);
# wurf_rang + wurf_name Spalten nachrüsten
try:
conn.execute("ALTER TABLE litters ADD COLUMN wurf_rang TEXT")
except Exception: pass
try:
conn.execute("ALTER TABLE litters ADD COLUMN wurf_name TEXT")
except Exception: pass
CREATE TABLE IF NOT EXISTS puppies (
id INTEGER PRIMARY KEY AUTOINCREMENT,

View file

@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request):
raise _HE(404, "Nicht gefunden.")
return _media_response(filepath)
APP_VER = "911" # muss mit APP_VER in app.js übereinstimmen
APP_VER = "912" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json")
async def assetlinks():

View file

@ -27,23 +27,27 @@ def _require_breeder(user=Depends(get_current_user)):
# Schemas
# ------------------------------------------------------------------
class LitterCreate(BaseModel):
wurf_rang: Optional[str] = None # A, B, C …
wurf_name: Optional[str] = None # z.B. "Vatertags-Wurf"
vater_name: Optional[str] = None
mutter_name: Optional[str] = None
vater_id: Optional[int] = None # FK zucht_hunde
mutter_id: Optional[int] = None # FK zucht_hunde
geburt_datum: Optional[str] = None # YYYY-MM-DD
erwartetes_datum: Optional[str] = None # YYYY-MM-DD
vater_id: Optional[int] = None
mutter_id: Optional[int] = None
geburt_datum: Optional[str] = None
erwartetes_datum: Optional[str] = None
welpen_gesamt: Optional[int] = None
welpen_verfuegbar: Optional[int] = None
beschreibung: Optional[str] = None
gesundheitstests: Optional[str] = None
preis_spanne: Optional[str] = None
status: str = "geplant" # geplant|geboren|verfuegbar|abgeschlossen
status: str = "geplant"
sichtbar: int = 0
sichtbar_bis: Optional[str] = None
class LitterUpdate(BaseModel):
wurf_rang: Optional[str] = None
wurf_name: Optional[str] = None
vater_name: Optional[str] = None
mutter_name: Optional[str] = None
vater_id: Optional[int] = None
@ -189,13 +193,16 @@ async def create_litter(body: LitterCreate, user=Depends(_require_breeder)):
cur = conn.execute(
"""INSERT INTO litters
(breeder_id, vater_name, mutter_name, vater_id, mutter_id,
(breeder_id, wurf_rang, wurf_name,
vater_name, mutter_name, vater_id, mutter_id,
geburt_datum, erwartetes_datum,
welpen_gesamt, welpen_verfuegbar, beschreibung, gesundheitstests,
preis_spanne, status, sichtbar, sichtbar_bis)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
profile["id"],
body.wurf_rang,
body.wurf_name,
body.vater_name,
body.mutter_name,
body.vater_id,

View file

@ -591,10 +591,10 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=911"></script>
<script src="/js/ui.js?v=911"></script>
<script src="/js/app.js?v=911"></script>
<script src="/js/worlds.js?v=911"></script>
<script src="/js/api.js?v=912"></script>
<script src="/js/ui.js?v=912"></script>
<script src="/js/app.js?v=912"></script>
<script src="/js/worlds.js?v=912"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '911'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '912'; // ← 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

@ -353,8 +353,13 @@ window.Page_litters = (() => {
<div style="padding:var(--space-4) var(--space-4) var(--space-3);border-bottom:1px solid var(--c-border)">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap">
<div style="min-width:0">
${(l.wurf_rang || l.wurf_name) ? `
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
${l.wurf_rang ? `<span style="background:var(--c-primary);color:white;border-radius:999px;padding:1px 10px;font-size:var(--text-xs);font-weight:700">${_esc(l.wurf_rang)}-Wurf</span>` : ''}
${l.wurf_name ? `<span style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${_esc(l.wurf_name)}</span>` : ''}
</div>` : ''}
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-2)">
<span style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${elternLabel}</span>
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${elternLabel}</span>
${_statusBadge(l.status)}
${sichtbarChip}
</div>
@ -925,9 +930,29 @@ window.Page_litters = (() => {
value="${_esc(currentName || '')}" placeholder="oder Namen frei eingeben">`;
};
const rangOpts = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(l =>
`<option value="${l}" ${v.wurf_rang === l ? 'selected' : ''}>${l}-Wurf</option>`
).join('');
const body = `
<form id="litter-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 2fr;gap:var(--space-3)">
<div class="form-group">
<label class="form-label">Wurf-Buchstabe</label>
<select class="form-control" name="wurf_rang">
<option value=""> kein </option>
${rangOpts}
</select>
</div>
<div class="form-group">
<label class="form-label">Wurf-Name <span style="font-weight:normal;color:var(--c-text-muted)">(optional)</span></label>
<input class="form-control" type="text" name="wurf_name"
placeholder="z.B. Vatertags-Wurf, Frühlings-Wurf …"
value="${_esc(v.wurf_name || '')}">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="form-group">
<label class="form-label">Vater</label>
@ -1042,6 +1067,8 @@ window.Page_litters = (() => {
const fd = new FormData(e.target);
const payload = {
wurf_rang: fd.get('wurf_rang') || null,
wurf_name: fd.get('wurf_name')?.trim() || null,
vater_name: fd.get('vater_name')?.trim() || null,
mutter_name: fd.get('mutter_name')?.trim() || null,
vater_id: fd.get('vater_id') ? parseInt(fd.get('vater_id')) : null,

View file

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