stats table
This commit is contained in:
@@ -16,6 +16,7 @@ import { Route as LogoutRouteImport } from './routes/logout'
|
|||||||
import { Route as LoginRouteImport } from './routes/login'
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
import { Route as AuthedRouteImport } from './routes/_authed'
|
import { Route as AuthedRouteImport } from './routes/_authed'
|
||||||
import { Route as AuthedIndexRouteImport } from './routes/_authed/index'
|
import { Route as AuthedIndexRouteImport } from './routes/_authed/index'
|
||||||
|
import { Route as AuthedStatsRouteImport } from './routes/_authed/stats'
|
||||||
import { Route as AuthedSettingsRouteImport } from './routes/_authed/settings'
|
import { Route as AuthedSettingsRouteImport } from './routes/_authed/settings'
|
||||||
import { Route as AuthedAdminRouteImport } from './routes/_authed/admin'
|
import { Route as AuthedAdminRouteImport } from './routes/_authed/admin'
|
||||||
import { Route as AuthedTournamentsIndexRouteImport } from './routes/_authed/tournaments/index'
|
import { Route as AuthedTournamentsIndexRouteImport } from './routes/_authed/tournaments/index'
|
||||||
@@ -63,6 +64,11 @@ const AuthedIndexRoute = AuthedIndexRouteImport.update({
|
|||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => AuthedRoute,
|
getParentRoute: () => AuthedRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthedStatsRoute = AuthedStatsRouteImport.update({
|
||||||
|
id: '/stats',
|
||||||
|
path: '/stats',
|
||||||
|
getParentRoute: () => AuthedRoute,
|
||||||
|
} as any)
|
||||||
const AuthedSettingsRoute = AuthedSettingsRouteImport.update({
|
const AuthedSettingsRoute = AuthedSettingsRouteImport.update({
|
||||||
id: '/settings',
|
id: '/settings',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
@@ -178,6 +184,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/refresh-session': typeof RefreshSessionRoute
|
'/refresh-session': typeof RefreshSessionRoute
|
||||||
'/admin': typeof AuthedAdminRouteWithChildren
|
'/admin': typeof AuthedAdminRouteWithChildren
|
||||||
'/settings': typeof AuthedSettingsRoute
|
'/settings': typeof AuthedSettingsRoute
|
||||||
|
'/stats': typeof AuthedStatsRoute
|
||||||
'/': typeof AuthedIndexRoute
|
'/': typeof AuthedIndexRoute
|
||||||
'/admin/preview': typeof AuthedAdminPreviewRoute
|
'/admin/preview': typeof AuthedAdminPreviewRoute
|
||||||
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
||||||
@@ -194,6 +201,7 @@ export interface FileRoutesByTo {
|
|||||||
'/logout': typeof LogoutRoute
|
'/logout': typeof LogoutRoute
|
||||||
'/refresh-session': typeof RefreshSessionRoute
|
'/refresh-session': typeof RefreshSessionRoute
|
||||||
'/settings': typeof AuthedSettingsRoute
|
'/settings': typeof AuthedSettingsRoute
|
||||||
|
'/stats': typeof AuthedStatsRoute
|
||||||
'/': typeof AuthedIndexRoute
|
'/': typeof AuthedIndexRoute
|
||||||
'/admin/preview': typeof AuthedAdminPreviewRoute
|
'/admin/preview': typeof AuthedAdminPreviewRoute
|
||||||
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
'/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
||||||
@@ -213,6 +221,7 @@ export interface FileRoutesById {
|
|||||||
'/refresh-session': typeof RefreshSessionRoute
|
'/refresh-session': typeof RefreshSessionRoute
|
||||||
'/_authed/admin': typeof AuthedAdminRouteWithChildren
|
'/_authed/admin': typeof AuthedAdminRouteWithChildren
|
||||||
'/_authed/settings': typeof AuthedSettingsRoute
|
'/_authed/settings': typeof AuthedSettingsRoute
|
||||||
|
'/_authed/stats': typeof AuthedStatsRoute
|
||||||
'/_authed/': typeof AuthedIndexRoute
|
'/_authed/': typeof AuthedIndexRoute
|
||||||
'/_authed/admin/preview': typeof AuthedAdminPreviewRoute
|
'/_authed/admin/preview': typeof AuthedAdminPreviewRoute
|
||||||
'/_authed/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
'/_authed/profile/$playerId': typeof AuthedProfilePlayerIdRoute
|
||||||
@@ -232,6 +241,7 @@ export interface FileRouteTypes {
|
|||||||
| '/refresh-session'
|
| '/refresh-session'
|
||||||
| '/admin'
|
| '/admin'
|
||||||
| '/settings'
|
| '/settings'
|
||||||
|
| '/stats'
|
||||||
| '/'
|
| '/'
|
||||||
| '/admin/preview'
|
| '/admin/preview'
|
||||||
| '/profile/$playerId'
|
| '/profile/$playerId'
|
||||||
@@ -248,6 +258,7 @@ export interface FileRouteTypes {
|
|||||||
| '/logout'
|
| '/logout'
|
||||||
| '/refresh-session'
|
| '/refresh-session'
|
||||||
| '/settings'
|
| '/settings'
|
||||||
|
| '/stats'
|
||||||
| '/'
|
| '/'
|
||||||
| '/admin/preview'
|
| '/admin/preview'
|
||||||
| '/profile/$playerId'
|
| '/profile/$playerId'
|
||||||
@@ -266,6 +277,7 @@ export interface FileRouteTypes {
|
|||||||
| '/refresh-session'
|
| '/refresh-session'
|
||||||
| '/_authed/admin'
|
| '/_authed/admin'
|
||||||
| '/_authed/settings'
|
| '/_authed/settings'
|
||||||
|
| '/_authed/stats'
|
||||||
| '/_authed/'
|
| '/_authed/'
|
||||||
| '/_authed/admin/preview'
|
| '/_authed/admin/preview'
|
||||||
| '/_authed/profile/$playerId'
|
| '/_authed/profile/$playerId'
|
||||||
@@ -403,6 +415,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthedIndexRouteImport
|
preLoaderRoute: typeof AuthedIndexRouteImport
|
||||||
parentRoute: typeof AuthedRoute
|
parentRoute: typeof AuthedRoute
|
||||||
}
|
}
|
||||||
|
'/_authed/stats': {
|
||||||
|
id: '/_authed/stats'
|
||||||
|
path: '/stats'
|
||||||
|
fullPath: '/stats'
|
||||||
|
preLoaderRoute: typeof AuthedStatsRouteImport
|
||||||
|
parentRoute: typeof AuthedRoute
|
||||||
|
}
|
||||||
'/_authed/settings': {
|
'/_authed/settings': {
|
||||||
id: '/_authed/settings'
|
id: '/_authed/settings'
|
||||||
path: '/settings'
|
path: '/settings'
|
||||||
@@ -573,6 +592,7 @@ const AuthedAdminRouteWithChildren = AuthedAdminRoute._addFileChildren(
|
|||||||
interface AuthedRouteChildren {
|
interface AuthedRouteChildren {
|
||||||
AuthedAdminRoute: typeof AuthedAdminRouteWithChildren
|
AuthedAdminRoute: typeof AuthedAdminRouteWithChildren
|
||||||
AuthedSettingsRoute: typeof AuthedSettingsRoute
|
AuthedSettingsRoute: typeof AuthedSettingsRoute
|
||||||
|
AuthedStatsRoute: typeof AuthedStatsRoute
|
||||||
AuthedIndexRoute: typeof AuthedIndexRoute
|
AuthedIndexRoute: typeof AuthedIndexRoute
|
||||||
AuthedProfilePlayerIdRoute: typeof AuthedProfilePlayerIdRoute
|
AuthedProfilePlayerIdRoute: typeof AuthedProfilePlayerIdRoute
|
||||||
AuthedTeamsTeamIdRoute: typeof AuthedTeamsTeamIdRoute
|
AuthedTeamsTeamIdRoute: typeof AuthedTeamsTeamIdRoute
|
||||||
@@ -583,6 +603,7 @@ interface AuthedRouteChildren {
|
|||||||
const AuthedRouteChildren: AuthedRouteChildren = {
|
const AuthedRouteChildren: AuthedRouteChildren = {
|
||||||
AuthedAdminRoute: AuthedAdminRouteWithChildren,
|
AuthedAdminRoute: AuthedAdminRouteWithChildren,
|
||||||
AuthedSettingsRoute: AuthedSettingsRoute,
|
AuthedSettingsRoute: AuthedSettingsRoute,
|
||||||
|
AuthedStatsRoute: AuthedStatsRoute,
|
||||||
AuthedIndexRoute: AuthedIndexRoute,
|
AuthedIndexRoute: AuthedIndexRoute,
|
||||||
AuthedProfilePlayerIdRoute: AuthedProfilePlayerIdRoute,
|
AuthedProfilePlayerIdRoute: AuthedProfilePlayerIdRoute,
|
||||||
AuthedTeamsTeamIdRoute: AuthedTeamsTeamIdRoute,
|
AuthedTeamsTeamIdRoute: AuthedTeamsTeamIdRoute,
|
||||||
|
|||||||
25
src/app/routes/_authed/stats.tsx
Normal file
25
src/app/routes/_authed/stats.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { playerQueries, useAllPlayerStats } from "@/features/players/queries";
|
||||||
|
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||||
|
import PlayerStatsTable from "@/features/players/components/player-stats-table";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authed/stats")({
|
||||||
|
component: Stats,
|
||||||
|
beforeLoad: async ({ context }) => {
|
||||||
|
const queryClient = context.queryClient;
|
||||||
|
await ensureServerQueryData(queryClient, playerQueries.allStats());
|
||||||
|
},
|
||||||
|
loader: () => ({
|
||||||
|
withPadding: true,
|
||||||
|
fullWidth: true,
|
||||||
|
header: {
|
||||||
|
title: "Player Stats"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function Stats() {
|
||||||
|
const { data: playerStats } = useAllPlayerStats();
|
||||||
|
|
||||||
|
return <PlayerStatsTable playerStats={playerStats} />;
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ export const useLinks = (userId: string | undefined, roles: string[]) =>
|
|||||||
Icon: HouseIcon
|
Icon: HouseIcon
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Leaderboard',
|
label: 'Statistics',
|
||||||
href: '/leaderboard',
|
href: '/stats',
|
||||||
Icon: RankingIcon
|
Icon: RankingIcon
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
256
src/features/players/components/player-stats-table.tsx
Normal file
256
src/features/players/components/player-stats-table.tsx
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
Box,
|
||||||
|
ThemeIcon,
|
||||||
|
Container,
|
||||||
|
Title,
|
||||||
|
ScrollArea,
|
||||||
|
Paper,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import {
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
CaretUpIcon,
|
||||||
|
CaretDownIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
import { PlayerStats } from "../types";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
interface PlayerStatsTableProps {
|
||||||
|
playerStats: PlayerStats[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortKey = keyof PlayerStats;
|
||||||
|
type SortDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
|
interface SortConfig {
|
||||||
|
key: SortKey;
|
||||||
|
direction: SortDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||||
|
key: 'win_percentage',
|
||||||
|
direction: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSort = (key: SortKey) => {
|
||||||
|
setSortConfig(prev => ({
|
||||||
|
key,
|
||||||
|
direction: prev.key === key && prev.direction === 'desc' ? 'asc' : 'desc'
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSortIcon = (key: SortKey) => {
|
||||||
|
if (sortConfig.key !== key) return null;
|
||||||
|
return sortConfig.direction === 'desc' ? <CaretDownIcon size={14} /> : <CaretUpIcon size={14} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredAndSortedStats = useMemo(() => {
|
||||||
|
let filtered = playerStats.filter(stat =>
|
||||||
|
stat.player_name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return filtered.sort((a, b) => {
|
||||||
|
const aValue = a[sortConfig.key];
|
||||||
|
const bValue = b[sortConfig.key];
|
||||||
|
|
||||||
|
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||||
|
return sortConfig.direction === 'desc' ? bValue - aValue : aValue - bValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
||||||
|
return sortConfig.direction === 'desc'
|
||||||
|
? bValue.localeCompare(aValue)
|
||||||
|
: aValue.localeCompare(bValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}, [playerStats, search, sortConfig]);
|
||||||
|
|
||||||
|
const formatPercentage = (value: number) => `${value.toFixed(1)}%`;
|
||||||
|
const formatDecimal = (value: number) => value.toFixed(2);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'player_name' as SortKey, label: 'Player', width: 200 },
|
||||||
|
{ key: 'win_percentage' as SortKey, label: 'Win %', width: 110 },
|
||||||
|
{ key: 'matches' as SortKey, label: 'Matches', width: 90 },
|
||||||
|
{ key: 'wins' as SortKey, label: 'Wins', width: 80 },
|
||||||
|
{ key: 'losses' as SortKey, label: 'Losses', width: 80 },
|
||||||
|
{ key: 'total_cups_made' as SortKey, label: 'Cups Made', width: 110 },
|
||||||
|
{ key: 'total_cups_against' as SortKey, label: 'Cups Against', width: 120 },
|
||||||
|
{ key: 'avg_cups_per_match' as SortKey, label: 'Avg/Match', width: 100 },
|
||||||
|
{ key: 'margin_of_victory' as SortKey, label: 'Win Margin', width: 110 },
|
||||||
|
{ key: 'margin_of_loss' as SortKey, label: 'Loss Margin', width: 110 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderCellContent = (stat: PlayerStats, column: typeof columns[0], index: number) => {
|
||||||
|
switch (column.key) {
|
||||||
|
case 'player_name':
|
||||||
|
return (
|
||||||
|
<Group gap="sm">
|
||||||
|
<Text size="xs" c="dimmed" fw={500}>#{index + 1}</Text>
|
||||||
|
<Text fw={600}>{stat.player_name}</Text>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
case 'win_percentage':
|
||||||
|
return (
|
||||||
|
<Text size='sm' c={stat.win_percentage >= 70 ? "green" : stat.win_percentage >= 50 ? "yellow" : "red"}>
|
||||||
|
{formatPercentage(stat.win_percentage)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
case 'wins':
|
||||||
|
return <Text c="green" fw={500}>{stat.wins}</Text>;
|
||||||
|
case 'losses':
|
||||||
|
return <Text c="red" fw={500}>{stat.losses}</Text>;
|
||||||
|
case 'total_cups_made':
|
||||||
|
return <Text fw={500}>{stat.total_cups_made}</Text>;
|
||||||
|
case 'matches':
|
||||||
|
return <Text fw={500}>{stat.matches}</Text>;
|
||||||
|
case 'avg_cups_per_match':
|
||||||
|
return <Text>{formatDecimal(stat.avg_cups_per_match)}</Text>;
|
||||||
|
case 'margin_of_victory':
|
||||||
|
return <Text>{stat.margin_of_victory ? formatDecimal(stat.margin_of_victory) : 'N/A'}</Text>;
|
||||||
|
case 'margin_of_loss':
|
||||||
|
return <Text>{stat.margin_of_loss ? formatDecimal(stat.margin_of_loss) : 'N/A'}</Text>;
|
||||||
|
default:
|
||||||
|
return <Text>{(stat as any)[column.key]}</Text>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (playerStats.length === 0) {
|
||||||
|
return (
|
||||||
|
<Container size="md">
|
||||||
|
<Stack align="center" gap="md" py="xl">
|
||||||
|
<ThemeIcon size="xl" variant="light" radius="md">
|
||||||
|
<ChartBarIcon size={32} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Title order={3} c="dimmed">No Stats Available</Title>
|
||||||
|
<Text c="dimmed" ta="center">
|
||||||
|
Player statistics will appear here once matches have been played.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container size="100%" px={0}>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Group justify="space-between" align="flex-end" wrap="wrap">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={2}>Player Statistics</Title>
|
||||||
|
<Text c="dimmed">
|
||||||
|
{filteredAndSortedStats.length} of {playerStats.length} players
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search players..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
leftSection={<MagnifyingGlassIcon size={16} />}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Paper withBorder radius="md" p={0} style={{ overflow: 'hidden' }}>
|
||||||
|
<ScrollArea>
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
striped
|
||||||
|
withTableBorder={false}
|
||||||
|
style={{
|
||||||
|
minWidth: 1000,
|
||||||
|
borderRadius: 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table.Thead
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table.Tr>
|
||||||
|
{columns.map((column, index) => (
|
||||||
|
<Table.Th
|
||||||
|
key={column.key}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
width: column.width,
|
||||||
|
minWidth: column.width,
|
||||||
|
padding: '12px 16px',
|
||||||
|
fontWeight: 600,
|
||||||
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
|
borderBottom: '2px solid var(--mantine-color-default-border)',
|
||||||
|
...(index === 0 && {
|
||||||
|
borderTopLeftRadius: 'var(--mantine-radius-md)',
|
||||||
|
}),
|
||||||
|
...(index === columns.length - 1 && {
|
||||||
|
borderTopRightRadius: 'var(--mantine-radius-md)',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
onClick={() => handleSort(column.key)}
|
||||||
|
>
|
||||||
|
<Group gap="xs" wrap="nowrap">
|
||||||
|
<Text size="sm" fw={600}>
|
||||||
|
{column.label}
|
||||||
|
</Text>
|
||||||
|
<Box style={{ minWidth: 16, display: 'flex', justifyContent: 'center' }}>
|
||||||
|
{getSortIcon(column.key)}
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{filteredAndSortedStats.map((stat, index) => (
|
||||||
|
<motion.tr
|
||||||
|
key={stat.id}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.2, delay: index * 0.01 }}
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<Table.Td
|
||||||
|
key={`${stat.id}-${column.key}`}
|
||||||
|
style={{
|
||||||
|
padding: '12px 16px',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderCellContent(stat, column, index)}
|
||||||
|
</Table.Td>
|
||||||
|
))}
|
||||||
|
</motion.tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{filteredAndSortedStats.length === 0 && search && (
|
||||||
|
<Text ta="center" c="dimmed" py="xl">
|
||||||
|
No players found matching "{search}"
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayerStatsTable;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||||
import { listPlayers, getPlayer, getUnassociatedPlayers, fetchMe, getPlayerStats } from "./server";
|
import { listPlayers, getPlayer, getUnassociatedPlayers, fetchMe, getPlayerStats, getAllPlayerStats } from "./server";
|
||||||
|
|
||||||
export const playerKeys = {
|
export const playerKeys = {
|
||||||
auth: ['auth'],
|
auth: ['auth'],
|
||||||
@@ -7,6 +7,7 @@ export const playerKeys = {
|
|||||||
details: (id: string) => ['players', 'details', id],
|
details: (id: string) => ['players', 'details', id],
|
||||||
unassociated: ['players','unassociated'],
|
unassociated: ['players','unassociated'],
|
||||||
stats: (id: string) => ['players', 'stats', id],
|
stats: (id: string) => ['players', 'stats', id],
|
||||||
|
allStats: ['players', 'stats', 'all'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const playerQueries = {
|
export const playerQueries = {
|
||||||
@@ -30,6 +31,10 @@ export const playerQueries = {
|
|||||||
queryKey: playerKeys.stats(id),
|
queryKey: playerKeys.stats(id),
|
||||||
queryFn: async () => await getPlayerStats({ data: id })
|
queryFn: async () => await getPlayerStats({ data: id })
|
||||||
}),
|
}),
|
||||||
|
allStats: () => ({
|
||||||
|
queryKey: playerKeys.allStats,
|
||||||
|
queryFn: async () => await getAllPlayerStats()
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useMe = () => {
|
export const useMe = () => {
|
||||||
@@ -66,3 +71,6 @@ export const useUnassociatedPlayers = () =>
|
|||||||
|
|
||||||
export const usePlayerStats = (id: string) =>
|
export const usePlayerStats = (id: string) =>
|
||||||
useServerSuspenseQuery(playerQueries.stats(id));
|
useServerSuspenseQuery(playerQueries.stats(id));
|
||||||
|
|
||||||
|
export const useAllPlayerStats = () =>
|
||||||
|
useServerSuspenseQuery(playerQueries.allStats());
|
||||||
@@ -126,3 +126,9 @@ export const getPlayerStats = createServerFn()
|
|||||||
.handler(async ({ data }) =>
|
.handler(async ({ data }) =>
|
||||||
toServerResult<PlayerStats[]>(async () => await pbAdmin.getPlayerStats(data))
|
toServerResult<PlayerStats[]>(async () => await pbAdmin.getPlayerStats(data))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getAllPlayerStats = createServerFn()
|
||||||
|
.middleware([superTokensFunctionMiddleware])
|
||||||
|
.handler(async () =>
|
||||||
|
toServerResult<PlayerStats[]>(async () => await pbAdmin.getAllPlayerStats())
|
||||||
|
);
|
||||||
@@ -70,5 +70,12 @@ export function createPlayersService(pb: PocketBase) {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getAllPlayerStats(): Promise<PlayerStats[]> {
|
||||||
|
const result = await pb.collection("player_stats").getFullList<PlayerStats>({
|
||||||
|
sort: "-win_percentage,-total_cups_made",
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user