diff --git a/backend/static/icons/phosphor.svg b/backend/static/icons/phosphor.svg
index 7f2cf85..f9e55a2 100644
--- a/backend/static/icons/phosphor.svg
+++ b/backend/static/icons/phosphor.svg
@@ -166,4 +166,6 @@
+
+
\ No newline at end of file
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 1c458d3..08b7828 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 = '403'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '404'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
diff --git a/backend/static/js/pages/diary.js b/backend/static/js/pages/diary.js
index cac5114..bac3d5c 100644
--- a/backend/static/js/pages/diary.js
+++ b/backend/static/js/pages/diary.js
@@ -1105,11 +1105,17 @@ window.Page_diary = (() => {
Zurück
${datumLang}
- ${!_appState?.activeDog?.is_guest
- ? ``
- : '
'}
+
+ ${!_appState?.activeDog?.is_guest
+ ? `
+
`
+ : '
'}
+
${heroSection}
@@ -1174,6 +1180,13 @@ window.Page_diary = (() => {
};
view.querySelector('#diary-dv-back').addEventListener('click', _closeDetail);
+ // Notiz-Button in Detailansicht
+ view.querySelector('#diary-dv-note')?.addEventListener('click', e => {
+ e.stopPropagation();
+ const label = entry.titel || entry.datum || String(entry.id);
+ _openNoteModal('diary', entry.id, label, entry.location_name || null);
+ });
+
// Bearbeiten
view.querySelector('#diary-dv-edit')?.addEventListener('click', async () => {
_container.querySelector('#diary-fab')?.style.removeProperty('display');
diff --git a/backend/static/js/pages/friends.js b/backend/static/js/pages/friends.js
index 514f18a..837474c 100644
--- a/backend/static/js/pages/friends.js
+++ b/backend/static/js/pages/friends.js
@@ -434,6 +434,16 @@ window.Page_friends = (() => {
`;
+ // Notiz-Buttons
+ el.querySelectorAll('.fr-note-btn').forEach(btn => {
+ btn.addEventListener('click', e => {
+ e.stopPropagation();
+ const id = parseInt(btn.dataset.frNoteId);
+ const name = btn.dataset.frNoteName || '';
+ _openNoteModal('friends', id, name, null);
+ });
+ });
+
// Klick auf Karte → Mini-Profil
el.querySelectorAll('.fr-card').forEach(card => {
card.addEventListener('click', e => {
@@ -492,6 +502,13 @@ 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
+
${_esc(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/lost.js b/backend/static/js/pages/lost.js
index 943e45b..deaa92d 100644
--- a/backend/static/js/pages/lost.js
+++ b/backend/static/js/pages/lost.js
@@ -275,6 +275,14 @@ window.Page_lost = (() => {
if (r) _openDetail(r);
});
});
+ listEl.querySelectorAll('.lost-note-btn').forEach(btn => {
+ btn.addEventListener('click', e => {
+ e.stopPropagation();
+ const id = parseInt(btn.dataset.lostNoteId);
+ const name = btn.dataset.lostNoteName || '';
+ _openNoteModal('lost', id, name, null);
+ });
+ });
}
function _reportCard(r) {
@@ -324,6 +332,14 @@ window.Page_lost = (() => {
Gemeldet ${_fmtDate(r.created_at)}
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
+ ${_appState.user ? `
+
+
` : ''}
@@ -693,6 +709,87 @@ window.Page_lost = (() => {
`;
}
+ // ----------------------------------------------------------
+ // 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
+
${_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/notes.js b/backend/static/js/pages/notes.js
index afc38f4..cb23e27 100644
--- a/backend/static/js/pages/notes.js
+++ b/backend/static/js/pages/notes.js
@@ -34,6 +34,10 @@ window.Page_notes = (() => {
{ type: 'walk', label: 'Gassi-Treffen',color: '#f39c12', icon: 'paw-print' },
{ type: 'sitting', label: 'Sitting', color: '#16a085', icon: 'house-line' },
{ type: 'erste_hilfe', label: 'Erste Hilfe', color: '#c0392b', icon: 'first-aid' },
+ { type: 'trainingsplan', label: 'Trainingsplan',color: '#059669', icon: 'clipboard-text' },
+ { type: 'friends', label: 'Freunde', color: '#7c3aed', icon: 'users' },
+ { type: 'poison', label: 'Giftköder', color: '#dc2626', icon: 'warning-octagon' },
+ { type: 'lost', label: 'Vermisste', color: '#b45309', icon: 'magnifying-glass' },
];
function _rubrik(type) {
@@ -135,6 +139,15 @@ window.Page_notes = (() => {
${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''}
+
+
+
+ Alle Notizen sind privat — nur du kannst sie lesen.
+
+
${kiEnabled ? _kiPanelHtml() : ''}
diff --git a/backend/static/js/pages/poison.js b/backend/static/js/pages/poison.js
index 513c827..dd4eb43 100644
--- a/backend/static/js/pages/poison.js
+++ b/backend/static/js/pages/poison.js
@@ -240,6 +240,13 @@ window.Page_poison = (() => {
if (r) _openDetail(r);
});
});
+ listEl.querySelectorAll('.poison-note-btn').forEach(btn => {
+ btn.addEventListener('click', e => {
+ e.stopPropagation();
+ const id = parseInt(btn.dataset.poisonNoteId);
+ _openNoteModal('poison', id, 'Giftköder-Meldung ' + id, null);
+ });
+ });
}
function _reportCard(r) {
@@ -279,6 +286,13 @@ window.Page_poison = (() => {
+ ${_appState.user ? `
+
+
` : ''}
${r.foto_url
? `
{
});
}
+ // ----------------------------------------------------------
+ // 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/trainingsplaene.js b/backend/static/js/pages/trainingsplaene.js
index 525444c..dfa7bee 100644
--- a/backend/static/js/pages/trainingsplaene.js
+++ b/backend/static/js/pages/trainingsplaene.js
@@ -537,6 +537,16 @@ window.Page_trainingsplaene = (() => {
// BIND EVENTS
// ----------------------------------------------------------
function _bindEvents() {
+ // Notiz-Button
+ const dogId = _dogId();
+ _container.querySelector('#tp-note-btn')?.addEventListener('click', e => {
+ e.stopPropagation();
+ const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate'
+ : _activePlan === 'junior' ? 'Junior 6–18 Monate'
+ : `Erwachsener Hund – ${_activeAdultTab}`;
+ _openNoteModal('trainingsplan', dogId, planLabel, null);
+ });
+
// Plan selector
_container.querySelectorAll('[data-plan]').forEach(btn => {
btn.addEventListener('click', () => {
@@ -596,11 +606,21 @@ window.Page_trainingsplaene = (() => {
else if (_activePlan === 'junior') planContent = _renderJunior();
else planContent = _renderErwachsen();
+ const dogId = _dogId();
+ const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate'
+ : _activePlan === 'junior' ? 'Junior 6–18 Monate'
+ : `Erwachsener Hund – ${_activeAdultTab}`;
+
_container.innerHTML = `
-
- ${_icon('clipboard-text')} Trainingspläne
-
+
+
+ ${_icon('clipboard-text')} Trainingspläne
+
+ ${dogId ? `` : ''}
+
${_renderPlanSelector()}
${planContent}
@@ -750,6 +770,88 @@ window.Page_trainingsplaene = (() => {
`;
}
+ // ----------------------------------------------------------
+ // 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
+
${_esc(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/sw.js b/backend/static/sw.js
index fb865ed..e8b12c9 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
-const CACHE_VERSION = 'by-v424';
+const CACHE_VERSION = 'by-v425';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten