From 6a7d119d3e6fb8742eaeb678ce22fabcb6a7dcea Mon Sep 17 00:00:00 2001 From: yohlo Date: Sat, 11 Oct 2025 00:29:29 -0500 Subject: [PATCH] more stats --- .../player-stats-table-skeleton.tsx | 60 +++-- .../players/components/player-stats-table.tsx | 206 ++++++++++-------- 2 files changed, 151 insertions(+), 115 deletions(-) diff --git a/src/features/players/components/player-stats-table-skeleton.tsx b/src/features/players/components/player-stats-table-skeleton.tsx index 013c768..b341608 100644 --- a/src/features/players/components/player-stats-table-skeleton.tsx +++ b/src/features/players/components/player-stats-table-skeleton.tsx @@ -5,52 +5,66 @@ import { Container, Divider, Skeleton, + ScrollArea, } from "@mantine/core"; const PlayerListItemSkeleton = () => { return ( - - - - - - - - - - - + + + + + + + + + + + + - + - + - + - + - + + + + + + + + + + + + + - + - - + + ); @@ -60,13 +74,13 @@ const PlayerStatsTableSkeleton = () => { return ( - + + - - - + + diff --git a/src/features/players/components/player-stats-table.tsx b/src/features/players/components/player-stats-table.tsx index d1b48d6..8f47063 100644 --- a/src/features/players/components/player-stats-table.tsx +++ b/src/features/players/components/player-stats-table.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback, memo, use } from "react"; +import { useState, useMemo, useCallback, memo, useRef, useEffect } from "react"; import { Text, TextInput, @@ -12,6 +12,7 @@ import { UnstyledButton, Popover, ActionIcon, + ScrollArea, } from "@mantine/core"; import { MagnifyingGlassIcon, @@ -37,9 +38,41 @@ interface PlayerListItemProps { stat: PlayerStats; onPlayerClick: (playerId: string) => void; mmr: number; + onRegisterViewport: (viewport: HTMLDivElement) => void; + onUnregisterViewport: (viewport: HTMLDivElement) => void; } -const PlayerListItem = memo(({ stat, onPlayerClick, mmr }: PlayerListItemProps) => { +interface StatCellProps { + label: string; + value: string | number; +} + +const StatCell = memo(({ label, value }: StatCellProps) => ( + + + {label} + + + {value} + + +)); + +const PlayerListItem = memo(({ stat, onPlayerClick, mmr, onRegisterViewport, onUnregisterViewport }: PlayerListItemProps) => { + const viewportRef = useRef(null); + + const avg_cups_against = useMemo(() => stat.total_cups_against / stat.matches || 0, [stat.total_cups_against, stat.matches]); + + useEffect(() => { + if (viewportRef.current) { + onRegisterViewport(viewportRef.current); + return () => { + if (viewportRef.current) { + onUnregisterViewport(viewportRef.current); + } + }; + } + }, [onRegisterViewport, onUnregisterViewport]); return ( <> @@ -59,92 +92,41 @@ const PlayerListItem = memo(({ stat, onPlayerClick, mmr }: PlayerListItemProps) }, }} > - - - - - - - {stat.player_name} - - - {stat.matches} - M - - - {stat.tournaments} - T - + + + + + + {stat.player_name} + + + {stat.matches} + M + + + {stat.tournaments} + T + + + + + + + + + + + + + + + - - - - MMR - - - {mmr.toFixed(1)} - - - - - W - - - {stat.wins} - - - - - L - - - {stat.losses} - - - - - W% - - - {stat.win_percentage.toFixed(1)}% - - - - - AWM - - - {stat.margin_of_victory?.toFixed(1) || 0} - - - - - AC - - - {stat.avg_cups_per_match.toFixed(1)} - - - - - CF - - - {stat.total_cups_made} - - - - - CA - - - {stat.total_cups_against} - - - - - - + + @@ -160,6 +142,37 @@ const PlayerStatsTable = () => { direction: "desc", }); + const viewportsRef = useRef>(new Set()); + const isScrollingRef = useRef(false); + + const handleRegisterViewport = useCallback((viewport: HTMLDivElement) => { + viewportsRef.current.add(viewport); + + const handleScroll = (e: Event) => { + if (isScrollingRef.current) return; + + isScrollingRef.current = true; + const scrollLeft = (e.target as HTMLDivElement).scrollLeft; + + viewportsRef.current.forEach((vp) => { + if (vp !== e.target) { + vp.scrollLeft = scrollLeft; + } + }); + + requestAnimationFrame(() => { + isScrollingRef.current = false; + }); + }; + + viewport.addEventListener('scroll', handleScroll); + viewport.dataset.scrollHandler = 'attached'; + }, []); + + const handleUnregisterViewport = useCallback((viewport: HTMLDivElement) => { + viewportsRef.current.delete(viewport); + }, []); + const calculateMMR = (stat: PlayerStats): number => { if (stat.matches === 0) return 0; @@ -259,6 +272,9 @@ const PlayerStatsTable = () => { return ( + + Showing {filteredAndSortedStats.length} of {playerStats.length} players + { /> - - {filteredAndSortedStats.length} of {playerStats.length} players - - - Sort: + +
+ Sort: handleSort("mmr")} style={{ display: "flex", alignItems: "center", gap: 4 }} @@ -335,9 +349,15 @@ const PlayerStatsTable = () => { AWM: Average Win Margin + + • ALM: Average Loss Margin + AC: Average Cups Per Match + + • ACA: Average Cups Against + CF: Cups For @@ -381,6 +401,8 @@ const PlayerStatsTable = () => { stat={stat} onPlayerClick={handlePlayerClick} mmr={stat.mmr} + onRegisterViewport={handleRegisterViewport} + onUnregisterViewport={handleUnregisterViewport} /> {index < filteredAndSortedStats.length - 1 && }