match list

This commit is contained in:
yohlo
2025-09-14 21:59:15 -05:00
parent d11e50d4ef
commit 8efc0a7a4b
9 changed files with 411 additions and 111 deletions

View File

@@ -0,0 +1,155 @@
import {
Text,
Group,
Stack,
Paper,
ThemeIcon,
Indicator,
Box,
Badge,
} from "@mantine/core";
import { TrophyIcon, CrownIcon } from "@phosphor-icons/react";
import { useNavigate } from "@tanstack/react-router";
import { Match } from "../types";
import Avatar from "@/components/avatar";
interface MatchCardProps {
match: Match;
}
const MatchCard = ({ match }: MatchCardProps) => {
const navigate = useNavigate();
const isHomeWin = match.home_cups > match.away_cups;
const isAwayWin = match.away_cups > match.home_cups;
const isStarted = match.status === "started";
const handleHomeTeamClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (match.home?.id) {
navigate({ to: `/teams/${match.home.id}` });
}
};
const handleAwayTeamClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (match.away?.id) {
navigate({ to: `/teams/${match.away.id}` });
}
};
return (
<Indicator
disabled={!isStarted}
size={12}
color="red"
processing
position="top-end"
offset={2}
>
<Paper p="md" withBorder radius="md">
<Stack gap="sm">
<Group gap="xs">
<Text size="xs" fw={600} lineClamp={1} c="dimmed">
{match.tournament.name}
</Text>
<Text c="dimmed">-</Text>
<Text size="xs" c="dimmed">
Round {match.round}
{match.is_losers_bracket && " (Losers)"}
</Text>
</Group>
<Group justify="space-between" align="center">
<Group gap="sm" style={{ flex: 1 }}>
<Box
style={{ position: "relative", cursor: "pointer" }}
onClick={handleHomeTeamClick}
>
<Avatar size={40} name={match.home?.name!} radius="sm" />
{isHomeWin && (
<Box
style={{
position: "absolute",
top: -10,
left: -4,
transform: "rotate(-25deg)",
color: "gold",
}}
>
<CrownIcon size={16} weight="fill" />
</Box>
)}
</Box>
<Text
size="sm"
fw={600}
lineClamp={1}
style={{ minWidth: 0, flex: 1 }}
>
{match.home?.name!}
</Text>
</Group>
<Group gap="sm" align="center">
<Text
size="md"
fw={700}
c={"dimmed"}
>
{match.home_cups}
</Text>
<Text size="md" c="dimmed" fw={500}>
-
</Text>
<Text
size="md"
fw={700}
c={"dimmed"}
>
{match.away_cups}
</Text>
{match.ot_count > 0 && (
<Badge size="xs" color="orange" variant="light">
{match.ot_count}OT
</Badge>
)}
</Group>
<Group gap="sm" style={{ flex: 1 }} justify="flex-end">
<Text
size="sm"
fw={600}
lineClamp={1}
ta="right"
style={{ minWidth: 0, flex: 1 }}
>
{match.away?.name}
</Text>
<Box
style={{ position: "relative", cursor: "pointer" }}
onClick={handleAwayTeamClick}
>
<Avatar size={40} name={match.away?.name!} radius="sm" />
{isAwayWin && (
<Box
style={{
position: "absolute",
top: -10,
right: -4,
transform: "rotate(25deg)",
color: "gold",
}}
>
<CrownIcon size={16} weight="fill" />
</Box>
)}
</Box>
</Group>
</Group>
</Stack>
</Paper>
</Indicator>
);
};
export default MatchCard;

View File

@@ -0,0 +1,48 @@
import { Stack, Text, ThemeIcon, Box } from "@mantine/core";
import { TrophyIcon } from "@phosphor-icons/react";
import { motion, AnimatePresence } from "framer-motion";
import { Match } from "../types";
import MatchCard from "./match-card";
interface MatchListProps {
matches: Match[];
}
const MatchList = ({ matches }: MatchListProps) => {
const filteredMatches = matches?.filter(match =>
match.home && match.away && !match.bye && match.status != "tbd"
) || [];
if (!filteredMatches.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 matches found
</Text>
</Box>
);
}
return (
<Stack gap="sm">
<AnimatePresence>
{filteredMatches.map((match, index) => (
<motion.div
key={`match-${match.id}-${index}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2, delay: index * 0.01 }}
>
<MatchCard match={match} />
</motion.div>
))}
</AnimatePresence>
</Stack>
);
};
export default MatchList;