280 lines
7.8 KiB
TypeScript
280 lines
7.8 KiB
TypeScript
import { Stack, Text, Group, Box, Divider, Paper } from "@mantine/core";
|
|
import { usePlayerHeadToHead } from "@/features/matches/queries";
|
|
import { useMemo, useEffect, useState, Suspense } from "react";
|
|
import { CrownIcon } from "@phosphor-icons/react";
|
|
import MatchList from "@/features/matches/components/match-list";
|
|
import PlayerHeadToHeadSkeleton from "./player-head-to-head-skeleton";
|
|
|
|
interface PlayerHeadToHeadSheetProps {
|
|
player1Id: string;
|
|
player1Name: string;
|
|
player2Id: string;
|
|
player2Name: string;
|
|
isOpen?: boolean;
|
|
}
|
|
|
|
const PlayerHeadToHeadContent = ({
|
|
player1Id,
|
|
player1Name,
|
|
player2Id,
|
|
player2Name,
|
|
isOpen = true,
|
|
}: PlayerHeadToHeadSheetProps) => {
|
|
const [shouldFetch, setShouldFetch] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && !shouldFetch) {
|
|
setShouldFetch(true);
|
|
}
|
|
}, [isOpen, shouldFetch]);
|
|
|
|
const { data: matches, isLoading } = usePlayerHeadToHead(player1Id, player2Id, shouldFetch);
|
|
|
|
const stats = useMemo(() => {
|
|
if (!matches || matches.length === 0) {
|
|
return {
|
|
player1Wins: 0,
|
|
player2Wins: 0,
|
|
player1CupsFor: 0,
|
|
player2CupsFor: 0,
|
|
player1CupsAgainst: 0,
|
|
player2CupsAgainst: 0,
|
|
player1AvgMargin: 0,
|
|
player2AvgMargin: 0,
|
|
};
|
|
}
|
|
|
|
let player1Wins = 0;
|
|
let player2Wins = 0;
|
|
let player1CupsFor = 0;
|
|
let player2CupsFor = 0;
|
|
let player1CupsAgainst = 0;
|
|
let player2CupsAgainst = 0;
|
|
let player1TotalWinMargin = 0;
|
|
let player2TotalWinMargin = 0;
|
|
|
|
matches.forEach((match) => {
|
|
const isPlayer1Home = match.home?.players?.some((p) => p.id === player1Id);
|
|
const player1Cups = isPlayer1Home ? match.home_cups : match.away_cups;
|
|
const player2Cups = isPlayer1Home ? match.away_cups : match.home_cups;
|
|
|
|
if (player1Cups > player2Cups) {
|
|
player1Wins++;
|
|
player1TotalWinMargin += (player1Cups - player2Cups);
|
|
} else if (player2Cups > player1Cups) {
|
|
player2Wins++;
|
|
player2TotalWinMargin += (player2Cups - player1Cups);
|
|
}
|
|
|
|
player1CupsFor += player1Cups;
|
|
player2CupsFor += player2Cups;
|
|
player1CupsAgainst += player2Cups;
|
|
player2CupsAgainst += player1Cups;
|
|
});
|
|
|
|
const player1AvgMargin =
|
|
player1Wins > 0 ? player1TotalWinMargin / player1Wins : 0;
|
|
const player2AvgMargin =
|
|
player2Wins > 0 ? player2TotalWinMargin / player2Wins : 0;
|
|
|
|
return {
|
|
player1Wins,
|
|
player2Wins,
|
|
player1CupsFor,
|
|
player2CupsFor,
|
|
player1CupsAgainst,
|
|
player2CupsAgainst,
|
|
player1AvgMargin,
|
|
player2AvgMargin,
|
|
};
|
|
}, [matches, player1Id]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Stack p="md" gap="md">
|
|
<Text size="sm" c="dimmed" ta="center">
|
|
Loading...
|
|
</Text>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
if (!matches || matches.length === 0) {
|
|
return (
|
|
<Stack p="md" gap="md">
|
|
<Text size="sm" c="dimmed" ta="center">
|
|
These players have not faced each other yet.
|
|
</Text>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
const totalMatches = stats.player1Wins + stats.player2Wins;
|
|
const leader =
|
|
stats.player1Wins > stats.player2Wins
|
|
? player1Name
|
|
: stats.player2Wins > stats.player1Wins
|
|
? player2Name
|
|
: null;
|
|
|
|
return (
|
|
<Stack gap="md">
|
|
<Paper p="md" withBorder radius="md">
|
|
<Stack gap="sm">
|
|
<Group justify="center" gap="xs">
|
|
<Text size="lg" fw={700}>
|
|
{player1Name}
|
|
</Text>
|
|
<Text size="sm" c="dimmed">
|
|
vs
|
|
</Text>
|
|
<Text size="lg" fw={700}>
|
|
{player2Name}
|
|
</Text>
|
|
</Group>
|
|
|
|
<Group justify="center" gap="lg">
|
|
<Stack gap={0} align="center">
|
|
<Text size="xl" fw={700}>
|
|
{stats.player1Wins}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{player1Name}
|
|
</Text>
|
|
</Stack>
|
|
<Text size="md" c="dimmed">
|
|
-
|
|
</Text>
|
|
<Stack gap={0} align="center">
|
|
<Text size="xl" fw={700}>
|
|
{stats.player2Wins}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{player2Name}
|
|
</Text>
|
|
</Stack>
|
|
</Group>
|
|
|
|
{leader && (
|
|
<Group justify="center" gap="xs">
|
|
<CrownIcon size={16} weight="fill" color="gold" />
|
|
<Text size="xs" c="dimmed">
|
|
{leader} leads the series
|
|
</Text>
|
|
</Group>
|
|
)}
|
|
|
|
{!leader && totalMatches > 0 && (
|
|
<Text size="xs" c="dimmed" ta="center">
|
|
Series is tied
|
|
</Text>
|
|
)}
|
|
</Stack>
|
|
</Paper>
|
|
|
|
<Stack gap={0}>
|
|
<Text size="sm" fw={600} px="md" mb="xs">
|
|
Stats Comparison
|
|
</Text>
|
|
|
|
<Paper withBorder>
|
|
<Stack gap={0}>
|
|
<Group justify="space-between" px="md" py="sm">
|
|
<Group gap="xs">
|
|
<Text size="sm" fw={600}>
|
|
{stats.player1CupsFor}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
cups
|
|
</Text>
|
|
</Group>
|
|
<Text size="xs" fw={500}>
|
|
Total Cups
|
|
</Text>
|
|
<Group gap="xs">
|
|
<Text size="xs" c="dimmed">
|
|
cups
|
|
</Text>
|
|
<Text size="sm" fw={600}>
|
|
{stats.player2CupsFor}
|
|
</Text>
|
|
</Group>
|
|
</Group>
|
|
<Divider />
|
|
|
|
<Group justify="space-between" px="md" py="sm">
|
|
<Group gap="xs">
|
|
<Text size="sm" fw={600}>
|
|
{totalMatches > 0
|
|
? (stats.player1CupsFor / totalMatches).toFixed(1)
|
|
: "0.0"}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
avg
|
|
</Text>
|
|
</Group>
|
|
<Text size="xs" fw={500}>
|
|
Avg Cups/Match
|
|
</Text>
|
|
<Group gap="xs">
|
|
<Text size="xs" c="dimmed">
|
|
avg
|
|
</Text>
|
|
<Text size="sm" fw={600}>
|
|
{totalMatches > 0
|
|
? (stats.player2CupsFor / totalMatches).toFixed(1)
|
|
: "0.0"}
|
|
</Text>
|
|
</Group>
|
|
</Group>
|
|
<Divider />
|
|
|
|
<Group justify="space-between" px="md" py="sm">
|
|
<Group gap="xs">
|
|
<Text size="sm" fw={600}>
|
|
{!isNaN(stats.player1AvgMargin)
|
|
? stats.player1AvgMargin.toFixed(1)
|
|
: "0.0"}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
margin
|
|
</Text>
|
|
</Group>
|
|
<Text size="xs" fw={500}>
|
|
Avg Win Margin
|
|
</Text>
|
|
<Group gap="xs">
|
|
<Text size="xs" c="dimmed">
|
|
margin
|
|
</Text>
|
|
<Text size="sm" fw={600}>
|
|
{!isNaN(stats.player2AvgMargin)
|
|
? stats.player2AvgMargin.toFixed(1)
|
|
: "0.0"}
|
|
</Text>
|
|
</Group>
|
|
</Group>
|
|
</Stack>
|
|
</Paper>
|
|
</Stack>
|
|
|
|
<Stack gap="xs">
|
|
<Text size="sm" fw={600} px="md">
|
|
Match History ({totalMatches})
|
|
</Text>
|
|
<MatchList matches={matches} hideH2H />
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
const PlayerHeadToHeadSheet = (props: PlayerHeadToHeadSheetProps) => {
|
|
return (
|
|
<Suspense fallback={<PlayerHeadToHeadSkeleton />}>
|
|
<PlayerHeadToHeadContent {...props} />
|
|
</Suspense>
|
|
);
|
|
};
|
|
|
|
export default PlayerHeadToHeadSheet;
|