Fix World Cup group stage display and upcoming events #55
7 changed files with 171 additions and 40 deletions
|
|
@ -63,14 +63,17 @@ function MatchResult({ match }: { match: GroupMatch }) {
|
||||||
|
|
||||||
const kickoff = match.scheduledAt ? new Date(match.scheduledAt) : null;
|
const kickoff = match.scheduledAt ? new Date(match.scheduledAt) : null;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
<div className="flex flex-col gap-0">
|
||||||
<span className="truncate max-w-[80px]">{p1}</span>
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<span className="shrink-0 text-muted-foreground/60">
|
<span className="truncate max-w-[80px]">{p1}</span>
|
||||||
{kickoff
|
<span className="shrink-0 text-muted-foreground/60">vs</span>
|
||||||
? kickoff.toLocaleDateString("en-US", { month: "short", day: "numeric" })
|
<span className="truncate max-w-[80px]">{p2}</span>
|
||||||
: "vs"}
|
</div>
|
||||||
</span>
|
{kickoff && (
|
||||||
<span className="truncate max-w-[80px] text-right">{p2}</span>
|
<span className="text-[10px] text-muted-foreground/50">
|
||||||
|
{kickoff.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -159,24 +162,36 @@ export function GroupStageStandings({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Match results */}
|
{/* Match results */}
|
||||||
{group.matches.length > 0 && (
|
{group.matches.length > 0 && (() => {
|
||||||
<div className="px-4 pb-3 space-y-0.5 border-t pt-2">
|
const sorted = group.matches.toSorted((a, b) => {
|
||||||
{[1, 2, 3].map((day) => {
|
if (!a.scheduledAt && !b.scheduledAt) return 0;
|
||||||
const dayMatches = group.matches.filter((m) => m.matchday === day);
|
if (!a.scheduledAt) return 1;
|
||||||
if (dayMatches.length === 0) return null;
|
if (!b.scheduledAt) return -1;
|
||||||
return (
|
return new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime();
|
||||||
|
});
|
||||||
|
const byDay = new Map<string, GroupMatch[]>();
|
||||||
|
for (const m of sorted) {
|
||||||
|
const key = m.scheduledAt
|
||||||
|
? new Date(m.scheduledAt).toLocaleDateString("en-US", { month: "short", day: "numeric" })
|
||||||
|
: "TBD";
|
||||||
|
if (!byDay.has(key)) byDay.set(key, []);
|
||||||
|
byDay.get(key)?.push(m);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="px-4 pb-3 space-y-0.5 border-t pt-2">
|
||||||
|
{[...byDay.entries()].map(([day, matches]) => (
|
||||||
<div key={day} className="space-y-0.5">
|
<div key={day} className="space-y-0.5">
|
||||||
<p className="text-[10px] text-muted-foreground/70 uppercase tracking-wide pt-1">
|
<p className="text-[10px] text-muted-foreground/70 uppercase tracking-wide pt-1">
|
||||||
MD {day}
|
{day}
|
||||||
</p>
|
</p>
|
||||||
{dayMatches.map((m) => (
|
{matches.map((m) => (
|
||||||
<MatchResult key={m.id} match={m} />
|
<MatchResult key={m.id} match={m} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
))}
|
||||||
})}
|
</div>
|
||||||
</div>
|
);
|
||||||
)}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ function EventRow({ event, showLeague }: { event: CalendarPanelEvent; showLeague
|
||||||
? format(gameDate, "MMM d")
|
? format(gameDate, "MMM d")
|
||||||
: formatEventDate(event.eventDate);
|
: formatEventDate(event.eventDate);
|
||||||
const participantCount = event.relevantParticipants.length;
|
const participantCount = event.relevantParticipants.length;
|
||||||
const isAllCompete = event.eventType !== "playoff_game";
|
const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match";
|
||||||
const displayName = event.matchLabel
|
const displayName = event.matchLabel
|
||||||
? `${event.name} — ${event.matchLabel}`
|
? `${event.name} — ${event.matchLabel}`
|
||||||
: event.name;
|
: event.name;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ function groupEvents(events: CalendarPanelEvent[]): GroupedEvent[] {
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const existing = map.get(event.id);
|
const existing = map.get(event.id);
|
||||||
const isAllCompete = event.eventType !== "playoff_game";
|
const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match";
|
||||||
|
|
||||||
const leagueEntry: LeagueParticipants = {
|
const leagueEntry: LeagueParticipants = {
|
||||||
leagueId: event.leagueId ?? "unknown",
|
leagueId: event.leagueId ?? "unknown",
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
const mockDb = {
|
const mockDb = {
|
||||||
query: {
|
query: {
|
||||||
scoringEvents: { findMany: vi.fn() },
|
scoringEvents: { findMany: vi.fn() },
|
||||||
|
seasonParticipants: { findMany: vi.fn() },
|
||||||
},
|
},
|
||||||
selectDistinct: vi.fn(),
|
selectDistinct: vi.fn(),
|
||||||
|
select: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.mock("~/database/context", () => ({
|
vi.mock("~/database/context", () => ({
|
||||||
|
|
@ -22,11 +24,24 @@ vi.mock("~/database/context", () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("~/database/schema", () => ({
|
vi.mock("~/database/schema", () => ({
|
||||||
scoringEvents: { sportsSeasonId: "se.sports_season_id", isComplete: "se.is_complete", eventDate: "se.event_date", eventType: "se.event_type" },
|
scoringEvents: { sportsSeasonId: "se.sports_season_id", isComplete: "se.is_complete", eventDate: "se.event_date", eventType: "se.event_type", name: "se.name" },
|
||||||
playoffMatches: { id: "pm.id", scoringEventId: "pm.scoring_event_id", participant1Id: "pm.participant1_id", participant2Id: "pm.participant2_id", round: "pm.round", matchNumber: "pm.match_number", isComplete: "pm.is_complete" },
|
playoffMatches: { id: "pm.id", scoringEventId: "pm.scoring_event_id", participant1Id: "pm.participant1_id", participant2Id: "pm.participant2_id", round: "pm.round", matchNumber: "pm.match_number", isComplete: "pm.is_complete" },
|
||||||
playoffMatchGames: { playoffMatchId: "pmg.playoff_match_id", scheduledAt: "pmg.scheduled_at", gameNumber: "pmg.game_number" },
|
playoffMatchGames: { playoffMatchId: "pmg.playoff_match_id", scheduledAt: "pmg.scheduled_at", gameNumber: "pmg.game_number" },
|
||||||
|
groupStageMatches: { id: "gsm.id", tournamentGroupId: "gsm.tournament_group_id", participant1Id: "gsm.participant1_id", participant2Id: "gsm.participant2_id", scheduledAt: "gsm.scheduled_at", isComplete: "gsm.is_complete" },
|
||||||
|
tournamentGroups: { id: "tg.id", groupName: "tg.group_name", scoringEventId: "tg.scoring_event_id" },
|
||||||
|
seasonParticipants: { id: "sp.id" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Chain helper for the group stage db.select() query (resolves at orderBy).
|
||||||
|
function makeGroupStageSelectChain(rows: unknown[]) {
|
||||||
|
return {
|
||||||
|
from: vi.fn().mockReturnThis(),
|
||||||
|
innerJoin: vi.fn().mockReturnThis(),
|
||||||
|
where: vi.fn().mockReturnThis(),
|
||||||
|
orderBy: vi.fn().mockResolvedValue(rows),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
vi.mock("drizzle-orm", () => ({
|
vi.mock("drizzle-orm", () => ({
|
||||||
eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }),
|
eq: (col: unknown, val: unknown) => ({ type: "eq", col, val }),
|
||||||
and: (...args: unknown[]) => ({ type: "and", args }),
|
and: (...args: unknown[]) => ({ type: "and", args }),
|
||||||
|
|
@ -70,8 +85,11 @@ function makeScoringEvent(overrides: Partial<{
|
||||||
describe("getUpcomingEventsForDraftedParticipants — all-compete sports", () => {
|
describe("getUpcomingEventsForDraftedParticipants — all-compete sports", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
// selectDistinct won't be called for all-compete
|
// selectDistinct won't be called for all-compete; default for the "unknown
|
||||||
mockDb.selectDistinct.mockReturnValue({ from: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), orderBy: vi.fn().mockResolvedValue([]) });
|
// pattern" test which falls through to the bracket path.
|
||||||
|
mockDb.selectDistinct.mockReturnValue({ from: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(), leftJoin: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), orderBy: vi.fn().mockResolvedValue([]) });
|
||||||
|
mockDb.select.mockReturnValue(makeGroupStageSelectChain([]));
|
||||||
|
mockDb.query.seasonParticipants.findMany.mockResolvedValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns [] when draftedParticipants is empty", async () => {
|
it("returns [] when draftedParticipants is empty", async () => {
|
||||||
|
|
@ -169,6 +187,9 @@ describe("getUpcomingEventsForDraftedParticipants — bracket sports", () => {
|
||||||
// Default for the second selectDistinct call (max-game-number lookup).
|
// Default for the second selectDistinct call (max-game-number lookup).
|
||||||
// Individual tests override the first call with mockReturnValueOnce.
|
// Individual tests override the first call with mockReturnValueOnce.
|
||||||
mockDb.selectDistinct.mockReturnValue(makeMaxGameChain([]));
|
mockDb.selectDistinct.mockReturnValue(makeMaxGameChain([]));
|
||||||
|
// Default: no group stage matches (db.select used by the group stage query).
|
||||||
|
mockDb.select.mockReturnValue(makeGroupStageSelectChain([]));
|
||||||
|
mockDb.query.seasonParticipants.findMany.mockResolvedValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns [] when draftedParticipants is empty", async () => {
|
it("returns [] when draftedParticipants is empty", async () => {
|
||||||
|
|
@ -624,7 +645,12 @@ describe("getUpcomingEventsForDraftedParticipants — bracket misc", () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => vi.clearAllMocks());
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
// Default: no group stage matches.
|
||||||
|
mockDb.select.mockReturnValue(makeGroupStageSelectChain([]));
|
||||||
|
mockDb.query.seasonParticipants.findMany.mockResolvedValue([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("excludes scored (isComplete) matches by passing the filter to the DB query", async () => {
|
it("excludes scored (isComplete) matches by passing the filter to the DB query", async () => {
|
||||||
// The DB mock returns rows only for matches the WHERE clause matches.
|
// The DB mock returns rows only for matches the WHERE clause matches.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { and, asc, eq, gte, inArray, lte, or } from "drizzle-orm";
|
import { and, asc, eq, gte, inArray, lte, or, sql } from "drizzle-orm";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { logger } from "~/lib/logger";
|
import { logger } from "~/lib/logger";
|
||||||
|
|
@ -146,7 +146,7 @@ export async function findMatchesByGroupId(groupId: string) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all matches for multiple groups in a single query.
|
* Fetch all matches for multiple groups in a single query.
|
||||||
* Returns a Map from groupId → matches (ordered by matchday, createdAt).
|
* Returns a Map from groupId → matches (ordered by scheduledAt, createdAt).
|
||||||
* Use this instead of calling findMatchesByGroupId N times.
|
* Use this instead of calling findMatchesByGroupId N times.
|
||||||
*/
|
*/
|
||||||
export async function findMatchesByGroupIds(
|
export async function findMatchesByGroupIds(
|
||||||
|
|
@ -157,7 +157,7 @@ export async function findMatchesByGroupIds(
|
||||||
const rows = await db.query.groupStageMatches.findMany({
|
const rows = await db.query.groupStageMatches.findMany({
|
||||||
where: inArray(schema.groupStageMatches.tournamentGroupId, groupIds),
|
where: inArray(schema.groupStageMatches.tournamentGroupId, groupIds),
|
||||||
orderBy: [
|
orderBy: [
|
||||||
asc(schema.groupStageMatches.matchday),
|
sql`${schema.groupStageMatches.scheduledAt} ASC NULLS LAST`,
|
||||||
asc(schema.groupStageMatches.createdAt),
|
asc(schema.groupStageMatches.createdAt),
|
||||||
],
|
],
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -183,7 +183,7 @@ export async function findMatchesByEventId(eventId: string) {
|
||||||
with: {
|
with: {
|
||||||
matches: {
|
matches: {
|
||||||
orderBy: [
|
orderBy: [
|
||||||
asc(schema.groupStageMatches.matchday),
|
sql`${schema.groupStageMatches.scheduledAt} ASC NULLS LAST`,
|
||||||
asc(schema.groupStageMatches.createdAt),
|
asc(schema.groupStageMatches.createdAt),
|
||||||
],
|
],
|
||||||
with: {
|
with: {
|
||||||
|
|
|
||||||
|
|
@ -588,7 +588,79 @@ export async function getUpcomingEventsForDraftedParticipants(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(entryMap.values());
|
const bracketResults = Array.from(entryMap.values());
|
||||||
|
|
||||||
|
// Also include group stage matches for the drafted participants.
|
||||||
|
// Group stage matches live in groupStageMatches, not playoffMatches, so
|
||||||
|
// the bracket query above misses them entirely.
|
||||||
|
const groupStageRows = await db
|
||||||
|
.select({
|
||||||
|
matchId: schema.groupStageMatches.id,
|
||||||
|
groupName: schema.tournamentGroups.groupName,
|
||||||
|
scoringEventName: schema.scoringEvents.name,
|
||||||
|
sportsSeasonId: schema.scoringEvents.sportsSeasonId,
|
||||||
|
scheduledAt: schema.groupStageMatches.scheduledAt,
|
||||||
|
participant1Id: schema.groupStageMatches.participant1Id,
|
||||||
|
participant2Id: schema.groupStageMatches.participant2Id,
|
||||||
|
})
|
||||||
|
.from(schema.groupStageMatches)
|
||||||
|
.innerJoin(
|
||||||
|
schema.tournamentGroups,
|
||||||
|
eq(schema.groupStageMatches.tournamentGroupId, schema.tournamentGroups.id)
|
||||||
|
)
|
||||||
|
.innerJoin(
|
||||||
|
schema.scoringEvents,
|
||||||
|
eq(schema.tournamentGroups.scoringEventId, schema.scoringEvents.id)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId),
|
||||||
|
eq(schema.groupStageMatches.isComplete, false),
|
||||||
|
or(
|
||||||
|
inArray(schema.groupStageMatches.participant1Id, draftedIds),
|
||||||
|
inArray(schema.groupStageMatches.participant2Id, draftedIds)
|
||||||
|
),
|
||||||
|
isNotNull(schema.groupStageMatches.scheduledAt),
|
||||||
|
gte(schema.groupStageMatches.scheduledAt, dateFromTimestamp),
|
||||||
|
lte(schema.groupStageMatches.scheduledAt, dateToTimestamp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(asc(schema.groupStageMatches.scheduledAt));
|
||||||
|
|
||||||
|
if (groupStageRows.length > 0) {
|
||||||
|
const allGroupParticipantIds = [
|
||||||
|
...new Set(groupStageRows.flatMap((r) => [r.participant1Id, r.participant2Id])),
|
||||||
|
];
|
||||||
|
const groupParticipantRows = await db.query.seasonParticipants.findMany({
|
||||||
|
where: inArray(schema.seasonParticipants.id, allGroupParticipantIds),
|
||||||
|
});
|
||||||
|
const groupParticipantMap = new Map(groupParticipantRows.map((p) => [p.id, p]));
|
||||||
|
|
||||||
|
for (const row of groupStageRows) {
|
||||||
|
if (!row.scheduledAt) continue;
|
||||||
|
const p1 = groupParticipantMap.get(row.participant1Id);
|
||||||
|
const p2 = groupParticipantMap.get(row.participant2Id);
|
||||||
|
const relevantParticipants: Array<{ id: string; name: string }> = [];
|
||||||
|
if (draftedIds.includes(row.participant1Id) && p1) {
|
||||||
|
relevantParticipants.push({ id: row.participant1Id, name: p1.name });
|
||||||
|
}
|
||||||
|
if (draftedIds.includes(row.participant2Id) && p2) {
|
||||||
|
relevantParticipants.push({ id: row.participant2Id, name: p2.name });
|
||||||
|
}
|
||||||
|
bracketResults.push({
|
||||||
|
id: `group|${row.matchId}`,
|
||||||
|
name: row.scoringEventName,
|
||||||
|
eventDate: null,
|
||||||
|
earliestGameTime: row.scheduledAt.toISOString(),
|
||||||
|
matchLabel: `Group ${row.groupName} — ${p1?.name ?? "?"} vs ${p2?.name ?? "?"}`,
|
||||||
|
eventType: "group_stage_match",
|
||||||
|
sportsSeasonId: row.sportsSeasonId,
|
||||||
|
relevantParticipants,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bracketResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardScoringEvent {
|
export interface DashboardScoringEvent {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
|
||||||
|
|
||||||
export { loader };
|
export { loader };
|
||||||
|
|
||||||
type BracketView = "standings" | "playoffs" | "finished";
|
type BracketView = "standings" | "playoffs" | "finished" | "groups";
|
||||||
|
|
||||||
export default function SportSeasonDetail({
|
export default function SportSeasonDetail({
|
||||||
loaderData,
|
loaderData,
|
||||||
|
|
@ -66,20 +66,35 @@ export default function SportSeasonDetail({
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
// Show the 3-way toggle only for bracket sports that also have regular season standings
|
// Show the 3-way toggle for bracket sports with regular season standings (NBA/AFL/etc.)
|
||||||
const showToggle = hasStandings && scoringPattern === "playoff_bracket";
|
const showToggle = hasStandings && scoringPattern === "playoff_bracket";
|
||||||
|
// Show Groups/Bracket toggle for tournaments with a group stage AND a knockout bracket.
|
||||||
|
// Independent of showToggle — a sport can have both standings and group stage.
|
||||||
|
const hasGroupStage = groupStandings.length > 0;
|
||||||
|
const showGroupStageToggle = hasGroupStage && hasBracket;
|
||||||
|
const showAnyToggle = showToggle || showGroupStageToggle;
|
||||||
|
|
||||||
const [view, setView] = useState<BracketView>(() => {
|
const [view, setView] = useState<BracketView>(() => {
|
||||||
|
if (showGroupStageToggle) {
|
||||||
|
return sportsSeason.status === "completed" ? "finished" : "playoffs";
|
||||||
|
}
|
||||||
if (!hasBracket) return "standings";
|
if (!hasBracket) return "standings";
|
||||||
if (sportsSeason.status === "completed") return "finished";
|
if (sportsSeason.status === "completed") return "finished";
|
||||||
return "playoffs";
|
return "playoffs";
|
||||||
});
|
});
|
||||||
|
|
||||||
const TOGGLE_VIEWS: { value: BracketView; label: string }[] = [
|
const TOGGLE_VIEWS: { value: BracketView; label: string }[] = showGroupStageToggle
|
||||||
{ value: "standings", label: "Standings" },
|
? [
|
||||||
{ value: "playoffs", label: "Playoffs" },
|
{ value: "groups", label: "Groups" },
|
||||||
{ value: "finished", label: "Finished" },
|
...(showToggle ? [{ value: "standings" as BracketView, label: "Standings" }] : []),
|
||||||
];
|
{ value: "playoffs", label: "Bracket" },
|
||||||
|
...(sportsSeason.status === "completed" ? [{ value: "finished" as BracketView, label: "Final" }] : []),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ value: "standings", label: "Standings" },
|
||||||
|
{ value: "playoffs", label: "Playoffs" },
|
||||||
|
{ value: "finished", label: "Finished" },
|
||||||
|
];
|
||||||
|
|
||||||
const bracketDisplay = (bracketMode: "bracket" | "rankings") => (
|
const bracketDisplay = (bracketMode: "bracket" | "rankings") => (
|
||||||
<SportSeasonDisplay
|
<SportSeasonDisplay
|
||||||
|
|
@ -143,7 +158,7 @@ export default function SportSeasonDetail({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showToggle && (
|
{showAnyToggle && (
|
||||||
<div className="flex gap-1 rounded-lg bg-muted p-1 sm:shrink-0">
|
<div className="flex gap-1 rounded-lg bg-muted p-1 sm:shrink-0">
|
||||||
{TOGGLE_VIEWS.map(({ value, label }) => (
|
{TOGGLE_VIEWS.map(({ value, label }) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -165,8 +180,11 @@ export default function SportSeasonDetail({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showToggle ? (
|
{showAnyToggle ? (
|
||||||
<div>
|
<div>
|
||||||
|
{view === "groups" && (
|
||||||
|
<GroupStageStandings groups={groupStandings} ownershipMap={ownershipMap} showEmpty={true} />
|
||||||
|
)}
|
||||||
{view === "standings" && standingsDisplay}
|
{view === "standings" && standingsDisplay}
|
||||||
{view === "playoffs" && bracketDisplay("bracket")}
|
{view === "playoffs" && bracketDisplay("bracket")}
|
||||||
{view === "finished" && bracketDisplay("rankings")}
|
{view === "finished" && bracketDisplay("rankings")}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue