mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
177 lines
7.4 KiB
TypeScript
177 lines
7.4 KiB
TypeScript
import { Box, Tabs, TabList, TabPanels, Tab, TabPanel, VStack, HStack, Image, Text, Skeleton, Badge } from '@chakra-ui/react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import React from 'react';
|
|
import { facrApi } from '../../services/facr/facrApi';
|
|
import { usePublicSettings } from '../../hooks/usePublicSettings';
|
|
import { getCompetitionAliasesPublic, CompetitionAlias } from '../../services/competitionAliases';
|
|
import { TeamLogo } from '../common/TeamLogo';
|
|
import { sortCategoriesWithOrder } from '../../utils/categorySort';
|
|
import { sanitizeClubName } from '../../utils/url';
|
|
import '../../styles/logos.css';
|
|
|
|
const Row: React.FC<{ d: string; h: string; hid?: string; hl?: string; a: string; aid?: string; al?: string; s?: string; clubName?: string; clubId?: string }> = ({ d, h, hid, hl, a, aid, al, s, clubName, clubId }) => (
|
|
<HStack justify="space-between" borderRadius="lg" p={3} bg="white" boxShadow="sm">
|
|
<Text w="140px" fontSize="sm" color="gray.600">{d}</Text>
|
|
<HStack flex={1} justify="flex-end" spacing={4}>
|
|
<HStack minW="40%" justify="flex-end" spacing={2}>
|
|
<Text noOfLines={1} textAlign="right" flex={1}>{sanitizeClubName(h)}</Text>
|
|
<Box className="logo-container" w="28px" h="28px">
|
|
<TeamLogo
|
|
teamId={hid}
|
|
teamName={h}
|
|
facrLogo={hl}
|
|
size="custom"
|
|
boxSize="28px"
|
|
/>
|
|
</Box>
|
|
</HStack>
|
|
<HStack minW="60px" justify="center" spacing={2}>
|
|
<Text fontWeight="bold" textAlign="center">{s || '-:-'}</Text>
|
|
{(() => {
|
|
if (!s) return null;
|
|
const m = s.match(/^(\d+)\s*[:\-]\s*(\d+)$/);
|
|
if (!m) return null;
|
|
const hG = parseInt(m[1], 10), aG = parseInt(m[2], 10);
|
|
|
|
// First try ID-based matching (most reliable)
|
|
let ourHome = false;
|
|
let ourAway = false;
|
|
if (clubId && hid && aid) {
|
|
ourHome = hid === clubId;
|
|
ourAway = aid === clubId;
|
|
}
|
|
|
|
// Fallback to name matching if IDs not available or no match
|
|
if (!ourHome && !ourAway && clubName) {
|
|
const norm = (x: string) => String(x||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/\s+/g,' ').trim().toLowerCase();
|
|
const strip = (x: string) => norm(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();
|
|
const A = strip(h);
|
|
const B = strip(clubName);
|
|
ourHome = Boolean(A && B && (A===B || A.endsWith(B) || B.endsWith(A)));
|
|
const C = strip(a);
|
|
ourAway = Boolean(C && B && (C===B || C.endsWith(B) || B.endsWith(C)));
|
|
}
|
|
|
|
if (!ourHome && !ourAway) return null;
|
|
if (hG === aG) return <Badge colorScheme="blue" variant="subtle">Remíza</Badge>;
|
|
const our = ourHome ? hG : aG; const opp = ourHome ? aG : hG;
|
|
return our > opp ? <Badge colorScheme="green" variant="subtle">Výhra</Badge> : <Badge colorScheme="red" variant="subtle">Prohra</Badge>;
|
|
})()}
|
|
</HStack>
|
|
<HStack minW="40%" spacing={2}>
|
|
<Box className="logo-container" w="28px" h="28px">
|
|
<TeamLogo
|
|
teamId={aid}
|
|
teamName={a}
|
|
facrLogo={al}
|
|
size="custom"
|
|
boxSize="28px"
|
|
/>
|
|
</Box>
|
|
<Text noOfLines={1} flex={1}>{sanitizeClubName(a)}</Text>
|
|
</HStack>
|
|
</HStack>
|
|
</HStack>
|
|
);
|
|
|
|
const CompetitionMatches: React.FC = () => {
|
|
const { data: settings } = usePublicSettings();
|
|
const clubId = settings?.club_id;
|
|
const clubType = settings?.club_type || 'football';
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['facr-club', clubId, clubType],
|
|
queryFn: () => facrApi.getClub(clubId!, clubType as any),
|
|
enabled: !!clubId,
|
|
});
|
|
// Load competition aliases
|
|
const [aliases, setAliases] = React.useState<Record<string, { alias: string; original_name?: string; display_order?: number }>>({});
|
|
React.useEffect(() => {
|
|
let mounted = true;
|
|
(async () => {
|
|
try {
|
|
const list: CompetitionAlias[] = await getCompetitionAliasesPublic();
|
|
if (!mounted) return;
|
|
const map: Record<string, { alias: string; original_name?: string; display_order?: number }> = {};
|
|
(list || []).forEach((a) => { if (a?.code && a?.alias) map[a.code] = { alias: a.alias, original_name: a.original_name, display_order: a.display_order }; });
|
|
setAliases(map);
|
|
} catch {}
|
|
})();
|
|
return () => { mounted = false; };
|
|
}, []);
|
|
|
|
// Precompute sorted competitions safely (must be before any early returns to keep hooks order stable)
|
|
const competitions = data?.competitions ?? [];
|
|
const sortedCompetitions = React.useMemo(() => {
|
|
const arr = Array.isArray(competitions) ? competitions : [];
|
|
return sortCategoriesWithOrder(
|
|
arr.map(c => ({
|
|
...c,
|
|
name: aliases[c.code]?.alias || aliases[c.id]?.alias || c.name,
|
|
alias: aliases[c.code]?.alias || aliases[c.id]?.alias,
|
|
display_order: (aliases[c.code]?.display_order) ?? (aliases[c.id]?.display_order),
|
|
}))
|
|
);
|
|
}, [competitions, aliases]);
|
|
|
|
if (isLoading) return <Skeleton height="200px" />;
|
|
|
|
if (!clubId) {
|
|
return (
|
|
<Box p={4} bg="yellow.50" borderRadius="md" borderWidth="1px" borderColor="yellow.200">
|
|
<Text color="gray.700">
|
|
Pro zobrazení zápasů je potřeba nastavit klub v administraci (Nastavení → Základní údaje).
|
|
</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!data || !data.competitions || data.competitions.length === 0) {
|
|
return (
|
|
<Box p={4} bg="gray.50" borderRadius="md" borderWidth="1px" borderColor="gray.200">
|
|
<Text color="gray.600">
|
|
Žádné soutěže ani zápasy nejsou k dispozici pro vybraný klub.
|
|
</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// Sort competitions by age (Muži first, then U19, U17, etc.) and respect custom order (computed above)
|
|
|
|
return (
|
|
<Box>
|
|
<Tabs variant="soft-rounded" colorScheme="blue" size="sm">
|
|
<TabList px={2} pt={2} overflowX="auto" overflowY="hidden" css={{
|
|
'&::-webkit-scrollbar': { height: '4px' },
|
|
'&::-webkit-scrollbar-track': { background: 'transparent' },
|
|
'&::-webkit-scrollbar-thumb': { background: 'gray.300', borderRadius: '4px' },
|
|
}}>
|
|
{sortedCompetitions.map((c) => {
|
|
const label = c.alias || c.name;
|
|
return (
|
|
<Tab key={c.id} flex="0 0 auto" px={3} py={2} fontSize="sm">
|
|
<Text as="span" noOfLines={1} maxW="220px" title={label}>{label}</Text>
|
|
</Tab>
|
|
);
|
|
})}
|
|
</TabList>
|
|
<TabPanels>
|
|
{sortedCompetitions.map((c) => (
|
|
<TabPanel key={c.id} px={0}>
|
|
<VStack align="stretch" spacing={3}>
|
|
{(c.matches || []).slice(0, 6).map((m, idx) => (
|
|
<Row key={m.match_id || idx} d={m.date_time} h={m.home} hid={m.home_id} hl={m.home_logo_url} a={m.away} aid={m.away_id} al={m.away_logo_url} s={m.score} clubName={data.name} clubId={data.club_internal_id} />
|
|
))}
|
|
{(c.matches || []).length === 0 && (
|
|
<Text color="gray.500">Žádné zápasy k dispozici.</Text>
|
|
)}
|
|
</VStack>
|
|
</TabPanel>
|
|
))}
|
|
</TabPanels>
|
|
</Tabs>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default CompetitionMatches;
|