import { STAT_KEYS, STAT_NAMES, UNLOCALIZED_REGION_NAMES_SHORT, deltaTankStats, fetchAverageDefinitions, fetchTankDefinitions, generateStats, getAccountInfo, getTankStats, idToRegion, prettifyStats, searchPlayersAcrossRegions, sumBlitzStarsStats, type AccountListWithServer, type IndividualAccountInfo, type IndividualTankStats, type Stat, } from '@blitzkit/core'; import { ArrowDownIcon, MagnifyingGlassIcon, PlusIcon, } from '@radix-ui/react-icons'; import { AlertDialog, Button, DropdownMenu, Flex, Heading, IconButton, Select, Table, Text, TextField, } from '@radix-ui/themes'; import { debounce } from 'lodash-es'; import { useEffect, useMemo, useRef, useState } from 'react'; import usePromise from 'react-promise-suspense'; import { PageWrapper } from '../../../components/PageWrapper'; import { TankRowHeaderCell } from '../../../components/TankRowHeaderCell'; import { Session, type SessionTracking } from '../../../stores/session'; const tankDefinitions = await fetchTankDefinitions(); const tankAverages = await fetchAverageDefinitions(); export function Page() { return ( ); } function Content() { const [showSearch, setShowSearch] = useState(false); const [searching, setSearching] = useState(false); const [searchResults, setSearchResults] = useState([]); const [showCCInaccessibilityPrompt, setShowCCInaccessibilityPrompt] = useState(false); const input = useRef(null); const session = Session.use(); const mutateSession = Session.useMutation(); const [tankStatsB, setTankStatsB] = useState( null, ); const accountInfo = usePromise( (id?: number) => new Promise(async (resolve) => { if (!id) return resolve(null); resolve( session.tracking ? await getAccountInfo(idToRegion(id), id) : null, ); }), [session.tracking ? session.player.id : undefined], ); const delta = useMemo( () => session.tracking && tankStatsB ? deltaTankStats(session.player.stats, tankStatsB) .sort((a, b) => b.last_battle_time - a.last_battle_time) .map((entry) => { const tank = tankDefinitions.tanks[entry.tank_id]; const average = tankAverages.averages[tank.id]; const stats = generateStats(entry.all, average.mu); const statsPretty = prettifyStats(stats); return { tank, stats, statsPretty, }; }) : undefined, [session.tracking && session.player, tankStatsB], ); const total = useMemo( () => delta ? prettifyStats(sumBlitzStarsStats(delta.map(({ stats }) => stats))) : undefined, [delta], ); const [showLinkOverridePrompt, setShowLinkOverridePrompt] = useState(true); const search = debounce(async () => { if (!input.current) return; setSearchResults(await searchPlayersAcrossRegions(input.current.value, 15)); setSearching(false); }, 500); async function track(id: number) { const region = idToRegion(id); const player = await getAccountInfo(region, id); const stats = await getTankStats(region, id); if (stats === null) { setShowCCInaccessibilityPrompt(true); return; } setShowSearch(false); mutateSession((draft) => { if (!input.current) return; draft.tracking = true; (draft as SessionTracking).player = { id, region, since: Date.now(), stats, }; input.current.value = player.nickname; }); } useEffect(() => { async function update() { if (!session.tracking) return; const tankStats = await getTankStats( idToRegion(session.player.id), session.player.id, ); setTankStatsB(tankStats); } update(); const interval = setInterval(update, 5 * 1000); return () => clearInterval(interval); }, [session.tracking && session.player.id]); return ( You're attempting to track a CC account CC (community contributor) accounts do not have public tank statistics. We are in the process of adding embeds for CC accounts, stay tuned. {!session.tracking && ( Look up a player to get started { if (!input.current) return; const sanitized = input.current.value.trim(); if (sanitized.length > 0) { setShowSearch(true); setSearching(true); search(); } else { setShowSearch(false); } }} > {showSearch && ( {searching && ( Searching... )} {!searching && searchResults.length === 0 && ( No players found )} {!searching && searchResults.length > 0 && ( {searchResults.map((player) => ( ))} )} )} )} {session.tracking && ( Tracking {accountInfo?.nickname} Since {new Date(session.player.since).toLocaleString()} )} {session.tracking && delta && delta.length > 0 && ( Tank {session.columns.map((column, index) => ( {index === 0 && session.columns.length < 4 && ( {STAT_KEYS.filter( (item) => !session.columns.includes(item), ).map((key) => ( { mutateSession((draft) => { (draft as SessionTracking).columns.unshift( key as Stat, ); }); }} > {STAT_NAMES[key]} ))} )} { mutateSession((draft) => { if (value === 'remove') { (draft as SessionTracking).columns.splice(index, 1); } else { (draft as SessionTracking).columns[index] = value as Stat; } }); }} > {STAT_KEYS.filter( (item) => !session.columns.includes(item) || item === column, ).map((key) => ( {STAT_NAMES[key]} ))} Remove ))} Total {session.columns.map((column) => ( {total![column]} ))} {delta.map(({ statsPretty, tank }) => { return ( {session.columns.map((column) => ( {statsPretty[column]} ))} ); })} )} {session.tracking && delta && delta.length === 0 && ( No battles Go ahead and play a game )} ); }