Session 2026-04-23: Desktop Multi-Column, Forum, Fixes, Analytics
- Desktop ≥1024px: page-container 680→860px
- Walks: Liste+Karte nebeneinander, View-Toggle ausgeblendet
- Forum: Rubriken 2-zeilig via CSS Grid (ceil(n/2) Spalten, zentriert)
- Welcome: max-width 920px, Feature-Sections 2-spaltig
- Wissen: Toggle-Mechanismus entfernt, Items immer sichtbar
- Übungen Plan-Karten: vertikal statt horizontal gestapelt
- Admin Analytics: Umami v2 gibt plain numbers statt {value:X}
- CSS-Spezifität: #page-forum nötig wegen layout.css < components.css
- SW by-v312, APP_VER 300
This commit is contained in:
parent
44081a6b9d
commit
71a1371b44
10 changed files with 149 additions and 72 deletions
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '290'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '300'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
|
||||
const App = (() => {
|
||||
|
||||
|
|
@ -103,7 +103,6 @@ const App = (() => {
|
|||
|
||||
state.page = pageId;
|
||||
UI.scrollTop();
|
||||
_expandWissenIfActive(pageId);
|
||||
|
||||
// Seiten-Modul lazy laden (einmalig)
|
||||
_loadPage(pageId, params);
|
||||
|
|
@ -310,12 +309,6 @@ const App = (() => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Wissen-Toggle aufklappen/zuklappen
|
||||
if (e.target.closest('#wissen-toggle')) {
|
||||
_toggleWissen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sidebar-Item auf Mobile → schließen nach Navigation
|
||||
if (e.target.closest('#sidebar .sidebar-item')) {
|
||||
_closeSidebar();
|
||||
|
|
@ -353,22 +346,6 @@ const App = (() => {
|
|||
document.getElementById('sidebar-backdrop')?.classList.remove('visible');
|
||||
}
|
||||
|
||||
const _WISSEN_PAGES = new Set(['wiki', 'knigge', 'movies', 'erste-hilfe']);
|
||||
|
||||
function _toggleWissen(force) {
|
||||
const toggle = document.getElementById('wissen-toggle');
|
||||
const body = document.getElementById('wissen-body');
|
||||
if (!toggle || !body) return;
|
||||
const open = force !== undefined ? force : toggle.getAttribute('aria-expanded') !== 'true';
|
||||
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
body.classList.toggle('open', open);
|
||||
try { localStorage.setItem('by_wissen_open', open ? '1' : '0'); } catch (_) {}
|
||||
}
|
||||
|
||||
function _expandWissenIfActive(page) {
|
||||
if (_WISSEN_PAGES.has(page)) _toggleWissen(true);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// SCHNELL-HINZUFÜGEN (+ Button)
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -770,10 +747,7 @@ const App = (() => {
|
|||
|
||||
_bindNavigation();
|
||||
|
||||
// Wissen-Sektion: gespeicherten Zustand wiederherstellen
|
||||
try {
|
||||
if (localStorage.getItem('by_wissen_open') === '1') _toggleWissen(true);
|
||||
} catch (_) {}
|
||||
try { localStorage.removeItem('by_wissen_open'); } catch (_) {}
|
||||
|
||||
await _checkAuth();
|
||||
|
||||
|
|
|
|||
|
|
@ -114,17 +114,20 @@ window.Page_admin = (() => {
|
|||
</svg>`;
|
||||
}
|
||||
|
||||
const tv = v => v?.value ?? 0;
|
||||
// Umami v2 liefert plain numbers, v1 liefert {value: X} — beide abfangen
|
||||
const tv = v => (v != null && typeof v === 'object') ? (v.value ?? 0) : (v ?? 0);
|
||||
const fmt = v => Number(v).toLocaleString('de');
|
||||
|
||||
// Bounce Rate & Verweildauer
|
||||
const bounceToday = d.today?.bounceRate?.value != null
|
||||
? (d.today.bounceRate.value * 100).toFixed(0) + ' %'
|
||||
: (d.today?.bounces?.value != null && d.today?.visits?.value > 0
|
||||
? ((d.today.bounces.value / d.today.visits.value) * 100).toFixed(0) + ' %'
|
||||
: '—');
|
||||
const timeWeek = d.week?.totaltime?.value > 0 && d.week?.visits?.value > 0
|
||||
? Math.round(d.week.totaltime.value / d.week.visits.value) + ' s'
|
||||
const _bounces = tv(d.today?.bounces);
|
||||
const _visits = tv(d.today?.visits);
|
||||
const bounceToday = _visits > 0
|
||||
? ((_bounces / _visits) * 100).toFixed(0) + ' %'
|
||||
: '—';
|
||||
const _totaltime = tv(d.week?.totaltime);
|
||||
const _visitsW = tv(d.week?.visits);
|
||||
const timeWeek = _totaltime > 0 && _visitsW > 0
|
||||
? Math.round(_totaltime / _visitsW) + ' s'
|
||||
: '—';
|
||||
|
||||
el.innerHTML = `
|
||||
|
|
|
|||
|
|
@ -104,22 +104,31 @@ window.Page_forum = (() => {
|
|||
data-section="map">${UI.icon('users')} Mitgliederkarte</button>
|
||||
</div>
|
||||
|
||||
<!-- Suchleiste -->
|
||||
<div class="forum-search-wrap">
|
||||
<input type="search" class="forum-search" id="forum-search"
|
||||
placeholder="Forum durchsuchen…" autocomplete="off">
|
||||
</div>
|
||||
<!-- Rechte Spalte: Suche + Threads -->
|
||||
<div class="forum-main-col">
|
||||
|
||||
<div class="forum-search-wrap">
|
||||
<input type="search" class="forum-search" id="forum-search"
|
||||
placeholder="Forum durchsuchen…" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Thread-Liste / Karte / Suche -->
|
||||
<div id="forum-main">
|
||||
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt…</p>
|
||||
</div>
|
||||
|
||||
<!-- Thread-Liste / Karte / Suche -->
|
||||
<div id="forum-main">
|
||||
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt…</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Desktop: Tabs gleichmäßig auf 2 Zeilen verteilen
|
||||
const _tabsEl = document.getElementById('forum-tabs');
|
||||
const _tabCount = _tabsEl.querySelectorAll('.by-tab').length;
|
||||
_tabsEl.style.setProperty('--forum-tab-cols', Math.ceil(_tabCount / 2));
|
||||
|
||||
// Tab-Klicks
|
||||
document.getElementById('forum-tabs').addEventListener('click', e => {
|
||||
_tabsEl.addEventListener('click', e => {
|
||||
const btn = e.target.closest('[data-kat], [data-section]');
|
||||
if (!btn) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ window.Page_uebungen = (() => {
|
|||
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
|
||||
color:var(--c-text);line-height:1.3">${_esc(r.exercise_name)}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.4">${_esc(r.reason)}</div>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:auto;padding-top:var(--space-1)">
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);margin-top:auto;padding-top:var(--space-1)">
|
||||
<div>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${r.suggested_reps}× empfohlen</span>
|
||||
<span style="font-size:var(--text-xs);font-weight:700;color:${trendColor};margin-left:4px">${trend}</span>
|
||||
|
|
@ -640,7 +640,7 @@ window.Page_uebungen = (() => {
|
|||
</div>
|
||||
<button class="ueb-trainer-btn btn btn-primary"
|
||||
data-tab="${_esc(r.tab)}" data-name="${_esc(r.exercise_name)}" data-reps="${r.suggested_reps}"
|
||||
style="font-size:var(--text-xs);padding:4px 10px;flex-shrink:0">
|
||||
style="font-size:var(--text-xs);padding:4px 10px;width:100%">
|
||||
Üben
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -656,7 +656,7 @@ window.Page_uebungen = (() => {
|
|||
Dein Plan für heute
|
||||
</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:stretch">
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${cards.join('')}
|
||||
</div>`;
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ window.Page_walks = (() => {
|
|||
_view = view;
|
||||
document.querySelectorAll('.walks-view-btn').forEach(b =>
|
||||
b.classList.toggle('active', b.dataset.view === view));
|
||||
|
||||
// Desktop: beide Panels bleiben via CSS sichtbar
|
||||
if (window.innerWidth >= 1024) return;
|
||||
|
||||
document.getElementById('walks-list-view').style.display = view === 'liste' ? '' : 'none';
|
||||
document.getElementById('walks-map-view').style.display = view === 'karte' ? '' : 'none';
|
||||
|
||||
|
|
@ -134,6 +138,14 @@ window.Page_walks = (() => {
|
|||
);
|
||||
_renderList();
|
||||
_renderMarkers();
|
||||
// Desktop: Karte direkt initialisieren (beide Panels sichtbar)
|
||||
if (window.innerWidth >= 1024) {
|
||||
UI.loadLeaflet().then(() => {
|
||||
_initMap();
|
||||
setTimeout(() => _map?.invalidateSize(), 150);
|
||||
setTimeout(() => _map?.invalidateSize(), 400);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Laden.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ window.Page_welcome = (() => {
|
|||
|| window.navigator.standalone === true;
|
||||
|
||||
_container.innerHTML = `
|
||||
<div style="max-width:480px;margin:0 auto;padding:var(--space-6) var(--space-4) var(--space-8)">
|
||||
<div class="welcome-layout" style="margin:0 auto;padding:var(--space-6) var(--space-4) var(--space-8)">
|
||||
|
||||
<!-- Hero -->
|
||||
<div style="text-align:center;margin-bottom:var(--space-8)">
|
||||
|
|
@ -68,7 +68,9 @@ window.Page_welcome = (() => {
|
|||
</div>
|
||||
</button>
|
||||
|
||||
<!-- Features: Mein Hund -->
|
||||
<!-- Feature-Abschnitte (auf Desktop 2-spaltig) -->
|
||||
<div class="welcome-sections">
|
||||
|
||||
${_featureCard('Mein Hund', [
|
||||
['book-open', 'Tagebuch', 'Momente, Fotos und Meilensteine festhalten', 'diary'],
|
||||
['first-aid', 'Gesundheit', 'Impfungen, Tierarztbesuche & Medikamente', 'health'],
|
||||
|
|
@ -76,21 +78,18 @@ window.Page_welcome = (() => {
|
|||
['clipboard-text', 'Trainingspläne', 'Strukturierte Pläne für jedes Lernziel', 'trainingsplaene'],
|
||||
])}
|
||||
|
||||
<!-- Features: Entdecken -->
|
||||
${_featureCard('Entdecken', [
|
||||
['map-trifold', 'Karte', 'Orte, Routen und Meldungen in der Nähe', 'map'],
|
||||
['path', 'Routen', 'GPS-Routen aufzeichnen und bewerten', 'routes'],
|
||||
['calendar-dots', 'Events', 'Turniere und Veranstaltungen', 'events'],
|
||||
])}
|
||||
|
||||
<!-- Features: Soziales -->
|
||||
${_featureCard('Soziales', [
|
||||
['users', 'Freunde', 'Verbinde dich mit anderen Hundebesitzern', 'friends'],
|
||||
['chat-circle-dots', 'Nachrichten', 'Private Chats mit deinen Freunden', 'chat'],
|
||||
['bell', 'Aktuelles', 'Benachrichtigungen und Neuigkeiten', 'notifications'],
|
||||
])}
|
||||
|
||||
<!-- Features: Community -->
|
||||
${_featureCard('Community', [
|
||||
['warning-octagon', 'Giftköder-Alarm', 'Warnungen sofort melden und empfangen', 'poison'],
|
||||
['paw-print', 'Gassi-Treffen', 'Hunde-Dates mit anderen Besitzern', 'walks'],
|
||||
|
|
@ -99,7 +98,6 @@ window.Page_welcome = (() => {
|
|||
['magnifying-glass', 'Verlorene Hunde', 'Hilf vermisste Hunde zu finden', 'lost'],
|
||||
])}
|
||||
|
||||
<!-- Features: Wissen -->
|
||||
${_featureCard('Wissen', [
|
||||
['books', 'Wiki', 'Rassendatenbank, Gesundheits-Wiki, Quiz', 'wiki'],
|
||||
['handshake', 'Knigge', 'Regeln, Begegnungen, Leinenpflicht', 'knigge'],
|
||||
|
|
@ -107,6 +105,8 @@ window.Page_welcome = (() => {
|
|||
['first-aid', 'Erste Hilfe','Notfallratgeber für häufige Situationen', 'erste-hilfe'],
|
||||
])}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- App installieren -->
|
||||
<div class="card" style="margin-bottom:var(--space-5)" id="install-section">
|
||||
<div style="padding:var(--space-3) var(--space-4);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue