This commit is contained in:
Zeeshaun
2026-05-06 18:31:27 +00:00
parent 2263dc3aa5
commit c48023ace4
11 changed files with 404 additions and 97 deletions
+6 -1
View File
@@ -18,7 +18,12 @@ const statFields = [
"runsBattedIn", "runsBattedIn",
"walks", "walks",
"strikeouts", "strikeouts",
"stolenBases" "stolenBases",
"inningsPitchedOuts",
"earnedRunsAllowed",
"hitsAllowed",
"walksAllowed",
"strikeoutsPitched"
] as const; ] as const;
export async function DELETE(_request: Request, { params }: { params: Promise<{ id: string }> }) { export async function DELETE(_request: Request, { params }: { params: Promise<{ id: string }> }) {
+6 -1
View File
@@ -21,7 +21,12 @@ const statFields = [
"runsBattedIn", "runsBattedIn",
"walks", "walks",
"strikeouts", "strikeouts",
"stolenBases" "stolenBases",
"inningsPitchedOuts",
"earnedRunsAllowed",
"hitsAllowed",
"walksAllowed",
"strikeoutsPitched"
] as const; ] as const;
export async function GET(request: Request) { export async function GET(request: Request) {
+72 -27
View File
@@ -10,11 +10,11 @@ import { Alert } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { getBattingSummary, getPitchingSummary, hasBattingStats, hasPitchingStats } from "@/lib/baseball-stats";
import { fetchJson } from "@/lib/client"; import { fetchJson } from "@/lib/client";
import type { GameRecord } from "@/lib/types"; import type { GameRecord } from "@/lib/types";
const statFields = [ const battingFields = [
["pitchCount", "Pitches"],
["atBats", "AB"], ["atBats", "AB"],
["runs", "R"], ["runs", "R"],
["hits", "H"], ["hits", "H"],
@@ -27,6 +27,15 @@ const statFields = [
["stolenBases", "SB"] ["stolenBases", "SB"]
] as const; ] as const;
const pitchingFields = [
["pitchCount", "Pitches"],
["inningsPitchedOuts", "IP Outs"],
["earnedRunsAllowed", "ER"],
["hitsAllowed", "H Allowed"],
["walksAllowed", "BB Allowed"],
["strikeoutsPitched", "Pitching K"]
] as const;
export function GameHistoryClient() { export function GameHistoryClient() {
const [gameDate, setGameDate] = useState(""); const [gameDate, setGameDate] = useState("");
const [games, setGames] = useState<GameRecord[]>([]); const [games, setGames] = useState<GameRecord[]>([]);
@@ -165,31 +174,67 @@ export function GameHistoryClient() {
</div> </div>
) : null} ) : null}
<div className="overflow-x-auto rounded-[1rem] border border-border bg-white/70"> <div className="grid gap-3">
<table className="min-w-full text-left text-xs"> {game.playerStats.map((playerStat) => (
<thead className="border-b border-border bg-muted/60 uppercase tracking-[0.18em] text-muted-foreground"> <div key={playerStat.playerId} className="rounded-[1rem] border border-border bg-white/70 p-3">
<tr> <div className="flex items-center justify-between gap-3">
<th className="px-3 py-2 font-semibold">Player</th> <p className="text-sm font-semibold">{playerStat.playerName}</p>
{statFields.map(([, label]) => ( <p className="text-xs text-muted-foreground">Saved stat line</p>
<th key={label} className="px-3 py-2 font-semibold"> </div>
{label} <div className="mt-3 grid gap-3 md:grid-cols-2">
</th> <div>
))} <p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
</tr> Hitting Summary
</thead> </p>
<tbody className="divide-y divide-border"> {hasBattingStats(playerStat) ? (
{game.playerStats.map((playerStat) => ( <div className="grid grid-cols-2 gap-x-3 gap-y-1 text-xs">
<tr key={playerStat.playerId}> {getBattingSummary(playerStat).map(([label, value]) => (
<td className="px-3 py-2 font-medium">{playerStat.playerName}</td> <div key={label}>
{statFields.map(([field, label]) => ( <span className="font-semibold text-muted-foreground">{label}</span>{" "}
<td key={label} className="px-3 py-2"> <span>{value ?? "-"}</span>
{playerStat[field] ?? "-"} </div>
</td> ))}
))} </div>
</tr> ) : (
))} <p className="text-xs text-muted-foreground">No batting stats recorded.</p>
</tbody> )}
</table> <div className="mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs">
{battingFields.map(([field, label]) => (
<div key={label}>
<span className="font-semibold text-muted-foreground">{label}</span>{" "}
<span>{playerStat[field] ?? "-"}</span>
</div>
))}
</div>
</div>
<div>
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Pitching Summary
</p>
{hasPitchingStats(playerStat) ? (
<div className="grid grid-cols-2 gap-x-3 gap-y-1 text-xs">
{getPitchingSummary(playerStat).map(([label, value]) => (
<div key={label}>
<span className="font-semibold text-muted-foreground">{label}</span>{" "}
<span>{value ?? "-"}</span>
</div>
))}
</div>
) : (
<p className="text-xs text-muted-foreground">No pitching stats recorded.</p>
)}
<div className="mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs">
{pitchingFields.map(([field, label]) => (
<div key={label}>
<span className="font-semibold text-muted-foreground">{label}</span>{" "}
<span>{playerStat[field] ?? "-"}</span>
</div>
))}
</div>
</div>
</div>
</div>
))}
</div> </div>
</div> </div>
</details> </details>
+72 -27
View File
@@ -12,8 +12,7 @@ import { Textarea } from "@/components/ui/textarea";
import { fetchJson } from "@/lib/client"; import { fetchJson } from "@/lib/client";
import type { GamePlayerStatsInput, GameRecord, LineupRecord } from "@/lib/types"; import type { GamePlayerStatsInput, GameRecord, LineupRecord } from "@/lib/types";
const statFields = [ const battingFields = [
["pitchCount", "Pitches"],
["atBats", "AB"], ["atBats", "AB"],
["runs", "R"], ["runs", "R"],
["hits", "H"], ["hits", "H"],
@@ -26,6 +25,15 @@ const statFields = [
["stolenBases", "SB"] ["stolenBases", "SB"]
] as const; ] as const;
const pitchingFields = [
["pitchCount", "Pitches"],
["inningsPitchedOuts", "IP Outs"],
["earnedRunsAllowed", "ER"],
["hitsAllowed", "H Allowed"],
["walksAllowed", "BB Allowed"],
["strikeoutsPitched", "Pitching K"]
] as const;
function pickDefaultLineupId(gameDate: string, lineups: LineupRecord[]) { function pickDefaultLineupId(gameDate: string, lineups: LineupRecord[]) {
const exactDate = lineups.find((lineup) => lineup.gameDate === gameDate); const exactDate = lineups.find((lineup) => lineup.gameDate === gameDate);
return exactDate?._id ?? lineups[0]?._id ?? ""; return exactDate?._id ?? lineups[0]?._id ?? "";
@@ -196,16 +204,6 @@ export function GameScoringClient() {
<span className="field-label">Opponent</span> <span className="field-label">Opponent</span>
<Input value={opponent} onChange={(event) => setOpponent(event.target.value)} placeholder="Optional" /> <Input value={opponent} onChange={(event) => setOpponent(event.target.value)} placeholder="Optional" />
</label> </label>
<div className="grid grid-cols-2 gap-3">
<label>
<span className="field-label">Our Score</span>
<Input type="number" min={0} value={teamScore} onChange={(event) => setTeamScore(event.target.value)} />
</label>
<label>
<span className="field-label">Their Score</span>
<Input type="number" min={0} value={opponentScore} onChange={(event) => setOpponentScore(event.target.value)} />
</label>
</div>
<label className="md:col-span-2"> <label className="md:col-span-2">
<span className="field-label">Game Notes</span> <span className="field-label">Game Notes</span>
<Textarea rows={3} value={notes} onChange={(event) => setNotes(event.target.value)} /> <Textarea rows={3} value={notes} onChange={(event) => setNotes(event.target.value)} />
@@ -230,9 +228,29 @@ export function GameScoringClient() {
{selectedLineup && isScoring ? ( {selectedLineup && isScoring ? (
<SectionCard <SectionCard
title="Player Results" title="Player Results"
description="Stat entry is optional. Leave any field blank if you do not want to track it for that player." description="Enter player results as the game unfolds. Final score lives here because you know it once the game is actually being scored."
> >
<div className="grid gap-3"> <div className="grid gap-3">
<div className="grid grid-cols-2 gap-3 rounded-[1.5rem] border border-border bg-muted/30 p-4 md:max-w-sm">
<label>
<span className="field-label">Our Score</span>
<Input
type="number"
min={0}
value={teamScore}
onChange={(event) => setTeamScore(event.target.value)}
/>
</label>
<label>
<span className="field-label">Their Score</span>
<Input
type="number"
min={0}
value={opponentScore}
onChange={(event) => setOpponentScore(event.target.value)}
/>
</label>
</div>
{selectedLineup.battingOrder.map((slot, index) => { {selectedLineup.battingOrder.map((slot, index) => {
const statLine = playerStats[slot.playerId] ?? { const statLine = playerStats[slot.playerId] ?? {
playerId: slot.playerId, playerId: slot.playerId,
@@ -251,20 +269,47 @@ export function GameScoringClient() {
<p className="text-xs text-muted-foreground">Batting order slot {index + 1}</p> <p className="text-xs text-muted-foreground">Batting order slot {index + 1}</p>
</div> </div>
</div> </div>
<div className="grid grid-cols-3 gap-2 sm:grid-cols-5 lg:grid-cols-6"> <div className="grid gap-3">
{statFields.map(([field, label]) => ( <div>
<label key={field}> <p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
<span className="field-label">{label}</span> Batting Line
<Input </p>
type="number" <div className="grid grid-cols-3 gap-2 sm:grid-cols-5 lg:grid-cols-6">
min={0} {battingFields.map(([field, label]) => (
value={statLine[field] ?? ""} <label key={field}>
onChange={(event) => <span className="field-label">{label}</span>
updatePlayerStat(slot.playerId, slot.playerName, field, event.target.value) <Input
} type="number"
/> min={0}
</label> value={statLine[field] ?? ""}
))} onChange={(event) =>
updatePlayerStat(slot.playerId, slot.playerName, field, event.target.value)
}
/>
</label>
))}
</div>
</div>
<div>
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Pitching Line
</p>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6">
{pitchingFields.map(([field, label]) => (
<label key={field}>
<span className="field-label">{label}</span>
<Input
type="number"
min={0}
value={statLine[field] ?? ""}
onChange={(event) =>
updatePlayerStat(slot.playerId, slot.playerName, field, event.target.value)
}
/>
</label>
))}
</div>
</div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
+57 -20
View File
@@ -52,6 +52,14 @@ const statFields = [
["stolenBases", "SB"] ["stolenBases", "SB"]
] as const; ] as const;
const pitchingStatFields = [
["inningsPitchedOuts", "IP Outs"],
["earnedRunsAllowed", "ER"],
["hitsAllowed", "H Allowed"],
["walksAllowed", "BB Allowed"],
["strikeoutsPitched", "Pitching K"]
] as const;
export function PlayerForm({ export function PlayerForm({
value, value,
onChange, onChange,
@@ -135,33 +143,62 @@ export function PlayerForm({
<span className="field-label">Notes</span> <span className="field-label">Notes</span>
<Textarea rows={4} value={value.notes ?? ""} onChange={(event) => onChange({ ...value, notes: event.target.value })} /> <Textarea rows={4} value={value.notes ?? ""} onChange={(event) => onChange({ ...value, notes: event.target.value })} />
</label> </label>
<div className="md:col-span-2 space-y-3 rounded-[1.5rem] border border-border bg-muted/40 p-4"> <div className="md:col-span-2 space-y-4 rounded-[1.5rem] border border-border bg-muted/40 p-4">
<div> <div>
<p className="text-sm font-semibold">Optional Player Stats</p> <p className="text-sm font-semibold">Optional Player Stats</p>
<p className="mt-1 text-xs text-muted-foreground"> <p className="mt-1 text-xs text-muted-foreground">
Leave any field blank if you do not want to track it. Leave any field blank if you do not want to track it.
</p> </p>
</div> </div>
<div className="grid gap-3 grid-cols-2 md:grid-cols-4"> <div className="space-y-3">
{statFields.map(([key, label]) => ( <div>
<label key={key}> <p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">Batting Totals</p>
<span className="field-label">{label}</span> <div className="mt-2 grid grid-cols-2 gap-3 md:grid-cols-4">
<Input {statFields.map(([key, label]) => (
type="number" <label key={key}>
min={0} <span className="field-label">{label}</span>
value={value.stats?.[key] ?? ""} <Input
onChange={(event) => type="number"
onChange({ min={0}
...value, value={value.stats?.[key] ?? ""}
stats: { onChange={(event) =>
...value.stats, onChange({
[key]: event.target.value === "" ? null : Number(event.target.value) ...value,
stats: {
...value.stats,
[key]: event.target.value === "" ? null : Number(event.target.value)
}
})
} }
}) />
} </label>
/> ))}
</label> </div>
))} </div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">Pitching Totals</p>
<div className="mt-2 grid grid-cols-2 gap-3 md:grid-cols-4">
{pitchingStatFields.map(([key, label]) => (
<label key={key}>
<span className="field-label">{label}</span>
<Input
type="number"
min={0}
value={value.stats?.[key] ?? ""}
onChange={(event) =>
onChange({
...value,
stats: {
...value.stats,
[key]: event.target.value === "" ? null : Number(event.target.value)
}
})
}
/>
</label>
))}
</div>
</div>
</div> </div>
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
+28 -16
View File
@@ -12,6 +12,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { getBattingSummary, getPitchingSummary, hasBattingStats, hasPitchingStats } from "@/lib/baseball-stats";
import { fetchJson } from "@/lib/client"; import { fetchJson } from "@/lib/client";
import { formatPlayerName } from "@/lib/utils"; import { formatPlayerName } from "@/lib/utils";
import type { PlayerInput, PlayerPitchingInput, PlayerRecord } from "@/lib/types"; import type { PlayerInput, PlayerPitchingInput, PlayerRecord } from "@/lib/types";
@@ -33,15 +34,6 @@ export function RosterClient() {
}); });
const playerFormRef = useRef<HTMLDivElement | null>(null); const playerFormRef = useRef<HTMLDivElement | null>(null);
const statColumns = [
["G", "gamesPlayed"],
["AB", "atBats"],
["R", "runs"],
["H", "hits"],
["RBI", "runsBattedIn"],
["SB", "stolenBases"]
] as const;
const editingPlayer = useMemo( const editingPlayer = useMemo(
() => players.find((player) => player._id === editingPlayerId) ?? null, () => players.find((player) => player._id === editingPlayerId) ?? null,
[editingPlayerId, players] [editingPlayerId, players]
@@ -253,14 +245,34 @@ export function RosterClient() {
</td> </td>
<td className="px-3 py-3 align-top">{player.batsThrows}</td> <td className="px-3 py-3 align-top">{player.batsThrows}</td>
<td className="px-3 py-3 align-top"> <td className="px-3 py-3 align-top">
{player.stats && Object.values(player.stats).some((value) => value !== null && value !== undefined) ? ( {hasBattingStats(player.stats) || hasPitchingStats(player.stats) ? (
<div className="grid grid-cols-3 gap-x-3 gap-y-1 text-xs"> <div className="space-y-2 text-xs">
{statColumns.map(([label, key]) => ( {hasBattingStats(player.stats) ? (
<div key={label}> <div>
<span className="font-semibold text-muted-foreground">{label}</span>{" "} <div className="mb-1 font-semibold text-muted-foreground">Hitting</div>
<span>{player.stats?.[key] ?? 0}</span> <div className="grid grid-cols-2 gap-x-3 gap-y-1">
{getBattingSummary(player.stats).map(([label, value]) => (
<div key={label}>
<span className="font-semibold text-muted-foreground">{label}</span>{" "}
<span>{value ?? "-"}</span>
</div>
))}
</div>
</div> </div>
))} ) : null}
{hasPitchingStats(player.stats) ? (
<div>
<div className="mb-1 font-semibold text-muted-foreground">Pitching</div>
<div className="grid grid-cols-2 gap-x-3 gap-y-1">
{getPitchingSummary(player.stats).map(([label, value]) => (
<div key={label}>
<span className="font-semibold text-muted-foreground">{label}</span>{" "}
<span>{value ?? "-"}</span>
</div>
))}
</div>
</div>
) : null}
</div> </div>
) : ( ) : (
<span className="text-xs text-muted-foreground">No stats</span> <span className="text-xs text-muted-foreground">No stats</span>
+117
View File
@@ -0,0 +1,117 @@
import type { PlayerStatsInput } from "@/lib/types";
type StatValue = number | null | undefined;
function stat(value: StatValue) {
return value ?? 0;
}
function hasValue(value: StatValue) {
return value !== null && value !== undefined;
}
function formatRate(value: number | null, digits = 3) {
if (value === null || Number.isNaN(value)) {
return null;
}
return value.toFixed(digits).replace(/^0(?=\.)/, "");
}
function formatDecimal(value: number | null, digits = 2) {
if (value === null || Number.isNaN(value)) {
return null;
}
return value.toFixed(digits);
}
export function formatInningsPitched(outs: StatValue) {
const totalOuts = stat(outs);
if (totalOuts <= 0) {
return null;
}
const wholeInnings = Math.floor(totalOuts / 3);
const remainder = totalOuts % 3;
return `${wholeInnings}.${remainder}`;
}
export function hasBattingStats(stats?: PlayerStatsInput | null) {
if (!stats) return false;
return [
stats.atBats,
stats.runs,
stats.hits,
stats.doubles,
stats.triples,
stats.homeRuns,
stats.runsBattedIn,
stats.walks,
stats.strikeouts,
stats.stolenBases
].some(hasValue);
}
export function hasPitchingStats(stats?: PlayerStatsInput | null) {
if (!stats) return false;
return [
stats.inningsPitchedOuts,
stats.earnedRunsAllowed,
stats.hitsAllowed,
stats.walksAllowed,
stats.strikeoutsPitched
].some(hasValue);
}
export function getBattingSummary(stats?: PlayerStatsInput | null) {
if (!hasBattingStats(stats)) {
return [];
}
const atBats = stat(stats?.atBats);
const hits = stat(stats?.hits);
const walks = stat(stats?.walks);
const doubles = stat(stats?.doubles);
const triples = stat(stats?.triples);
const homeRuns = stat(stats?.homeRuns);
const singles = Math.max(hits - doubles - triples - homeRuns, 0);
const totalBases = singles + doubles * 2 + triples * 3 + homeRuns * 4;
const average = atBats > 0 ? hits / atBats : null;
const onBase = atBats + walks > 0 ? (hits + walks) / (atBats + walks) : null;
const slugging = atBats > 0 ? totalBases / atBats : null;
const ops = onBase !== null || slugging !== null ? (onBase ?? 0) + (slugging ?? 0) : null;
return [
["AVG", formatRate(average)],
["OBP", formatRate(onBase)],
["SLG", formatRate(slugging)],
["OPS", formatRate(ops)]
] as const;
}
export function getPitchingSummary(stats?: PlayerStatsInput | null) {
if (!hasPitchingStats(stats)) {
return [];
}
const outs = stat(stats?.inningsPitchedOuts);
const innings = outs / 3;
const earnedRuns = stat(stats?.earnedRunsAllowed);
const hitsAllowed = stat(stats?.hitsAllowed);
const walksAllowed = stat(stats?.walksAllowed);
const strikeoutsPitched = stat(stats?.strikeoutsPitched);
const era = outs > 0 ? (earnedRuns * 27) / outs : null;
const whip = innings > 0 ? (hitsAllowed + walksAllowed) / innings : null;
return [
["IP", formatInningsPitched(outs)],
["ERA", formatDecimal(era)],
["WHIP", formatDecimal(whip)],
["K", strikeoutsPitched > 0 || hasValue(stats?.strikeoutsPitched) ? String(strikeoutsPitched) : null]
] as const;
}
+11 -1
View File
@@ -26,6 +26,11 @@ export interface PlayerStatsInput {
walks?: number | null; walks?: number | null;
strikeouts?: number | null; strikeouts?: number | null;
stolenBases?: number | null; stolenBases?: number | null;
inningsPitchedOuts?: number | null;
earnedRunsAllowed?: number | null;
hitsAllowed?: number | null;
walksAllowed?: number | null;
strikeoutsPitched?: number | null;
} }
export interface PlayerPitchingInput { export interface PlayerPitchingInput {
@@ -38,6 +43,7 @@ export interface PlayerPitchingInput {
export interface GamePlayerStatsInput { export interface GamePlayerStatsInput {
playerId: string; playerId: string;
playerName: string; playerName: string;
pitchCount?: number | null;
atBats?: number | null; atBats?: number | null;
runs?: number | null; runs?: number | null;
hits?: number | null; hits?: number | null;
@@ -48,7 +54,11 @@ export interface GamePlayerStatsInput {
walks?: number | null; walks?: number | null;
strikeouts?: number | null; strikeouts?: number | null;
stolenBases?: number | null; stolenBases?: number | null;
pitchCount?: number | null; inningsPitchedOuts?: number | null;
earnedRunsAllowed?: number | null;
hitsAllowed?: number | null;
walksAllowed?: number | null;
strikeoutsPitched?: number | null;
} }
export interface PitchingRuleInput { export interface PitchingRuleInput {
+12 -2
View File
@@ -25,7 +25,12 @@ export const playerSchema = z.object({
runsBattedIn: optionalStat, runsBattedIn: optionalStat,
walks: optionalStat, walks: optionalStat,
strikeouts: optionalStat, strikeouts: optionalStat,
stolenBases: optionalStat stolenBases: optionalStat,
inningsPitchedOuts: optionalStat,
earnedRunsAllowed: optionalStat,
hitsAllowed: optionalStat,
walksAllowed: optionalStat,
strikeoutsPitched: optionalStat
}) })
.optional() .optional()
}); });
@@ -91,7 +96,12 @@ export const saveGameSchema = z.object({
walks: optionalStat, walks: optionalStat,
strikeouts: optionalStat, strikeouts: optionalStat,
stolenBases: optionalStat, stolenBases: optionalStat,
pitchCount: optionalStat pitchCount: optionalStat,
inningsPitchedOuts: optionalStat,
earnedRunsAllowed: optionalStat,
hitsAllowed: optionalStat,
walksAllowed: optionalStat,
strikeoutsPitched: optionalStat
}) })
) )
}); });
+12 -1
View File
@@ -14,6 +14,7 @@ export interface GameDocument {
playerStats: Array<{ playerStats: Array<{
playerId: Types.ObjectId; playerId: Types.ObjectId;
playerName: string; playerName: string;
pitchCount?: number | null;
atBats?: number | null; atBats?: number | null;
runs?: number | null; runs?: number | null;
hits?: number | null; hits?: number | null;
@@ -24,6 +25,11 @@ export interface GameDocument {
walks?: number | null; walks?: number | null;
strikeouts?: number | null; strikeouts?: number | null;
stolenBases?: number | null; stolenBases?: number | null;
inningsPitchedOuts?: number | null;
earnedRunsAllowed?: number | null;
hitsAllowed?: number | null;
walksAllowed?: number | null;
strikeoutsPitched?: number | null;
}>; }>;
createdAt?: Date; createdAt?: Date;
updatedAt?: Date; updatedAt?: Date;
@@ -55,7 +61,12 @@ const gameSchema = new Schema<GameDocument>(
walks: { type: Number, default: null }, walks: { type: Number, default: null },
strikeouts: { type: Number, default: null }, strikeouts: { type: Number, default: null },
stolenBases: { type: Number, default: null }, stolenBases: { type: Number, default: null },
pitchCount: { type: Number, default: null } pitchCount: { type: Number, default: null },
inningsPitchedOuts: { type: Number, default: null },
earnedRunsAllowed: { type: Number, default: null },
hitsAllowed: { type: Number, default: null },
walksAllowed: { type: Number, default: null },
strikeoutsPitched: { type: Number, default: null }
} }
] ]
}, },
+11 -1
View File
@@ -22,6 +22,11 @@ export interface PlayerDocument {
walks?: number | null; walks?: number | null;
strikeouts?: number | null; strikeouts?: number | null;
stolenBases?: number | null; stolenBases?: number | null;
inningsPitchedOuts?: number | null;
earnedRunsAllowed?: number | null;
hitsAllowed?: number | null;
walksAllowed?: number | null;
strikeoutsPitched?: number | null;
}; };
createdAt?: Date; createdAt?: Date;
updatedAt?: Date; updatedAt?: Date;
@@ -49,7 +54,12 @@ const playerSchema = new Schema<PlayerDocument>(
runsBattedIn: { type: Number, default: null }, runsBattedIn: { type: Number, default: null },
walks: { type: Number, default: null }, walks: { type: Number, default: null },
strikeouts: { type: Number, default: null }, strikeouts: { type: Number, default: null },
stolenBases: { type: Number, default: null } stolenBases: { type: Number, default: null },
inningsPitchedOuts: { type: Number, default: null },
earnedRunsAllowed: { type: Number, default: null },
hitsAllowed: { type: Number, default: null },
walksAllowed: { type: Number, default: null },
strikeoutsPitched: { type: Number, default: null }
} }
}, },
{ timestamps: true } { timestamps: true }