feat: WM2026 Tippspiel - Initial Backend + Frontend
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user