diff --git a/pb_migrations/1758575563_updated_free_agents.js b/pb_migrations/1758575563_updated_free_agents.js new file mode 100644 index 0000000..c67af3f --- /dev/null +++ b/pb_migrations/1758575563_updated_free_agents.js @@ -0,0 +1,28 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_2929550049") + + // update collection data + unmarshal({ + "createRule": "", + "deleteRule": "", + "listRule": "", + "updateRule": "", + "viewRule": "" + }, collection) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_2929550049") + + // update collection data + unmarshal({ + "createRule": null, + "deleteRule": null, + "listRule": null, + "updateRule": null, + "viewRule": null + }, collection) + + return app.save(collection) +}) diff --git a/pb_migrations/1758575597_updated_free_agents.js b/pb_migrations/1758575597_updated_free_agents.js new file mode 100644 index 0000000..1df1cac --- /dev/null +++ b/pb_migrations/1758575597_updated_free_agents.js @@ -0,0 +1,28 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_2929550049") + + // update collection data + unmarshal({ + "createRule": null, + "deleteRule": null, + "listRule": null, + "updateRule": null, + "viewRule": null + }, collection) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_2929550049") + + // update collection data + unmarshal({ + "createRule": "", + "deleteRule": "", + "listRule": "", + "updateRule": "", + "viewRule": "" + }, collection) + + return app.save(collection) +}) diff --git a/src/app/routes/_authed/stats.tsx b/src/app/routes/_authed/stats.tsx index 8f67a78..83c1991 100644 --- a/src/app/routes/_authed/stats.tsx +++ b/src/app/routes/_authed/stats.tsx @@ -1,13 +1,15 @@ import { createFileRoute } from "@tanstack/react-router"; -import { playerQueries, useAllPlayerStats } from "@/features/players/queries"; -import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure"; +import { playerQueries } from "@/features/players/queries"; import PlayerStatsTable from "@/features/players/components/player-stats-table"; +import { Suspense } from "react"; +import PlayerStatsTableSkeleton from "@/features/players/components/player-stats-table-skeleton"; +import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch"; export const Route = createFileRoute("/_authed/stats")({ component: Stats, - beforeLoad: async ({ context }) => { + beforeLoad: ({ context }) => { const queryClient = context.queryClient; - ensureServerQueryData(queryClient, playerQueries.allStats()); + prefetchServerQuery(queryClient, playerQueries.allStats()); }, loader: () => ({ withPadding: false, @@ -20,7 +22,7 @@ export const Route = createFileRoute("/_authed/stats")({ }); function Stats() { - const { data: playerStats } = useAllPlayerStats(); - - return ; -} \ No newline at end of file + return }> + + ; +} diff --git a/src/shared/components/stats-overview.tsx b/src/components/stats-overview.tsx similarity index 95% rename from src/shared/components/stats-overview.tsx rename to src/components/stats-overview.tsx index b18dce5..22a9349 100644 --- a/src/shared/components/stats-overview.tsx +++ b/src/components/stats-overview.tsx @@ -19,7 +19,7 @@ import { ArrowUpIcon, ArrowDownIcon, } from "@phosphor-icons/react"; -import { BaseStats } from "@/shared/types/stats"; +import { BaseStats } from "@/types/stats"; interface StatsOverviewProps { statsData: BaseStats | null; @@ -51,17 +51,13 @@ const StatItem = ({ - {value !== null ? `${value}${suffix}` : "—"} + {value !== null ? `${value}${suffix}` : } ); }; const StatsOverview = ({ statsData, isLoading = false }: StatsOverviewProps) => { - if (isLoading || (!statsData && isLoading)) { - return - } - if (!statsData && !isLoading) { return ( @@ -126,7 +122,7 @@ const StatsOverview = ({ statsData, isLoading = false }: StatsOverviewProps) => ); }; -const StatsSkeleton = () => { +export const StatsSkeleton = () => { const skeletonStats = [ { label: "Matches Played", Icon: BoxingGloveIcon }, { label: "Wins", Icon: CrownIcon }, diff --git a/src/features/players/components/player-stats-table-skeleton.tsx b/src/features/players/components/player-stats-table-skeleton.tsx new file mode 100644 index 0000000..013c768 --- /dev/null +++ b/src/features/players/components/player-stats-table-skeleton.tsx @@ -0,0 +1,87 @@ +import { + Stack, + Group, + Box, + Container, + Divider, + Skeleton, +} from "@mantine/core"; + +const PlayerListItemSkeleton = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const PlayerStatsTableSkeleton = () => { + return ( + + + + + + + + + + + + + + + {Array(10).fill(null).map((_, index) => ( + + + {index < 9 && } + + ))} + + + + ); +}; + +export default PlayerStatsTableSkeleton; \ No newline at end of file diff --git a/src/features/players/components/player-stats-table.tsx b/src/features/players/components/player-stats-table.tsx index 5ffa203..f16d95b 100644 --- a/src/features/players/components/player-stats-table.tsx +++ b/src/features/players/components/player-stats-table.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, useCallback, memo } from "react"; import { Text, TextInput, @@ -12,7 +12,6 @@ import { UnstyledButton, Popover, ActionIcon, - Skeleton, } from "@mantine/core"; import { MagnifyingGlassIcon, @@ -24,10 +23,7 @@ import { import { PlayerStats } from "../types"; import Avatar from "@/components/avatar"; import { useNavigate } from "@tanstack/react-router"; - -interface PlayerStatsTableProps { - playerStats: PlayerStats[]; -} +import { useAllPlayerStats } from "../queries"; type SortKey = keyof PlayerStats | "mmr"; type SortDirection = "asc" | "desc"; @@ -39,33 +35,11 @@ interface SortConfig { interface PlayerListItemProps { stat: PlayerStats; - index: number; onPlayerClick: (playerId: string) => void; + mmr: number; } -const PlayerListItem = ({ stat, index, onPlayerClick }: PlayerListItemProps) => { - const calculateMMR = (stat: PlayerStats): number => { - if (stat.matches === 0) return 0; - - const winScore = stat.win_percentage; - const matchConfidence = Math.min(stat.matches / 15, 1); - const avgCupsScore = Math.min(stat.avg_cups_per_match * 10, 100); - const marginScore = stat.margin_of_victory - ? Math.min(stat.margin_of_victory * 20, 50) - : 0; - const volumeBonus = Math.min(stat.matches * 0.5, 10); - - const baseMMR = - winScore * 0.5 + - avgCupsScore * 0.25 + - marginScore * 0.15 + - volumeBonus * 0.1; - - const finalMMR = baseMMR * matchConfidence; - return Math.round(finalMMR * 10) / 10; - }; - - const mmr = calculateMMR(stat); +const PlayerListItem = memo(({ stat, onPlayerClick, mmr }: PlayerListItemProps) => { return ( <> @@ -165,9 +139,12 @@ const PlayerListItem = ({ stat, index, onPlayerClick }: PlayerListItemProps) => ); -}; +}); -const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => { +PlayerListItem.displayName = 'PlayerListItem'; + +const PlayerStatsTable = () => { + const { data: playerStats } = useAllPlayerStats(); const navigate = useNavigate(); const [search, setSearch] = useState(""); const [sortConfig, setSortConfig] = useState({ @@ -196,8 +173,15 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => { return Math.round(finalMMR * 10) / 10; }; + const statsWithMMR = useMemo(() => { + return playerStats.map((stat) => ({ + ...stat, + mmr: calculateMMR(stat), + })); + }, [playerStats]); + const filteredAndSortedStats = useMemo(() => { - let filtered = playerStats.filter((stat) => + let filtered = statsWithMMR.filter((stat) => stat.player_name.toLowerCase().includes(search.toLowerCase()) ); @@ -206,8 +190,8 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => { let bValue: number | string; if (sortConfig.key === "mmr") { - aValue = calculateMMR(a); - bValue = calculateMMR(b); + aValue = a.mmr; + bValue = b.mmr; } else { aValue = a[sortConfig.key]; bValue = b[sortConfig.key]; @@ -227,11 +211,11 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => { return 0; }); - }, [playerStats, search, sortConfig]); + }, [statsWithMMR, search, sortConfig]); - const handlePlayerClick = (playerId: string) => { + const handlePlayerClick = useCallback((playerId: string) => { navigate({ to: `/profile/${playerId}` }); - }; + }, [navigate]); const handleSort = (key: SortKey) => { setSortConfig((prev) => ({ @@ -351,8 +335,8 @@ const PlayerStatsTable = ({ playerStats }: PlayerStatsTableProps) => { {index < filteredAndSortedStats.length - 1 && } diff --git a/src/features/players/components/profile/index.tsx b/src/features/players/components/profile/index.tsx index 479022b..15acb78 100644 --- a/src/features/players/components/profile/index.tsx +++ b/src/features/players/components/profile/index.tsx @@ -3,7 +3,7 @@ import Header from "./header"; import SwipeableTabs from "@/components/swipeable-tabs"; import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries"; import TeamList from "@/features/teams/components/team-list"; -import StatsOverview from "@/shared/components/stats-overview"; +import StatsOverview from "@/components/stats-overview"; import MatchList from "@/features/matches/components/match-list"; interface ProfileProps { diff --git a/src/features/players/components/profile/skeleton.tsx b/src/features/players/components/profile/skeleton.tsx index 0373ca4..32a3518 100644 --- a/src/features/players/components/profile/skeleton.tsx +++ b/src/features/players/components/profile/skeleton.tsx @@ -3,7 +3,7 @@ import Header from "./header"; import SwipeableTabs from "@/components/swipeable-tabs"; import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries"; import TeamList from "@/features/teams/components/team-list"; -import StatsOverview from "@/shared/components/stats-overview"; +import StatsOverview, { StatsSkeleton } from "@/components/stats-overview"; import MatchList from "@/features/matches/components/match-list"; import HeaderSkeleton from "./header-skeleton"; @@ -17,7 +17,7 @@ const ProfileSkeleton = () => { const tabs = [ { label: "Overview", - content: , + content: , }, { label: "Matches", diff --git a/src/features/teams/components/team-profile/index.tsx b/src/features/teams/components/team-profile/index.tsx index 37889d7..9517a91 100644 --- a/src/features/teams/components/team-profile/index.tsx +++ b/src/features/teams/components/team-profile/index.tsx @@ -2,7 +2,7 @@ import { Box, Divider, Text, Stack } from "@mantine/core"; import Header from "./header"; import SwipeableTabs from "@/components/swipeable-tabs"; import TournamentList from "@/features/tournaments/components/tournament-list"; -import StatsOverview from "@/shared/components/stats-overview"; +import StatsOverview from "@/components/stats-overview"; import { useTeam, useTeamMatches, useTeamStats } from "../../queries"; import MatchList from "@/features/matches/components/match-list"; import PlayerList from "@/features/players/components/player-list"; diff --git a/src/shared/types/stats.ts b/src/types/stats.ts similarity index 100% rename from src/shared/types/stats.ts rename to src/types/stats.ts