From 08e93e955ac4456767ec115bc21a5aaf20bef77b Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Tue, 12 May 2026 08:14:34 -0700 Subject: [PATCH] Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout (#413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix MLB streak doubling bug, hide empty standings columns, improve mobile layout - mlb.ts: use streakCode alone (the API already returns the full string like "W3"); appending streakNumber was doubling the digit, causing "L33" display - Update mlb.test.ts mocks to match real API format (streakCode "W3" not "W") - RegularSeasonStandings: conditionally hide GB/L10/STK columns when no rows have data, so pre-season or stats-free sports don't show blank columns - Mobile two-row layout: GP/PCT/GB/L10 move to a secondary sub-row (sm:hidden) so all data stays visible without horizontal scroll on small screens; STK and W/L remain on the primary row; reduce min-w from 740px to 360px https://claude.ai/code/session_01RADi3LhYMPbRDm5no1ZpdF * 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 --------- Co-authored-by: Claude --- .../sport-season/RegularSeasonStandings.tsx | 84 +++++++++++++++---- .../__tests__/RegularSeasonStandings.test.tsx | 43 ++++++++++ .../standings-sync/__tests__/mlb.test.ts | 6 +- app/services/standings-sync/mlb.ts | 11 +-- 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/app/components/sport-season/RegularSeasonStandings.tsx b/app/components/sport-season/RegularSeasonStandings.tsx index 8bd5b4d..8974f04 100644 --- a/app/components/sport-season/RegularSeasonStandings.tsx +++ b/app/components/sport-season/RegularSeasonStandings.tsx @@ -236,19 +236,31 @@ function StandingsTable({ showOtLosses: boolean; showSoccerTable?: boolean; }) { - // # Team GP W L [OTL] [PTS] PCT GB L10 STK Mgr - // OTL and PTS are both shown for hockey (showOtLosses = true). - // Soccer table mode shows D/GF/GA/GD/Pts instead of PCT/GB/form columns. - const totalCols = showSoccerTable ? 11 : 10 + (showOtLosses ? 2 : 0); + const allRows = sections.flatMap((s) => s.rows); + const hasStreak = allRows.some((r) => r.streak != null); + const hasLastTen = allRows.some((r) => r.lastTen != null); + const hasGB = allRows.some((r) => r.gamesBack != null); + + // # 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 (
- +
- + {showSoccerTable && } @@ -258,10 +270,10 @@ function StandingsTable({ {showSoccerTable && } {showOtLosses && } {showOtLosses && } - {!showSoccerTable && } - {!showSoccerTable && } - {!showSoccerTable && } - {!showSoccerTable && } + {!showSoccerTable && } + {!showSoccerTable && hasGB && } + {!showSoccerTable && hasLastTen && } + {!showSoccerTable && hasStreak && } @@ -311,7 +323,7 @@ function StandingsTable({ sectionRows.push( @@ -326,7 +338,7 @@ function StandingsTable({ )} - @@ -355,21 +367,21 @@ function StandingsTable({ )} {!showSoccerTable && ( - )} - {!showSoccerTable && ( - )} - {!showSoccerTable && ( - )} - {!showSoccerTable && ( + {!showSoccerTable && hasStreak && ( ); + + if (showSubRow) { + sectionRows.push( + + + + ); + } }); return sectionRows; diff --git a/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx b/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx index 4d9e681..3911fd7 100644 --- a/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx +++ b/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx @@ -109,6 +109,49 @@ describe("RegularSeasonStandings", () => { expect(screen.queryByText("OTL")).not.toBeInTheDocument(); }); + it("hides STK column when no rows have streak data", () => { + render( + + ); + expect(screen.queryByText("STK")).not.toBeInTheDocument(); + }); + + it("shows STK column when at least one row has streak data", () => { + render( + + ); + expect(screen.getByText("STK")).toBeInTheDocument(); + }); + + it("hides L10 column when no rows have lastTen data", () => { + render( + + ); + expect(screen.queryByText("L10")).not.toBeInTheDocument(); + }); + + it("hides GB column when no rows have gamesBack data", () => { + render( + + ); + expect(screen.queryByText("GB")).not.toBeInTheDocument(); + }); it("shows soccer table columns when showSoccerTable=true", () => { render( diff --git a/app/services/standings-sync/__tests__/mlb.test.ts b/app/services/standings-sync/__tests__/mlb.test.ts index 119cc83..0968412 100644 --- a/app/services/standings-sync/__tests__/mlb.test.ts +++ b/app/services/standings-sync/__tests__/mlb.test.ts @@ -20,7 +20,7 @@ const SAMPLE_MLB_RESPONSE = { divisionRank: "1", leagueRank: "1", gamesBack: "-", - streak: { streakCode: "W", streakNumber: 3 }, + streak: { streakCode: "W3", streakNumber: 3 }, records: { splitRecords: [ { type: "home", wins: 24, losses: 15 }, @@ -43,7 +43,7 @@ const SAMPLE_MLB_RESPONSE = { divisionRank: "2", leagueRank: "4", gamesBack: "5.0", - streak: { streakCode: "L", streakNumber: 2 }, + streak: { streakCode: "L2", streakNumber: 2 }, records: { splitRecords: [ { type: "home", wins: 22, losses: 18 }, @@ -71,7 +71,7 @@ const SAMPLE_MLB_RESPONSE = { divisionRank: "1", leagueRank: "1", gamesBack: "-", - streak: { streakCode: "W", streakNumber: 1 }, + streak: { streakCode: "W1", streakNumber: 1 }, records: { splitRecords: [ { type: "home", wins: 22, losses: 17 }, diff --git a/app/services/standings-sync/mlb.ts b/app/services/standings-sync/mlb.ts index 4bc90ba..c84711e 100644 --- a/app/services/standings-sync/mlb.ts +++ b/app/services/standings-sync/mlb.ts @@ -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,10 +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: e.g. "W3" or "L2" - const streak = team.streak - ? `${team.streak.streakCode}${team.streak.streakNumber}` - : 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 ?? [];
# TeamGPGP WDLPTSOTLPTSPCTGBL10STKPCTGBL10STKMgr
+ {row.gamesPlayed} {row.wins} + {formatWinPct(row.winPct, row.wins, row.gamesPlayed)} + {!showSoccerTable && hasGB && ( + {formatGB(row.gamesBack)} + {!showSoccerTable && hasLastTen && ( + {row.lastTen ?? "—"} {row.streak ? (
+
+ + GP{" "} + {row.gamesPlayed} + + + PCT{" "} + {formatWinPct(row.winPct, row.wins, row.gamesPlayed)} + + {hasGB && ( + + GB{" "} + {formatGB(row.gamesBack)} + + )} + {hasLastTen && ( + + L10{" "} + {row.lastTen ?? "—"} + + )} +
+