diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts index e52e709..f0f0fb3 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts @@ -52,7 +52,6 @@ import { logger } from "~/lib/logger"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq } from "drizzle-orm"; -import { getSocketIO } from "../../../server/socket"; export async function loader({ params }: Route.LoaderArgs) { const sportsSeason = await findSportsSeasonById(params.id); @@ -598,6 +597,12 @@ export async function action({ request, params }: Route.ActionArgs) { // Replay each completed match in bracket order (earlier rounds first). const template = event.bracketTemplateId ? getBracketTemplate(event.bracketTemplateId) : null; const roundOrder = template ? template.rounds.map((r) => r.name) : []; + // Build a round→isScoring map from the template so we use the template as the + // source of truth rather than the DB column (which defaults to true and may be + // wrong for brackets created before the isScoring column was added). + const templateRoundIsScoring = new Map( + template?.rounds.map((r) => [r.name, r.isScoring]) ?? [] + ); const sortedMatches = completed.toSorted((a, b) => { const ai = roundOrder.indexOf(a.round); @@ -606,12 +611,19 @@ export async function action({ request, params }: Route.ActionArgs) { }); for (const match of sortedMatches) { + // Prefer the template-defined isScoring over the DB field: the DB column + // defaults to true, so legacy play-in rows that predate the column are + // incorrectly marked as scoring and would assign finalPosition=0 to losers + // who still have a second game (e.g. NBA 7v8 play-in loser → Round 2). + const isScoring = templateRoundIsScoring.has(match.round) + ? templateRoundIsScoring.get(match.round)! + : (match.isScoring ?? true); await processMatchResult( { round: match.round, winnerId: match.winnerId ?? "", loserId: match.loserId ?? "", - isScoring: match.isScoring ?? true, + isScoring, sportsSeasonId: event.sportsSeasonId, bracketTemplateId: event.bracketTemplateId, skipSideEffects: true, @@ -623,19 +635,6 @@ export async function action({ request, params }: Route.ActionArgs) { // skipDiscord: recalculate-floors is a data-correction tool, not a result announcement. await recalculateAffectedLeagues(event.sportsSeasonId, undefined, { skipDiscord: true }); - // Notify connected league pages to refresh their elimination/standings data. - try { - const seasonSports = await db.query.seasonSports.findMany({ - where: eq(schema.seasonSports.sportsSeasonId, event.sportsSeasonId), - }); - const io = getSocketIO(); - for (const ss of seasonSports) { - io.to(`draft-${ss.seasonId}`).emit("standings-updated", { sportsSeasonId: event.sportsSeasonId }); - } - } catch (socketError) { - logger.warn("[recalculate-floors] Could not emit standings-updated:", socketError); - } - return { success: `Recalculated floors from ${sortedMatches.length} completed match(es)` }; } catch (error) { logger.error("Error recalculating floors:", error); diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx index 95121ce..9746f77 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx @@ -1,6 +1,4 @@ -import { Link, useRevalidator } from "react-router"; -import { useEffect } from "react"; -import { io } from "socket.io-client"; +import { Link } from "react-router"; import type { Route } from "./+types/$leagueId.sports-seasons.$sportsSeasonId"; import { loader } from "./$leagueId.sports-seasons.$sportsSeasonId.server"; @@ -71,25 +69,6 @@ export default function SportSeasonDetail({ groupStandings, } = loaderData; - const { revalidate } = useRevalidator(); - - // Revalidate when the admin updates standings (e.g. recalculate-floors). - useEffect(() => { - const socket = io({ path: "/socket.io", transports: ["websocket", "polling"] }); - socket.on("connect", () => { - socket.emit("join-draft", season.id); - }); - socket.on("standings-updated", (data: { sportsSeasonId: string }) => { - if (data.sportsSeasonId === sportsSeason.id) { - revalidate(); - } - }); - return () => { - socket.emit("leave-draft", season.id); - socket.disconnect(); - }; - }, [season.id, sportsSeason.id, revalidate]); - const hasBracket = playoffMatches && playoffMatches.length > 0; const hasStandings = regularSeasonStandings && regularSeasonStandings.length > 0; const showOtLosses = hasStandings && regularSeasonStandings.some((s) => s.otLosses !== null); diff --git a/server/socket.ts b/server/socket.ts index b2d6e01..8bdea68 100644 --- a/server/socket.ts +++ b/server/socket.ts @@ -37,7 +37,6 @@ interface ServerToClientEvents { "draft-resumed": (data: { seasonId: string; paused: boolean }) => void; "participant-removed-from-queues": (data: { participantId: string }) => void; "queue-updated": (data: { queue: Array<{ id: string; teamId: string; seasonId: string; participantId: string; queuePosition: number }> }) => void; - "standings-updated": (data: { sportsSeasonId: string }) => void; "draft-state-sync": (data: { currentPickNumber: number; isPaused: boolean;