258 lines
8.4 KiB
JavaScript
258 lines
8.4 KiB
JavaScript
/* ============================================================
|
|
BAN YARO — App Core
|
|
Router, State-Management, Navigation, Initialisierung.
|
|
============================================================ */
|
|
|
|
const App = (() => {
|
|
|
|
// ----------------------------------------------------------
|
|
// STATE — zentraler App-Zustand
|
|
// ----------------------------------------------------------
|
|
const state = {
|
|
user: null, // eingeloggter User (oder null)
|
|
dogs: [], // Hunde des Users
|
|
activeDog: null, // aktuell gewählter Hund
|
|
page: 'diary', // aktive Seite
|
|
};
|
|
|
|
// ----------------------------------------------------------
|
|
// SEITENDEFINITIONEN
|
|
// Jede Seite: { id, title, load() }
|
|
// load() wird beim ersten Aufruf einmalig ausgeführt
|
|
// ----------------------------------------------------------
|
|
const pages = {
|
|
diary: { title: 'Tagebuch', module: null },
|
|
health: { title: 'Gesundheit', module: null },
|
|
'dog-profile': { title: 'Mein Hund', module: null },
|
|
map: { title: 'Karte', module: null },
|
|
routes: { title: 'Routen', module: null },
|
|
places: { title: 'Orte', module: null },
|
|
events: { title: 'Events', module: null },
|
|
poison: { title: 'Giftköder-Alarm', module: null },
|
|
walks: { title: 'Gassi-Treffen', module: null },
|
|
sitting: { title: 'Sitting', module: null },
|
|
forum: { title: 'Forum', module: null },
|
|
wiki: { title: 'Wiki', module: null },
|
|
knigge: { title: 'Knigge', module: null },
|
|
movies: { title: 'Filme', module: null },
|
|
settings: { title: 'Einstellungen', module: null },
|
|
};
|
|
|
|
// ----------------------------------------------------------
|
|
// ROUTER
|
|
// ----------------------------------------------------------
|
|
function navigate(pageId, pushHistory = true) {
|
|
if (!pages[pageId]) return;
|
|
|
|
// Aktive Seite ausblenden
|
|
document.querySelector('.page.active')?.classList.remove('active');
|
|
document.querySelectorAll('.nav-item.active, .sidebar-item.active')
|
|
.forEach(el => el.classList.remove('active'));
|
|
|
|
// Neue Seite einblenden
|
|
document.getElementById(`page-${pageId}`)?.classList.add('active');
|
|
|
|
// Navigation markieren
|
|
document.querySelectorAll(`[data-page="${pageId}"]`)
|
|
.forEach(el => el.classList.add('active'));
|
|
|
|
// Header-Titel setzen
|
|
document.getElementById('header-title').textContent = pages[pageId].title;
|
|
|
|
// History
|
|
if (pushHistory) {
|
|
history.pushState({ page: pageId }, '', `#${pageId}`);
|
|
}
|
|
|
|
state.page = pageId;
|
|
UI.scrollTop();
|
|
|
|
// Seiten-Modul lazy laden (einmalig)
|
|
_loadPage(pageId);
|
|
}
|
|
|
|
async function _loadPage(pageId) {
|
|
const page = pages[pageId];
|
|
if (page.module) {
|
|
// Bereits geladen → nur refresh aufrufen wenn vorhanden
|
|
page.module.refresh?.();
|
|
return;
|
|
}
|
|
|
|
const container = document.querySelector(`#page-${pageId} .page-body`);
|
|
if (!container) return;
|
|
|
|
// Skeleton während Laden
|
|
container.innerHTML = UI.skeleton(4);
|
|
|
|
try {
|
|
// Seiten-Script dynamisch laden
|
|
await _loadScript(`/js/pages/${pageId}.js`);
|
|
const mod = window[`Page_${pageId.replace(/-/g, '_')}`];
|
|
if (mod?.init) {
|
|
await mod.init(container, state);
|
|
page.module = mod;
|
|
} else {
|
|
// Platzhalter wenn Seite noch nicht gebaut
|
|
container.innerHTML = UI.emptyState({
|
|
icon: '🚧',
|
|
title: pages[pageId].title,
|
|
text: 'Diese Seite ist noch in Entwicklung.',
|
|
});
|
|
page.module = {}; // verhindert erneutes Laden
|
|
}
|
|
} catch {
|
|
container.innerHTML = UI.emptyState({
|
|
icon: '🚧',
|
|
title: pages[pageId].title,
|
|
text: 'Diese Seite ist noch in Entwicklung.',
|
|
});
|
|
page.module = {};
|
|
}
|
|
}
|
|
|
|
function _loadScript(src) {
|
|
return new Promise((resolve, reject) => {
|
|
if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
|
|
const s = document.createElement('script');
|
|
s.src = src;
|
|
s.onload = resolve;
|
|
s.onerror = reject;
|
|
document.head.appendChild(s);
|
|
});
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// NAVIGATION EVENTS
|
|
// ----------------------------------------------------------
|
|
function _bindNavigation() {
|
|
// Bottom Nav + Sidebar Klicks
|
|
document.addEventListener('click', e => {
|
|
const item = e.target.closest('[data-page]');
|
|
if (item) {
|
|
navigate(item.dataset.page);
|
|
return;
|
|
}
|
|
|
|
// + Button
|
|
if (e.target.closest('#nav-add')) {
|
|
_showQuickAdd();
|
|
}
|
|
});
|
|
|
|
// Browser Back/Forward
|
|
window.addEventListener('popstate', e => {
|
|
const page = e.state?.page || 'diary';
|
|
navigate(page, false);
|
|
});
|
|
|
|
// Initial: URL-Hash auslesen
|
|
const hash = location.hash.replace('#', '');
|
|
if (hash && pages[hash]) {
|
|
navigate(hash, false);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// SCHNELL-HINZUFÜGEN (+ Button)
|
|
// ----------------------------------------------------------
|
|
function _showQuickAdd() {
|
|
UI.modal.open({
|
|
title: 'Was möchtest du hinzufügen?',
|
|
body: `
|
|
<div class="flex flex-col gap-3">
|
|
<button class="btn btn-secondary w-full" data-quick="diary">
|
|
📖 Tagebuch-Eintrag
|
|
</button>
|
|
<button class="btn btn-secondary w-full" data-quick="health">
|
|
💉 Gesundheits-Eintrag
|
|
</button>
|
|
<button class="btn btn-danger w-full" data-quick="poison">
|
|
⚠️ Giftköder melden
|
|
</button>
|
|
<button class="btn btn-secondary w-full" data-quick="place">
|
|
📍 Ort hinzufügen
|
|
</button>
|
|
<button class="btn btn-nature w-full" data-quick="walk">
|
|
🦮 Gassi-Treffen erstellen
|
|
</button>
|
|
</div>
|
|
`,
|
|
});
|
|
|
|
// Quick-Add Aktionen
|
|
document.querySelector('#modal-container').addEventListener('click', e => {
|
|
const btn = e.target.closest('[data-quick]');
|
|
if (!btn) return;
|
|
UI.modal.close();
|
|
const action = btn.dataset.quick;
|
|
if (action === 'diary') { navigate('diary'); pages['diary'].module?.openNew?.(); }
|
|
if (action === 'health') { navigate('health'); pages['health'].module?.openNew?.(); }
|
|
if (action === 'poison') { navigate('poison'); pages['poison'].module?.openNew?.(); }
|
|
if (action === 'place') { navigate('places'); pages['places'].module?.openNew?.(); }
|
|
if (action === 'walk') { navigate('walks'); pages['walks'].module?.openNew?.(); }
|
|
}, { once: true });
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// AUTH
|
|
// ----------------------------------------------------------
|
|
async function _checkAuth() {
|
|
try {
|
|
const user = await API.auth.me();
|
|
state.user = user;
|
|
_onLoggedIn();
|
|
} catch {
|
|
_onLoggedOut();
|
|
}
|
|
}
|
|
|
|
function _onLoggedIn() {
|
|
document.getElementById('sidebar-username').textContent = state.user.name;
|
|
_loadDogs();
|
|
}
|
|
|
|
function _onLoggedOut() {
|
|
state.user = null;
|
|
// Zeige Login wenn nötig
|
|
// Für MVP: direkte Weiterleitung zu Einstellungen/Login
|
|
}
|
|
|
|
async function _loadDogs() {
|
|
try {
|
|
state.dogs = await API.dogs.list();
|
|
if (state.dogs.length > 0) {
|
|
state.activeDog = state.dogs[0];
|
|
}
|
|
// Seitenmodule über neuen Hund informieren
|
|
_notifyDogChange();
|
|
} catch { /* kein Hund vorhanden */ }
|
|
}
|
|
|
|
function _notifyDogChange() {
|
|
// Alle geladenen Seiten-Module über Hundwechsel informieren
|
|
Object.values(pages).forEach(p => p.module?.onDogChange?.(state.activeDog));
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// INITIALISIERUNG
|
|
// ----------------------------------------------------------
|
|
async function init() {
|
|
_bindNavigation();
|
|
await _checkAuth();
|
|
|
|
// Erste Seite laden (Hash oder Standard: diary)
|
|
const startPage = location.hash.replace('#', '') || 'diary';
|
|
navigate(pages[startPage] ? startPage : 'diary', false);
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// ÖFFENTLICHE API
|
|
// (andere Module können App.state, App.navigate etc. nutzen)
|
|
// ----------------------------------------------------------
|
|
return { init, navigate, state };
|
|
|
|
})();
|
|
|
|
// App starten
|
|
document.addEventListener('DOMContentLoaded', () => App.init());
|