Feature+Fix: Referral-Admin, Pro-Gates, Karten-Layer, onDogChange, Staging-Media (SW by-v855)
Features: - Admin: Referral-Tab (Virality Factor, Top-Werber, letzte Einladungen) - Karte: Regenradar (RainViewer, zoom→7, color=4), Temperatur-Layer (OWM) mit Zahlen-Grid + Legende - Wetter-Chip: Umschwung-Warnung bei ≥40%-Sprung in Niederschlagswahrscheinlichkeit - Freundschaftsanfragen: Accept/Decline direkt in Notifications (kein Pro nötig) - Freunde-Seite für Standard-User freigeschaltet Pro-Gates: - KI-Trainer, Routenvorschläge, Regenradar, Temperatur-Layer jetzt Pro-Feature - Pro-Badge (P) auf Chips für Admins/Mods in allen Welten + Welten-einrichten - Oranger Banner auf Pro-Seiten für Admin/Mod/Manager Bugfixes: - onDogChange: uebungen.js (Cache leeren + _render), trainingsplaene.js (war leer) - robots.txt vereinfacht (nur Disallow, kein Allow-Durcheinander) - Hintergrund-Foto: Querformat-Filter korrigiert (kein Fallback auf Hochformat) - Staging Media: FileResponse mit korrektem MIME-Type, no-cache statt immutable - Staging Docker: MEDIA_DIR=/data/media + /prod-media:ro Fallback-Handler - Staging-Fix: Bild-Upload auf zweitem Hund (war Read-only file system)
This commit is contained in:
parent
2f021f54c2
commit
79fa5684b9
22 changed files with 570 additions and 58 deletions
|
|
@ -25,6 +25,7 @@ window.Page_admin = (() => {
|
|||
{ id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' },
|
||||
{ id: 'hilfe', label: 'Hilfe/FAQ', icon: 'question' },
|
||||
{ id: 'uebungen_admin', label: 'Übungen', icon: 'barbell' },
|
||||
{ id: 'referrals', label: 'Referrals', icon: 'share-network' },
|
||||
];
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -161,6 +162,7 @@ window.Page_admin = (() => {
|
|||
case 'bewerbungen': await _renderBewerbungen(el); break;
|
||||
case 'hilfe': await _renderHilfe(el); break;
|
||||
case 'uebungen_admin': await _renderUebungenAdmin(el); break;
|
||||
case 'referrals': await _renderReferrals(el); break;
|
||||
}
|
||||
} catch (e) {
|
||||
el.innerHTML = _emptyState('warning', 'Fehler', e.message || 'Unbekannter Fehler.');
|
||||
|
|
@ -3344,6 +3346,79 @@ window.Page_admin = (() => {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------
|
||||
// TAB: REFERRALS
|
||||
// ------------------------------------------------------------------
|
||||
async function _renderReferrals(el) {
|
||||
el.innerHTML = `<div style="padding:var(--space-4);color:var(--c-text-muted);font-size:var(--text-sm)">Lade…</div>`;
|
||||
let d;
|
||||
try { d = await API.get('/admin/referrals'); } catch { el.innerHTML = `<div style="padding:var(--space-4);color:var(--c-danger)">Fehler beim Laden.</div>`; return; }
|
||||
|
||||
const pct = d.total_users > 0 ? Math.round(d.total_referred / d.total_users * 100) : 0;
|
||||
|
||||
const topRows = d.top_referrers.map((r, i) => `
|
||||
<tr>
|
||||
<td style="padding:8px 10px;color:var(--c-text-muted);font-weight:600">${i + 1}</td>
|
||||
<td style="padding:8px 10px;font-weight:600">${_esc(r.name)}</td>
|
||||
<td style="padding:8px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(r.email)}</td>
|
||||
<td style="padding:8px 10px;text-align:right">
|
||||
<span style="font-size:var(--text-lg);font-weight:800;color:var(--c-primary)">${r.invited_count}</span>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
|
||||
const recentRows = d.recent_invites.slice(0, 50).map(r => `
|
||||
<tr>
|
||||
<td style="padding:6px 10px;font-weight:500">${_esc(r.name)}</td>
|
||||
<td style="padding:6px 10px;color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(r.referrer_name)}</td>
|
||||
<td style="padding:6px 10px;color:var(--c-text-muted);font-size:var(--text-xs)">${(r.created_at || '').slice(0, 10)}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
el.innerHTML = `
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-3);margin-bottom:var(--space-4)">
|
||||
<div class="card" style="padding:var(--space-4);text-align:center">
|
||||
<div style="font-size:var(--text-2xl);font-weight:800;color:var(--c-primary)">${d.total_referred}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">Geworbene User</div>
|
||||
</div>
|
||||
<div class="card" style="padding:var(--space-4);text-align:center">
|
||||
<div style="font-size:var(--text-2xl);font-weight:800;color:var(--c-success)">${pct}%</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">Anteil geworbener User</div>
|
||||
</div>
|
||||
<div class="card" style="padding:var(--space-4);text-align:center">
|
||||
<div style="font-size:var(--text-2xl);font-weight:800;color:var(--c-warning)">${d.viral_factor}</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">Virality Factor</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom:var(--space-4);overflow:hidden">
|
||||
<div class="by-card-section-header">Top Werber</div>
|
||||
<div style="overflow-x:auto">
|
||||
<table style="width:100%;border-collapse:collapse">
|
||||
<thead><tr style="border-bottom:2px solid var(--c-border);font-size:var(--text-xs);color:var(--c-text-muted)">
|
||||
<th style="padding:8px 10px;text-align:left">#</th>
|
||||
<th style="padding:8px 10px;text-align:left">Name</th>
|
||||
<th style="padding:8px 10px;text-align:left">E-Mail</th>
|
||||
<th style="padding:8px 10px;text-align:right">Eingeladen</th>
|
||||
</tr></thead>
|
||||
<tbody>${topRows || '<tr><td colspan="4" style="padding:var(--space-4);text-align:center;color:var(--c-text-muted)">Noch keine Empfehlungen</td></tr>'}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="overflow:hidden">
|
||||
<div class="by-card-section-header">Zuletzt geworbene User</div>
|
||||
<div style="overflow-x:auto">
|
||||
<table style="width:100%;border-collapse:collapse">
|
||||
<thead><tr style="border-bottom:2px solid var(--c-border);font-size:var(--text-xs);color:var(--c-text-muted)">
|
||||
<th style="padding:6px 10px;text-align:left">User</th>
|
||||
<th style="padding:6px 10px;text-align:left">Geworben von</th>
|
||||
<th style="padding:6px 10px;text-align:left">Datum</th>
|
||||
</tr></thead>
|
||||
<tbody>${recentRows || '<tr><td colspan="3" style="padding:var(--space-4);text-align:center;color:var(--c-text-muted)">Noch keine Daten</td></tr>'}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue