Address code review: cleanup mlb streak type, fix soccer GP on mobile, hoist showSubRow

- mlb.ts: drop redundant `?? undefined` after optional chain; document that
  streakCode is the full string (e.g. "W3") and streakNumber is unused
- RegularSeasonStandings: hoist hasSecondaryStats → showSubRow to component
  level (it only depends on a prop, not on individual row data)
- Remove non-functional `truncate`/`min-w-0` from team name cell — truncation
  requires table-layout:fixed which we don't use; team names size naturally
- Soccer GP was hidden on mobile with no sub-row to surface it; GP now shows
  inline for soccer on all viewports, hidden only for non-soccer (which has
  the secondary sub-row)
- Add comment explaining totalCols counts hidden-on-mobile columns for colSpan
- Add missing test for GB column hiding when no rows have gamesBack data

https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF
This commit is contained in:
Claude 2026-05-12 14:57:16 +00:00
parent cee9469f48
commit 5af507f234
No known key found for this signature in database
3 changed files with 29 additions and 13 deletions

View file

@ -243,10 +243,16 @@ function StandingsTable({
// # Team GP W [D] L [GF GA GD PTS] [OTL PTS] [PCT] [GB] [L10] [STK] Mgr
// Soccer: 11 fixed columns. Non-soccer: 5 base + GP + optional cols.
// GP and PCT are always counted even when hidden on mobile; colSpan must
// reflect all rendered columns regardless of CSS visibility.
const totalCols = showSoccerTable
? 11
: 5 + 1 /* GP */ + (showOtLosses ? 2 : 0) + 1 /* PCT */ + (hasGB ? 1 : 0) + (hasLastTen ? 1 : 0) + (hasStreak ? 1 : 0);
// Non-soccer rows get a secondary sub-row on mobile showing GP/PCT/GB/L10.
// Soccer tables show GP inline (no sub-row) since they already show many columns.
const showSubRow = !showSoccerTable;
return (
<div className="overflow-x-auto -mx-6 px-6">
<table className="w-full min-w-[360px] text-sm">
@ -254,7 +260,7 @@ function StandingsTable({
<tr className="text-xs text-muted-foreground uppercase tracking-wide border-b">
<th className="text-left py-1.5 pr-2 w-6">#</th>
<th className="text-left py-1.5">Team</th>
<th className="hidden sm:table-cell text-right py-1.5 px-2 w-10">GP</th>
<th className={`${showSubRow ? "hidden sm:table-cell " : ""}text-right py-1.5 px-2 w-10`}>GP</th>
<th className="text-right py-1.5 px-2 w-8">W</th>
{showSoccerTable && <th className="text-right py-1.5 px-2 w-8">D</th>}
<th className="text-right py-1.5 px-2 w-8">L</th>
@ -314,19 +320,16 @@ function StandingsTable({
const isUserTeam = userParticipantIds.includes(row.participantId);
const ownership = teamOwnerships[row.participantId];
// Non-soccer rows get a secondary sub-row on mobile showing GP/PCT/GB/L10
const hasSecondaryStats = !showSoccerTable;
sectionRows.push(
<tr
key={row.id}
className={`${hasSecondaryStats ? "" : "border-b border-border/50 last:border-0"} ${
className={`${showSubRow ? "" : "border-b border-border/50 last:border-0"} ${
isUserTeam ? "bg-primary/5" : "hover:bg-muted/30"
}`}
>
<td className="py-2 pr-2 text-muted-foreground tabular-nums">{rank}</td>
<td className="py-2 min-w-0">
<span className={`font-medium truncate ${isUserTeam ? "text-primary" : ""}`}>
<td className="py-2">
<span className={`font-medium ${isUserTeam ? "text-primary" : ""}`}>
{row.participant.shortName ?? row.participant.name}
</span>
{section.showDivisionLabel && row.division && (
@ -335,7 +338,7 @@ function StandingsTable({
</span>
)}
</td>
<td className="hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground">
<td className={`${showSubRow ? "hidden sm:table-cell " : ""}py-2 px-2 text-right tabular-nums text-muted-foreground`}>
{row.gamesPlayed}
</td>
<td className="py-2 px-2 text-right tabular-nums font-medium">{row.wins}</td>
@ -411,8 +414,7 @@ function StandingsTable({
</tr>
);
// Secondary stats sub-row for mobile (hidden on sm+)
if (hasSecondaryStats) {
if (showSubRow) {
sectionRows.push(
<tr
key={`${row.id}-sub`}

View file

@ -142,6 +142,17 @@ describe("RegularSeasonStandings", () => {
expect(screen.queryByText("L10")).not.toBeInTheDocument();
});
it("hides GB column when no rows have gamesBack data", () => {
render(
<RegularSeasonStandings
standings={[makeRow({ gamesBack: null })]}
teamOwnerships={NO_OWNERSHIPS}
userParticipantIds={NO_USER_IDS}
/>
);
expect(screen.queryByText("GB")).not.toBeInTheDocument();
});
it("shows soccer table columns when showSoccerTable=true", () => {
render(
<RegularSeasonStandings

View file

@ -27,7 +27,10 @@ interface MlbTeamRecord {
leagueRank?: string; // rank within the 15-team AL or NL
gamesBack: string; // "-" for leader, "2.0" otherwise
wildCardGamesBack?: string;
streak?: { streakCode: string; streakNumber: number };
streak?: {
streakCode: string; // full streak string e.g. "W3" or "L5" — NOT just the letter
streakNumber: number; // numeric part; redundant with streakCode, not used
};
records?: { splitRecords?: MlbSplitRecord[] };
}
@ -115,8 +118,8 @@ export class MlbStandingsAdapter implements StandingsSyncAdapter {
// Conference rank: use the per-league rank computed above
const conferenceRank = conferenceRankMap.get(team.team.id) ?? null;
// Streak: streakCode from the MLB API already contains the full string e.g. "W3" or "L2"
const streak = team.streak?.streakCode ?? undefined;
// streakCode is the full string e.g. "W3"; streakNumber is the numeric part embedded within it
const streak = team.streak?.streakCode;
// Split records
const splits = team.records?.splitRecords ?? [];