diff --git a/VERSION b/VERSION index 9c6266b..a624bd7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1132 \ No newline at end of file +1133 \ No newline at end of file diff --git a/backend/routes/jobs.py b/backend/routes/jobs.py index 59c73c2..0b678e5 100644 --- a/backend/routes/jobs.py +++ b/backend/routes/jobs.py @@ -268,14 +268,20 @@ async def update_application( "UPDATE users SET is_social_media=1 WHERE id=?", (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"],) - ) + # 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"],) + ) # Status-Mail an Bewerber try: diff --git a/backend/static/index.html b/backend/static/index.html index 45ab38a..1e0a5d0 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/app.js b/backend/static/js/app.js index f957ae0..a7b5e90 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 = '1132'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1133'; // ← 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 f2137d8..653b117 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; - _openNoteModal('diary', id, label, location || null); + UI.noteModal('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); - _openNoteModal('diary', entry.id, label, entry.location_name || null); + UI.noteModal('diary', entry.id, label, entry.location_name || null); }); // Bearbeiten @@ -1953,83 +1953,6 @@ 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 e8f1582..780356b 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; - _openNoteModal('erste_hilfe', katId, label, null); + UI.noteModal('erste_hilfe', katId, label, null); }); }); } @@ -469,85 +469,6 @@ 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 afb7e62..62c2d23 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(); - _openNoteModal( + UI.noteModal( 'event', parseInt(noteBtn.dataset.evNoteId), noteBtn.dataset.evNoteLabel, @@ -660,55 +660,6 @@ 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 9c68959..ea4405c 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 || ''; - _openNoteModal('friends', id, name, null); + UI.noteModal('friends', id, name, null); }); }); @@ -866,83 +866,6 @@ 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 ff59b94..0cf9b93 100644 --- a/backend/static/js/pages/health.js +++ b/backend/static/js/pages/health.js @@ -998,7 +998,7 @@ window.Page_health = (() => { e.stopPropagation(); const id = parseInt(btn.dataset.entryId); const label = btn.dataset.label || ''; - _openNoteModal('health', id, label, null); + UI.noteModal('health', id, label, null); }); }); // Praxis öffnen → Detail-Modal mit Bewertungen @@ -2975,85 +2975,6 @@ 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 ef4aab5..50c0a98 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 || ''; - _openNoteModal('lost', id, name, null); + UI.noteModal('lost', id, name, null); }); }); } @@ -804,83 +804,6 @@ 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 f9d1151..71fafec 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); - _openNoteModal('poison', id, 'Giftköder-Meldung ' + id, null); + UI.noteModal('poison', id, 'Giftköder-Meldung ' + id, null); }); }); } @@ -650,83 +650,6 @@ 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 acb48c6..0c2295e 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'); - _openNoteModal('route', route.id, label, null); + UI.noteModal('route', route.id, label, null); }); // Mini-Map @@ -3054,55 +3054,6 @@ 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 fd59ec9..095a8f8 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(); - _openNoteModal( + UI.noteModal( 'sitting', parseInt(noteBtn.dataset.sitNoteId), noteBtn.dataset.sitNoteLabel, @@ -763,55 +763,6 @@ 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 7752ea7..d7c62d2 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}`; - _openNoteModal('trainingsplan', dogId, planLabel, null); + UI.noteModal('trainingsplan', dogId, planLabel, null); }); // Plan selector @@ -768,84 +768,6 @@ 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 e56f569..2d2072e 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(); - _openNoteModal( + UI.noteModal( 'walk', parseInt(btn.dataset.wkNoteId), btn.dataset.wkNoteLabel, @@ -1211,55 +1211,6 @@ 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 2c5486a..9f50342 100644 --- a/backend/static/js/ui.js +++ b/backend/static/js/ui.js @@ -1327,9 +1327,91 @@ 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 0bc6044..06ddcf5 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 ded7238..b11d249 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 = '1132'; +const VER = '1133'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten