import { useState, useMemo } from "react";
import {
Text,
TextInput,
Stack,
Group,
Box,
ThemeIcon,
Container,
Title,
Divider,
UnstyledButton,
Popover,
ActionIcon,
Skeleton,
} from "@mantine/core";
import {
MagnifyingGlassIcon,
CaretUpIcon,
CaretDownIcon,
ChartBarIcon,
InfoIcon,
} from "@phosphor-icons/react";
import { PlayerStats } from "../types";
import Avatar from "@/components/avatar";
import { useNavigate } from "@tanstack/react-router";
interface PlayerStatsTableProps {
playerStats: PlayerStats[];
}
type SortKey = keyof PlayerStats | "mmr";
type SortDirection = "asc" | "desc";
interface SortConfig {
key: SortKey;
direction: SortDirection;
}
interface PlayerListItemProps {
stat: PlayerStats;
index: number;
onPlayerClick: (playerId: string) => void;
}
const PlayerListItem = ({ stat, index, onPlayerClick }: PlayerListItemProps) => {
const calculateMMR = (stat: PlayerStats): number => {
if (stat.matches === 0) return 0;
const winScore = stat.win_percentage;
const matchConfidence = Math.min(stat.matches / 15, 1);
const avgCupsScore = Math.min(stat.avg_cups_per_match * 10, 100);
const marginScore = stat.margin_of_victory
? Math.min(stat.margin_of_victory * 20, 50)
: 0;
const volumeBonus = Math.min(stat.matches * 0.5, 10);
const baseMMR =
winScore * 0.5 +
avgCupsScore * 0.25 +
marginScore * 0.15 +
volumeBonus * 0.1;
const finalMMR = baseMMR * matchConfidence;
return Math.round(finalMMR * 10) / 10;
};
const mmr = calculateMMR(stat);
return (
<>
onPlayerClick(stat.id)}
style={{
borderRadius: 0,
transition: "background-color 0.15s ease",
}}
styles={{
root: {
'&:hover': {
backgroundColor: 'var(--mantine-color-gray-0)',
},
},
}}
>
{stat.player_name}
{stat.matches} matches
{stat.tournaments} tournaments
MMR
{mmr.toFixed(1)}
W
{stat.wins}
L
{stat.losses}
W%
{stat.win_percentage.toFixed(1)}%
AVG
{stat.avg_cups_per_match.toFixed(1)}
CF
{stat.total_cups_made}
CA
{stat.total_cups_against}
>
);
};
const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
const navigate = useNavigate();
const [search, setSearch] = useState("");
const [sortConfig, setSortConfig] = useState({
key: "mmr" as SortKey,
direction: "desc",
});
const calculateMMR = (stat: PlayerStats): number => {
if (stat.matches === 0) return 0;
const winScore = stat.win_percentage;
const matchConfidence = Math.min(stat.matches / 15, 1);
const avgCupsScore = Math.min(stat.avg_cups_per_match * 10, 100);
const marginScore = stat.margin_of_victory
? Math.min(stat.margin_of_victory * 20, 50)
: 0;
const volumeBonus = Math.min(stat.matches * 0.5, 10);
const baseMMR =
winScore * 0.5 +
avgCupsScore * 0.25 +
marginScore * 0.15 +
volumeBonus * 0.1;
const finalMMR = baseMMR * matchConfidence;
return Math.round(finalMMR * 10) / 10;
};
const filteredAndSortedStats = useMemo(() => {
let filtered = playerStats.filter((stat) =>
stat.player_name.toLowerCase().includes(search.toLowerCase())
);
return filtered.sort((a, b) => {
let aValue: number | string;
let bValue: number | string;
if (sortConfig.key === "mmr") {
aValue = calculateMMR(a);
bValue = calculateMMR(b);
} else {
aValue = a[sortConfig.key];
bValue = b[sortConfig.key];
}
if (typeof aValue === "number" && typeof bValue === "number") {
return sortConfig.direction === "desc"
? bValue - aValue
: aValue - bValue;
}
if (typeof aValue === "string" && typeof bValue === "string") {
return sortConfig.direction === "desc"
? bValue.localeCompare(aValue)
: aValue.localeCompare(bValue);
}
return 0;
});
}, [playerStats, search, sortConfig]);
const handlePlayerClick = (playerId: string) => {
navigate({ to: `/profile/${playerId}` });
};
const handleSort = (key: SortKey) => {
setSortConfig((prev) => ({
key,
direction: prev.key === key && prev.direction === "desc" ? "asc" : "desc",
}));
};
const getSortIcon = (key: SortKey) => {
if (sortConfig.key !== key) return null;
return sortConfig.direction === "desc" ? (
) : (
);
};
if (playerStats.length === 0) {
return (
No Stats Available
);
}
return (
setSearch(e.currentTarget.value)}
leftSection={}
size="md"
px="md"
/>
{filteredAndSortedStats.length} of {playerStats.length} players
Sort:
handleSort("mmr")}
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
MMR
{getSortIcon("mmr")}
•
handleSort("wins")}
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
Wins
{getSortIcon("wins")}
•
handleSort("matches")}
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
Matches
{getSortIcon("matches")}
MMR Calculation:
• Win Rate (50%)
• Average Cups/Match (25%)
• Average Win Margin (15%)
• Match Volume Bonus (10%)
* Confidence penalty applied for players with <15 matches
** Not an official rating
{filteredAndSortedStats.map((stat, index) => (
{index < filteredAndSortedStats.length - 1 && }
))}
{filteredAndSortedStats.length === 0 && search && (
No players found matching "{search}"
)}
);
};
export default PlayerStatsTable;