player stats in profile

This commit is contained in:
yohlo
2025-09-13 11:21:05 -05:00
parent 7d3c0a3fa4
commit 3fe92be980
7 changed files with 243 additions and 17 deletions

View File

@@ -4,6 +4,7 @@ import { Player } from "@/features/players/types";
import SwipeableTabs from "@/components/swipeable-tabs";
import { usePlayer } from "../../queries";
import TeamList from "@/features/teams/components/team-list";
import StatsOverview from "../stats-overview";
interface ProfileProps {
id: string;
@@ -14,7 +15,7 @@ const Profile = ({ id }: ProfileProps) => {
const tabs = [
{
label: "Overview",
content: <Text p="md">Stats/Badges will go here</Text>,
content: <StatsOverview playerId={id} />,
},
{
label: "Matches",

View File

@@ -0,0 +1,187 @@
import { Box, Grid, Text, Group, Stack, ThemeIcon, Card, Avatar } from "@mantine/core";
import { TrophyIcon, CrownIcon, XIcon, FireIcon, ShieldIcon, ChartLineUpIcon, ShieldCheckIcon, BoxingGloveIcon, Icon } from "@phosphor-icons/react";
import { usePlayerStats } from "../queries";
interface StatsOverviewProps {
playerId: string;
}
const StatCard = ({
label,
value,
suffix = "",
Icon
}: {
label: string;
value: number | null;
suffix?: string;
Icon?: Icon;
}) => (
<Card p='xs'>
<Group justify="space-between" align="center" gap="xs">
<div>
<Text size="xs" c="dimmed" fw={500} tt="uppercase">
{label}
</Text>
<Text size="sm" fw={600}>
{value !== null ? `${value}${suffix}` : "—"}
</Text>
</div>
{Icon && (
<ThemeIcon size="sm" variant="subtle" c="blue">
<Icon size={14} />
</ThemeIcon>
)}
</Group>
</Card>
);
const StatsOverview = ({ playerId }: StatsOverviewProps) => {
const { data: statsData } = usePlayerStats(playerId);
if (!statsData || statsData.length === 0) {
return (
<Box p="sm">
<div style={{ padding: '12px', border: '1px solid var(--mantine-color-gray-3)', borderRadius: '4px', textAlign: 'center' }}>
<Text size="sm" c="dimmed">
No stats available yet
</Text>
</div>
</Box>
);
}
// Aggregate stats across all teams
const overallStats = statsData.reduce(
(acc, stat) => ({
matches: acc.matches + stat.matches,
wins: acc.wins + stat.wins,
losses: acc.losses + stat.losses,
total_cups_made: acc.total_cups_made + stat.total_cups_made,
total_cups_against: acc.total_cups_against + stat.total_cups_against,
}),
{ matches: 0, wins: 0, losses: 0, total_cups_made: 0, total_cups_against: 0 }
);
const winPercentage = overallStats.matches > 0
? ((overallStats.wins / overallStats.matches) * 100)
: 0;
const avgCupsPerMatch = overallStats.matches > 0
? (overallStats.total_cups_made / overallStats.matches)
: 0;
const avgCupsAgainstPerMatch = overallStats.matches > 0
? (overallStats.total_cups_against / overallStats.matches)
: 0;
// Calculate average margins from individual team stats
const validMarginOfVictory = statsData.filter(stat => stat.margin_of_victory > 0);
const validMarginOfLoss = statsData.filter(stat => stat.margin_of_loss > 0);
const avgMarginOfVictory = validMarginOfVictory.length > 0
? (validMarginOfVictory.reduce((acc, stat) => acc + stat.margin_of_victory, 0) / validMarginOfVictory.length)
: 0;
const avgMarginOfLoss = validMarginOfLoss.length > 0
? (validMarginOfLoss.reduce((acc, stat) => acc + stat.margin_of_loss, 0) / validMarginOfLoss.length)
: 0;
return (
<Box p="sm">
<Stack gap="xs">
<Text size="md" fw={600} mb="xs">Stats</Text>
<Grid gutter="xs">
<Grid.Col span={6}>
<StatCard
label="Matches"
value={overallStats.matches}
Icon={BoxingGloveIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Win Rate"
value={parseFloat(winPercentage.toFixed(1))}
suffix="%"
Icon={TrophyIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Wins"
value={overallStats.wins}
Icon={CrownIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Losses"
value={overallStats.losses}
Icon={XIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Cups Made"
value={overallStats.total_cups_made}
Icon={FireIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Cups Against"
value={overallStats.total_cups_against}
Icon={ShieldIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Avg Per Game"
value={parseFloat(avgCupsPerMatch.toFixed(1))}
Icon={ChartLineUpIcon}
/>
</Grid.Col>
<Grid.Col span={6}>
<StatCard
label="Avg Against"
value={parseFloat(avgCupsAgainstPerMatch.toFixed(1))}
Icon={ShieldCheckIcon}
/>
</Grid.Col>
</Grid>
{/* Team Breakdown */}
{statsData.length > 1 && (
<div>
<Text size="md" fw={600} mb="xs">Teams</Text>
<Stack gap={4}>
{statsData.map((stat) => (
<div key={stat.id} style={{ padding: '8px', border: '1px solid var(--mantine-color-gray-3)', borderRadius: '4px' }}>
<Group justify="space-between">
<Group gap="xs">
<Avatar size="xs" color="blue">
{stat.player_name.split(' ').map(n => n[0]).join('')}
</Avatar>
<div>
<Text size="xs" fw={500}>{stat.player_name}</Text>
<Text size="xs" c="dimmed">
{stat.matches}M {stat.wins}W - {stat.losses}L
</Text>
</div>
</Group>
<Text size="xs" fw={600}>
{((stat.wins / stat.matches) * 100).toFixed(0)}%
</Text>
</Group>
</div>
))}
</Stack>
</div>
)}
</Stack>
</Box>
);
};
export default StatsOverview;