Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/app/nextapi/player/[id]/playerrecord/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextRequest } from 'next/server';

export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;

const response = await fetch(`https://mmolb.com/api/playerrecord/${id}`, {
headers: {
'Accept': 'application/json',
},
next: { revalidate: 60 },
});

const data = await response.json();

return new Response(JSON.stringify(data), {
status: response.status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
94 changes: 62 additions & 32 deletions src/components/player/PlayerStatsTables.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { usePlayer } from "@/hooks/api/Player";
import { useMmolbTime } from "@/hooks/api/Time";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { BattingStats, BattingStatsTable } from "./BattingStats";
import { PitchingStats, PitchingStatsTable } from "./PitchingStats";
import { FieldingStats, FieldingStatsTable } from "./FieldingStats";
import { useMemo, useState } from "react";
import { BattingStatsTable } from "./BattingStats";
import { PitchingStatsTable } from "./PitchingStats";
import { FieldingStatsTable } from "./FieldingStats";
import { LoadingMini } from "../Loading";
import { defaultStats, PlayerRecord, PlayerRecordResponse, PlayerStatKey } from "@/types/PlayerStats";

export type Season = {
season: number;
Expand Down Expand Up @@ -106,47 +106,77 @@ type PlayerStatsTablesProps = {
playerId: string
};

function recordToStats(record: PlayerRecord) {
const stats = { ...defaultStats };
for (const teamStats of Object.values(record.Stats)) {
for (const [key, value] of Object.entries(teamStats)) {
if (value !== undefined)
stats[key as PlayerStatKey] += value;
}
}
return { season: record.Season, ...stats };
}

export default function PlayerStatsTables({ playerId }: PlayerStatsTablesProps) {
const { data: currentSeason } = useMmolbTime({
select: time => time.seasonNumber
});
const { data: player, isPending: currentSeasonStatsPending } = usePlayer({
const [selectedStatus, setSelectedStatus] = useState('Regular Season');

const { data: player } = usePlayer({
playerId,
select: player => ({ posType: player.position_type, currentSeasonStats: player.stats[player.team_id] }),
select: player => player.position_type,
});
const { data: cashewsStats } = useQuery({
queryKey: ['player-cashews-stats', playerId],
const { data: records, isPending } = useQuery({
queryKey: ['player-playerrecord', playerId],
queryFn: async () => {
const res = await fetch(`/nextapi/player/${playerId}/cashews-stats`);
if (!res.ok) throw new Error('Failed to load player stats');
return await res.json() as (Season & BattingStats & PitchingStats & FieldingStats)[];
const res = await fetch(`/nextapi/player/${playerId}/playerrecord`);
if (!res.ok) throw new Error('Failed to load player record');
return (await res.json() as PlayerRecordResponse).records;
},
staleTime: 60 * 60 * 1000,
staleTime: 60 * 1000,
});

const allSeasonsStats = useMemo(() => {
const seasons = player?.currentSeasonStats && currentSeason
? [{ ...player?.currentSeasonStats, season: currentSeason }, ...(cashewsStats?.filter(x => x.season !== currentSeason) ?? [])]
: [...(cashewsStats ?? [])];
return seasons.sort((a, b) => b.season - a.season);
}, [currentSeason, player?.currentSeasonStats, cashewsStats]);
const availableStatuses = useMemo(() => {
if (!records) return [];
const seen = new Set<string>();
for (const r of records) seen.add(r.SeasonStatus);
return Array.from(seen).sort();
}, [records]);

if (currentSeasonStatsPending || !cashewsStats)
const seasonStats = useMemo(() => {
if (!records) return [];
return records
.filter(r => r.SeasonStatus === selectedStatus)
.map(recordToStats)
.sort((a, b) => b.season - a.season);
}, [records, selectedStatus]);

if (isPending)
return <div className="h-80"><LoadingMini /></div>

return (
<div className="flex flex-col gap-8 max-w-full">
{player?.posType === 'Batter' ?
<div className="flex flex-col gap-8">
<div className="flex gap-2 items-center">
<div className="text-sm font-medium text-theme-secondary opacity-80">Season Type:</div>
<select
value={selectedStatus}
onChange={e => setSelectedStatus(e.target.value)}
className="text-sm bg-(--theme-primary) p-1 rounded-sm"
>
{availableStatuses.map(status => (
<option key={status} value={status}>{status}</option>
))}
</select>
</div>
{player === 'Batter' ?
<>
<BattingStatsTable playerId={playerId} data={allSeasonsStats} />
<FieldingStatsTable playerId={playerId} data={allSeasonsStats} />
<PitchingStatsTable playerId={playerId} data={allSeasonsStats} />
<BattingStatsTable playerId={playerId} data={seasonStats} />
<FieldingStatsTable playerId={playerId} data={seasonStats} />
<PitchingStatsTable playerId={playerId} data={seasonStats} />
</> : <>
<PitchingStatsTable playerId={playerId} data={allSeasonsStats} />
<FieldingStatsTable playerId={playerId} data={allSeasonsStats} />
<BattingStatsTable playerId={playerId} data={allSeasonsStats} />
<PitchingStatsTable playerId={playerId} data={seasonStats} />
<FieldingStatsTable playerId={playerId} data={seasonStats} />
<BattingStatsTable playerId={playerId} data={seasonStats} />
</>
}
</div>
);
}
}
17 changes: 16 additions & 1 deletion src/types/PlayerStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ export type DerivedPlayerStats = PlayerStats & {
ip: number;
};

export type PlayerRecord = {
FirstName: string;
LastName: string;
PlayerID: string;
Season: number;
SeasonID: string;
SeasonStatus: string;
Stats: Record<string, Partial<PlayerStats>>;
_id: string;
};

export type PlayerRecordResponse = {
records: PlayerRecord[];
};

export function MapAPIPlayerStats(rawStats: Partial<PlayerStats>): DerivedPlayerStats {
const base: PlayerStats = { ...defaultStats, ...rawStats };

Expand Down Expand Up @@ -277,4 +292,4 @@ export function MapAPIPlayerStats(rawStats: Partial<PlayerStats>): DerivedPlayer
bb9,
hr9,
};
}
}
Loading