infinite scroll
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useCallback, memo, useRef, useEffect } from "react";
|
||||
import { useState, useMemo, useCallback, memo, useRef, useEffect, useDeferredValue } from "react";
|
||||
import {
|
||||
Text,
|
||||
TextInput,
|
||||
@@ -23,8 +23,10 @@ import {
|
||||
} from "@phosphor-icons/react";
|
||||
import { PlayerStats } from "../types";
|
||||
import PlayerAvatar from "@/components/player-avatar";
|
||||
import InfiniteScroll from "@/components/infinite-scroll";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useAllPlayerStats } from "../queries";
|
||||
import { PlayerListItemSkeleton } from "./player-stats-table-skeleton";
|
||||
|
||||
type SortKey = keyof PlayerStats | "mmr";
|
||||
type SortDirection = "asc" | "desc";
|
||||
@@ -146,10 +148,32 @@ interface PlayerStatsTableProps {
|
||||
viewType?: 'all' | 'mainline' | 'regional';
|
||||
}
|
||||
|
||||
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 PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
const { data: playerStats } = useAllPlayerStats(viewType);
|
||||
const navigate = useNavigate();
|
||||
const [search, setSearch] = useState("");
|
||||
const deferredSearch = useDeferredValue(search);
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||
key: "mmr" as SortKey,
|
||||
direction: "desc",
|
||||
@@ -159,10 +183,15 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
const scrollHandlersRef = useRef<Map<HTMLDivElement, (e: Event) => void>>(new Map());
|
||||
const scrollLeaderRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollTimeoutRef = useRef<number | null>(null);
|
||||
const lastScrollLeftRef = useRef(0);
|
||||
|
||||
const handleRegisterViewport = useCallback((viewport: HTMLDivElement) => {
|
||||
viewportsRef.current.add(viewport);
|
||||
|
||||
if (lastScrollLeftRef.current > 0) {
|
||||
viewport.scrollLeft = lastScrollLeftRef.current;
|
||||
}
|
||||
|
||||
const handleScrollStart = () => {
|
||||
scrollLeaderRef.current = viewport;
|
||||
};
|
||||
@@ -179,6 +208,7 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
}
|
||||
|
||||
const scrollLeft = target.scrollLeft;
|
||||
lastScrollLeftRef.current = scrollLeft;
|
||||
|
||||
viewportsRef.current.forEach((vp) => {
|
||||
if (vp !== target && Math.abs(vp.scrollLeft - scrollLeft) > 0.5) {
|
||||
@@ -213,27 +243,6 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
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 statsWithMMR = useMemo(() => {
|
||||
return playerStats.map((stat) => ({
|
||||
...stat,
|
||||
@@ -243,7 +252,7 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
|
||||
const filteredAndSortedStats = useMemo(() => {
|
||||
let filtered = statsWithMMR.filter((stat) =>
|
||||
stat.player_name.toLowerCase().includes(search.toLowerCase())
|
||||
stat.player_name.toLowerCase().includes(deferredSearch.toLowerCase())
|
||||
);
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
@@ -272,7 +281,7 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
|
||||
return 0;
|
||||
});
|
||||
}, [statsWithMMR, search, sortConfig]);
|
||||
}, [statsWithMMR, deferredSearch, sortConfig]);
|
||||
|
||||
const handlePlayerClick = useCallback((playerId: string) => {
|
||||
navigate({ to: `/profile/${playerId}` });
|
||||
@@ -433,18 +442,30 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
|
||||
</Group>
|
||||
|
||||
<Stack gap={0}>
|
||||
{filteredAndSortedStats.map((stat, index) => (
|
||||
<Box key={stat.id}>
|
||||
<PlayerListItem
|
||||
stat={stat}
|
||||
onPlayerClick={handlePlayerClick}
|
||||
mmr={stat.mmr}
|
||||
onRegisterViewport={handleRegisterViewport}
|
||||
onUnregisterViewport={handleUnregisterViewport}
|
||||
/>
|
||||
{index < filteredAndSortedStats.length - 1 && <Divider />}
|
||||
</Box>
|
||||
))}
|
||||
<InfiniteScroll
|
||||
items={filteredAndSortedStats}
|
||||
batchSize={25}
|
||||
renderItem={(stat, index) => (
|
||||
<Box key={stat.id}>
|
||||
{index > 0 && <Divider />}
|
||||
<PlayerListItem
|
||||
stat={stat}
|
||||
onPlayerClick={handlePlayerClick}
|
||||
mmr={stat.mmr}
|
||||
onRegisterViewport={handleRegisterViewport}
|
||||
onUnregisterViewport={handleUnregisterViewport}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
loader={
|
||||
<>
|
||||
<Divider />
|
||||
<PlayerListItemSkeleton />
|
||||
<Divider />
|
||||
<PlayerListItemSkeleton />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{filteredAndSortedStats.length === 0 && search && (
|
||||
|
||||
Reference in New Issue
Block a user