This commit is contained in:
Tomáš Dvořák
2025-10-16 13:32:05 +02:00
commit 12cba639b9
663 changed files with 168914 additions and 0 deletions
+661
View File
@@ -0,0 +1,661 @@
import React, { useEffect, useState, useMemo } from 'react';
import { Tabs, TabList, TabPanels, Tab, TabPanel, useColorModeValue } from '@chakra-ui/react';
import MainLayout from '../components/layout/MainLayout';
import { getPublicSettings } from '../services/settings';
import { getCompetitionAliasesPublic, CompetitionAlias } from '../services/competitionAliases';
import { sortCategoriesWithOrder } from '../utils/categorySort';
import { assetUrl } from '../utils/url';
import MatchModal from '../components/home/MatchModal';
import { useCountdown, useMultipleCountdowns } from '../hooks/useCountdown';
import '../styles/theme.css';
type MatchItem = {
id: number | string;
date: string;
time: string;
home: string;
away: string;
home_logo_url?: string;
away_logo_url?: string;
score?: string | null;
facr_link?: string | null;
report_url?: string | null;
venue?: string;
competition?: string;
competitionName?: string;
};
const MatchesPage: React.FC = () => {
const [clubName, setClubName] = useState<string>('');
const [clubId, setClubId] = useState<string>('');
const [clubType, setClubType] = useState<'football' | 'futsal'>('football');
const [facrCompetitions, setFacrCompetitions] = useState<Array<{ name: string; matches: Array<any>; matches_link?: string }>>([]);
const [activeTab, setActiveTab] = useState<number>(0);
const [isMatchModalOpen, setIsMatchModalOpen] = useState<boolean>(false);
const [selectedMatch, setSelectedMatch] = useState<any | null>(null);
const [aliases, setAliases] = useState<CompetitionAlias[]>([]);
const [aliasMap, setAliasMap] = useState<Record<string, { alias: string; original_name?: string; display_order?: number }>>({});
const [sortAscending, setSortAscending] = useState<boolean>(true); // true = oldest first, false = newest first
// Dark mode colors
const bgColor = useColorModeValue('#f8f9fb', '#0f1115');
const cardBg = useColorModeValue('#fff', '#1a1d29');
const borderColor = useColorModeValue('#e5e7eb', '#2a2e3a');
const textPrimary = useColorModeValue('#1a1a1a', '#e8eaf0');
const textSecondary = useColorModeValue('#666', '#9ca3af');
const headingColor = useColorModeValue('#000', '#fff');
const openMatch = (m: any) => {
try {
setSelectedMatch(m);
setIsMatchModalOpen(true);
} catch {
// noop
}
};
// Helper function to truncate long club names
const truncateClubName = (name: string, maxLength: number = 35) => {
if (!name) return name;
if (name.length <= maxLength) return name;
return name.substring(0, maxLength).trim() + '…';
};
// Format date to Czech format
const formatCzechDate = (dateStr: string, timeStr: string) => {
try {
const date = new Date(`${dateStr}T${timeStr}:00`);
return date.toLocaleDateString('cs-CZ', {
day: 'numeric',
month: 'numeric',
year: 'numeric'
});
} catch {
return dateStr;
}
};
// Sentiment helpers for win/draw/loss detection
const isClubTeam = (team: string) => {
const normalize = (s: string) => s.toLowerCase().trim();
const a = normalize(team);
const b = normalize(clubName || '');
return a.includes(b) || b.includes(a);
};
const parseScore = (score?: string | null): { h: number; a: number } | null => {
if (!score) return null;
const m = score.match(/^(\d+)\s*[:\-]\s*(\d+)$/);
if (!m) return null;
return { h: parseInt(m[1], 10), a: parseInt(m[2], 10) };
};
const getSentiment = (m: MatchItem): { label: 'Výhra'|'Remíza'|'Prohra'; colorScheme: 'green'|'blue'|'red' } | null => {
const s = parseScore(m.score);
if (!s) return null;
const ourIsHome = isClubTeam(m.home);
const ourIsAway = isClubTeam(m.away);
if (!ourIsHome && !ourIsAway) return null;
if (s.h === s.a) return { label: 'Remíza', colorScheme: 'blue' };
const ourGoals = ourIsHome ? s.h : s.a;
const oppGoals = ourIsHome ? s.a : s.h;
if (ourGoals > oppGoals) return { label: 'Výhra', colorScheme: 'green' };
return { label: 'Prohra', colorScheme: 'red' };
};
// Get current date for accurate comparisons
const now = useMemo(() => Date.now(), []);
// Get sorted matches based on sort order
const sortedCompetitions = useMemo(() => {
const sorted = facrCompetitions.map(comp => ({
...comp,
matches: [...comp.matches].sort((a: any, b: any) => {
const aTime = new Date(`${a.date}T${(a.time || '00:00')}:00`).getTime();
const bTime = new Date(`${b.date}T${(b.time || '00:00')}:00`).getTime();
return sortAscending ? aTime - bTime : bTime - aTime;
})
}));
// Add "Všechny kategorie" as first tab with all matches combined
if (sorted.length > 0) {
const allMatches = sorted.flatMap(comp =>
comp.matches.map(m => ({ ...m, competitionName: comp.name }))
);
allMatches.sort((a: any, b: any) => {
const aTime = new Date(`${a.date}T${(a.time || '00:00')}:00`).getTime();
const bTime = new Date(`${b.date}T${(b.time || '00:00')}:00`).getTime();
return sortAscending ? aTime - bTime : bTime - aTime;
});
return [
{
name: 'Všechny kategorie',
matches: allMatches,
matches_link: undefined
},
...sorted
];
}
return sorted;
}, [facrCompetitions, sortAscending]);
// Get all upcoming matches for countdown tracking
const upcomingMatches = useMemo(() => {
if (activeTab >= sortedCompetitions.length) return [];
const comp = sortedCompetitions[activeTab];
const currentTime = Date.now();
return (comp?.matches || []).filter((m: any) => {
const matchTime = new Date(`${m.date}T${(m.time || '00:00')}:00`).getTime();
return matchTime > currentTime;
});
}, [sortedCompetitions, activeTab]);
// Track countdowns for all upcoming matches in current tab
const liveCountdowns = useMultipleCountdowns(upcomingMatches, 30000);
useEffect(() => {
let cancelled = false;
const resolveBackendUrl = (path: string) => {
try {
if (/^https?:\/\//i.test(path)) return path;
if (path.startsWith('/cache') || path.startsWith('/uploads') || path.startsWith('/api/')) {
const base = process.env.REACT_APP_API_BASE_URL || process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
const b = new URL(base);
const abs = new URL(path, `${b.protocol}//${b.host}`);
return abs.toString();
}
return path;
} catch {
return path;
}
};
const fetchJSON = async (url: string) => {
try {
const res = await fetch(resolveBackendUrl(url), { cache: 'no-cache' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch {
return null;
}
};
(async () => {
const [
cachedSettingsJSON,
facrClubJSON,
teamLogoOverridesAPI,
teamLogoOverridesFile,
] = await Promise.all([
fetchJSON('/cache/prefetch/settings.json'),
fetchJSON('/cache/prefetch/facr_club_info.json'),
fetchJSON(`/api/v1/public/team-logo-overrides?t=${Date.now()}`),
fetchJSON('/cache/prefetch/team_logo_overrides.json'),
]);
// Load aliases
let aliasesList: CompetitionAlias[] = [];
try {
aliasesList = await getCompetitionAliasesPublic();
} catch {}
const amap: Record<string, { alias: string; original_name?: string; display_order?: number }> = {};
(aliasesList || []).forEach((a) => { if (a?.code && a?.alias) amap[a.code] = { alias: a.alias, original_name: a.original_name, display_order: a.display_order }; });
let liveSettings: any = null;
try {
liveSettings = await getPublicSettings();
} catch {}
if (!cancelled) {
setAliases(aliasesList || []);
setAliasMap(amap);
// Build override helpers
const teamLogoOverridesJSON = (teamLogoOverridesAPI && teamLogoOverridesAPI.by_name) ? teamLogoOverridesAPI : (teamLogoOverridesFile || {});
const byName: Record<string, string> = (teamLogoOverridesJSON?.by_name || {}) as any;
const normalize = (s: string) => String(s)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
const stripPrefixes = (s: string) => {
let x = normalize(s);
x = x.replace(/\b(mestsky|m\.?f\.?k\.?|mfk|tj|sk|sokol|fotbalovy|fotbalový|fotbalovy\s+klub|fotbalovy\s+klub)\b/g, '').replace(/\s+/g, ' ').trim();
return x;
};
const byNameNormalized: Record<string, string> = Object.keys(byName || {}).reduce((acc: Record<string, string>, k: string) => {
acc[normalize(k)] = byName[k];
return acc;
}, {});
const byNameStrippedPairs: Array<{ keyNorm: string; url: string }> = Object.keys(byName || {}).map((k: string) => ({ keyNorm: stripPrefixes(k), url: byName[k] }));
const getFallbackLogo = (teamName?: string, original?: string) => {
if (teamName) {
const exact = (byName || {})[teamName];
const normName = normalize(teamName);
let candidate = exact || byNameNormalized[normName];
if (!candidate) {
const stripped = stripPrefixes(teamName);
for (const { keyNorm, url } of byNameStrippedPairs) {
if (!keyNorm) continue;
if (stripped.endsWith(keyNorm) || keyNorm.endsWith(stripped)) { candidate = url; break; }
}
}
if (candidate) {
if (typeof candidate === 'string' && candidate.startsWith('/')) return resolveBackendUrl(candidate);
return candidate;
}
}
if (original) {
if (typeof original === 'string' && original.startsWith('/')) return resolveBackendUrl(original);
return original;
}
return undefined;
};
const getOverrideLogo = (teamName?: string, teamId?: string, original?: string) => {
if (teamId) {
return `http://logoapi.sportcreative.eu/logos/${teamId}`;
}
return getFallbackLogo(teamName, original);
};
const settingsJSON = liveSettings || cachedSettingsJSON;
if (settingsJSON) {
const name = settingsJSON?.club_name || settingsJSON?.clubName || '';
const id = settingsJSON?.club_id || settingsJSON?.clubId || '';
const type = settingsJSON?.club_type || 'football';
if (name) setClubName(name);
if (id) setClubId(id);
if (type === 'football' || type === 'futsal') setClubType(type);
}
// Load ALL matches from FACR (no time filtering)
if (facrClubJSON?.competitions?.length) {
const comps = (facrClubJSON.competitions || []).map((c: any) => ({
name: (amap?.[c?.code]?.alias) || (amap?.[c?.id]?.alias) || c.name || c.code || 'Soutěž',
alias: (amap?.[c?.code]?.alias) || (amap?.[c?.id]?.alias),
display_order: (amap?.[c?.code]?.display_order) ?? (amap?.[c?.id]?.display_order),
matches_link: c.matches_link,
matches: (Array.isArray(c.matches) ? c.matches : []).map((m: any, idx: number) => {
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 || '18:00').slice(0,5);
// Check if match is in the future - if so, ignore score
const matchTime = new Date(`${isoDate}T${time}:00`).getTime();
const isFutureMatch = matchTime > Date.now();
const actualScore = isFutureMatch ? null : m.score;
return {
id: m.match_id || idx + 1,
date: isoDate,
time,
home: m.home,
away: m.away,
home_logo_url: getOverrideLogo(m.home, m.home_id, m.home_logo_url),
away_logo_url: getOverrideLogo(m.away, m.away_id, m.away_logo_url),
score: actualScore,
facr_link: m.facr_link,
report_url: m.report_url,
venue: m.venue || '',
competition: c.name || c.code || '',
};
})
}));
const sortedComps = sortCategoriesWithOrder(comps) as typeof comps;
setFacrCompetitions(sortedComps);
}
}
})();
return () => {
cancelled = true;
};
}, []);
return (
<MainLayout>
<div style={{ maxWidth: 1200, margin: '0 auto', padding: '24px 16px', background: bgColor, minHeight: '100vh' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24, flexWrap: 'wrap', gap: 16 }}>
<div>
<h1 style={{ fontSize: '2rem', fontWeight: 800, marginBottom: 8, color: headingColor }}>Všechny zápasy</h1>
<p style={{ color: textSecondary }}>{clubName || 'Klub'}</p>
</div>
<button
onClick={() => setSortAscending(!sortAscending)}
style={{
padding: '10px 20px',
background: 'var(--primary-color, #3b82f6)',
color: 'white',
border: 'none',
borderRadius: 8,
fontWeight: 600,
fontSize: '0.9rem',
cursor: 'pointer',
transition: 'all 0.2s',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
display: 'flex',
alignItems: 'center',
gap: 8
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
{sortAscending ? (
<path d="M11 5h4M11 9h7M11 13h10M3 17l3 3m0 0l3-3m-3 3V4" />
) : (
<path d="M11 5h10M11 9h7M11 13h4M3 17l3-3m0 0l3 3m-3-3v16" />
)}
</svg>
{sortAscending ? 'Nejstarší první' : 'Nejnovější první'}
</button>
</div>
{sortedCompetitions.length > 0 ? (
<Tabs
variant="soft-rounded"
colorScheme="blue"
index={activeTab}
onChange={setActiveTab}
>
<TabList
mb={4}
flexWrap="wrap"
gap={2}
justifyContent="flex-start"
sx={{
'& > button': {
minW: 'auto',
px: { base: 3, md: 4 },
py: { base: 1.5, md: 2 },
fontSize: { base: 'xs', md: 'sm' },
fontWeight: 600,
borderRadius: 'md',
transition: 'all 0.2s',
_selected: {
bg: 'brand.primary',
color: 'white',
transform: 'translateY(-2px)',
boxShadow: 'md'
},
_hover: {
transform: 'translateY(-1px)',
boxShadow: 'sm'
}
}
}}
>
{sortedCompetitions.map((c, i) => (
<Tab key={`${c.name}-${i}`}>
{c.name}
</Tab>
))}
</TabList>
<TabPanels>
{sortedCompetitions.map((c, compIdx) => (
<TabPanel key={`panel-${c.name}-${compIdx}`} px={0}>
{c.matches.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px 0', color: textSecondary }}>
Žádné zápasy k zobrazení
</div>
) : (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
gap: 16,
}}
>
{c.matches.map((m: MatchItem, idx: number) => {
const matchTime = new Date(`${m.date}T${(m.time || '00:00')}:00`).getTime();
const currentTime = Date.now();
const isFuture = matchTime > currentTime;
const isPast = matchTime < currentTime;
const hasScore = m.score && m.score.trim() !== '';
const countdown = liveCountdowns[String(m.id)];
return (
<div
key={m.id || idx}
onClick={() => openMatch({ ...m, competitionName: c.name })}
style={{
background: cardBg,
border: `2px solid ${borderColor}`,
borderRadius: 16,
padding: 20,
cursor: 'pointer',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
color: textPrimary,
position: 'relative',
overflow: 'hidden',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-8px)';
e.currentTarget.style.boxShadow = '0 16px 40px rgba(0,0,0,0.12), 0 6px 16px rgba(0,0,0,0.08)';
e.currentTarget.style.borderColor = 'var(--chakra-colors-brand-primary, #3b82f6)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)';
e.currentTarget.style.borderColor = borderColor;
}}
>
<div style={{ fontSize: '0.85rem', color: textSecondary, marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontWeight: 600 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
{formatCzechDate(m.date, m.time || '00:00')}
</span>
<span style={{ background: 'var(--chakra-colors-brand-primary, #3b82f6)', color: 'white', padding: '4px 10px', borderRadius: 8, fontSize: '0.8rem', fontWeight: 700 }}>{m.time}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1, minWidth: 0 }}>
<div style={{ width: 48, height: 48, borderRadius: '50%', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', background: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<img
src={assetUrl(m.home_logo_url) || '/images/club-logo.png'}
alt={m.home}
style={{
width: 40,
height: 40,
objectFit: 'contain',
padding: assetUrl(m.home_logo_url)?.includes('logoapi.sportcreative.eu') ? '4px' : '0px',
boxSizing: 'border-box'
}}
onError={(e) => { (e.target as HTMLImageElement).src = '/images/club-logo.png'; }}
/>
</div>
<span style={{ fontWeight: 700, fontSize: '1rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={m.home}>
{truncateClubName(m.home)}
</span>
</div>
</div>
<div style={{ textAlign: 'center', margin: '20px 0', fontSize: '1.8rem', fontWeight: 'bold' }}>
{isFuture ? (
countdown ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: '0.7rem', color: '#f97316', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.5px' }}>Začátek za</span>
<span style={{ fontSize: '1.4rem', color: '#f97316', fontWeight: 800, fontFamily: 'monospace' }}>{countdown}</span>
</div>
) : (
<span style={{ fontSize: '1.2rem', color: '#3b82f6', fontWeight: 600 }}>vs</span>
)
) : hasScore ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
<span style={{ fontSize: '2rem', fontWeight: 800, background: 'linear-gradient(135deg, #3b82f6, #2563eb)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>{m.score}</span>
</div>
) : (
<span style={{ fontSize: '1.2rem', color: '#9ca3af', fontWeight: 600 }}>:</span>
)}
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1, minWidth: 0 }}>
<div style={{ width: 48, height: 48, borderRadius: '50%', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', background: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<img
src={assetUrl(m.away_logo_url) || '/images/club-opponent.png'}
alt={m.away}
style={{
width: 40,
height: 40,
objectFit: 'contain',
padding: assetUrl(m.away_logo_url)?.includes('logoapi.sportcreative.eu') ? '4px' : '0px',
boxSizing: 'border-box'
}}
onError={(e) => { (e.target as HTMLImageElement).src = '/images/club-opponent.png'; }}
/>
</div>
<span style={{ fontWeight: 700, fontSize: '1rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={m.away}>
{truncateClubName(m.away)}
</span>
</div>
</div>
{m.venue && (
<div style={{ fontSize: '0.85rem', color: textSecondary, marginTop: 12, textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
{m.venue}
</div>
)}
{(m as any).competitionName && activeTab === 0 && (
<div style={{ marginTop: 12, textAlign: 'center' }}>
<span style={{ fontSize: '0.75rem', color: 'white', background: 'var(--chakra-colors-brand-primary, #3b82f6)', padding: '4px 12px', borderRadius: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.5px' }}>
{(m as any).competitionName}
</span>
</div>
)}
{(() => {
const sentiment = hasScore && isPast ? getSentiment(m) : null;
if (sentiment) {
const colors = {
green: { bg: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', shadow: 'rgba(16, 185, 129, 0.3)' },
blue: { bg: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', shadow: 'rgba(59, 130, 246, 0.3)' },
red: { bg: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)', shadow: 'rgba(239, 68, 68, 0.3)' }
};
const color = colors[sentiment.colorScheme];
return (
<div style={{
fontSize: '0.75rem',
background: color.bg,
color: 'white',
marginTop: 12,
padding: '6px 12px',
borderRadius: 8,
textAlign: 'center',
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.5px',
boxShadow: `0 2px 8px ${color.shadow}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 6
}}>
{sentiment.label === 'Výhra' && '🏆'}
{sentiment.label === 'Remíza' && '⚖️'}
{sentiment.label === 'Prohra' && '😔'}
{sentiment.label}
</div>
);
}
if (hasScore && isPast) {
return (
<div style={{
fontSize: '0.75rem',
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
color: 'white',
marginTop: 12,
padding: '6px 12px',
borderRadius: 8,
textAlign: 'center',
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.5px',
boxShadow: '0 2px 8px rgba(16, 185, 129, 0.3)'
}}>
Skončeno
</div>
);
}
if (!hasScore && isPast) {
return (
<div style={{
fontSize: '0.75rem',
background: 'linear-gradient(135deg, #6b7280 0%, #4b5563 100%)',
color: 'white',
marginTop: 12,
padding: '6px 12px',
borderRadius: 8,
textAlign: 'center',
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.5px',
boxShadow: '0 2px 8px rgba(107, 114, 128, 0.3)'
}}>
Odehráno
</div>
);
}
if (!hasScore && isFuture && !countdown) {
return (
<div style={{
fontSize: '0.75rem',
background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
color: 'white',
marginTop: 12,
padding: '6px 12px',
borderRadius: 8,
textAlign: 'center',
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.5px',
boxShadow: '0 2px 8px rgba(59, 130, 246, 0.3)'
}}>
Nadcházející
</div>
);
}
return null;
})()}
</div>
);
})}
</div>
)}
</TabPanel>
))}
</TabPanels>
</Tabs>
) : (
<div style={{ textAlign: 'center', padding: '60px 0', color: textSecondary }}>
<p style={{ fontSize: '1.2rem', marginBottom: 8, color: headingColor }}>Žádné zápasy k zobrazení</p>
<p style={{ fontSize: '0.9rem', color: textSecondary }}>Zkontrolujte nastavení klubu v administraci</p>
</div>
)}
</div>
<MatchModal isOpen={isMatchModalOpen} match={selectedMatch} onClose={() => setIsMatchModalOpen(false)} />
</MainLayout>
);
};
export default MatchesPage;