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
This commit is contained in:
parent
6c3065f6f2
commit
cee9469f48
4 changed files with 103 additions and 27 deletions
|
|
@ -236,19 +236,25 @@ 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.
|
||||
const totalCols = showSoccerTable
|
||||
? 11
|
||||
: 5 + 1 /* GP */ + (showOtLosses ? 2 : 0) + 1 /* PCT */ + (hasGB ? 1 : 0) + (hasLastTen ? 1 : 0) + (hasStreak ? 1 : 0);
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto -mx-6 px-6">
|
||||
<table className="w-full min-w-[740px] text-sm">
|
||||
<table className="w-full min-w-[360px] text-sm">
|
||||
<thead>
|
||||
<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="text-right py-1.5 px-2 w-10">GP</th>
|
||||
<th className="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>
|
||||
|
|
@ -258,10 +264,10 @@ function StandingsTable({
|
|||
{showSoccerTable && <th className="text-right py-1.5 px-2 w-10">PTS</th>}
|
||||
{showOtLosses && <th className="text-right py-1.5 px-2 w-10">OTL</th>}
|
||||
{showOtLosses && <th className="text-right py-1.5 px-2 w-10">PTS</th>}
|
||||
{!showSoccerTable && <th className="text-right py-1.5 px-2 w-12">PCT</th>}
|
||||
{!showSoccerTable && <th className="text-right py-1.5 px-2 w-10">GB</th>}
|
||||
{!showSoccerTable && <th className="text-right py-1.5 px-2 w-12">L10</th>}
|
||||
{!showSoccerTable && <th className="text-right py-1.5 px-2 w-12">STK</th>}
|
||||
{!showSoccerTable && <th className="hidden sm:table-cell text-right py-1.5 px-2 w-12">PCT</th>}
|
||||
{!showSoccerTable && hasGB && <th className="hidden sm:table-cell text-right py-1.5 px-2 w-10">GB</th>}
|
||||
{!showSoccerTable && hasLastTen && <th className="hidden sm:table-cell text-right py-1.5 px-2 w-12">L10</th>}
|
||||
{!showSoccerTable && hasStreak && <th className="text-right py-1.5 px-2 w-12">STK</th>}
|
||||
<th className="text-right py-1.5 pl-4 w-40">Mgr</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -308,16 +314,19 @@ 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={`border-b border-border/50 last:border-0 ${
|
||||
className={`${hasSecondaryStats ? "" : "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">
|
||||
<span className={`font-medium ${isUserTeam ? "text-primary" : ""}`}>
|
||||
<td className="py-2 min-w-0">
|
||||
<span className={`font-medium truncate ${isUserTeam ? "text-primary" : ""}`}>
|
||||
{row.participant.shortName ?? row.participant.name}
|
||||
</span>
|
||||
{section.showDivisionLabel && row.division && (
|
||||
|
|
@ -326,7 +335,7 @@ function StandingsTable({
|
|||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
<td className="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>
|
||||
|
|
@ -355,21 +364,21 @@ function StandingsTable({
|
|||
</td>
|
||||
)}
|
||||
{!showSoccerTable && (
|
||||
<td className="py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
<td className="hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
{formatWinPct(row.winPct, row.wins, row.gamesPlayed)}
|
||||
</td>
|
||||
)}
|
||||
{!showSoccerTable && (
|
||||
<td className="py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
{!showSoccerTable && hasGB && (
|
||||
<td className="hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
{formatGB(row.gamesBack)}
|
||||
</td>
|
||||
)}
|
||||
{!showSoccerTable && (
|
||||
<td className="py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
{!showSoccerTable && hasLastTen && (
|
||||
<td className="hidden sm:table-cell py-2 px-2 text-right tabular-nums text-muted-foreground">
|
||||
{row.lastTen ?? "—"}
|
||||
</td>
|
||||
)}
|
||||
{!showSoccerTable && (
|
||||
{!showSoccerTable && hasStreak && (
|
||||
<td className="py-2 px-2 text-right tabular-nums">
|
||||
{row.streak ? (
|
||||
<span
|
||||
|
|
@ -401,6 +410,43 @@ function StandingsTable({
|
|||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
// Secondary stats sub-row for mobile (hidden on sm+)
|
||||
if (hasSecondaryStats) {
|
||||
sectionRows.push(
|
||||
<tr
|
||||
key={`${row.id}-sub`}
|
||||
className={`sm:hidden border-b border-border/50 last:border-0 ${
|
||||
isUserTeam ? "bg-primary/5" : "hover:bg-muted/30"
|
||||
}`}
|
||||
>
|
||||
<td colSpan={totalCols} className="pb-2 pt-0 pr-2">
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-muted-foreground pl-4">
|
||||
<span>
|
||||
<span className="uppercase tracking-wide">GP</span>{" "}
|
||||
<span className="tabular-nums text-foreground">{row.gamesPlayed}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="uppercase tracking-wide">PCT</span>{" "}
|
||||
<span className="tabular-nums text-foreground">{formatWinPct(row.winPct, row.wins, row.gamesPlayed)}</span>
|
||||
</span>
|
||||
{hasGB && (
|
||||
<span>
|
||||
<span className="uppercase tracking-wide">GB</span>{" "}
|
||||
<span className="tabular-nums text-foreground">{formatGB(row.gamesBack)}</span>
|
||||
</span>
|
||||
)}
|
||||
{hasLastTen && (
|
||||
<span>
|
||||
<span className="uppercase tracking-wide">L10</span>{" "}
|
||||
<span className="tabular-nums text-foreground">{row.lastTen ?? "—"}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return sectionRows;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,38 @@ describe("RegularSeasonStandings", () => {
|
|||
expect(screen.queryByText("OTL")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides STK column when no rows have streak data", () => {
|
||||
render(
|
||||
<RegularSeasonStandings
|
||||
standings={[makeRow({ streak: null }), makeRow({ id: "row-2", participantId: "p-2", streak: null, participant: { id: "p-2", name: "Toronto Raptors" } })]}
|
||||
teamOwnerships={NO_OWNERSHIPS}
|
||||
userParticipantIds={NO_USER_IDS}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText("STK")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows STK column when at least one row has streak data", () => {
|
||||
render(
|
||||
<RegularSeasonStandings
|
||||
standings={[makeRow({ streak: "W3" }), makeRow({ id: "row-2", participantId: "p-2", streak: null, participant: { id: "p-2", name: "Toronto Raptors" } })]}
|
||||
teamOwnerships={NO_OWNERSHIPS}
|
||||
userParticipantIds={NO_USER_IDS}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("STK")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides L10 column when no rows have lastTen data", () => {
|
||||
render(
|
||||
<RegularSeasonStandings
|
||||
standings={[makeRow({ lastTen: null })]}
|
||||
teamOwnerships={NO_OWNERSHIPS}
|
||||
userParticipantIds={NO_USER_IDS}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText("L10")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows soccer table columns when showSoccerTable=true", () => {
|
||||
render(
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -115,10 +115,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;
|
||||
// Streak: streakCode from the MLB API already contains the full string e.g. "W3" or "L2"
|
||||
const streak = team.streak?.streakCode ?? undefined;
|
||||
|
||||
// Split records
|
||||
const splits = team.records?.splitRecords ?? [];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue