Feature: Ratings, Lightbox, Forum-Standort, Notifications, Routen-Recording, Chat-Picker

- Bewertungssystem (ratings.py): Sterne für Sitter/Walks/Places/Routen
- Admin: Server-Log-Viewer + OSM-Cache-Statistiken
- Chat: "Neue Nachricht"-Button mit Freundesliste-Picker
- Forum: 5 neue Kategorien, Standorteingabe (locationPicker), Absende-Toast, Lightbox
- Freunde: Aktivitäts-Filter (Chips), Freundschaftsanfrage → In-App-Notification
- Sitter: locationPicker statt manuelle Koordinateneingabe + ratingStars
- Tagebuch: Bilder-Lightbox im Detail-View, iOS-Modal-Header-Fix (90svh)
- Routen: Start/Stopp-Button wechselt Zustand, nutzt Page_map.isRecording()
- Benachrichtigungen: Delete-Button sichtbar, typ-basierte Navigation, Toast-Feedback
- OSM: globales Semaphore + 429-Retry-Logic; Scheduler: München-Umland, täglich
- SW by-v225, APP_VER 202
This commit is contained in:
rene 2026-04-19 09:40:35 +02:00
parent aa70a838f2
commit e56183b642
21 changed files with 648 additions and 175 deletions

View file

@ -104,6 +104,20 @@ window.Page_admin = (() => {
${_statCard('image', 'Media-Einträge', s.media_count, 'var(--c-text-secondary)')}
${_statCard('map-pin', 'Routen', s.routes_total, 'var(--c-text-secondary)')}
${_statCard('calendar', 'Events', s.events_total, 'var(--c-text-secondary)')}
${_statCard('map-trifold', 'OSM-Marker', s.osm_total.toLocaleString('de'), 'var(--c-success)')}
${_statCard('squares-four', 'Gecachte Tiles', s.osm_tiles.toLocaleString('de'), 'var(--c-text-secondary)')}
</div>
<div class="card" style="padding:var(--space-4)">
<p style="font-size:var(--text-sm);font-weight:600;margin:0 0 var(--space-3)">OSM-Cache nach Typ</p>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
${Object.entries(s.osm_by_type).map(([type, count]) => `
<div style="display:flex;justify-content:space-between;font-size:var(--text-sm)">
<span style="color:var(--c-text-secondary)">${type}</span>
<span style="font-weight:600">${count.toLocaleString('de')}</span>
</div>
`).join('')}
</div>
</div>
<div class="card" style="padding:var(--space-4)">
@ -543,9 +557,46 @@ window.Page_admin = (() => {
</button>
</div>
<div id="adm-sys-cards">Lade</div>
<div style="margin-top:var(--space-5)">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<span style="font-size:var(--text-sm);font-weight:600">Server-Logs</span>
<select id="adm-log-level" class="input" style="width:auto;padding:2px 8px;font-size:var(--text-xs)">
<option value="">Alle</option>
<option value="INFO">INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
</select>
<button class="btn btn-ghost btn-sm" id="adm-log-refresh">${UI.icon('arrows-clockwise')}</button>
</div>
<div id="adm-log-box" style="
background:var(--c-surface-2);border-radius:var(--radius-md);
padding:var(--space-3);font-family:monospace;font-size:11px;
max-height:420px;overflow-y:auto;line-height:1.6">Lade</div>
</div>
`;
el.querySelector('#adm-sys-refresh').addEventListener('click', () => _loadSystemCards(el.querySelector('#adm-sys-cards')));
const loadLogs = async () => {
const level = el.querySelector('#adm-log-level').value;
const box = el.querySelector('#adm-log-box');
box.textContent = 'Lade…';
const rows = await API.get(`/admin/logs?lines=200${level ? '&level=' + level : ''}`);
const COLORS = { ERROR: '#ef4444', WARNING: '#f59e0b', INFO: '#6b7280', DEBUG: '#94a3b8' };
box.innerHTML = rows.reverse().map(r => {
const color = COLORS[r.l] || '#6b7280';
return `<div style="border-bottom:1px solid var(--c-border);padding:2px 0">` +
`<span style="color:var(--c-text-muted)">${r.t}</span> ` +
`<span style="color:${color};font-weight:600">${r.l}</span> ` +
`<span style="color:var(--c-text-secondary)">${_esc(r.n)}</span> ` +
`<span>${_esc(r.m)}</span></div>`;
}).join('') || '<span style="color:var(--c-text-muted)">Keine Einträge</span>';
};
el.querySelector('#adm-sys-refresh').addEventListener('click', () => {
_loadSystemCards(el.querySelector('#adm-sys-cards'));
loadLogs();
});
el.querySelector('#adm-log-refresh').addEventListener('click', loadLogs);
el.querySelector('#adm-log-level').addEventListener('change', loadLogs);
await _loadSystemCards(el.querySelector('#adm-sys-cards'));
await loadLogs();
}
async function _loadSystemCards(el) {