mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-05 03:02:56 +00:00
upload
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
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';
|
||||
|
||||
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 apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/api/v1';
|
||||
const origin = new URL(apiUrl).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;
|
||||
Reference in New Issue
Block a user