feat: Ergebnis-Banner, Dev-Simulations-Panel & Spiele-Reset

- MatchCard: farbiger Ergebnis-Banner (Exakt/Tendenz/Falsch) ersetzt
  tipRow, passender Card-Glow je Ergebnis; Lucide-Icons (lucide-react)
- MatchCard: Ändern-Button links, vertikal mittig zur Tipp-Box ausgerichtet
- DevPanel: Simulationsmodus mit User-Switcher, Zeit- & Status-Presets
- DevPanel: Reset-Section mit "Spiel zurücksetzen", "Alle Spiele" und
  "Tipps löschen" (3 Buttons, farblich differenziert)
- Backend: dev.ts mit set-time, set-status, reset-tips, reset-match
- reset-match stellt Original-Datum wieder her (original_utc_date Spalte)
- set-time sichert Original-Datum per COALESCE beim ersten Aufruf
- Supabase Migration: original_utc_date TIMESTAMPTZ zu matches hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ronny
2026-04-04 01:10:55 +02:00
parent e27a62a37b
commit 3d3ff097cf
13 changed files with 1023 additions and 72 deletions
+61 -21
View File
@@ -1,3 +1,4 @@
import { Check, TrendingUp, X } from 'lucide-react';
import { Match } from '../api/client';
import styles from './MatchCard.module.css';
@@ -47,9 +48,19 @@ export default function MatchCard({ match, onTip }: Props) {
const isFinished = match.status === 'FINISHED';
const isLive = match.status === 'IN_PLAY' || match.status === 'PAUSED';
const hasTip = !!match.userTip;
const points = match.userTip?.points ?? null;
const resultClass =
points === 3 ? styles.exact :
points === 1 ? styles.tendency :
(points === 0 && isFinished) ? styles.wrong : '';
const glowClass =
isFinished && points === 3 ? styles.glowExact :
isFinished && points === 1 ? styles.glowTendency :
isFinished && points === 0 ? styles.glowWrong : '';
return (
<div className={`card ${styles.card} ${isLive ? styles.live : ''}`}>
<div className={`card ${styles.card} ${isLive ? styles.live : ''} ${glowClass}`}>
{/* Top row: Status / Kickoff / Badges */}
<div className={styles.topRow}>
@@ -92,27 +103,56 @@ export default function MatchCard({ match, onTip }: Props) {
</div>
</div>
{/* Tipp area */}
<div className={styles.tipRow}>
{/* Tipp area — wird zum farbigen Banner wenn Punkte ausgewertet */}
<div className={`${styles.tipRow} ${hasTip && points !== null ? `${styles.resultBanner} ${resultClass}` : ''}`}>
{hasTip ? (
<div className={styles.tipDisplay}>
<span className={styles.tipLabel}>Dein Tipp</span>
<span className={styles.tipScore}>
{match.userTip!.home} : {match.userTip!.away}
</span>
{match.userTip!.points !== null && (
<span className={`${styles.points} ${
match.userTip!.points === 3 ? styles.exact :
match.userTip!.points === 1 ? styles.tendency : styles.wrong
}`}>
{match.userTip!.points === 3 ? '🎯 3 Punkte' :
match.userTip!.points === 1 ? '✓ 1 Punkt' : '✗ 0 Punkte'}
</span>
)}
{match.tippable && (
<button className={styles.editBtn} onClick={onTip}>Ändern</button>
)}
</div>
points !== null ? (
/* ── Auswertungs-Banner ── */
<div className={styles.tipDisplay}>
{/* Links: Icon + Ergebnis-Label nebeneinander, zentriert zur Tippbox */}
<div className={`${styles.tipLeft} ${styles.bannerLeft}`}>
<span className={styles.resultIcon}>
{points === 3 ? <Check size={14} strokeWidth={3} /> :
points === 1 ? <TrendingUp size={14} strokeWidth={2.5} /> :
<X size={14} strokeWidth={3} />}
</span>
<span className={styles.resultLabel}>
{points === 3 ? 'Exakt' : points === 1 ? 'Tendenz' : 'Falsch'}
</span>
</div>
{/* Mitte: nur Score, kein Label */}
<div className={styles.tipCenter}>
<span className={styles.tipScoreBanner}>
{match.userTip!.home} : {match.userTip!.away}
</span>
</div>
{/* Rechts: Punkte */}
<div className={styles.tipRight}>
<span className={styles.resultPoints}>
{points === 0 ? '0 Pkt.' : `+${points} Pkt.`}
</span>
</div>
</div>
) : (
/* ── Tipp vorhanden, noch nicht ausgewertet ── */
<div className={styles.tipDisplay}>
<div className={styles.tipLeft}>
{match.tippable && (
<button className={styles.editBtn} onClick={onTip}>Ändern</button>
)}
</div>
<div className={styles.tipCenter}>
{/* Label nur zeigen wenn kein Ändern-Button da ist, sonst fluchtet der Button nicht */}
{!match.tippable && <span className={styles.tipLabel}>DEIN TIPP</span>}
<span className={styles.tipScore}>
{match.userTip!.home} : {match.userTip!.away}
</span>
</div>
<div className={styles.tipRight} />
</div>
)
) : match.tippable ? (
<button className={`btn-primary ${styles.tipBtn}`} onClick={onTip}>
Tipp abgeben