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 = (() => {
@@ -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())
);
});