Files
flxn-app/src/features/tournaments/components/tournament-list.tsx
2025-09-26 12:55:04 -05:00

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;