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