import React, { useEffect, useMemo, useState } from 'react'; import MainLayout from '../components/layout/MainLayout'; import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Container, Flex, Heading, Image, Link, Spinner, Stack, Text, useToast } from '@chakra-ui/react'; import { Link as RouterLink, useParams } from 'react-router-dom'; import { usePublicSettings } from '../hooks/usePublicSettings'; import { useQuery } from '@tanstack/react-query'; import { getCompetitionAliasesPublic } from '../services/competitionAliases'; import SponsorsSection from '../components/common/SponsorsSection'; import NewsletterCTA from '../components/common/NewsletterCTA'; import { API_URL } from '../services/api'; interface MatchItem { id: string | number; date: string; time: string; home: string; away: string; venue?: string; home_logo_url?: string; away_logo_url?: string; report_url?: string; facr_link?: string; score?: string; } const resolveBackendUrl = (path: string) => { try { if (/^https?:\/\//i.test(path)) return path; if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) { const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin; return new URL(path, origin).toString(); } return path; } catch { return path; } }; const MatchDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const toast = useToast(); const { data: settings } = usePublicSettings(); const [loading, setLoading] = useState(true); const [match, setMatch] = useState(null); const [competitionName, setCompetitionName] = useState(""); const [competitionId, setCompetitionId] = useState(""); const [competitionCode, setCompetitionCode] = useState(""); const [prevMatch, setPrevMatch] = useState(null); const [nextMatch, setNextMatch] = useState(null); useEffect(() => { let cancelled = false; (async () => { if (!id) return; setLoading(true); try { // Try main prefetch file first const res = await fetch(resolveBackendUrl('/cache/prefetch/facr_club_info.json'), { cache: 'no-cache' }); let found: MatchItem | null = null; let neighborSource: MatchItem[] = []; if (res.ok) { const json = await res.json(); for (const c of (json?.competitions || [])) { const listForNeighbors: MatchItem[] = []; for (const m of (c?.matches || [])) { const mid = String(m.match_id || ''); if (mid && mid === String(id)) { const dt: string = String(m.date_time || ''); const [d, t] = dt.includes(' ') ? dt.split(' ') : [dt, '']; const [day, month, year] = d.split('.'); const isoDate = (day && month && year) ? `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}` : new Date().toISOString().slice(0,10); const time = (t || '00:00').slice(0,5); const score = (m.score || m.result || (typeof m.goals_home === 'number' && typeof m.goals_away === 'number' ? `${m.goals_home}:${m.goals_away}` : '') || '').toString(); found = { id: mid, date: isoDate, time, home: m.home, away: m.away, venue: m.venue, home_logo_url: m.home_logo_url, away_logo_url: m.away_logo_url, report_url: m.report_url, facr_link: m.facr_link, score: score && /\d+\s*:\s*\d+/.test(score) ? score.replace(/\s+/g,'') : undefined, }; setCompetitionName(String(c?.name || c?.code || '')); if (c?.id != null) setCompetitionId(String(c.id)); if (c?.code) setCompetitionCode(String(c.code)); break; } // also push into neighbor list when iterating const dt2: string = String(m.date_time || ''); const [d2, t2] = dt2.includes(' ') ? dt2.split(' ') : [dt2, '']; const [day2, month2, year2] = d2.split('.'); const iso2 = (day2 && month2 && year2) ? `${year2}-${month2.padStart(2,'0')}-${day2.padStart(2,'0')}` : new Date().toISOString().slice(0,10); const tshort = (t2 || '00:00').slice(0,5); listForNeighbors.push({ id: m.match_id || '', date: iso2, time: tshort, home: m.home, away: m.away } as MatchItem); } if (found) break; neighborSource = listForNeighbors; } } // Fallback to club-specific info file if (!found && settings?.club_id) { const clubType = settings.club_type || 'football'; const infoPath = `/cache/facr/${clubType}_${settings.club_id}_info.json`; const res2 = await fetch(resolveBackendUrl(infoPath), { cache: 'no-cache' }); if (res2.ok) { const j2 = await res2.json(); for (const c of (j2?.competitions || [])) { const listForNeighbors: MatchItem[] = []; for (const m of (c?.matches || [])) { const mid = String(m.match_id || ''); if (mid && mid === String(id)) { const dt: string = String(m.date_time || ''); const [d, t] = dt.includes(' ') ? dt.split(' ') : [dt, '']; const [day, month, year] = d.split('.'); const isoDate = (day && month && year) ? `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}` : new Date().toISOString().slice(0,10); const time = (t || '00:00').slice(0,5); const score = (m.score || m.result || (typeof m.goals_home === 'number' && typeof m.goals_away === 'number' ? `${m.goals_home}:${m.goals_away}` : '') || '').toString(); found = { id: mid, date: isoDate, time, home: m.home, away: m.away, venue: m.venue, home_logo_url: m.home_logo_url, away_logo_url: m.away_logo_url, report_url: m.report_url, facr_link: m.facr_link, score: score && /\d+\s*:\s*\d+/.test(score) ? score.replace(/\s+/g,'') : undefined, }; setCompetitionName(String(c?.name || c?.code || '')); if (c?.id != null) setCompetitionId(String(c.id)); if (c?.code) setCompetitionCode(String(c.code)); break; } // collect neighbor list const dt2: string = String(m.date_time || ''); const [d2, t2] = dt2.includes(' ') ? dt2.split(' ') : [dt2, '']; const [day2, month2, year2] = d2.split('.'); const iso2 = (day2 && month2 && year2) ? `${year2}-${month2.padStart(2,'0')}-${day2.padStart(2,'0')}` : new Date().toISOString().slice(0,10); const tshort = (t2 || '00:00').slice(0,5); listForNeighbors.push({ id: m.match_id || '', date: iso2, time: tshort, home: m.home, away: m.away } as MatchItem); } if (found) break; neighborSource = listForNeighbors; } } } // compute neighbors if (found && neighborSource.length) { const sorted = neighborSource.sort((a, b) => new Date(`${a.date}T${a.time}:00`).getTime() - new Date(`${b.date}T${b.time}:00`).getTime()); const idx = sorted.findIndex(x => String(x.id) === String(found!.id)); setPrevMatch(idx > 0 ? sorted[idx - 1] : null); setNextMatch(idx >= 0 && idx < sorted.length - 1 ? sorted[idx + 1] : null); } else { setPrevMatch(null); setNextMatch(null); } if (!cancelled) setMatch(found); } catch (e: any) { if (!cancelled) toast({ title: 'Chyba', description: e?.message || 'Nelze načíst zápas', status: 'error' }); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [id, settings, toast]); const isPast = useMemo(() => { if (!match) return false; return new Date(`${match.date}T${(match.time||'00:00')}:00`).getTime() < Date.now(); }, [match]); // Load aliases and resolve display name for competition const { data: aliases } = useQuery({ queryKey: ['public-competition-aliases'], queryFn: getCompetitionAliasesPublic, }); const compDisplay = useMemo(() => { if (!competitionCode) return competitionName; const hit = (aliases || []).find(a => a.code === competitionCode); return hit?.alias || competitionName; }, [aliases, competitionCode, competitionName]); return ( Domů Kalendář {compDisplay && ( {compDisplay} )} {match && ( {match.home} vs {match.away} )} {loading && ( Načítám… )} {!loading && !match && ( Zápas nebyl nalezen. )} {match && ( {match.home} vs {match.away} {match.home_logo_url && ( {match.home} )} {isPast && match.score ? match.score : 'vs'} {match.away_logo_url && ( {match.away} )} {match.date} {match.time} {match.venue ? `• ${match.venue}` : ''} {(prevMatch || nextMatch) && ( {prevMatch && ( )} {nextMatch && ( )} )} {(match.facr_link || match.report_url) && ( {match.facr_link && ( )} {match.report_url && ( )} )} )} {/* Newsletter CTA */} {/* Sponsors Section */} ); }; export default MatchDetailPage;