import { Check, TrendingUp, X } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Match } from '../api/client'; import styles from './MatchCard.module.css'; interface Props { match: Match; onTip: () => void; } type CardState = 'open' | 'tipped' | 'live' | 'finished' | 'missed'; function getCardState(match: Match): CardState { if (match.status === 'IN_PLAY' || match.status === 'PAUSED') return 'live'; if (match.status === 'FINISHED') { return match.userTip ? 'finished' : 'missed'; } // SCHEDULED or TIMED return match.userTip ? 'tipped' : 'open'; } function useCountdown(minutesUntilKickoff: number) { const [remaining, setRemaining] = useState(minutesUntilKickoff); useEffect(() => { if (minutesUntilKickoff > 60) return; // only active for <1h setRemaining(minutesUntilKickoff); const interval = setInterval(() => { setRemaining(r => Math.max(0, r - 1 / 60)); }, 1000); return () => clearInterval(interval); }, [minutesUntilKickoff]); return remaining; } const STATUS_LABELS: Record = { SCHEDULED: 'Geplant', TIMED: 'Terminiert', IN_PLAY: 'Live', PAUSED: 'Pause', FINISHED: 'Beendet', POSTPONED: 'Verschoben', CANCELLED: 'Abgesagt', }; function formatKickoff(utcDate: string): string { return new Date(utcDate).toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Berlin', }); } function FlagBox({ crest, name }: { crest: string | null; name: string }) { return (
{crest ? {name} : 🏳️ }
); } export default function MatchCard({ match, onTip }: Props) { const state = getCardState(match); const remaining = useCountdown(match.minutesUntilKickoff); const remainingMins = Math.ceil(remaining); const isFinished = state === 'finished' || state === 'missed'; const isLive = state === 'live'; 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 (
{/* Top row: Status/Group */}
{(isLive || isFinished) && ( {isLive && } {STATUS_LABELS[match.status] ?? match.status} )} {match.group && ( {match.group.replace('GROUP_', 'Gruppe ')} )}
{/* Kickoff β€” stadium LED display, centered */} {!isFinished && !isLive && (
{formatKickoff(match.utcDate)}
)} {/* Countdown badge β€” left-aligned */} {(state === 'open' || state === 'tipped') && match.tippable && (
{match.minutesUntilKickoff < 60 ? `Noch ${remainingMins} Min!` : (() => { const h = Math.floor(match.minutesUntilKickoff / 60); if (h < 24) return `in ${h}h`; const d = Math.floor(h / 24); return `in ${d} Tag${d > 1 ? 'en' : ''}`; })() }
)} {/* Teams + Score */}
{/* Home */}
{match.homeTeam.shortName}
{/* Center: Score or VS separator */}
{isFinished || isLive ? (
{match.score.home ?? '–'} : {match.score.away ?? '–'} {isLive && hasTip && ( Tipp: {match.userTip!.home}:{match.userTip!.away} )}
) : ( – )}
{/* Away */}
{match.awayTeam.shortName}
{/* Tipp area β€” wird zum farbigen Banner wenn Punkte ausgewertet */}
{state === 'missed' ? ( /* ── Missed: no tip, match finished ── */ Nicht getippt ) : state === 'live' ? ( /* ── Live: no tip input, locked ── */ hasTip ? (
DEIN TIPP {match.userTip!.home} : {match.userTip!.away}
) : ( Kein Tipp abgegeben ) ) : hasTip ? ( points !== null ? ( /* ── Auswertungs-Banner ── */
{/* Links: Icon + Ergebnis-Label nebeneinander, zentriert zur Tippbox */}
{points === 3 ? : points === 1 ? : } {points === 3 ? 'Exakt' : points === 1 ? 'Tendenz' : 'Falsch'}
{/* Mitte: nur Score, kein Label */}
{match.userTip!.home} : {match.userTip!.away}
{/* Rechts: Punkte */}
{points === 0 ? '0 Pkt.' : `+${points} Pkt.`}
) : ( /* ── Tipp vorhanden, noch nicht ausgewertet (tipped state) ── */
βœ“ Dein Tipp {match.userTip!.home} : {match.userTip!.away} {match.tippable && Γ„ndern}
) ) : match.tippable ? ( ) : ( Kein Tipp abgegeben )}
); }