feat: sort

This commit is contained in:
TrèsAbhi committed 2024-07-01 20:18:09 -05:00
1 parent 16bee48989
commit 2dcfd13457
5 files changed
+199 -65

No files matched your search

@@ -1,49 +1,62 @@
import { Table } from '@radix-ui/themes';
'use client';
import {
CaretDownIcon,
CaretSortIcon,
CaretUpIcon,
} from '@radix-ui/react-icons';
import { Flex, IconButton, Table } from '@radix-ui/themes';
import {
tankPerformanceSortTypeNames,
tankPerformanceSortTypeNamesArray,
useTankPerformanceSort,
} from '../../../../stores/tankPerformanceSort';
export function Header() {
const sort = useTankPerformanceSort();
return (
<Table.Header style={{ whiteSpace: 'nowrap' }}>
<Table.Row align="end">
<Table.Row align="center">
<Table.ColumnHeaderCell>Tank</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Winrate
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Players (30d)
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Damage
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Survival
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
XP
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Kills
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Spots
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Accuracy
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Shots
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Hits
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Dmg. ratio
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Dmg. taken
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" justify="center" minWidth="0px">
Cap points
</Table.ColumnHeaderCell>
{tankPerformanceSortTypeNamesArray.map((type) => {
const isSelected = sort.type === type;
return (
<Table.ColumnHeaderCell
key={type}
width="0"
justify="center"
minWidth="0px"
>
<Flex align="center" gap="1">
<IconButton
variant={isSelected ? 'soft' : 'ghost'}
color={isSelected ? undefined : 'gray'}
highContrast={!isSelected}
onClick={() => {
if (isSelected) {
useTankPerformanceSort.setState({
direction: -sort.direction as 1 | -1,
});
} else {
useTankPerformanceSort.setState({ type });
}
}}
>
{!isSelected && <CaretSortIcon />}
{isSelected && (
<>
{sort.direction === 1 && <CaretUpIcon />}
{sort.direction === -1 && <CaretDownIcon />}
</>
)}
</IconButton>
{tankPerformanceSortTypeNames[type]}
</Flex>
</Table.ColumnHeaderCell>
);
})}
</Table.Row>
</Table.Header>
);
@@ -13,6 +13,7 @@ export function Info() {
const ratio =
awaitedDiscoveredIdsDefinitions.count /
awaitedAverageDefinitions.scanned_players;
const samples = ratio * awaitedAverageDefinitions.sampled_players;
return (
<Flex justify="center">
@@ -21,12 +22,12 @@ export function Info() {
<InfoCircledIcon />
</Callout.Icon>
<Callout.Text>
Career stats based on{' '}
{numberFormat.format(
Math.round(ratio * awaitedAverageDefinitions.sampled_players),
)}{' '}
Career stats based on {numberFormat.format(Math.round(samples))}{' '}
players with at least 5K career battles and 1 battle in the past 30
days. Updated daily.
days; updated daily.
<br />
May differ from BlitzStars because BlitzKit includes{' '}
{Math.round(samples / 1e4)}x more players.
</Callout.Text>
</Callout.Root>
</Flex>
@@ -28,6 +28,11 @@ export const TankRow = memo<TankRowProps>(
<Table.Cell align="center">
{numberFormat.format(Math.round(ratio * averages.samples))}
</Table.Cell>
<Table.Cell align="center">
{numberFormat.format(
Math.round(ratio * averages.mu.battles * averages.samples),
)}
</Table.Cell>
<Table.Cell align="center">
{Math.round(
averages.mu.damage_dealt / averages.mu.battles,
@@ -49,13 +54,7 @@ export const TankRow = memo<TankRowProps>(
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
</Table.Cell>
<Table.Cell align="center">
{Math.round(
(averages.mu.hits /
averages.mu.battles /
(averages.mu.shots / averages.mu.battles)) *
100,
)}
%
{Math.round((averages.mu.hits / averages.mu.shots) * 100)}%
</Table.Cell>
<Table.Cell align="center">
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
@@ -64,11 +63,7 @@ export const TankRow = memo<TankRowProps>(
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
</Table.Cell>
<Table.Cell align="center">
{(
averages.mu.damage_dealt /
averages.mu.battles /
(averages.mu.damage_received / averages.mu.battles)
).toFixed(2)}
{(averages.mu.damage_dealt / averages.mu.damage_received).toFixed(2)}
</Table.Cell>
<Table.Cell align="center">
{Math.round(
@@ -4,19 +4,103 @@ import { Table } from '@radix-ui/themes';
import { Suspense, use, useMemo } from 'react';
import { averageDefinitionsArray } from '../../../../core/blitzkit/averageDefinitions';
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
import { useTankPerformanceSort } from '../../../../stores/tankPerformanceSort';
import { RowLoader } from './RowLoader';
import { TankRow } from './TankRow';
export function Tanks() {
const awaitedTankDefinitions = use(tankDefinitions);
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
const tankAverages = useMemo(
() =>
awaitedAverageDefinitionsArray.sort(
(a, b) => b.mu.wins / b.mu.battles - a.mu.wins / a.mu.battles,
),
[],
);
const sort = useTankPerformanceSort();
const tankAverages = useMemo(() => {
switch (sort.type) {
case 'accuracy':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots),
);
case 'battles':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.samples * a.mu.battles - b.samples * b.mu.battles),
);
case 'capturePoints':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.capture_points / a.mu.battles -
b.mu.capture_points / b.mu.battles),
);
case 'damage':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_dealt / a.mu.battles -
b.mu.damage_dealt / b.mu.battles),
);
case 'damageRatio':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_dealt / a.mu.damage_received -
b.mu.damage_dealt / b.mu.damage_received),
);
case 'damageTaken':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_received / a.mu.battles -
b.mu.damage_received / b.mu.battles),
);
case 'hits':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles),
);
case 'kills':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles),
);
case 'shots':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles),
);
case 'players':
return awaitedAverageDefinitionsArray.sort(
(a, b) => sort.direction * (a.samples - b.samples),
);
case 'spots':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles),
);
case 'survival':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.survived_battles / a.mu.battles -
b.mu.survived_battles / b.mu.battles),
);
case 'winrate':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles),
);
case 'xp':
return awaitedAverageDefinitionsArray.sort(
(a, b) =>
sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles),
);
}
}, [sort]);
return (
<Table.Body>
+41
View File
@@ -0,0 +1,41 @@
import { produce } from 'immer';
import { create } from 'zustand';
export const tankPerformanceSortTypeNames = {
winrate: 'Winrate',
players: 'Players (30d)',
battles: 'Battles (career)',
damage: 'Damage',
survival: 'Survival',
xp: 'XP',
kills: 'Kills',
spots: 'Spots',
accuracy: 'Accuracy',
shots: 'Shots',
hits: 'Hits',
damageRatio: 'Dmg. ratio',
damageTaken: 'Dmg. taken',
capturePoints: 'Cap points',
} as const;
export const tankPerformanceSortTypeNamesArray = Object.keys(
tankPerformanceSortTypeNames,
) as TankPerformanceSortType[];
type TankPerformanceSortType = keyof typeof tankPerformanceSortTypeNames;
export interface TankPerformanceSort {
type: TankPerformanceSortType;
direction: -1 | 1;
}
export const useTankPerformanceSort = create<TankPerformanceSort>()(() => ({
type: 'battles',
direction: -1,
}));
export function mutateTankPerformanceSort(
recipe: (draft: TankPerformanceSort) => void,
) {
useTankPerformanceSort.setState(produce(recipe));
}