import { useState, useEffect, useCallback } from 'react'; import { api, Match } from '../api/client'; import MatchCard from '../components/MatchCard'; import TipModal from '../components/TipModal'; import { useRevealQueue } from '../hooks/useRevealQueue'; import ConfettiReveal from '../components/ConfettiReveal'; import styles from './MatchesPage.module.css'; type Section = { key: string; label: string; matches: Match[]; defaultOpen: boolean; highlight: boolean; }; function groupIntoSections(matches: Match[]): Section[] { const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const tomorrowStart = new Date(todayStart); tomorrowStart.setDate(todayStart.getDate() + 1); const dayAfterTomorrow = new Date(todayStart); dayAfterTomorrow.setDate(todayStart.getDate() + 2); const weekEnd = new Date(todayStart); weekEnd.setDate(todayStart.getDate() + 7); const sections: Section[] = [ { key: 'today', label: 'Heute', matches: [], defaultOpen: true, highlight: true }, { key: 'tomorrow', label: 'Morgen', matches: [], defaultOpen: true, highlight: false }, { key: 'week', label: 'Diese Woche', matches: [], defaultOpen: false, highlight: false }, { key: 'later', label: 'Demnächst', matches: [], defaultOpen: false, highlight: false }, { key: 'past', label: 'Vergangene Spiele', matches: [], defaultOpen: false, highlight: false }, ]; for (const match of matches) { const d = new Date(match.utcDate); if (d < todayStart) { sections[4].matches.push(match); // past } else if (d < tomorrowStart) { sections[0].matches.push(match); // today } else if (d < dayAfterTomorrow) { sections[1].matches.push(match); // tomorrow } else if (d < weekEnd) { sections[2].matches.push(match); // this week } else { sections[3].matches.push(match); // later } } // Past matches: most recent first sections[4].matches.reverse(); return sections.filter(s => s.matches.length > 0); } export default function MatchesPage() { const [allMatches, setAllMatches] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [stageFilter, setStageFilter] = useState(''); const [selectedMatch, setSelectedMatch] = useState(null); const [openSections, setOpenSections] = useState>(new Set()); const loadMatches = useCallback(async () => { setLoading(true); setError(null); try { const res = await api.getMatches(); setAllMatches(res.matches); } catch (e) { setError((e as Error).message); } finally { setLoading(false); } }, []); useEffect(() => { loadMatches(); }, [loadMatches]); // Initialize open sections after initial load useEffect(() => { if (allMatches.length > 0) { const filteredMatches = allMatches.filter(m => !stageFilter || m.stage === stageFilter); const sections = groupIntoSections(filteredMatches); // If only 1-2 sections exist, open all of them if (sections.length <= 2) { setOpenSections(new Set(sections.map(s => s.key))); } else { setOpenSections(new Set(sections.filter(s => s.defaultOpen).map(s => s.key))); } } }, [allMatches]); // only on initial load const handleTipSaved = (matchId: number, tipHome: number, tipAway: number) => { setAllMatches(prev => prev.map(m => m.id === matchId ? { ...m, userTip: { home: tipHome, away: tipAway, points: null } } : m )); setSelectedMatch(null); }; function toggleSection(key: string) { setOpenSections(prev => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); } const { current: revealMatch, dismissCurrent } = useRevealQueue(allMatches); const filteredMatches = allMatches.filter(m => !stageFilter || m.stage === stageFilter); const sections = groupIntoSections(filteredMatches); // Stats always over all matches (unfiltered) const tipped = allMatches.filter(m => m.userTip).length; const tippable = allMatches.filter(m => m.tippable && !m.userTip).length; return (
{revealMatch && ( )} {/* Header Stats */}
{allMatches.length} Spiele gesamt
{tipped} Tipps abgegeben
{tippable} Noch tippbar
{/* Stage Filter Dropdown */} {/* Content */} {loading && (
Spiele werden geladen…
)} {error && (
⚠️ {error}
)} {!loading && !error && filteredMatches.length === 0 && (

Noch keine Spiele vorhanden.

Geh auf die Admin-Seite und klicke "Spiele synchronisieren".

)} {!loading && !error && sections.map(section => (
{openSections.has(section.key) && (
{section.matches.map(match => ( setSelectedMatch(match)} /> ))}
)}
))} {selectedMatch && ( setSelectedMatch(null)} onSaved={handleTipSaved} /> )}
); }