match list
This commit is contained in:
155
src/features/matches/components/match-card.tsx
Normal file
155
src/features/matches/components/match-card.tsx
Normal 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;
|
||||
48
src/features/matches/components/match-list.tsx
Normal file
48
src/features/matches/components/match-list.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user