Fix: Benachrichtigungen navigieren jetzt in den richtigen Kontext

- App.callModule() öffentlich: navigiert + ruft Modul-Methode auf
- chat_message → öffnet direkt den richtigen Chat-Thread (conversation_id)
- friend_request → Freunde-Seite
- walk_invite → Gassi-Treffen (+ page-Feld im Push-Payload ergänzt)
- poison_alert → Giftköder-Seite
- health_reminder → Gesundheit
- _execNav() zentralisiert alle typ-spezifischen Navigationen
This commit is contained in:
rene 2026-04-19 09:54:46 +02:00
parent 8386e20ca1
commit f05ef9eeca
4 changed files with 90 additions and 23 deletions

View file

@ -252,6 +252,7 @@ async def invite_friend(walk_id: int, data: InviteRequest, user=Depends(get_curr
"type": "walk_invite", "type": "walk_invite",
"title": f"Einladung: {walk['titel']}", "title": f"Einladung: {walk['titel']}",
"body": f"{user['name']} lädt dich zu einem Gassi-Treffen ein ({walk['datum']} {walk['uhrzeit']})", "body": f"{user['name']} lädt dich zu einem Gassi-Treffen ein ({walk['datum']} {walk['uhrzeit']})",
"page": "walks",
"walk_id": walk_id, "walk_id": walk_id,
}) })

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '205'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '206'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => { const App = (() => {
@ -748,7 +748,13 @@ const App = (() => {
// ÖFFENTLICHE API // ÖFFENTLICHE API
// (andere Module können App.state, App.navigate etc. nutzen) // (andere Module können App.state, App.navigate etc. nutzen)
// ---------------------------------------------------------- // ----------------------------------------------------------
return { init, navigate, state, setActiveDog, renderDogSwitcher: _renderDogSwitcher, function callModule(pageId, method, ...args) {
navigate(pageId);
setTimeout(() => pages[pageId]?.module?.[method]?.(...args), 500);
}
return { init, navigate, callModule, state, setActiveDog,
renderDogSwitcher: _renderDogSwitcher,
getInstallPrompt: () => _installPrompt, requireAuth, getInstallPrompt: () => _installPrompt, requireAuth,
showOnboarding: _showOnboardingModal, showOnboarding: _showOnboardingModal,
updateNotifBadge: _updateNotifBadge }; updateNotifBadge: _updateNotifBadge };

View file

@ -40,18 +40,60 @@ window.Page_notifications = (() => {
try { return JSON.parse(raw); } catch (_) { return {}; } try { return JSON.parse(raw); } catch (_) { return {}; }
} }
/** Ermittelt Ziel-Seite und optionale Label für den Toast */ /**
* Gibt { page, label, go } zurück.
* go() führt die Navigation inklusive kontextspezifischer Aktion aus.
*/
function _navTarget(n) { function _navTarget(n) {
const d = _parseData(n.data); const d = _parseData(n.data);
// Explizit gesetzte Zielseite hat Vorrang
if (d.page) return { page: d.page, label: d.page };
// Typ-basiertes Fallback
switch (n.type) { switch (n.type) {
case 'chat_message': return { page: d.conversation_id ? `chat?id=${d.conversation_id}` : 'chat', label: 'Chat' }; case 'chat_message':
case 'friend_request': return { page: 'friends', label: 'Freunde' }; return {
case 'milestone': return { page: 'diary', label: 'Tagebuch' }; page: 'chat', label: 'Chat',
case 'poison_alert': return { page: 'map', label: 'Karte' }; go: d.conversation_id
default: return { page: '', label: '' }; ? () => App.callModule('chat', '_openThread', d.conversation_id)
: () => App.navigate('chat'),
};
case 'friend_request':
return {
page: 'friends', label: 'Freunde',
go: () => App.navigate('friends'),
};
case 'walk_invite':
return {
page: 'walks', label: 'Gassi-Treffen',
go: () => App.navigate('walks'),
};
case 'poison_alert':
return {
page: 'poison', label: 'Giftköder-Alarm',
go: () => App.navigate('poison'),
};
case 'health_reminder':
return {
page: 'health', label: 'Gesundheit',
go: () => App.navigate('health'),
};
case 'milestone':
return {
page: 'diary', label: 'Tagebuch',
go: () => App.navigate('diary'),
};
default: {
const page = d.page || '';
return {
page,
label: page,
go: page ? () => App.navigate(page) : null,
};
}
} }
} }
@ -61,9 +103,12 @@ window.Page_notifications = (() => {
const iconName = unread ? _iconForType(n.type) : 'bell'; const iconName = unread ? _iconForType(n.type) : 'bell';
const cls = ['notif-item', unread ? 'notif-unread' : ''].filter(Boolean).join(' '); const cls = ['notif-item', unread ? 'notif-unread' : ''].filter(Boolean).join(' ');
const nav = _navTarget(n); const nav = _navTarget(n);
// Nur serialisierbare Felder speichern (go ist eine Funktion, nicht serialisierbar)
const navData = JSON.stringify({ page: nav.page, label: nav.label, type: n.type,
data: _parseData(n.data) });
return ` return `
<div class="${cls}" data-id="${n.id}" data-page="${UI.escape(nav.page)}" data-nav-label="${UI.escape(nav.label)}"> <div class="${cls}" data-id="${n.id}" data-nav="${UI.escape(navData)}">
<span class="notif-icon">${UI.icon(iconName)}</span> <span class="notif-icon">${UI.icon(iconName)}</span>
<div class="notif-content"> <div class="notif-content">
<div class="notif-title">${UI.escape(n.title)}</div> <div class="notif-title">${UI.escape(n.title)}</div>
@ -77,6 +122,24 @@ window.Page_notifications = (() => {
</div>`; </div>`;
} }
/** Führt die kontextspezifische Navigation aus */
function _execNav(nav) {
switch (nav.type) {
case 'chat_message':
nav.data?.conversation_id
? App.callModule('chat', '_openThread', nav.data.conversation_id)
: App.navigate('chat');
break;
case 'friend_request': App.navigate('friends'); break;
case 'walk_invite': App.navigate('walks'); break;
case 'poison_alert': App.navigate('poison'); break;
case 'health_reminder':App.navigate('health'); break;
case 'milestone': App.navigate('diary'); break;
default:
if (nav.page) App.navigate(nav.page);
}
}
// ---------------------------------------------------------- // ----------------------------------------------------------
// init // init
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -142,22 +205,19 @@ window.Page_notifications = (() => {
if (e.target.closest('.notif-del-btn')) return; if (e.target.closest('.notif-del-btn')) return;
const id = parseInt(el.dataset.id, 10); const id = parseInt(el.dataset.id, 10);
const page = el.dataset.page; const nav = JSON.parse(el.dataset.nav || '{}');
const navLabel = el.dataset.navLabel;
// Sofortiges visuelles Feedback: als gelesen markieren // Sofortiges visuelles Feedback
el.classList.remove('notif-unread'); el.classList.remove('notif-unread');
el.style.opacity = '0.6'; el.style.opacity = '0.6';
// API-Call im Hintergrund — nicht abwarten // Als gelesen markieren (fire & forget)
API.notifications.read(id).catch(() => {}); API.notifications.read(id).catch(() => {});
if (page && window.App?.navigate) { if (nav.page) {
if (navLabel) UI.toast?.(`Öffne ${navLabel}`, 'info'); if (nav.label) UI.toast?.(`Öffne ${nav.label}`, 'info');
// Kurze Pause damit der Toast sichtbar wird, dann navigieren setTimeout(() => _execNav(nav), 150);
setTimeout(() => window.App.navigate(page), 150);
} else { } else {
// Keine Zielseite — Opacity nach kurzer Zeit zurücksetzen
setTimeout(() => { el.style.opacity = ''; }, 800); setTimeout(() => { el.style.opacity = ''; }, 800);
} }
}); });

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v228'; const CACHE_VERSION = 'by-v229';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten