import { type AccountListWithServer, type CompositeStatsKey, type IndividualAccountInfo, type IndividualTankStats, compositeStats, compositeStatsKeys, deltaTankStats, formatCompositeStat, getAccountInfo, getTankStats, idToRegion, searchPlayersAcrossRegions, sumCompositeStats, } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; 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 { PageWrapper } from "../../../components/PageWrapper"; import { StickyRowHeaderCell } from "../../../components/StickyRowHeaderCell"; import { StickyTableRoot } from "../../../components/StickyTableRoot"; import { TankRowHeaderCell } from "../../../components/TankRowHeaderCell"; import { awaitableAverageDefinitions } from "../../../core/awaitables/averageDefinitions"; import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions"; import { type LocaleAcceptorProps, LocaleProvider, useLocale, } from "../../../hooks/useLocale"; import { Session, type SessionTracking } from "../../../stores/session"; const [tankDefinitions, averageDefinitions] = await Promise.all([ awaitableTankDefinitions, awaitableAverageDefinitions, ]); export function Page({ locale }: LocaleAcceptorProps) { return ( ); } function Content() { const { locale, strings } = useLocale(); 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 [tankStatsB, setTankStatsB] = useState( null ); const [accountInfo, setAccountInfo] = useState< IndividualAccountInfo | null | undefined >(undefined); useEffect(() => { (async () => { const id = session.tracking ? session.player.id : undefined; if (!id) return setAccountInfo(null); setAccountInfo( session.tracking ? await getAccountInfo(idToRegion(id), id) : null ); })(); }, [session.tracking && session.player.id]); 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 = averageDefinitions.averages[tank.id]; const composite = compositeStats( { ...entry.all, battle_life_time: entry.battle_life_time }, average?.mu ); return { tank, composite }; }) : undefined, [session.tracking && session.player, tankStatsB] ); const total = useMemo( () => delta ? sumCompositeStats(delta.map(({ composite }) => composite)) : undefined, [delta] ); 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); Session.mutate((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 ( {strings.website.tools.session.no_cc.title} {strings.website.tools.session.no_cc.body} {!session.tracking && ( {strings.website.tools.session.search.lookup} { if (!input.current) return; const sanitized = input.current.value.trim(); if (sanitized.length > 0) { setShowSearch(true); setSearching(true); search(); } else { setShowSearch(false); } }} > {showSearch && ( {searching && ( {strings.website.tools.session.search.searching} )} {!searching && searchResults.length === 0 && ( {strings.website.tools.session.search.no_results} )} {!searching && searchResults.length > 0 && ( {searchResults.map((player) => ( ))} )} )} )} {session.tracking && ( {literals(strings.website.tools.session.controls.title, [ `${accountInfo?.nickname}`, ])} {literals(strings.website.tools.session.controls.subtitle, [ new Date(session.player.since).toLocaleString(locale), ])} )} {session.tracking && delta && delta.length > 0 && ( {strings.website.tools.session.table.tank} {session.columns.map((column, index) => ( {index === 0 && session.columns.length < 4 && ( {compositeStatsKeys .filter((item) => !session.columns.includes(item)) .map((key) => ( { Session.mutate((draft) => { (draft as SessionTracking).columns.unshift( key ); }); }} > {strings.common.composite_stats[key]} ))} )} { Session.mutate((draft) => { if (value === "remove") { (draft as SessionTracking).columns.splice(index, 1); } else { (draft as SessionTracking).columns[index] = value as CompositeStatsKey; } }); }} > {strings.website.tools.session.table.remove} {compositeStatsKeys .filter( (item) => !session.columns.includes(item) || item === column ) .map((key) => ( {strings.common.composite_stats[key]} ))} ))} {strings.website.tools.session.table.total} {session.columns.map((column) => ( {formatCompositeStat(total![column], column, total!)} ))} {delta.map(({ tank, composite }) => { return ( {session.columns.map((column) => ( {formatCompositeStat( composite[column], column, composite )} ))} ); })} )} {session.tracking && delta && delta.length === 0 && ( {strings.website.tools.session.no_battles.title} {strings.website.tools.session.no_battles.hint} )} ); }