Feature: Analytics Jahres-Balkendiagramm — Seitenaufrufe + Neuanmeldungen 12 Monate — SW by-v502, APP_VER 479

This commit is contained in:
rene 2026-04-29 14:32:10 +02:00
parent b2e7f1409a
commit 977fbeb0fd
4 changed files with 88 additions and 9 deletions

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '478'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '479'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {

View file

@ -343,6 +343,68 @@ window.Page_admin = (() => {
${_dualChart(pv, ses)}
</div>
<!-- Jahres-Übersicht -->
<div class="card" style="padding:var(--space-4)">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)">
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold)">Jahresübersicht letzte 12 Monate</span>
<div style="display:flex;gap:var(--space-3);font-size:10px;color:var(--c-text-muted)">
<span style="display:flex;align-items:center;gap:4px">
<span style="width:12px;height:10px;background:var(--c-primary);opacity:0.7;display:inline-block;border-radius:2px"></span> Seitenaufrufe
</span>
<span style="display:flex;align-items:center;gap:4px">
<span style="width:12px;height:10px;background:var(--c-success);opacity:0.7;display:inline-block;border-radius:2px"></span> Neuanmeldungen
</span>
</div>
</div>
${(() => {
const pvYear = d.pageviews_year?.pageviews ?? [];
const regs = d.monthly_registrations ?? [];
// Alle Monate der letzten 12 sammeln
const months = [];
const now = new Date();
for (let i = 11; i >= 0; i--) {
const d2 = new Date(now.getFullYear(), now.getMonth() - i, 1);
months.push(d2.getFullYear() + '-' + String(d2.getMonth()+1).padStart(2,'0'));
}
// Umami liefert x als ISO-Datum-String
const pvByMonth = {};
pvYear.forEach(p => {
const key = (p.x || '').slice(0, 7);
pvByMonth[key] = (pvByMonth[key] || 0) + (p.y ?? 0);
});
const regByMonth = {};
regs.forEach(r => { regByMonth[r.month] = r.count; });
const pvVals = months.map(m => pvByMonth[m] || 0);
const regVals = months.map(m => regByMonth[m] || 0);
const maxPv = Math.max(...pvVals, 1);
const maxReg = Math.max(...regVals, 1);
const W = 800, H = 120, n = months.length;
const barW = Math.floor((W - (n-1)*4) / n);
const bars = months.map((m, i) => {
const x = i * (barW + 4);
const pvH = Math.round((pvVals[i] / maxPv) * (H - 20));
const regH = Math.round((regVals[i] / maxReg) * (H - 20));
const label = m.slice(5); // MM
return `
<rect x="${x}" y="${H - 20 - pvH}" width="${barW}" height="${pvH}"
fill="var(--c-primary)" opacity="0.65" rx="2"/>
<rect x="${x + Math.floor(barW*0.55)}" y="${H - 20 - regH}" width="${Math.floor(barW*0.4)}" height="${regH}"
fill="var(--c-success)" opacity="0.8" rx="2"/>
<text x="${x + barW/2}" y="${H - 4}" text-anchor="middle"
font-size="9" fill="currentColor" style="color:var(--c-text-muted)">${label}</text>
`;
}).join('');
return `<svg viewBox="0 0 ${W} ${H}" style="width:100%;height:120px;display:block;overflow:visible"
preserveAspectRatio="none">${bars}</svg>`;
})()}
</div>
<!-- Top Seiten + Referrers nebeneinander -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)">
<div class="card" style="padding:var(--space-4)">