Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration

- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push
- Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push
- Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge,
  forum, wiki, walks) vollständig auf Phosphor-Icons migriert
- Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos)
- TheDogAPI lokal gespiegelt (169 Rassen + Fotos)
- Quiz-Result-Cards horizontal (korrekte Bildproportionen)
- SW by-v89
This commit is contained in:
rene 2026-04-15 21:33:53 +02:00
parent 96bd57f0ad
commit 097295c628
44 changed files with 9980 additions and 300 deletions

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '62'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '66'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
@ -37,12 +37,15 @@ events: { title: 'Events', module: null },
knigge: { title: 'Knigge', module: null },
movies: { title: 'Filme', module: null },
settings: { title: 'Einstellungen', module: null },
lost: { title: 'Verlorener Hund', module: null },
friends: { title: 'Freunde', module: null },
chat: { title: 'Nachrichten', module: null },
};
// ----------------------------------------------------------
// ROUTER
// ----------------------------------------------------------
function navigate(pageId, pushHistory = true) {
function navigate(pageId, pushHistory = true, params = {}) {
if (!pages[pageId]) return;
// Aktive Seite ausblenden
@ -70,14 +73,20 @@ events: { title: 'Events', module: null },
UI.scrollTop();
// Seiten-Modul lazy laden (einmalig)
_loadPage(pageId);
_loadPage(pageId, params);
}
async function _loadPage(pageId) {
async function _loadPage(pageId, params = {}) {
const page = pages[pageId];
if (page.module) {
// Bereits geladen → nur refresh aufrufen wenn vorhanden
page.module.refresh?.();
const hasParams = params && Object.keys(params).length > 0;
if (hasParams) {
// Re-init mit neuen Params (z.B. Chat mit bestimmter Konversation)
const container = document.querySelector(`#page-${pageId} .page-body`);
page.module.init?.(container, state, params);
} else {
page.module.refresh?.();
}
return;
}
@ -96,7 +105,7 @@ events: { title: 'Events', module: null },
await _loadScript(`/js/pages/${pageId}.js`);
const mod = window[`Page_${pageId.replace(/-/g, '_')}`];
if (mod?.init) {
await mod.init(container, state);
await mod.init(container, state, params);
page.module = mod;
} else {
// Platzhalter wenn Seite noch nicht gebaut
@ -214,16 +223,16 @@ events: { title: 'Events', module: null },
body: `
<div class="flex flex-col gap-3">
<button class="btn btn-secondary w-full" data-quick="diary">
📖 Tagebuch-Eintrag
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg> Tagebuch-Eintrag
</button>
<button class="btn btn-secondary w-full" data-quick="health">
💉 Gesundheits-Eintrag
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#syringe"></use></svg> Gesundheits-Eintrag
</button>
<button class="btn btn-danger w-full" data-quick="poison">
Giftköder melden
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning-octagon"></use></svg> Giftköder melden
</button>
<button class="btn btn-nature w-full" data-quick="walk">
🦮 Gassi-Treffen erstellen
<button class="btn btn-nature w-full" data-quick="walk">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Gassi-Treffen erstellen
</button>
</div>
`,
@ -423,9 +432,16 @@ if (action === 'walk') { navigate('walks'); pages['walks'].module?.openNew?.(
// Erste Seite laden: Hash aus URL oder Standard 'diary'.
// Bewusst NACH _checkAuth(), damit _loadPage() nur einmal aufgerufen wird
// (vorher war Hash-Navigation auch in _bindNavigation() → doppelter Aufruf).
const hash = location.hash.replace('#', '');
const startPage = (hash && pages[hash]) ? hash : 'diary';
navigate(startPage, false);
const rawHash = location.hash.replace('#', '');
const [hashPage, hashQuery] = rawHash.split('?');
const hashParams = {};
if (hashQuery) {
new URLSearchParams(hashQuery).forEach((v, k) => {
hashParams[k] = isNaN(v) ? v : Number(v);
});
}
const startPage = (hashPage && pages[hashPage]) ? hashPage : 'diary';
navigate(startPage, false, hashParams);
}
// ----------------------------------------------------------