diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 581a260..cb4b4a9 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,8 +3,8 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '536'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen -const APP_VERSION = '1.1.3'; // ← semantische Version, wird bei make release gesetzt +const APP_VER = '539'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VERSION = '1.1.4'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; const App = (() => { diff --git a/backend/static/js/pages/notes.js b/backend/static/js/pages/notes.js index d9ce75d..d78797d 100644 --- a/backend/static/js/pages/notes.js +++ b/backend/static/js/pages/notes.js @@ -136,7 +136,13 @@ window.Page_notes = (() => {

Notizblock

- ${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''} +
+ ${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''} + +
@@ -361,6 +367,11 @@ window.Page_notes = (() => { _container.querySelector('#notes-privacy-notice')?.remove(); }); + // Neue Notiz + _container.querySelector('#notes-new-btn')?.addEventListener('click', () => { + _openCreateModal(_filterType || ''); + }); + // Filter-Chips _container.querySelectorAll('.notes-chip').forEach(btn => { btn.addEventListener('click', () => { @@ -464,6 +475,101 @@ window.Page_notes = (() => { _render(); } + // ---------------------------------------------------------- + // Create-Modal — neue Notiz mit vorausgewählter Kategorie + // ---------------------------------------------------------- + function _openCreateModal(preselectedType = '') { + const ERSTELL_RUBRIKEN = RUBRIKEN.filter(r => r.type !== ''); // ohne "Alle" + let _selType = preselectedType || ERSTELL_RUBRIKEN[0].type; + + const modalId = 'notes-create-modal'; + document.getElementById(modalId)?.remove(); + + const overlay = document.createElement('div'); + overlay.id = modalId; + overlay.style.cssText = `position:fixed;inset:0;z-index:9999;display:flex;align-items:flex-end;justify-content:center;background:rgba(0,0,0,0.45)`; + + const _buildContent = () => { + const rb = _rubrik(_selType); + return ` +
+
+

Neue Notiz

+ + +
+ +
+ ${ERSTELL_RUBRIKEN.map(r => ` + `).join('')} +
+
+ + +
+ + +
+ +
+ + +
+
`; + }; + + overlay.innerHTML = _buildContent(); + document.body.appendChild(overlay); + + const _rebind = () => { + overlay.querySelectorAll('.nc-cat').forEach(btn => { + btn.addEventListener('click', () => { + _selType = btn.dataset.type; + overlay.innerHTML = _buildContent(); + _rebind(); + overlay.querySelector('#nc-text')?.focus(); + }); + }); + + overlay.querySelector('#nc-cancel')?.addEventListener('click', () => overlay.remove()); + overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); + + overlay.querySelector('#nc-save')?.addEventListener('click', async () => { + const text = overlay.querySelector('#nc-text')?.value?.trim(); + if (!text) { UI.toast.warning('Bitte einen Text eingeben.'); return; } + const btn = overlay.querySelector('#nc-save'); + await UI.asyncButton(btn, async () => { + const rb = _rubrik(_selType); + await API.notes.create(_selType, 'standalone', { + text, + parent_label: rb.label, + }); + overlay.remove(); + _filterType = _selType; + await _reload(); + UI.toast.success('Notiz gespeichert.'); + }); + }); + + setTimeout(() => overlay.querySelector('#nc-text')?.focus(), 100); + }; + + _rebind(); + } + // ---------------------------------------------------------- // Edit-Modal (Bottom-Sheet Stil) // ---------------------------------------------------------- diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js index 37f67b5..9a6b596 100644 --- a/backend/static/js/pages/settings.js +++ b/backend/static/js/pages/settings.js @@ -604,14 +604,27 @@ window.Page_settings = (() => { } if (btn) btn.textContent = 'Prüfe…'; try { + // Aktuelle Version vom Server holen (no-cache) + const serverResp = await fetch('/js/app.js', { cache: 'no-store' }); + const serverText = await serverResp.text(); + const match = serverText.match(/APP_VERSION\s*=\s*'([^']+)'/); + const serverVersion = match?.[1] || null; + const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0'; + + // SW update anstoßen const reg = await navigator.serviceWorker.getRegistration(); await reg?.update(); - if (reg?.waiting) { - // Neuer SW wartet — sofort aktivieren + + if (serverVersion && serverVersion !== localVersion) { + // Neuere Version verfügbar — Seite neu laden + if (reg?.waiting) reg.waiting.postMessage({ type: 'SKIP_WAITING' }); + UI.toast.info(`Update auf v${serverVersion} verfügbar — Seite wird neu geladen…`); + setTimeout(() => location.reload(), 1500); + } else if (reg?.waiting) { reg.waiting.postMessage({ type: 'SKIP_WAITING' }); UI.toast.success('Update wird installiert…'); } else { - UI.toast.success('Ban Yaro ist aktuell — v' + (typeof APP_VERSION !== 'undefined' ? APP_VERSION : '1.0.0') + '.'); + UI.toast.success(`Ban Yaro ist aktuell — v${localVersion}`); } } catch { UI.toast.error('Update-Prüfung fehlgeschlagen.'); diff --git a/backend/static/manifest.json b/backend/static/manifest.json index d61feb7..fd71b5d 100644 --- a/backend/static/manifest.json +++ b/backend/static/manifest.json @@ -1,6 +1,6 @@ { "id": "/", - "version": "1.1.3", + "version": "1.1.4", "name": "Ban Yaro — Die Hunde-Plattform", "short_name": "Ban Yaro", "description": "Alles rund um deinen Hund. Von Welpe bis Opa.", diff --git a/backend/static/sw.js b/backend/static/sw.js index ecb2350..b443693 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-v559'; +const CACHE_VERSION = 'by-v562'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache @@ -134,13 +134,13 @@ function _isCacheableGet(pathname) { // INSTALL — App Shell cachen // ---------------------------------------------------------- self.addEventListener('install', event => { + self.skipWaiting(); // Sofort übernehmen — kein Warten auf Cache-Aufbau event.waitUntil( caches.open(CACHE_STATIC) .then(cache => cache.addAll(STATIC_ASSETS)) .then(() => caches.open(CACHE_API).then(c => fetch('/api/training/exercises').then(r => { if (r.ok) c.put('/api/training/exercises', r); }).catch(() => {}) )) - .then(() => self.skipWaiting()) ); });