Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dumbmatter committed Feb 1, 2025
1 parent 6961fa1 commit 5f620fc
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 323 deletions.
21 changes: 21 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
season range highlighting like sports-reference on click highlight
- could compute stats in worker with seasonRange
- first click - highlight every row for that season
- second click - highlight every row for that season, and seasons in between
- should work regardless of sorting - still pick by season
- need to control table row highlight state outside of the table
- use normal row click feature, or build own and just pass a class down to each row? the latter might be easier
- mobile UI
- when to clear rows, and how to handle multiple clicks?
- b-r only clears when you explicitly click a button in the popup
- alternate UIs
- pop up a modal after clicking the 2nd year, and have the years in the modal show as dropdowns for changing to other ranges
- button to open the modal, default range is whole career
- have another row below the career totals that lets you select a range
- advantages of this: discoverable, no weird floating window, no confusing way to change a range after initial selection
- test
- baseball fielding stats, or disable
- career highs
- per game
- totals

show career totals per team at bottom of stats tables in player profile pages
- add some extra option to playersPlus like careerStatsPerTeam

retire players button in god mode, along with delete players in bulk actions https://discord.com/channels/290013534023057409/1333871313898573904/1333884981835075584

Expand Down
291 changes: 291 additions & 0 deletions src/ui/views/Player/StatsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import { useState } from "react";
import type { View } from "../../../common/types";
import { getCols, helpers } from "../../util";
import { isSport } from "../../../common";
import { highlightLeaderText, MaybeBold, SeasonLink } from "./common";
import { expandFieldingStats } from "../../util/expandFieldingStats.baseball";
import TeamAbbrevLink from "../../components/TeamAbbrevLink";
import { formatStatGameHigh } from "../PlayerStats";
import SeasonIcons from "./SeasonIcons";
import HideableSection from "../../components/HideableSection";
import { DataTable } from "../../components";
import clsx from "clsx";

export const StatsTable = ({
name,
onlyShowIf,
p,
stats,
superCols,
leaders,
}: {
name: string;
onlyShowIf?: string[];
p: View<"player">["player"];
stats: string[];
superCols?: any[];
leaders: View<"player">["leaders"];
}) => {
const hasRegularSeasonStats = p.careerStats.gp > 0;
const hasPlayoffStats = p.careerStatsPlayoffs.gp > 0;

// Show playoffs by default if that's all we have
const [playoffs, setPlayoffs] = useState<boolean | "combined">(
!hasRegularSeasonStats,
);

// If game sim means we switch from having no stats to having some stats, make sure we're showing what we have
if (hasRegularSeasonStats && !hasPlayoffStats && playoffs === true) {
setPlayoffs(false);
}
if (!hasRegularSeasonStats && hasPlayoffStats && playoffs === false) {
setPlayoffs(true);
}

if (!hasRegularSeasonStats && !hasPlayoffStats) {
return null;
}

let playerStats = p.stats.filter((ps) => ps.playoffs === playoffs);
const careerStats =
playoffs === "combined"
? p.careerStatsCombined
: playoffs
? p.careerStatsPlayoffs
: p.careerStats;

if (onlyShowIf !== undefined) {
let display = false;
for (const stat of onlyShowIf) {
if (
careerStats[stat] > 0 ||
(Array.isArray(careerStats[stat]) &&
(careerStats[stat] as any).length > 0)
) {
display = true;
break;
}
}

if (!display) {
return null;
}
}

const cols = getCols([
"Year",
"Team",
"Age",
...stats.map((stat) =>
stat === "pos"
? "Pos"
: `stat:${stat.endsWith("Max") ? stat.replace("Max", "") : stat}`,
),
]);

if (superCols) {
superCols = helpers.deepCopy(superCols);

// No name
superCols[0].colspan -= 1;
}

if (isSport("basketball") && name === "Shot Locations") {
cols.at(-3)!.title = "M";
cols.at(-2)!.title = "A";
cols.at(-1)!.title = "%";
}

let footer;
if (isSport("baseball") && name === "Fielding") {
playerStats = expandFieldingStats({
rows: playerStats,
stats,
});

footer = expandFieldingStats({
rows: [careerStats],
stats,
addDummyPosIndex: true,
}).map((object, i) => [
i === 0 ? "Career" : null,
null,
null,
...stats.map((stat) => formatStatGameHigh(object, stat)),
]);
} else {
footer = [
"Career",
null,
null,
...stats.map((stat) => formatStatGameHigh(careerStats, stat)),
];
}

const leadersType =
playoffs === "combined"
? "combined"
: playoffs === true
? "playoffs"
: "regularSeason";

let hasLeader = false;
if (leadersType) {
LEADERS_LOOP: for (const row of Object.values(leaders)) {
if (row?.attrs.has("age")) {
hasLeader = true;
break;
}

for (const stat of stats) {
if (row?.[leadersType].has(stat)) {
hasLeader = true;
break LEADERS_LOOP;
}
}
}
}

const rows = [];

let prevSeason;
for (let i = 0; i < playerStats.length; i++) {
const ps = playerStats[i];

// Add blank rows for gap years if necessary
if (prevSeason !== undefined && prevSeason < ps.season - 1) {
const gapSeason = prevSeason + 1;

rows.push({
key: `gap-${gapSeason}`,
data: [
{
searchValue: gapSeason,

// i is used to index other sorts, so we need to fit in between
sortValue: i - 0.5,

value: null,
},
null,
null,
...stats.map(() => null),
],
classNames: "table-secondary",
});
}

prevSeason = ps.season;

const className = ps.hasTot ? "text-body-secondary" : undefined;

rows.push({
key: i,
data: [
{
searchValue: ps.season,
sortValue: i,
value: (
<>
<SeasonLink
className={className}
pid={p.pid}
season={ps.season}
/>{" "}
<SeasonIcons
season={ps.season}
awards={p.awards}
playoffs={playoffs === true}
/>
</>
),
},
<TeamAbbrevLink
abbrev={ps.abbrev}
className={className}
season={ps.season}
tid={ps.tid}
/>,
<MaybeBold bold={leaders[ps.season]?.attrs.has("age")}>
{ps.age}
</MaybeBold>,
...stats.map((stat) => (
<MaybeBold
bold={!ps.hasTot && leaders[ps.season]?.[leadersType].has(stat)}
>
{formatStatGameHigh(ps, stat)}
</MaybeBold>
)),
],
classNames: className,
});
}

return (
<HideableSection
title={name}
description={hasLeader ? highlightLeaderText : null}
>
<DataTable
className="mb-3"
cols={cols}
defaultSort={[0, "asc"]}
defaultStickyCols={2}
footer={footer}
hideAllControls
name={`Player:${name}`}
rows={rows}
superCols={superCols}
title={
<ul className="nav nav-tabs border-bottom-0">
{hasRegularSeasonStats ? (
<li className="nav-item">
<button
className={clsx("nav-link", {
active: playoffs === false,
"border-bottom": playoffs === false,
})}
onClick={() => {
setPlayoffs(false);
}}
>
Regular Season
</button>
</li>
) : null}
{hasPlayoffStats ? (
<li className="nav-item">
<button
className={clsx("nav-link", {
active: playoffs === true,
"border-bottom": playoffs === true,
})}
onClick={() => {
setPlayoffs(true);
}}
>
Playoffs
</button>
</li>
) : null}
{hasRegularSeasonStats && hasPlayoffStats ? (
<li className="nav-item">
<button
className={clsx("nav-link", {
active: playoffs === "combined",
"border-bottom": playoffs === "combined",
})}
onClick={() => {
setPlayoffs("combined");
}}
>
Combined
</button>
</li>
) : null}
</ul>
}
/>
</HideableSection>
);
};
41 changes: 41 additions & 0 deletions src/ui/views/Player/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { JSX, ReactNode } from "react";
import { helpers } from "../../util";

export const SeasonLink = ({
className,
pid,
season,
}: {
className?: string;
pid: number;
season: number;
}) => {
return (
<a
className={className}
href={helpers.leagueUrl(["player_game_log", pid, season])}
>
{season}
</a>
);
};

export const highlightLeaderText = (
<>
<span className="highlight-leader">Bold</span> indicates league leader
</>
);

export const MaybeBold = ({
bold,
children,
}: {
bold: boolean | undefined;
children: ReactNode;
}) => {
if (bold) {
return <span className="highlight-leader">{children}</span>;
}

return children as JSX.Element;
};
Loading

0 comments on commit 5f620fc

Please sign in to comment.