139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import { List, ListItem, Divider, Skeleton, Text, Group, Box, ThemeIcon, Stack } from "@mantine/core";
|
|
import { useNavigate } from "@tanstack/react-router";
|
|
import Avatar from "@/components/avatar";
|
|
import { TournamentInfo } from "../types";
|
|
import { useCallback } from "react";
|
|
import React from "react";
|
|
import { TrophyIcon, CalendarIcon, MapPinIcon } from "@phosphor-icons/react";
|
|
|
|
interface TournamentListProps {
|
|
tournaments: TournamentInfo[];
|
|
loading?: boolean;
|
|
}
|
|
|
|
interface TournamentListItemProps {
|
|
tournament: TournamentInfo;
|
|
}
|
|
|
|
const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) => {
|
|
const startDate = tournament.start_time ? new Date(tournament.start_time) : null;
|
|
|
|
return (
|
|
<Group justify="space-between" w="100%">
|
|
<Stack gap={2}>
|
|
<Text fw={500} size="sm">
|
|
{tournament.name}
|
|
</Text>
|
|
<Group gap="md">
|
|
{tournament.location && (
|
|
<Group gap={4}>
|
|
<ThemeIcon size="xs" variant="light" radius="sm" color="gray">
|
|
<MapPinIcon size={10} />
|
|
</ThemeIcon>
|
|
<Text size="xs" c="dimmed">
|
|
{tournament.location}
|
|
</Text>
|
|
</Group>
|
|
)}
|
|
{startDate && !isNaN(startDate.getTime()) && (
|
|
<Group gap={4}>
|
|
<ThemeIcon size="xs" variant="light" radius="sm" color="gray">
|
|
<CalendarIcon size={10} />
|
|
</ThemeIcon>
|
|
<Text size="xs" c="dimmed">
|
|
{startDate.toLocaleDateString(undefined, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric'
|
|
})}
|
|
</Text>
|
|
</Group>
|
|
)}
|
|
</Group>
|
|
</Stack>
|
|
</Group>
|
|
);
|
|
});
|
|
|
|
const TournamentList = ({ tournaments, loading = false }: TournamentListProps) => {
|
|
const navigate = useNavigate();
|
|
|
|
const handleClick = useCallback((tournamentId: string) =>
|
|
navigate({ to: `/tournaments/${tournamentId}` }), [navigate]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<List p="0">
|
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
<ListItem
|
|
key={`skeleton-${i}`}
|
|
py="xs"
|
|
icon={<Skeleton height={40} width={40} radius="sm" />}
|
|
>
|
|
<Stack gap={4}>
|
|
<Skeleton height={16} width={200} />
|
|
<Group gap="md">
|
|
<Skeleton height={12} width={80} />
|
|
<Skeleton height={12} width={100} />
|
|
</Group>
|
|
</Stack>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
);
|
|
}
|
|
|
|
if (!tournaments?.length) {
|
|
return (
|
|
<Box ta="center" py="xl">
|
|
<ThemeIcon size="xl" variant="light" radius="md" mb="md">
|
|
<TrophyIcon size={32} />
|
|
</ThemeIcon>
|
|
<Text c="dimmed" size="lg">
|
|
No tournaments found
|
|
</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<List p="0">
|
|
{tournaments.map((tournament) => (
|
|
<>
|
|
<ListItem
|
|
key={tournament.id}
|
|
p="xs"
|
|
icon={
|
|
<Avatar
|
|
radius="sm"
|
|
size={40}
|
|
name={tournament.name}
|
|
contain
|
|
src={
|
|
tournament.logo
|
|
? `/api/files/tournaments/${tournament.id}/${tournament.logo}`
|
|
: undefined
|
|
}
|
|
>
|
|
<TrophyIcon size={16} />
|
|
</Avatar>
|
|
}
|
|
style={{ cursor: "pointer" }}
|
|
onClick={() => handleClick(tournament.id)}
|
|
styles={{
|
|
itemWrapper: { width: "100%" },
|
|
itemLabel: { width: "100%" }
|
|
}}
|
|
w="100%"
|
|
>
|
|
<TournamentListItem tournament={tournament} />
|
|
</ListItem>
|
|
<Divider />
|
|
</>
|
|
))}
|
|
</List>
|
|
);
|
|
}
|
|
|
|
export default TournamentList;
|