Feature: Wurf-Buchstabe (A–Z) + Wurf-Name — DB, Backend, Formular, Kartenanzeige (SW by-v912)
This commit is contained in:
parent
ca9d9a05c5
commit
53f0cb37d6
7 changed files with 56 additions and 15 deletions
|
|
@ -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_breeder ON litters(breeder_id, created_at DESC);
|
||||||
CREATE INDEX IF NOT EXISTS idx_litters_status ON litters(status, sichtbar);
|
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 (
|
CREATE TABLE IF NOT EXISTS puppies (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
|
||||||
|
|
@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request):
|
||||||
raise _HE(404, "Nicht gefunden.")
|
raise _HE(404, "Nicht gefunden.")
|
||||||
return _media_response(filepath)
|
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")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -27,23 +27,27 @@ def _require_breeder(user=Depends(get_current_user)):
|
||||||
# Schemas
|
# Schemas
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
class LitterCreate(BaseModel):
|
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
|
vater_name: Optional[str] = None
|
||||||
mutter_name: Optional[str] = None
|
mutter_name: Optional[str] = None
|
||||||
vater_id: Optional[int] = None # FK zucht_hunde
|
vater_id: Optional[int] = None
|
||||||
mutter_id: Optional[int] = None # FK zucht_hunde
|
mutter_id: Optional[int] = None
|
||||||
geburt_datum: Optional[str] = None # YYYY-MM-DD
|
geburt_datum: Optional[str] = None
|
||||||
erwartetes_datum: Optional[str] = None # YYYY-MM-DD
|
erwartetes_datum: Optional[str] = None
|
||||||
welpen_gesamt: Optional[int] = None
|
welpen_gesamt: Optional[int] = None
|
||||||
welpen_verfuegbar: Optional[int] = None
|
welpen_verfuegbar: Optional[int] = None
|
||||||
beschreibung: Optional[str] = None
|
beschreibung: Optional[str] = None
|
||||||
gesundheitstests: Optional[str] = None
|
gesundheitstests: Optional[str] = None
|
||||||
preis_spanne: Optional[str] = None
|
preis_spanne: Optional[str] = None
|
||||||
status: str = "geplant" # geplant|geboren|verfuegbar|abgeschlossen
|
status: str = "geplant"
|
||||||
sichtbar: int = 0
|
sichtbar: int = 0
|
||||||
sichtbar_bis: Optional[str] = None
|
sichtbar_bis: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class LitterUpdate(BaseModel):
|
class LitterUpdate(BaseModel):
|
||||||
|
wurf_rang: Optional[str] = None
|
||||||
|
wurf_name: Optional[str] = None
|
||||||
vater_name: Optional[str] = None
|
vater_name: Optional[str] = None
|
||||||
mutter_name: Optional[str] = None
|
mutter_name: Optional[str] = None
|
||||||
vater_id: Optional[int] = None
|
vater_id: Optional[int] = None
|
||||||
|
|
@ -189,13 +193,16 @@ async def create_litter(body: LitterCreate, user=Depends(_require_breeder)):
|
||||||
|
|
||||||
cur = conn.execute(
|
cur = conn.execute(
|
||||||
"""INSERT INTO litters
|
"""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,
|
geburt_datum, erwartetes_datum,
|
||||||
welpen_gesamt, welpen_verfuegbar, beschreibung, gesundheitstests,
|
welpen_gesamt, welpen_verfuegbar, beschreibung, gesundheitstests,
|
||||||
preis_spanne, status, sichtbar, sichtbar_bis)
|
preis_spanne, status, sichtbar, sichtbar_bis)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
profile["id"],
|
profile["id"],
|
||||||
|
body.wurf_rang,
|
||||||
|
body.wurf_name,
|
||||||
body.vater_name,
|
body.vater_name,
|
||||||
body.mutter_name,
|
body.mutter_name,
|
||||||
body.vater_id,
|
body.vater_id,
|
||||||
|
|
|
||||||
|
|
@ -591,10 +591,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=911"></script>
|
<script src="/js/api.js?v=912"></script>
|
||||||
<script src="/js/ui.js?v=911"></script>
|
<script src="/js/ui.js?v=912"></script>
|
||||||
<script src="/js/app.js?v=911"></script>
|
<script src="/js/app.js?v=912"></script>
|
||||||
<script src="/js/worlds.js?v=911"></script>
|
<script src="/js/worlds.js?v=912"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 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
|
||||||
|
|
|
||||||
|
|
@ -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="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="display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap">
|
||||||
<div style="min-width:0">
|
<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)">
|
<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)}
|
${_statusBadge(l.status)}
|
||||||
${sichtbarChip}
|
${sichtbarChip}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -925,9 +930,29 @@ window.Page_litters = (() => {
|
||||||
value="${_esc(currentName || '')}" placeholder="oder Namen frei eingeben">`;
|
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 = `
|
const body = `
|
||||||
<form id="litter-form" autocomplete="off">
|
<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 style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Vater</label>
|
<label class="form-label">Vater</label>
|
||||||
|
|
@ -1042,6 +1067,8 @@ window.Page_litters = (() => {
|
||||||
const fd = new FormData(e.target);
|
const fd = new FormData(e.target);
|
||||||
|
|
||||||
const payload = {
|
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,
|
vater_name: fd.get('vater_name')?.trim() || null,
|
||||||
mutter_name: fd.get('mutter_name')?.trim() || null,
|
mutter_name: fd.get('mutter_name')?.trim() || null,
|
||||||
vater_id: fd.get('vater_id') ? parseInt(fd.get('vater_id')) : null,
|
vater_id: fd.get('vater_id') ? parseInt(fd.get('vater_id')) : null,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v911';
|
const CACHE_VERSION = 'by-v912';
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue