Files
hcrb/public/js/statistik.js
T
guyg1474andClaude Opus 5.5 819219fb2d Rollen (Admin/Mitglied), Seite Kooperationen, Textanpassungen
- Gemeinsame Zugänge mit Rollen: Admin darf alles, Mitglied nutzt Kalender,
  Fahrtplaner, Treffpunkte und bearbeitet Mitglieder (kein Hinzufügen,
  Löschen, Import, keine Patches, keine Statistik-Korrektur)
- Server prüft die Rechte (403), Konten ohne Rolle gelten als Admin
- npm run user: add <name> [admin|mitglied], role <name> <rolle>
- Neue öffentliche Seite Kooperationen mit Titelbild, Anführer,
  Social-Media-Links und Beschreibung, Pflege durch Admin
- Kopfzeile bleibt bei mittlerer Breite einzeilig
- Texte auf Wunsch gekürzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:47:03 +02:00

72 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { $, api, esc, showError, renderHeader, renderFooter, isAdmin } from './common.js';
renderHeader('statistik');
renderFooter();
let stats = [];
let canEdit = false; // Admin und laufendes Jahr
function detail(s) {
if (!s.manual) return '';
const sign = s.manual > 0 ? '+' : '−';
return `<span class="meta">${s.calendar} aus Kalender ${sign} ${Math.abs(s.manual)} manuell</span>`;
}
function render() {
$('#stats-empty').hidden = stats.length > 0;
$('#stats-edit-hint').hidden = !canEdit;
// Gleiche Anzahl → gleicher Platz
const rank = (s) => 1 + stats.filter((x) => x.total > s.total).length;
const max = Math.max(1, ...stats.map((s) => s.total));
$('#stats-list').innerHTML = stats.map((s) => `
<li class="${rank(s) <= 3 && s.total > 0 ? 'top' : ''}">
<span class="rank">${rank(s)}.</span>
<span class="who">
<span class="name">${esc(s.name)} ${detail(s)}</span>
<span class="stat-bar" aria-hidden="true"><i style="width: ${(s.total / max) * 100}%"></i></span>
</span>
<span class="actions">
${canEdit ? `
<button class="small" data-step="-1" data-id="${s.id}" ${s.total ? '' : 'disabled'} aria-label="Ein Spiel weniger">−</button>` : ''}
<span class="stat-total">${s.total}</span>
${canEdit ? `
<button class="small" data-step="1" data-id="${s.id}" aria-label="Ein Spiel mehr">+</button>` : ''}
</span>
</li>`).join('');
}
$('#stats-list').addEventListener('click', async (e) => {
const { step, id } = e.target.closest('[data-step]')?.dataset ?? {};
if (!step) return;
const s = stats.find((x) => x.id === id);
try {
const { manual } = await api('PUT', `/members/${id}/plays-manual`, { manual: Math.max(s.manual, -s.calendar) + Number(step) });
s.manual = manual;
s.total = Math.max(0, s.calendar + manual);
stats.sort((a, b) => b.total - a.total || a.name.localeCompare(b.name, 'de'));
render();
} catch (err) {
showError(err);
}
});
let admin = false;
async function load(year) {
const data = await api('GET', `/stats${year ? `?year=${year}` : ''}`);
stats = data.entries;
canEdit = admin && data.live;
$('#stats-year').innerHTML = data.years
.map((y, i) => `<option value="${y}" ${y === data.year ? 'selected' : ''}>${y}${i === 0 ? ' (laufend)' : ''}</option>`).join('');
$('#stats-info').textContent = data.live
? `Laufendes Jahr: Spiel-Events seit dem 1. Januar ${data.year}, bei denen jemand zugesagt hat.`
: `Endstand ${data.year}, gespeichert am ${new Date(data.savedAt).toLocaleDateString('de-DE')}.`;
render();
}
$('#stats-year').addEventListener('change', (e) => load(e.target.value).catch(showError));
isAdmin()
.then((yes) => { admin = yes; return load(); })
.catch(showError);