mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { Heading, Text, Box, Spinner, Alert, AlertIcon, Table, Thead, Tbody, Tr, Th, Td, VStack, Select, HStack, Badge } from '@chakra-ui/react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useMemo, useState } from 'react';
|
|
import AdminLayout from '../../layouts/AdminLayout';
|
|
import { assetUrl } from '../../utils/url';
|
|
import { TeamLogo } from '../../components/common/TeamLogo';
|
|
import { API_URL } from '../../services/api';
|
|
|
|
type TableRow = {
|
|
rank?: string;
|
|
team?: string;
|
|
team_id?: string;
|
|
team_logo_url?: string;
|
|
played?: string;
|
|
wins?: string;
|
|
draws?: string;
|
|
losses?: string;
|
|
score?: string;
|
|
points?: string;
|
|
};
|
|
|
|
const StandingsAdminPage: React.FC = () => {
|
|
const { data, isLoading, error } = useQuery<any>({
|
|
queryKey: ['facr-tables-cache'],
|
|
queryFn: async () => {
|
|
const origin = new URL(API_URL, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000').origin;
|
|
const url = `${origin}/cache/prefetch/facr_tables.json`;
|
|
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
|
|
if (!res.ok) throw new Error(`Failed to load cache: ${res.status}`);
|
|
return res.json();
|
|
},
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
|
|
const competitions: any[] = Array.isArray(data?.competitions) ? data!.competitions : [];
|
|
|
|
// Optional category/code switcher based on competition code
|
|
const [code, setCode] = useState<string>('');
|
|
const options = useMemo(() => {
|
|
const items = competitions.map((c) => ({ code: c.code, name: c.name }));
|
|
const unique = new Map(items.map((i) => [i.code, i]));
|
|
return Array.from(unique.values());
|
|
}, [competitions]);
|
|
const filtered = code ? competitions.filter((c) => c.code === code) : competitions;
|
|
|
|
return (
|
|
<AdminLayout>
|
|
<Heading size="lg" mb={2}>Standings (FAČR)</Heading>
|
|
<Text mb={4}>Read-only view of standings from FACR cache. Fast and offline-friendly.</Text>
|
|
|
|
{isLoading && (
|
|
<VStack align="start" spacing={3} mb={6}>
|
|
<Spinner />
|
|
<Text>Načítám tabulky…</Text>
|
|
</VStack>
|
|
)}
|
|
|
|
{Boolean(error) && (
|
|
<Alert status="error" variant="left-accent" mb={4}>
|
|
<AlertIcon />
|
|
Nepodařilo se načíst data z cache.
|
|
</Alert>
|
|
)}
|
|
|
|
{!isLoading && !error && (
|
|
<HStack mb={4} spacing={3} align="center">
|
|
<Text color="gray.600">Soutěž:</Text>
|
|
<Select value={code} onChange={(e) => setCode(e.target.value)} maxW="260px" size="sm">
|
|
<option value="">Vše</option>
|
|
{options.map((o) => (
|
|
<option key={o.code} value={o.code}>{o.code} — {o.name}</option>
|
|
))}
|
|
</Select>
|
|
<Badge colorScheme="gray" variant="subtle">{filtered.length} soutěží</Badge>
|
|
</HStack>
|
|
)}
|
|
|
|
{!isLoading && !error && filtered.map((comp) => {
|
|
const rows: TableRow[] = comp?.table?.overall || [];
|
|
return (
|
|
<Box key={comp.id} mb={8}>
|
|
<Heading size="md" mb={3}>{comp.name}</Heading>
|
|
<Box
|
|
overflowX="auto"
|
|
borderWidth="1px"
|
|
borderRadius="md"
|
|
w="full"
|
|
maxW="100%"
|
|
sx={{
|
|
WebkitOverflowScrolling: 'touch',
|
|
'th, td': { whiteSpace: 'nowrap' },
|
|
}}
|
|
>
|
|
<Table size="sm" sx={{ width: 'max-content' }}>
|
|
<Thead>
|
|
<Tr>
|
|
<Th>#</Th>
|
|
<Th>Tým</Th>
|
|
<Th isNumeric>Z</Th>
|
|
<Th isNumeric>V</Th>
|
|
<Th isNumeric>R</Th>
|
|
<Th isNumeric>P</Th>
|
|
<Th isNumeric>Skóre</Th>
|
|
<Th isNumeric>Body</Th>
|
|
</Tr>
|
|
</Thead>
|
|
<Tbody>
|
|
{rows.map((r, idx) => (
|
|
<Tr key={`${comp.id}-${idx}`}>
|
|
<Td width="40px">{r.rank}</Td>
|
|
<Td>
|
|
<HStack spacing={2} align="center">
|
|
<TeamLogo
|
|
teamId={r.team_id}
|
|
teamName={r.team}
|
|
facrLogo={r.team_logo_url}
|
|
size="small"
|
|
alt={r.team}
|
|
objectFit="contain"
|
|
fallbackIcon={
|
|
<Box
|
|
w="20px"
|
|
h="20px"
|
|
bg="gray.200"
|
|
borderRadius="md"
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
color="gray.400"
|
|
fontSize="xs"
|
|
fontWeight="bold"
|
|
>
|
|
{r.team?.substring(0, 2).toUpperCase() || '??'}
|
|
</Box>
|
|
}
|
|
/>
|
|
<Text as="span">{r.team}</Text>
|
|
</HStack>
|
|
</Td>
|
|
<Td isNumeric>{r.played}</Td>
|
|
<Td isNumeric>{r.wins}</Td>
|
|
<Td isNumeric>{r.draws}</Td>
|
|
<Td isNumeric>{r.losses}</Td>
|
|
<Td isNumeric>{r.score}</Td>
|
|
<Td isNumeric fontWeight="bold">{r.points}</Td>
|
|
</Tr>
|
|
))}
|
|
</Tbody>
|
|
</Table>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
})}
|
|
</AdminLayout>
|
|
);
|
|
};
|
|
|
|
export default StandingsAdminPage;
|