feat: WM2026 Tippspiel - Initial Backend + Frontend

This commit is contained in:
Ronny Müller
2026-04-03 21:41:19 +02:00
commit 1c685b90a0
2507 changed files with 997210 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
import { useState } from 'react';
import { api } from '../api/client';
import styles from './AdminPage.module.css';
export default function AdminPage() {
const [syncStatus, setSyncStatus] = useState<string | null>(null);
const [evalStatus, setEvalStatus] = useState<string | null>(null);
const [syncing, setSyncing] = useState(false);
const [evaluating, setEvaluating] = useState(false);
const handleSync = async () => {
setSyncing(true);
setSyncStatus(null);
try {
const res = await api.syncMatches();
setSyncStatus(`${res.total} Spiele geladen — ${res.created} neu, ${res.updated} aktualisiert`);
} catch (e) {
setSyncStatus(`⚠️ Fehler: ${(e as Error).message}`);
} finally {
setSyncing(false);
}
};
const handleEvaluate = async () => {
setEvaluating(true);
setEvalStatus(null);
try {
const res = await api.evaluateTips();
setEvalStatus(`${res.matchesEvaluated} Spiele ausgewertet — ${res.tipsUpdated} Tipps bewertet`);
} catch (e) {
setEvalStatus(`⚠️ Fehler: ${(e as Error).message}`);
} finally {
setEvaluating(false);
}
};
return (
<div className={styles.page}>
<h1 className={`font-display ${styles.title}`}> Administration</h1>
<p className={styles.hint}>Diese Seite ist nur für Editoren. Nach der Staffbase-Integration wird sie durch Rollenprüfung geschützt.</p>
<div className={styles.cards}>
<div className={`card ${styles.actionCard}`}>
<div className={styles.cardIcon}>🔄</div>
<h2 className={styles.cardTitle}>Spiele synchronisieren</h2>
<p className={styles.cardDesc}>Lädt alle WM 2026-Spiele von football-data.org und speichert sie in der Datenbank. Täglich ausführen oder nach Spielplan-Änderungen.</p>
{syncStatus && (
<div className={`${styles.status} ${syncStatus.startsWith('✓') ? styles.success : styles.error}`}>
{syncStatus}
</div>
)}
<button className="btn-primary" onClick={handleSync} disabled={syncing}>
{syncing ? '⏳ Wird synchronisiert…' : '🔄 Jetzt synchronisieren'}
</button>
</div>
<div className={`card ${styles.actionCard}`}>
<div className={styles.cardIcon}>🧮</div>
<h2 className={styles.cardTitle}>Tipps auswerten</h2>
<p className={styles.cardDesc}>Berechnet Punkte für alle abgeschlossenen Spiele und aktualisiert die Rangliste. Nach jedem Spieltag ausführen.</p>
{evalStatus && (
<div className={`${styles.status} ${evalStatus.startsWith('✓') ? styles.success : styles.error}`}>
{evalStatus}
</div>
)}
<button className="btn-primary" onClick={handleEvaluate} disabled={evaluating}>
{evaluating ? '⏳ Wird ausgewertet…' : '🧮 Tipps auswerten'}
</button>
</div>
</div>
</div>
);
}