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}
)}
);
}