From 02120bb5322819e190e3fdbc66fd33f241bdb2b0 Mon Sep 17 00:00:00 2001 From: rene Date: Sun, 26 Apr 2026 10:52:28 +0200 Subject: [PATCH] =?UTF-8?q?Notizblock:=20Notiz-Button=20f=C3=BCr=206=20neu?= =?UTF-8?q?e=20Bereiche=20+=20RUBRIKEN=20+=20Datenschutz=20=E2=80=94=20SW?= =?UTF-8?q?=20by-v425?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue Notiz-Buttons: - Tagebuch: in der Detail-Ansicht (nicht Edit-Form) - Trainingspläne: im Plan-Header pro Plan - Freunde: in jedem Freund-Karten-Bereich - Giftköder: in jedem Meldungs-Karten (private Umstände) - Verlorener Hund: in jedem Eintrag Notizblock: - 4 neue RUBRIKEN: trainingsplan, friends, poison, lost - Datenschutz-Hinweis: "Alle Notizen sind privat" - lock-simple Icon zum Sprite hinzugefügt --- backend/static/icons/phosphor.svg | 2 + backend/static/js/app.js | 2 +- backend/static/js/pages/diary.js | 23 ++++- backend/static/js/pages/friends.js | 98 +++++++++++++++++++ backend/static/js/pages/lost.js | 97 ++++++++++++++++++ backend/static/js/pages/notes.js | 13 +++ backend/static/js/pages/poison.js | 95 ++++++++++++++++++ backend/static/js/pages/trainingsplaene.js | 108 ++++++++++++++++++++- backend/static/sw.js | 2 +- 9 files changed, 430 insertions(+), 10 deletions(-) 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 ? `Foto { }); } + // ---------------------------------------------------------- + // 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