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:
parent
8386e20ca1
commit
f05ef9eeca
4 changed files with 90 additions and 23 deletions
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue