diff --git a/VERSION b/VERSION
index a624bd7..2d0ce9f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1133
\ No newline at end of file
+1131
\ No newline at end of file
diff --git a/backend/routes/health.py b/backend/routes/health.py
index f16645e..7b9ed35 100644
--- a/backend/routes/health.py
+++ b/backend/routes/health.py
@@ -15,9 +15,7 @@ MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".pdf"}
# Erlaubte Typen
-# Routine-/Pflege-Typen (wiederkehrend mit intervall_tage): parasit, krallen, fellpflege
-TYPEN = {"impfung", "entwurmung", "tierarzt", "medikament", "gewicht", "allergie", "dokument",
- "laeufigkeit", "parasit", "krallen", "fellpflege"}
+TYPEN = {"impfung", "entwurmung", "tierarzt", "medikament", "gewicht", "allergie", "dokument", "laeufigkeit"}
# ------------------------------------------------------------------
@@ -166,15 +164,15 @@ async def create_health(dog_id: int, data: HealthCreate,
(dog_id, typ, bezeichnung, datum, naechstes, notiz,
wert, einheit, charge_nr, tierarzt_name, kosten, diagnose,
dosierung, haeufigkeit, aktiv, bis_datum,
- schweregrad, reaktion, erinnerung, intervall_tage, tierarzt_id,
+ schweregrad, reaktion, erinnerung, tierarzt_id,
deckdatum, wurftermin)
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(dog_id, data.typ, data.bezeichnung, data.datum, data.naechstes,
data.notiz, data.wert, data.einheit, data.charge_nr,
data.tierarzt_name, data.kosten, data.diagnose, data.dosierung,
data.haeufigkeit, data.aktiv, data.bis_datum,
- data.schweregrad, data.reaktion, data.erinnerung, data.intervall_tage,
- data.tierarzt_id, data.deckdatum, data.wurftermin)
+ data.schweregrad, data.reaktion, data.erinnerung, data.tierarzt_id,
+ data.deckdatum, data.wurftermin)
)
row = conn.execute(
"SELECT * FROM health WHERE dog_id=? ORDER BY id DESC LIMIT 1",
@@ -214,34 +212,6 @@ async def update_health(dog_id: int, entry_id: int, data: HealthUpdate,
return _entry_with_media(row, media_map)
-# ------------------------------------------------------------------
-# POST /api/dogs/{dog_id}/health/{id}/erledigt
-# Markiert eine wiederkehrende Routine als heute erledigt und schreibt
-# bei gesetztem intervall_tage das nächste Fälligkeitsdatum automatisch fort.
-# ------------------------------------------------------------------
-@router.post("/{dog_id}/health/{entry_id}/erledigt")
-async def complete_health(dog_id: int, entry_id: int, user=Depends(get_current_user)):
- from datetime import timedelta
- today = date.today()
- with db() as conn:
- _check_dog_owner(conn, dog_id, user["id"])
- entry = conn.execute(
- "SELECT * FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
- ).fetchone()
- if not entry:
- raise HTTPException(404, "Eintrag nicht gefunden.")
-
- intervall = entry["intervall_tage"]
- naechstes = (today + timedelta(days=intervall)).isoformat() if intervall else None
- conn.execute(
- "UPDATE health SET datum=?, naechstes=? WHERE id=?",
- (today.isoformat(), naechstes, entry_id),
- )
- row = conn.execute("SELECT * FROM health WHERE id=?", (entry_id,)).fetchone()
- media_map = _fetch_media_items(conn, [entry_id])
- return _entry_with_media(row, media_map)
-
-
# ------------------------------------------------------------------
# DELETE /api/dogs/{dog_id}/health/{id}
# ------------------------------------------------------------------
@@ -530,9 +500,6 @@ _TERMIN_TYPEN = {
'tierarzt': {'label': 'Tierarztbesuch','beim_tierarzt': True, 'icon': 'first-aid'},
'medikament': {'label': 'Medikament', 'beim_tierarzt': False, 'icon': 'pill'},
'laeufigkeit': {'label': 'Läufigkeit', 'beim_tierarzt': False, 'icon': 'calendar'},
- 'parasit': {'label': 'Zecken-/Flohschutz', 'beim_tierarzt': False, 'icon': 'bug-beetle'},
- 'krallen': {'label': 'Krallen schneiden', 'beim_tierarzt': False, 'icon': 'scissors'},
- 'fellpflege': {'label': 'Fellpflege', 'beim_tierarzt': False, 'icon': 'wind'},
}
@router.get("/{dog_id}/health/terminvorschlaege")
diff --git a/backend/routes/jobs.py b/backend/routes/jobs.py
index 0b678e5..59c73c2 100644
--- a/backend/routes/jobs.py
+++ b/backend/routes/jobs.py
@@ -268,20 +268,14 @@ async def update_application(
"UPDATE users SET is_social_media=1 WHERE id=?",
(row["user_id"],)
)
- # Atomare Gründer-Vergabe inkl. founder_number — Race-frei via Sub-Query
- # (konsistent mit dogs.py / partner.py).
- conn.execute(
- """UPDATE users
- SET is_founder = 1,
- founder_number = (
- SELECT IFNULL(MAX(founder_number), 0) + 1
- FROM users WHERE is_founder = 1
- )
- WHERE id = ?
- AND (is_founder IS NULL OR is_founder = 0)
- AND (SELECT COUNT(*) FROM users WHERE is_founder = 1) < 100""",
- (row["user_id"],)
- )
+ founder_count = conn.execute(
+ "SELECT COUNT(*) FROM users WHERE is_founder=1"
+ ).fetchone()[0]
+ if founder_count < 100:
+ conn.execute(
+ "UPDATE users SET is_founder=1 WHERE id=? AND is_founder=0",
+ (row["user_id"],)
+ )
# Status-Mail an Bewerber
try:
diff --git a/backend/scheduler.py b/backend/scheduler.py
index 4f8ce5e..01c17ae 100644
--- a/backend/scheduler.py
+++ b/backend/scheduler.py
@@ -638,8 +638,7 @@ async def _job_health_reminders():
FROM health h
JOIN dogs d ON d.id = h.dog_id
WHERE h.naechstes IN (?, ?, ?, ?)
- AND h.typ IN ('impfung', 'entwurmung', 'medikament',
- 'parasit', 'krallen', 'fellpflege')
+ AND h.typ IN ('impfung', 'entwurmung', 'medikament')
AND (h.erinnerung IS NULL OR h.erinnerung = 1)
""", (str(today), str(in7), str(in3), str(yesterday))).fetchall()
diff --git a/backend/static/index.html b/backend/static/index.html
index 1e0a5d0..2cbbd06 100644
--- a/backend/static/index.html
+++ b/backend/static/index.html
@@ -86,14 +86,14 @@
Ban Yaro
-
+
-
-
-
-
-
+
+
+
+
+
@@ -617,11 +617,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -631,7 +631,7 @@
-
+
diff --git a/backend/static/js/api.js b/backend/static/js/api.js
index de39835..af67c36 100644
--- a/backend/static/js/api.js
+++ b/backend/static/js/api.js
@@ -204,7 +204,6 @@ const API = (() => {
},
create(dogId, data) { return post(`/dogs/${dogId}/health`, data); },
update(dogId, id, d) { return patch(`/dogs/${dogId}/health/${id}`, d); },
- complete(dogId, id) { return post(`/dogs/${dogId}/health/${id}/erledigt`); },
delete(dogId, id) { return del(`/dogs/${dogId}/health/${id}`); },
uploadDokument(dogId, id, formData) {
return upload(`/dogs/${dogId}/health/${id}/dokument`, formData);
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index a7b5e90..bbc25c6 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '1133'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '1131'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;
diff --git a/backend/static/js/pages/diary.js b/backend/static/js/pages/diary.js
index 653b117..f2137d8 100644
--- a/backend/static/js/pages/diary.js
+++ b/backend/static/js/pages/diary.js
@@ -782,7 +782,7 @@ window.Page_diary = (() => {
const id = parseInt(btn.dataset.entryId);
const label = btn.dataset.label || '';
const location = btn.dataset.location || null;
- UI.noteModal('diary', id, label, location || null);
+ _openNoteModal('diary', id, label, location || null);
});
});
}
@@ -1190,7 +1190,7 @@ window.Page_diary = (() => {
view.querySelector('#diary-dv-note')?.addEventListener('click', e => {
e.stopPropagation();
const label = entry.titel || entry.datum || String(entry.id);
- UI.noteModal('diary', entry.id, label, entry.location_name || null);
+ _openNoteModal('diary', entry.id, label, entry.location_name || null);
});
// Bearbeiten
@@ -1953,6 +1953,83 @@ window.Page_diary = (() => {
// ----------------------------------------------------------
// NOTIZ-MODAL (custom DOM, kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, parentId);
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName, client_time: API.clientNow() };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, parentId, payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
// PUBLIC
diff --git a/backend/static/js/pages/erste-hilfe.js b/backend/static/js/pages/erste-hilfe.js
index 780356b..e8f1582 100644
--- a/backend/static/js/pages/erste-hilfe.js
+++ b/backend/static/js/pages/erste-hilfe.js
@@ -461,7 +461,7 @@ window.Page_erste_hilfe = (() => {
const titel = btn.dataset.titel;
const kat = KATEGORIEN.find(k => k.id === katId);
const label = kat ? `${kat.label} — ${titel}` : titel;
- UI.noteModal('erste_hilfe', katId, label, null);
+ _openNoteModal('erste_hilfe', katId, label, null);
});
});
}
@@ -469,6 +469,85 @@ window.Page_erste_hilfe = (() => {
// ----------------------------------------------------------------
// NOTIZ-MODAL (custom DOM, kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ const _esc = s => s ? String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"') : '';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, parentId);
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, parentId, payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------------
// PUBLIC
diff --git a/backend/static/js/pages/events.js b/backend/static/js/pages/events.js
index 62c2d23..afb7e62 100644
--- a/backend/static/js/pages/events.js
+++ b/backend/static/js/pages/events.js
@@ -643,7 +643,7 @@ window.Page_events = (() => {
const noteBtn = e.target.closest('.ev-note-btn');
if (noteBtn) {
e.stopPropagation();
- UI.noteModal(
+ _openNoteModal(
'event',
parseInt(noteBtn.dataset.evNoteId),
noteBtn.dataset.evNoteLabel,
@@ -660,6 +660,55 @@ window.Page_events = (() => {
// ----------------------------------------------------------
// Notiz-Modal (Custom DOM — kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ let existingNote = null;
+ try { existingNote = await API.notes.get(parentType, String(parentId)); } catch {}
+
+ const ovl = document.createElement('div');
+ ovl.style.cssText = 'position:fixed;inset:0;z-index:1200;background:rgba(0,0,0,0.55);display:flex;align-items:flex-end;justify-content:center';
+ ovl.innerHTML = `
+
+
+
+ Notiz — ${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+ `;
+ document.body.appendChild(ovl);
+
+ const close = () => ovl.remove();
+ ovl.querySelector('#ev-note-close')?.addEventListener('click', close);
+ ovl.querySelector('#ev-note-cancel')?.addEventListener('click', close);
+ ovl.addEventListener('click', e => { if (e.target === ovl) close(); });
+
+ ovl.querySelector('#ev-note-save')?.addEventListener('click', async () => {
+ const text = ovl.querySelector('#ev-note-text')?.value?.trim() || '';
+ const payload = { text, parent_label: parentLabel, location_name: locationName || null };
+ try {
+ if (existingNote?.id) {
+ await API.notes.update(existingNote.id, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ close();
+ } catch (err) { UI.toast.error(err.message || 'Fehler beim Speichern.'); }
+ });
+ }
return { init, refresh, openNew, _openDetail: _showDetail };
diff --git a/backend/static/js/pages/friends.js b/backend/static/js/pages/friends.js
index ea4405c..9c68959 100644
--- a/backend/static/js/pages/friends.js
+++ b/backend/static/js/pages/friends.js
@@ -442,7 +442,7 @@ window.Page_friends = (() => {
e.stopPropagation();
const id = parseInt(btn.dataset.frNoteId);
const name = btn.dataset.frNoteName || '';
- UI.noteModal('friends', id, name, null);
+ _openNoteModal('friends', id, name, null);
});
});
@@ -866,6 +866,83 @@ window.Page_friends = (() => {
// ----------------------------------------------------------
// NOTIZ-MODAL
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, String(parentId));
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
return { init, refresh, onDogChange, _accept, _decline, _cancel, _removeFriend, _openChat };
diff --git a/backend/static/js/pages/health.js b/backend/static/js/pages/health.js
index 0cf9b93..78f2430 100644
--- a/backend/static/js/pages/health.js
+++ b/backend/static/js/pages/health.js
@@ -19,7 +19,6 @@ window.Page_health = (() => {
{ key: 'tierarzt', label: 'Besuche', icon: '' },
{ key: 'gewicht', label: 'Gewicht', icon: '' },
{ key: 'medikament', label: 'Medikamente', icon: '' },
- { key: 'pflege', label: 'Pflege', icon: '' },
{ key: 'allergie', label: 'Allergien', icon: '' },
{ key: 'dokument', label: 'Dokumente', icon: '' },
{ key: 'praxen', label: 'Praxen', icon: '' },
@@ -28,14 +27,6 @@ window.Page_health = (() => {
];
const LAEUFIGKEIT_TAB = { key: 'laeufigkeit', label: 'Läufigkeit', icon: '' };
- // Pflege-Routinen — wiederkehrende Pflege-Aufgaben, gebündelt im 'pflege'-Tab
- const PFLEGE_TYPEN = ['parasit', 'krallen', 'fellpflege'];
- const PFLEGE_META = {
- parasit: { label: 'Zecken-/Flohschutz', icon: 'bug-beetle', placeholder: 'z.B. Frontline, Seresto-Halsband' },
- krallen: { label: 'Krallen schneiden', icon: 'scissors', placeholder: 'z.B. Krallen kürzen' },
- fellpflege: { label: 'Fellpflege', icon: 'wind', placeholder: 'z.B. Bürsten, Trimmen, Baden' },
- };
-
function _getTabs() {
const tabs = [...BASE_TABS];
if (_appState?.activeDog?.geschlecht === 'w') tabs.splice(2, 0, LAEUFIGKEIT_TAB);
@@ -299,8 +290,6 @@ window.Page_health = (() => {
_data = {};
_getTabs().forEach(t => { _data[t.key] = []; });
_data['laeufigkeit'] = _data['laeufigkeit'] || [];
- // Pflege-Routinen: eigene Listen je Typ (Tab 'pflege' bündelt sie beim Rendern)
- PFLEGE_TYPEN.forEach(t => { _data[t] = []; });
all.forEach(e => {
if (_data[e.typ] !== undefined) _data[e.typ].push(e);
});
@@ -344,7 +333,6 @@ window.Page_health = (() => {
case 'gewicht': content.innerHTML = _renderGewicht(entries); break;
case 'laeufigkeit': content.innerHTML = _renderLaeufigkeit(entries); break;
case 'medikament': content.innerHTML = _renderMedikamente(entries); break;
- case 'pflege': content.innerHTML = _renderPflege(); break;
case 'allergie': content.innerHTML = _renderAllergien(entries); break;
case 'dokument': content.innerHTML = _renderDokumente(entries); break;
case 'praxen': content.innerHTML = _renderPraxen(); break;
@@ -422,65 +410,6 @@ window.Page_health = (() => {
return { color: 'green', label: 'Aktuell', icon: '🟢' };
}
- // ----------------------------------------------------------
- // PFLEGE-ROUTINEN (Zecken-/Flohschutz, Krallen, Fellpflege)
- // ----------------------------------------------------------
- function _intervallLabel(tage) {
- if (!tage) return '';
- const m = { 30: 'monatlich', 60: 'alle 2 Monate', 90: 'vierteljährlich', 180: 'halbjährlich', 365: 'jährlich' };
- return m[tage] || `alle ${tage} Tage`;
- }
-
- function _renderPflege() {
- const addButtons = `
-
- ${PFLEGE_TYPEN.map(t => `
- `).join('')}
-
`;
-
- const all = PFLEGE_TYPEN.flatMap(t => (_data[t] || []).map(e => ({ ...e, _typ: t })));
-
- if (!all.length) return addButtons + _emptyState(
- 'paw-print',
- 'Noch keine Pflege-Routinen',
- 'Lege wiederkehrende Routinen wie Zecken-/Flohschutz, Krallenschneiden oder Fellpflege an — wir erinnern dich rechtzeitig.'
- );
-
- // Fällige zuerst (nach naechstes), Einträge ohne Folgedatum ans Ende
- all.sort((a, b) => {
- if (!a.naechstes) return 1;
- if (!b.naechstes) return -1;
- return a.naechstes.localeCompare(b.naechstes);
- });
-
- const items = all.map(e => {
- const meta = PFLEGE_META[e._typ];
- const ampel = e.naechstes ? _impfAmpel(e.naechstes) : null;
- const interv = _intervallLabel(e.intervall_tage);
- return `
-
- ${ampel ? `
` : ''}
-
-
${UI.icon(meta.icon)} ${UI.escape(e.bezeichnung || meta.label)}
-
- ${meta.label}${e.datum ? ` · zuletzt ${UI.time.format(e.datum + 'T00:00:00')}` : ''}${interv ? ` · ${interv}` : ''}
-
- ${e.naechstes ? `
- Nächste: ${UI.time.format(e.naechstes + 'T00:00:00')} ${ampel.icon}
-
` : ''}
-
-
-
`;
- }).join('');
-
- return addButtons + `${items}
`;
- }
-
// ----------------------------------------------------------
// TIERARZTBESUCHE
// ----------------------------------------------------------
@@ -954,51 +883,21 @@ window.Page_health = (() => {
// ----------------------------------------------------------
// EVENTS BINDEN
// ----------------------------------------------------------
- // Sucht einen Eintrag in der/den Liste(n) des aktiven Tabs.
- // Im Pflege-Tab sind die Einträge auf mehrere Typ-Listen verteilt.
- function _entriesForActiveTab() {
- if (_activeTab === 'pflege') return PFLEGE_TYPEN.flatMap(t => _data[t] || []);
- return _data[_activeTab] || [];
- }
-
function _bindTabEvents(content) {
content.querySelectorAll('[data-action="add-entry"]').forEach(btn => {
btn.addEventListener('click', () => _showForm(null, _activeTab));
});
- // Pflege: pro-Typ-Button "+ Routine" → Formular mit festem Typ
- content.querySelectorAll('[data-action="add-routine"]').forEach(btn => {
- btn.addEventListener('click', () => _showForm(null, btn.dataset.typ));
- });
- // Pflege: Routine als erledigt markieren → Backend schreibt naechstes fort
- content.querySelectorAll('[data-action="routine-erledigt"]').forEach(btn => {
- btn.addEventListener('click', async e => {
- e.stopPropagation();
- const id = parseInt(btn.dataset.id);
- await UI.asyncButton(btn, async () => {
- const saved = await API.health.complete(_appState.activeDog.id, id);
- const list = _data[saved.typ];
- if (list) {
- const idx = list.findIndex(x => x.id === id);
- if (idx !== -1) list[idx] = saved;
- }
- _renderTab();
- _renderErinnerungen();
- UI.toast.success('Als erledigt eingetragen.');
- });
- });
- });
content.querySelectorAll('[data-action="open-entry"]').forEach(card => {
const id = parseInt(card.dataset.id);
- const entry = _entriesForActiveTab().find(e => e.id === id);
- if (entry) card.addEventListener('click', () =>
- _activeTab === 'pflege' ? _showForm(entry, entry.typ) : _openDetail(entry));
+ const entry = (_data[_activeTab] || []).find(e => e.id === id);
+ if (entry) card.addEventListener('click', () => _openDetail(entry));
});
content.querySelectorAll('[data-action="open-note"]').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const id = parseInt(btn.dataset.entryId);
const label = btn.dataset.label || '';
- UI.noteModal('health', id, label, null);
+ _openNoteModal('health', id, label, null);
});
});
// Praxis öffnen → Detail-Modal mit Bewertungen
@@ -1081,19 +980,8 @@ window.Page_health = (() => {
// ----------------------------------------------------------
// DETAIL-ANSICHT
// ----------------------------------------------------------
- // Tab-Info (Icon + Label) für einen Typ — kennt auch die Pflege-Routine-Typen,
- // die keinen eigenen Tab haben (sie liegen im gebündelten 'pflege'-Tab).
- function _typInfo(typ) {
- const meta = PFLEGE_META[typ];
- if (meta) return {
- icon: ``,
- label: meta.label,
- };
- return _getTabs().find(t => t.key === typ) || BASE_TABS[0];
- }
-
function _openDetail(entry) {
- const tabInfo = _typInfo(entry.typ);
+ const tabInfo = _getTabs().find(t => t.key === entry.typ) || BASE_TABS[0];
const fields = _detailFields(entry);
// Media-Items zusammenstellen (neue + legacy)
@@ -1263,7 +1151,7 @@ window.Page_health = (() => {
`;
- const tabInfo = _typInfo(t);
+ const tabInfo = _getTabs().find(tab => tab.key === t) || BASE_TABS[0];
UI.modal.open({ title: `${tabInfo.icon} ${isEdit ? 'Bearbeiten' : tabInfo.label}`, body, footer });
const form = document.getElementById('health-form');
@@ -1406,9 +1294,6 @@ window.Page_health = (() => {
allergie: 'z.B. Hühnchen, Gras, Hausstaub',
dokument: 'z.B. Impfpass, Blutbild',
laeufigkeit: 'Läufigkeit',
- parasit: 'z.B. Frontline, Seresto-Halsband',
- krallen: 'z.B. Krallen kürzen',
- fellpflege: 'z.B. Bürsten, Trimmen, Baden',
};
return ph[typ] || '';
}
@@ -1478,17 +1363,6 @@ window.Page_health = (() => {
${_praxisSelectField(entry)}
`;
- case 'parasit':
- case 'krallen':
- case 'fellpflege': return `
-
-
-
-
-
- ${_intervallField(entry)}
-
- `;
case 'tierarzt': {
const aktivePraxen = _praxen.filter(p => p.aktiv);
const praxisField = aktivePraxen.length
@@ -2975,6 +2849,85 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
// ----------------------------------------------------------
// NOTIZ-MODAL (custom DOM, kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ // Vorhandenes Modal entfernen falls noch offen
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ // Vorhandene Notiz laden
+ try {
+ const existing = await API.notes.get(parentType, parentId);
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, parentId, payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
// KI-TIERARZTFRAGEN
diff --git a/backend/static/js/pages/lost.js b/backend/static/js/pages/lost.js
index 50c0a98..ef4aab5 100644
--- a/backend/static/js/pages/lost.js
+++ b/backend/static/js/pages/lost.js
@@ -353,7 +353,7 @@ window.Page_lost = (() => {
e.stopPropagation();
const id = parseInt(btn.dataset.lostNoteId);
const name = btn.dataset.lostNoteName || '';
- UI.noteModal('lost', id, name, null);
+ _openNoteModal('lost', id, name, null);
});
});
}
@@ -804,6 +804,83 @@ function _emptyState(icon, title, text, cta = '') {
// ----------------------------------------------------------
// NOTIZ-MODAL
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, String(parentId));
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
// PUBLIC
diff --git a/backend/static/js/pages/poison.js b/backend/static/js/pages/poison.js
index 71fafec..f9d1151 100644
--- a/backend/static/js/pages/poison.js
+++ b/backend/static/js/pages/poison.js
@@ -257,7 +257,7 @@ window.Page_poison = (() => {
btn.addEventListener('click', e => {
e.stopPropagation();
const id = parseInt(btn.dataset.poisonNoteId);
- UI.noteModal('poison', id, 'Giftköder-Meldung ' + id, null);
+ _openNoteModal('poison', id, 'Giftköder-Meldung ' + id, null);
});
});
}
@@ -650,6 +650,83 @@ window.Page_poison = (() => {
// ----------------------------------------------------------
// NOTIZ-MODAL
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, String(parentId));
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
// PUBLIC
diff --git a/backend/static/js/pages/routes.js b/backend/static/js/pages/routes.js
index 0c2295e..acb48c6 100644
--- a/backend/static/js/pages/routes.js
+++ b/backend/static/js/pages/routes.js
@@ -2397,7 +2397,7 @@ window.Page_routes = (() => {
// Notiz-Button
document.getElementById('rd-note')?.addEventListener('click', () => {
const label = route.name || (route.distanz_km ? route.distanz_km.toFixed(1) + ' km' : 'Route');
- UI.noteModal('route', route.id, label, null);
+ _openNoteModal('route', route.id, label, null);
});
// Mini-Map
@@ -3054,6 +3054,55 @@ window.Page_routes = (() => {
// ----------------------------------------------------------
// Notiz-Modal (Custom DOM — kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ let existingNote = null;
+ try { existingNote = await API.notes.get(parentType, String(parentId)); } catch {}
+
+ const ovl = document.createElement('div');
+ ovl.style.cssText = 'position:fixed;inset:0;z-index:1200;background:rgba(0,0,0,0.55);display:flex;align-items:flex-end;justify-content:center';
+ ovl.innerHTML = `
+
+
+
+ Notiz — ${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+ `;
+ document.body.appendChild(ovl);
+
+ const close = () => ovl.remove();
+ ovl.querySelector('#rk-note-close')?.addEventListener('click', close);
+ ovl.querySelector('#rk-note-cancel')?.addEventListener('click', close);
+ ovl.addEventListener('click', e => { if (e.target === ovl) close(); });
+
+ ovl.querySelector('#rk-note-save')?.addEventListener('click', async () => {
+ const text = ovl.querySelector('#rk-note-text')?.value?.trim() || '';
+ const payload = { text, parent_label: parentLabel, location_name: locationName || null, client_time: API.clientNow() };
+ try {
+ if (existingNote?.id) {
+ await API.notes.update(existingNote.id, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ close();
+ } catch (err) { UI.toast.error(err.message || 'Fehler beim Speichern.'); }
+ });
+ }
return { init, refresh, onDogChange };
diff --git a/backend/static/js/pages/sitting.js b/backend/static/js/pages/sitting.js
index 095a8f8..fd59ec9 100644
--- a/backend/static/js/pages/sitting.js
+++ b/backend/static/js/pages/sitting.js
@@ -714,7 +714,7 @@ window.Page_sitting = (() => {
const noteBtn = e.target.closest('.sit-note-btn');
if (noteBtn) {
e.stopPropagation();
- UI.noteModal(
+ _openNoteModal(
'sitting',
parseInt(noteBtn.dataset.sitNoteId),
noteBtn.dataset.sitNoteLabel,
@@ -763,6 +763,55 @@ window.Page_sitting = (() => {
// ----------------------------------------------------------
// Notiz-Modal (Custom DOM — kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ let existingNote = null;
+ try { existingNote = await API.notes.get(parentType, String(parentId)); } catch {}
+
+ const ovl = document.createElement('div');
+ ovl.style.cssText = 'position:fixed;inset:0;z-index:1200;background:rgba(0,0,0,0.55);display:flex;align-items:flex-end;justify-content:center';
+ ovl.innerHTML = `
+
+
+
+ Notiz — ${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+ `;
+ document.body.appendChild(ovl);
+
+ const close = () => ovl.remove();
+ ovl.querySelector('#sit-note-close')?.addEventListener('click', close);
+ ovl.querySelector('#sit-note-cancel')?.addEventListener('click', close);
+ ovl.addEventListener('click', e => { if (e.target === ovl) close(); });
+
+ ovl.querySelector('#sit-note-save')?.addEventListener('click', async () => {
+ const text = ovl.querySelector('#sit-note-text')?.value?.trim() || '';
+ const payload = { text, parent_label: parentLabel, location_name: locationName || null };
+ try {
+ if (existingNote?.id) {
+ await API.notes.update(existingNote.id, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ close();
+ } catch (err) { UI.toast.error(err.message || 'Fehler beim Speichern.'); }
+ });
+ }
return { init, refresh };
diff --git a/backend/static/js/pages/trainingsplaene.js b/backend/static/js/pages/trainingsplaene.js
index d7c62d2..7752ea7 100644
--- a/backend/static/js/pages/trainingsplaene.js
+++ b/backend/static/js/pages/trainingsplaene.js
@@ -538,7 +538,7 @@ function _icon(name) {
const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate'
: _activePlan === 'junior' ? 'Junior 6–18 Monate'
: `Erwachsener Hund – ${_activeAdultTab}`;
- UI.noteModal('trainingsplan', dogId, planLabel, null);
+ _openNoteModal('trainingsplan', dogId, planLabel, null);
});
// Plan selector
@@ -768,6 +768,84 @@ function _icon(name) {
// ----------------------------------------------------------
// NOTIZ-MODAL
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ // Vorhandenes Modal entfernen falls noch offen
+ document.getElementById('by-note-modal')?.remove();
+
+ const overlay = document.createElement('div');
+ overlay.id = 'by-note-modal';
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
+
+ overlay.innerHTML = `
+
+
+
+
Notiz
+
${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(overlay);
+
+ const textarea = document.getElementById('by-note-text');
+ const saveBtn = document.getElementById('by-note-save');
+ const cancelBtn = document.getElementById('by-note-cancel');
+ const closeBtn = document.getElementById('by-note-close');
+
+ let existingNoteId = null;
+
+ try {
+ const existing = await API.notes.get(parentType, String(parentId));
+ if (existing?.id) {
+ existingNoteId = existing.id;
+ textarea.value = existing.text || '';
+ }
+ } catch (_) { /* keine Notiz vorhanden — ok */ }
+
+ setTimeout(() => textarea.focus(), 100);
+
+ const _close = () => overlay.remove();
+ closeBtn.addEventListener('click', _close);
+ cancelBtn.addEventListener('click', _close);
+ overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
+
+ document.getElementById('by-note-form').addEventListener('submit', async e => {
+ e.preventDefault();
+ const text = textarea.value.trim();
+ UI.setLoading(saveBtn, true);
+ try {
+ const payload = { text, parent_label: parentLabel, location_name: locationName };
+ if (existingNoteId) {
+ await API.notes.update(existingNoteId, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ _close();
+ } catch (err) {
+ UI.toast.error(err.message || 'Fehler beim Speichern.');
+ UI.setLoading(saveBtn, false);
+ }
+ });
+ }
// ----------------------------------------------------------
// PUBLIC API
diff --git a/backend/static/js/pages/walks.js b/backend/static/js/pages/walks.js
index 2d2072e..e56f569 100644
--- a/backend/static/js/pages/walks.js
+++ b/backend/static/js/pages/walks.js
@@ -311,7 +311,7 @@ window.Page_walks = (() => {
el.querySelectorAll('.wk-note-btn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
- UI.noteModal(
+ _openNoteModal(
'walk',
parseInt(btn.dataset.wkNoteId),
btn.dataset.wkNoteLabel,
@@ -1211,6 +1211,55 @@ window.Page_walks = (() => {
// ----------------------------------------------------------
// Notiz-Modal (Custom DOM — kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------
+ async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
+ let existingNote = null;
+ try { existingNote = await API.notes.get(parentType, String(parentId)); } catch {}
+
+ const ovl = document.createElement('div');
+ ovl.style.cssText = 'position:fixed;inset:0;z-index:1200;background:rgba(0,0,0,0.55);display:flex;align-items:flex-end;justify-content:center';
+ ovl.innerHTML = `
+
+
+
+ Notiz — ${UI.escape(parentLabel)}
+
+
+
+
+
+
+
+
+ `;
+ document.body.appendChild(ovl);
+
+ const close = () => ovl.remove();
+ ovl.querySelector('#wk-note-close')?.addEventListener('click', close);
+ ovl.querySelector('#wk-note-cancel')?.addEventListener('click', close);
+ ovl.addEventListener('click', e => { if (e.target === ovl) close(); });
+
+ ovl.querySelector('#wk-note-save')?.addEventListener('click', async () => {
+ const text = ovl.querySelector('#wk-note-text')?.value?.trim() || '';
+ const payload = { text, parent_label: parentLabel, location_name: locationName || null };
+ try {
+ if (existingNote?.id) {
+ await API.notes.update(existingNote.id, payload);
+ } else {
+ await API.notes.create(parentType, String(parentId), payload);
+ }
+ UI.toast.success('Notiz gespeichert.');
+ close();
+ } catch (err) { UI.toast.error(err.message || 'Fehler beim Speichern.'); }
+ });
+ }
// ==============================================================
// FEATURE 1: Foto-Challenge der Woche
diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js
index 9f50342..2c5486a 100644
--- a/backend/static/js/ui.js
+++ b/backend/static/js/ui.js
@@ -1327,91 +1327,9 @@ const UI = (() => {
});
}
- // ----------------------------------------------------------
- // NOTE-MODAL — Notiz zu einem beliebigen Objekt (parentType/parentId)
- // erstellen/bearbeiten. Zentral, damit nicht jede Seite eine eigene Kopie hat.
- // ----------------------------------------------------------
- async function noteModal(parentType, parentId, parentLabel, locationName) {
- document.getElementById('by-note-modal')?.remove();
-
- const overlay = document.createElement('div');
- overlay.id = 'by-note-modal';
- overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
-
- overlay.innerHTML = `
-
-
-
-
${_svgIcon('note-pencil')} Notiz
-
${escape(parentLabel)}
-
-
-
-
-
-
-
-
-
-
-
- `;
-
- document.body.appendChild(overlay);
-
- const textarea = document.getElementById('by-note-text');
- const saveBtn = document.getElementById('by-note-save');
- const cancelBtn = document.getElementById('by-note-cancel');
- const closeBtn = document.getElementById('by-note-close');
-
- let existingNoteId = null;
- try {
- const existing = await API.notes.get(parentType, parentId);
- if (existing?.id) {
- existingNoteId = existing.id;
- textarea.value = existing.text || '';
- }
- } catch (_) { /* keine Notiz vorhanden — ok */ }
-
- setTimeout(() => textarea.focus(), 100);
-
- const _close = () => overlay.remove();
- closeBtn.addEventListener('click', _close);
- cancelBtn.addEventListener('click', _close);
- overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
-
- document.getElementById('by-note-form').addEventListener('submit', async e => {
- e.preventDefault();
- const text = textarea.value.trim();
- setLoading(saveBtn, true);
- try {
- const payload = { text, parent_label: parentLabel, location_name: locationName || null, client_time: API.clientNow() };
- if (existingNoteId) {
- await API.notes.update(existingNoteId, payload);
- } else {
- await API.notes.create(parentType, parentId, payload);
- }
- toast.success('Notiz gespeichert.');
- _close();
- } catch (err) {
- toast.error(err.message || 'Fehler beim Speichern.');
- setLoading(saveBtn, false);
- }
- });
- }
-
// Öffentliche API
return {
toast, modal,
- noteModal,
setLoading, asyncButton,
formData, setFormError, clearFormErrors,
emptyState, errorState, time, text, money,
diff --git a/backend/static/landing.html b/backend/static/landing.html
index 06ddcf5..a4d6f80 100644
--- a/backend/static/landing.html
+++ b/backend/static/landing.html
@@ -4,7 +4,7 @@
-
+
Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz
diff --git a/backend/static/sw.js b/backend/static/sw.js
index b11d249..ac211d5 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
-const VER = '1133';
+const VER = '1131';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten