Feat: Tierärzte-Verwaltung (Sprint 4)

Neue Praxen-Tab in Gesundheit: Tierarzt-Stammdaten (Name, Adresse,
Telefon, Notfall-Nr, E-Mail, Website, Notizen), Anruf- und
Notfall-Schnellzugriff via tel:-Links, Soft-Delete (aktiv=0) für
Praxiswechsel ohne Datenverlust. Tierarzt-Dropdown beim Eintragen
von Tierarzt-Besuchen. SW-Cache → by-v7.
This commit is contained in:
rene 2026-04-13 20:06:59 +02:00
parent c06d9e24a7
commit fc0f48c6d0
7 changed files with 371 additions and 42 deletions

View file

@ -249,6 +249,23 @@ def init_db():
created_at TEXT NOT NULL DEFAULT (datetime('now')) created_at TEXT NOT NULL DEFAULT (datetime('now'))
); );
-- TIERÄRZTE (user-level, nie löschen Historien-Erhalt bei Umzug)
CREATE TABLE IF NOT EXISTS tieraerzte (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
adresse TEXT,
telefon TEXT,
notfall_telefon TEXT,
email TEXT,
website TEXT,
notizen TEXT,
ist_notfallpraxis INTEGER NOT NULL DEFAULT 0,
aktiv INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_tieraerzte_user ON tieraerzte(user_id, aktiv);
""") """)
# Migrations: neue Spalten zu bestehenden Tabellen hinzufügen (idempotent) # Migrations: neue Spalten zu bestehenden Tabellen hinzufügen (idempotent)
@ -277,6 +294,7 @@ def _migrate(conn_factory):
("health", "reaktion", "TEXT"), ("health", "reaktion", "TEXT"),
("health", "datei_url", "TEXT"), ("health", "datei_url", "TEXT"),
("health", "datei_typ", "TEXT"), ("health", "datei_typ", "TEXT"),
("health", "tierarzt_id", "INTEGER"),
] ]
with conn_factory() as conn: with conn_factory() as conn:
for table, column, col_type in migrations: for table, column, col_type in migrations:

View file

@ -46,21 +46,23 @@ app = FastAPI(
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# API-Router registrieren (werden nach und nach hinzugefügt) # API-Router registrieren (werden nach und nach hinzugefügt)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
from routes.auth import router as auth_router from routes.auth import router as auth_router
from routes.dogs import router as dogs_router from routes.dogs import router as dogs_router
from routes.diary import router as diary_router from routes.diary import router as diary_router
from routes.health import router as health_router from routes.health import router as health_router
from routes.poison import router as poison_router from routes.poison import router as poison_router
from routes.push import router as push_router from routes.push import router as push_router
from routes.ki import router as ki_router from routes.ki import router as ki_router
from routes.tieraerzte import router as tieraerzte_router
app.include_router(auth_router, prefix="/api/auth", tags=["Auth"]) app.include_router(auth_router, prefix="/api/auth", tags=["Auth"])
app.include_router(dogs_router, prefix="/api/dogs", tags=["Hunde"]) app.include_router(dogs_router, prefix="/api/dogs", tags=["Hunde"])
app.include_router(diary_router, prefix="/api/dogs", tags=["Tagebuch"]) app.include_router(diary_router, prefix="/api/dogs", tags=["Tagebuch"])
app.include_router(health_router, prefix="/api/dogs", tags=["Gesundheit"]) app.include_router(health_router, prefix="/api/dogs", tags=["Gesundheit"])
app.include_router(poison_router, prefix="/api/poison", tags=["Giftköder"]) app.include_router(poison_router, prefix="/api/poison", tags=["Giftköder"])
app.include_router(push_router, prefix="/api/push", tags=["Push"]) app.include_router(push_router, prefix="/api/push", tags=["Push"])
app.include_router(ki_router, prefix="/api/ki", tags=["KI"]) app.include_router(ki_router, prefix="/api/ki", tags=["KI"])
app.include_router(tieraerzte_router, prefix="/api/tieraerzte", tags=["Tierärzte"])
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View file

@ -42,6 +42,8 @@ class HealthCreate(BaseModel):
schweregrad: Optional[str] = None # leicht | mittel | schwer schweregrad: Optional[str] = None # leicht | mittel | schwer
reaktion: Optional[str] = None reaktion: Optional[str] = None
erinnerung: Optional[int] = 1 erinnerung: Optional[int] = 1
# Tierarzt-Verknüpfung
tierarzt_id: Optional[int] = None
class HealthUpdate(BaseModel): class HealthUpdate(BaseModel):
@ -62,6 +64,7 @@ class HealthUpdate(BaseModel):
schweregrad: Optional[str] = None schweregrad: Optional[str] = None
reaktion: Optional[str] = None reaktion: Optional[str] = None
erinnerung: Optional[int] = None erinnerung: Optional[int] = None
tierarzt_id: Optional[int] = None
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -113,13 +116,13 @@ async def create_health(dog_id: int, data: HealthCreate,
(dog_id, typ, bezeichnung, datum, naechstes, notiz, (dog_id, typ, bezeichnung, datum, naechstes, notiz,
wert, einheit, charge_nr, tierarzt_name, kosten, diagnose, wert, einheit, charge_nr, tierarzt_name, kosten, diagnose,
dosierung, haeufigkeit, aktiv, bis_datum, dosierung, haeufigkeit, aktiv, bis_datum,
schweregrad, reaktion, erinnerung) schweregrad, reaktion, erinnerung, tierarzt_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(dog_id, data.typ, data.bezeichnung, data.datum, data.naechstes, (dog_id, data.typ, data.bezeichnung, data.datum, data.naechstes,
data.notiz, data.wert, data.einheit, data.charge_nr, data.notiz, data.wert, data.einheit, data.charge_nr,
data.tierarzt_name, data.kosten, data.diagnose, data.dosierung, data.tierarzt_name, data.kosten, data.diagnose, data.dosierung,
data.haeufigkeit, data.aktiv, data.bis_datum, data.haeufigkeit, data.aktiv, data.bis_datum,
data.schweregrad, data.reaktion, data.erinnerung) data.schweregrad, data.reaktion, data.erinnerung, data.tierarzt_id)
) )
row = conn.execute( row = conn.execute(
"SELECT * FROM health WHERE dog_id=? ORDER BY id DESC LIMIT 1", "SELECT * FROM health WHERE dog_id=? ORDER BY id DESC LIMIT 1",

View file

@ -0,0 +1,91 @@
"""BAN YARO — Tierärzte Routes (user-level, nie löschen)"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional
from database import db
from auth import get_current_user
router = APIRouter()
class TierarztCreate(BaseModel):
name: str
adresse: Optional[str] = None
telefon: Optional[str] = None
notfall_telefon: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
notizen: Optional[str] = None
ist_notfallpraxis: bool = False
class TierarztUpdate(BaseModel):
name: Optional[str] = None
adresse: Optional[str] = None
telefon: Optional[str] = None
notfall_telefon: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
notizen: Optional[str] = None
ist_notfallpraxis: Optional[bool] = None
aktiv: Optional[bool] = None # False = inaktiv (Umzug etc.)
@router.get("")
async def list_tieraerzte(user=Depends(get_current_user)):
"""Alle Tierärzte des Users — aktive zuerst, dann inaktive (für Historienansicht)."""
with db() as conn:
rows = conn.execute(
"SELECT * FROM tieraerzte WHERE user_id=? ORDER BY aktiv DESC, name",
(user["id"],)
).fetchall()
return [dict(r) for r in rows]
@router.post("", status_code=201)
async def create_tierarzt(data: TierarztCreate, user=Depends(get_current_user)):
with db() as conn:
conn.execute(
"""INSERT INTO tieraerzte
(user_id, name, adresse, telefon, notfall_telefon,
email, website, notizen, ist_notfallpraxis)
VALUES (?,?,?,?,?,?,?,?,?)""",
(user["id"], data.name, data.adresse, data.telefon,
data.notfall_telefon, data.email, data.website,
data.notizen, int(data.ist_notfallpraxis))
)
row = conn.execute(
"SELECT * FROM tieraerzte WHERE user_id=? ORDER BY id DESC LIMIT 1",
(user["id"],)
).fetchone()
return dict(row)
@router.patch("/{tierarzt_id}")
async def update_tierarzt(tierarzt_id: int, data: TierarztUpdate,
user=Depends(get_current_user)):
with db() as conn:
entry = conn.execute(
"SELECT id FROM tieraerzte WHERE id=? AND user_id=?",
(tierarzt_id, user["id"])
).fetchone()
if not entry:
raise HTTPException(404, "Tierarzt nicht gefunden.")
updates = {k: v for k, v in data.model_dump().items() if v is not None}
if "ist_notfallpraxis" in updates:
updates["ist_notfallpraxis"] = int(updates["ist_notfallpraxis"])
if "aktiv" in updates:
updates["aktiv"] = int(updates["aktiv"])
if not updates:
row = conn.execute("SELECT * FROM tieraerzte WHERE id=?", (tierarzt_id,)).fetchone()
return dict(row)
set_clause = ", ".join(f"{k}=?" for k in updates)
conn.execute(
f"UPDATE tieraerzte SET {set_clause} WHERE id=?",
list(updates.values()) + [tierarzt_id]
)
row = conn.execute("SELECT * FROM tieraerzte WHERE id=?", (tierarzt_id,)).fetchone()
return dict(row)

View file

@ -134,6 +134,15 @@ const API = (() => {
}, },
}; };
// ----------------------------------------------------------
// TIERÄRZTE
// ----------------------------------------------------------
const tieraerzte = {
list() { return get('/tieraerzte'); },
create(data) { return post('/tieraerzte', data); },
update(id, d) { return patch(`/tieraerzte/${id}`, d); },
};
// ---------------------------------------------------------- // ----------------------------------------------------------
// GIFTKÖDER-ALARM // GIFTKÖDER-ALARM
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -249,7 +258,7 @@ const API = (() => {
// Öffentliche API // Öffentliche API
return { return {
get, post, put, patch, del, upload, get, post, put, patch, del, upload,
auth, dogs, diary, health, poison, auth, dogs, diary, health, tieraerzte, poison,
places, routes, weather, push, places, routes, weather, push,
subscribeToPush, getLocation, subscribeToPush, getLocation,
APIError, APIError,

View file

@ -9,15 +9,17 @@ window.Page_health = (() => {
let _container = null; let _container = null;
let _appState = null; let _appState = null;
let _data = {}; // { impfung:[], tierarzt:[], gewicht:[], medikament:[], allergie:[], dokument:[] } let _data = {}; // { impfung:[], tierarzt:[], gewicht:[], medikament:[], allergie:[], dokument:[] }
let _praxen = [];
let _activeTab = 'impfung'; let _activeTab = 'impfung';
const TABS = [ const TABS = [
{ key: 'impfung', label: 'Impfpass', icon: '💉' }, { key: 'impfung', label: 'Impfpass', icon: '💉' },
{ key: 'tierarzt', label: 'Tierarzt', icon: '🏥' }, { key: 'tierarzt', label: 'Tierarzt', icon: '🩺' },
{ key: 'gewicht', label: 'Gewicht', icon: '⚖️' }, { key: 'gewicht', label: 'Gewicht', icon: '⚖️' },
{ key: 'medikament', label: 'Medikamente',icon: '💊' }, { key: 'medikament', label: 'Medikamente',icon: '💊' },
{ key: 'allergie', label: 'Allergien', icon: '🌿' }, { key: 'allergie', label: 'Allergien', icon: '🌿' },
{ key: 'dokument', label: 'Dokumente', icon: '📄' }, { key: 'dokument', label: 'Dokumente', icon: '📄' },
{ key: 'praxen', label: 'Praxen', icon: '🏥' },
]; ];
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -165,6 +167,11 @@ window.Page_health = (() => {
} catch (err) { } catch (err) {
UI.toast.error('Gesundheitsdaten konnten nicht geladen werden.'); UI.toast.error('Gesundheitsdaten konnten nicht geladen werden.');
} }
try {
_praxen = await API.tieraerzte.list();
} catch (err) {
// silent fail
}
} }
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -183,6 +190,7 @@ window.Page_health = (() => {
case 'medikament': content.innerHTML = _renderMedikamente(entries); break; case 'medikament': content.innerHTML = _renderMedikamente(entries); break;
case 'allergie': content.innerHTML = _renderAllergien(entries); break; case 'allergie': content.innerHTML = _renderAllergien(entries); break;
case 'dokument': content.innerHTML = _renderDokumente(entries); break; case 'dokument': content.innerHTML = _renderDokumente(entries); break;
case 'praxen': content.innerHTML = _renderPraxen(); break;
} }
_bindTabEvents(content); _bindTabEvents(content);
@ -438,6 +446,17 @@ window.Page_health = (() => {
const entry = (_data[_activeTab] || []).find(e => e.id === id); const entry = (_data[_activeTab] || []).find(e => e.id === id);
if (entry) card.addEventListener('click', () => _openDetail(entry)); if (entry) card.addEventListener('click', () => _openDetail(entry));
}); });
// Praxis öffnen
content.querySelectorAll('[data-action="open-praxis"]').forEach(el => {
el.addEventListener('click', () => {
const id = parseInt(el.dataset.praxisId);
const p = _praxen.find(x => x.id === id);
if (p) _showPraxForm(p);
});
});
// Praxis hinzufügen
content.querySelector('[data-action="add-praxis"]')
?.addEventListener('click', () => _showPraxForm(null));
} }
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -564,7 +583,20 @@ window.Page_health = (() => {
UI.modal.open({ title: `${tabInfo.icon} ${isEdit ? 'Bearbeiten' : tabInfo.label}`, body }); UI.modal.open({ title: `${tabInfo.icon} ${isEdit ? 'Bearbeiten' : tabInfo.label}`, body });
const form = document.getElementById('health-form'); const form = document.getElementById('health-form');
setTimeout(() => form?.querySelector('[name="bezeichnung"]')?.focus(), 150); setTimeout(() => {
form?.querySelector('[name="bezeichnung"]')?.focus();
// Praxis-Dropdown: Name auto-befüllen
const praxisSelect = document.getElementById('health-praxis-select');
const nameInput = document.getElementById('health-tierarzt-name-input');
if (praxisSelect && nameInput) {
praxisSelect.addEventListener('change', () => {
const selected = praxisSelect.options[praxisSelect.selectedIndex];
if (selected.value) {
nameInput.value = selected.dataset.name || selected.textContent.trim();
}
});
}
}, 150);
document.getElementById('health-form-cancel')?.addEventListener('click', UI.modal.close); document.getElementById('health-form-cancel')?.addEventListener('click', UI.modal.close);
@ -646,25 +678,42 @@ window.Page_health = (() => {
<input class="form-control" type="text" name="tierarzt_name" value="${_esc(entry?.tierarzt_name || '')}"> <input class="form-control" type="text" name="tierarzt_name" value="${_esc(entry?.tierarzt_name || '')}">
</div> </div>
`; `;
case 'tierarzt': return ` case 'tierarzt': {
<div class="form-group"> const aktivePraxen = _praxen.filter(p => p.aktiv);
<label class="form-label">Tierarzt / Praxis</label> const praxisDropdown = aktivePraxen.length ? `
<input class="form-control" type="text" name="tierarzt_name" value="${_esc(entry?.tierarzt_name || '')}"> <div class="form-group">
</div> <label class="form-label">Praxis auswählen</label>
<div class="form-group"> <select class="form-control" id="health-praxis-select" name="tierarzt_id">
<label class="form-label">Diagnose</label> <option value=""> Praxis wählen </option>
<textarea class="form-control" name="diagnose" rows="2">${_esc(entry?.diagnose || '')}</textarea> ${aktivePraxen.map(p => `
</div> <option value="${p.id}" data-name="${_esc(p.name)}"
<div class="form-group"> ${entry?.tierarzt_id === p.id ? 'selected' : ''}>
<label class="form-label">Kosten ()</label> ${_esc(p.name)}
<input class="form-control" type="number" step="0.01" min="0" name="kosten" </option>`).join('')}
value="${entry?.kosten ?? ''}"> </select>
</div> </div>` : '';
<div class="form-group"> return `
<label class="form-label">Nächster Termin (optional)</label> ${praxisDropdown}
<input class="form-control" type="date" name="naechstes" value="${entry?.naechstes || ''}"> <div class="form-group">
</div> <label class="form-label">Tierarzt / Praxis (Freitext)</label>
`; <input class="form-control" type="text" id="health-tierarzt-name-input"
name="tierarzt_name" value="${_esc(entry?.tierarzt_name || '')}">
</div>
<div class="form-group">
<label class="form-label">Diagnose</label>
<textarea class="form-control" name="diagnose" rows="2">${_esc(entry?.diagnose || '')}</textarea>
</div>
<div class="form-group">
<label class="form-label">Kosten ()</label>
<input class="form-control" type="number" step="0.01" min="0" name="kosten"
value="${entry?.kosten ?? ''}">
</div>
<div class="form-group">
<label class="form-label">Nächster Termin (optional)</label>
<input class="form-control" type="date" name="naechstes" value="${entry?.naechstes || ''}">
</div>
`;
}
case 'gewicht': return ` case 'gewicht': return `
<div class="form-group"> <div class="form-group">
<label class="form-label">Gewicht (kg) *</label> <label class="form-label">Gewicht (kg) *</label>
@ -728,8 +777,9 @@ window.Page_health = (() => {
schweregrad: fd.schweregrad || null, schweregrad: fd.schweregrad || null,
reaktion: fd.reaktion || null, reaktion: fd.reaktion || null,
}; };
if (fd.wert) p.wert = parseFloat(fd.wert); if (fd.wert) p.wert = parseFloat(fd.wert);
if (fd.kosten) p.kosten = parseFloat(fd.kosten); if (fd.kosten) p.kosten = parseFloat(fd.kosten);
if (fd.tierarzt_id) p.tierarzt_id = parseInt(fd.tierarzt_id);
if (typ === 'medikament') { if (typ === 'medikament') {
p.aktiv = 'aktiv' in fd ? 1 : 0; p.aktiv = 'aktiv' in fd ? 1 : 0;
} }
@ -738,6 +788,162 @@ window.Page_health = (() => {
return p; return p;
} }
// ----------------------------------------------------------
// PRAXEN — Liste
// ----------------------------------------------------------
function _renderPraxen() {
const addBtn = `<button class="btn btn-primary btn-sm" data-action="add-praxis">+ Praxis hinzufügen</button>`;
const aktive = _praxen.filter(p => p.aktiv);
const inaktive = _praxen.filter(p => !p.aktiv);
if (!_praxen.length) return UI.emptyState({
icon: '🏥', title: 'Noch keine Praxis eingetragen',
text: 'Trage deine Tierarztpraxis ein für schnellen Zugriff.',
action: addBtn
});
const renderCard = p => `
<div class="health-card praxis-card${!p.aktiv ? ' health-card--inactive' : ''}"
data-praxis-id="${p.id}" data-action="open-praxis">
<div style="font-size:1.5rem">${p.ist_notfallpraxis ? '🚨' : '🏥'}</div>
<div class="health-card-body">
<div class="health-card-title">
${_esc(p.name)}
${!p.aktiv ? '<span style="font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:400"> · Ehemalig</span>' : ''}
</div>
${p.adresse ? `<div class="health-card-meta">${_esc(p.adresse)}</div>` : ''}
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
${p.telefon ? `
<a href="tel:${_esc(p.telefon)}" class="btn btn-secondary btn-sm"
onclick="event.stopPropagation()">
📞 Anrufen
</a>` : ''}
${p.notfall_telefon ? `
<a href="tel:${_esc(p.notfall_telefon)}" class="btn btn-danger btn-sm"
onclick="event.stopPropagation()">
🚨 Notfall
</a>` : ''}
</div>
</div>
</div>
`;
return `
<div style="display:flex;justify-content:flex-end;margin-bottom:var(--space-3)">
${addBtn}
</div>
<div class="health-list">
${aktive.map(renderCard).join('')}
${inaktive.length ? `
<div style="margin-top:var(--space-4);padding-top:var(--space-3);
border-top:1px solid var(--c-border)">
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
margin:0 0 var(--space-3)">Ehemalige Praxen</p>
${inaktive.map(renderCard).join('')}
</div>` : ''}
</div>
`;
}
// ----------------------------------------------------------
// PRAXEN — Formular (Neu / Bearbeiten)
// ----------------------------------------------------------
function _showPraxForm(praxis) {
const isEdit = !!praxis;
UI.modal.open({
title: isEdit ? `${praxis.name} bearbeiten` : 'Praxis hinzufügen',
body: `
<form id="praxis-form" autocomplete="off">
<div class="form-group">
<label class="form-label">Name der Praxis *</label>
<input class="form-control" type="text" name="name"
value="${_esc(praxis?.name || '')}" placeholder="Dr. Muster Tierarztpraxis" required>
</div>
<div class="form-group">
<label class="form-label">Adresse</label>
<input class="form-control" type="text" name="adresse"
value="${_esc(praxis?.adresse || '')}" placeholder="Musterstraße 1, 12345 Stadt">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="form-group">
<label class="form-label">Telefon</label>
<input class="form-control" type="tel" name="telefon"
value="${_esc(praxis?.telefon || '')}" placeholder="089 123456">
</div>
<div class="form-group">
<label class="form-label">Notfall-Telefon</label>
<input class="form-control" type="tel" name="notfall_telefon"
value="${_esc(praxis?.notfall_telefon || '')}" placeholder="089 999999">
</div>
</div>
<div class="form-group">
<label class="form-label">E-Mail</label>
<input class="form-control" type="email" name="email"
value="${_esc(praxis?.email || '')}" placeholder="praxis@beispiel.de">
</div>
<div class="form-group">
<label class="form-label">Notizen</label>
<textarea class="form-control" name="notizen" rows="2"
placeholder="Öffnungszeiten, Besonderheiten…">${_esc(praxis?.notizen || '')}</textarea>
</div>
<div class="form-group">
<label class="form-label" style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
<input type="checkbox" name="ist_notfallpraxis" ${praxis?.ist_notfallpraxis ? 'checked' : ''}>
Notfallpraxis (24h / Wochenende)
</label>
</div>
${isEdit ? `
<div class="form-group">
<label class="form-label" style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
<input type="checkbox" name="inaktiv" ${!praxis?.aktiv ? 'checked' : ''}>
Als ehemalige Praxis markieren (bei Umzug / Arztwechsel)
</label>
</div>` : ''}
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-4)">
<button type="button" class="btn btn-secondary flex-1" id="praxis-cancel">Abbrechen</button>
<button type="submit" class="btn btn-primary flex-1">${isEdit ? 'Speichern' : 'Hinzufügen'}</button>
</div>
</form>
`,
});
document.getElementById('praxis-cancel')?.addEventListener('click', UI.modal.close);
document.getElementById('praxis-form')?.addEventListener('submit', async e => {
e.preventDefault();
const btn = e.target.querySelector('[type="submit"]');
const fd = UI.formData(e.target);
await UI.asyncButton(btn, async () => {
const payload = {
name: fd.name?.trim(),
adresse: fd.adresse || null,
telefon: fd.telefon || null,
notfall_telefon: fd.notfall_telefon || null,
email: fd.email || null,
notizen: fd.notizen || null,
ist_notfallpraxis: 'ist_notfallpraxis' in fd,
};
if (!payload.name) { UI.toast.warning('Bitte einen Namen eingeben.'); return; }
let saved;
if (isEdit) {
payload.aktiv = !('inaktiv' in fd);
saved = await API.tieraerzte.update(praxis.id, payload);
_praxen = _praxen.map(p => p.id === praxis.id ? saved : p);
UI.toast.success('Praxis gespeichert.');
} else {
saved = await API.tieraerzte.create(payload);
_praxen.push(saved);
UI.toast.success(`${saved.name} hinzugefügt.`);
}
UI.modal.close();
_renderTab();
});
});
}
// ---------------------------------------------------------- // ----------------------------------------------------------
// KI-ZUSAMMENFASSUNG // KI-ZUSAMMENFASSUNG
// ---------------------------------------------------------- // ----------------------------------------------------------

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications Offline-Cache + Push Notifications
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v6'; const CACHE_VERSION = 'by-v7';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
// Diese Dateien werden beim Install gecacht (App Shell) // Diese Dateien werden beim Install gecacht (App Shell)