feat: WM2026 Tippspiel - Initial Backend + Frontend
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { Match } from '../api/client';
|
||||
import styles from './MatchCard.module.css';
|
||||
|
||||
interface Props {
|
||||
match: Match;
|
||||
onTip: () => void;
|
||||
}
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
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'
|
||||
}) + ' Uhr';
|
||||
}
|
||||
|
||||
function CountdownBadge({ minutes }: { minutes: number }) {
|
||||
if (minutes <= 0) return null;
|
||||
if (minutes < 60) return <span className={styles.badgeUrgent}>in {minutes} Min.</span>;
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
if (h < 24) return <span className={styles.badge}>in {h}h {m > 0 ? `${m}m` : ''}</span>;
|
||||
const d = Math.floor(h / 24);
|
||||
return <span className={styles.badge}>in {d} Tag{d > 1 ? 'en' : ''}</span>;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return (
|
||||
<div className={`card ${styles.card} ${isLive ? styles.live : ''}`}>
|
||||
{/* Status + Kickoff */}
|
||||
<div className={styles.topRow}>
|
||||
<span className={`${styles.status} ${isLive ? styles.statusLive : ''}`}>
|
||||
{STATUS_LABELS[match.status] ?? match.status}
|
||||
</span>
|
||||
<span className={styles.kickoff}>{formatKickoff(match.utcDate)}</span>
|
||||
{match.tippable && <CountdownBadge minutes={match.minutesUntilKickoff} />}
|
||||
{match.group && <span className={styles.group}>{match.group.replace('GROUP_', 'Gruppe ')}</span>}
|
||||
</div>
|
||||
|
||||
{/* Teams + Score */}
|
||||
<div className={styles.matchRow}>
|
||||
{/* Home Team */}
|
||||
<div className={styles.teamHome}>
|
||||
{match.homeTeam.crest && (
|
||||
<img className={styles.crest} src={match.homeTeam.crest} alt="" />
|
||||
)}
|
||||
<span className={styles.teamName}>{match.homeTeam.name}</span>
|
||||
</div>
|
||||
|
||||
{/* Score / VS */}
|
||||
<div className={styles.scoreBox}>
|
||||
{isFinished || isLive ? (
|
||||
<span className={styles.score}>
|
||||
{match.score.home ?? '–'} : {match.score.away ?? '–'}
|
||||
</span>
|
||||
) : (
|
||||
<span className={styles.vs}>vs</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Away Team */}
|
||||
<div className={styles.teamAway}>
|
||||
<span className={styles.teamName}>{match.awayTeam.name}</span>
|
||||
{match.awayTeam.crest && (
|
||||
<img className={styles.crest} src={match.awayTeam.crest} alt="" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tipp-Bereich */}
|
||||
<div className={styles.tipRow}>
|
||||
{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>
|
||||
) : match.tippable ? (
|
||||
<button className={`btn-primary ${styles.tipBtn}`} onClick={onTip}>
|
||||
⚡ Tipp abgeben
|
||||
</button>
|
||||
) : (
|
||||
<span className={styles.noTip}>Kein Tipp abgegeben</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user