claude/shared-tournaments-events-ux-6z5jkt #125
3 changed files with 49 additions and 12 deletions
|
|
@ -119,6 +119,24 @@ describe("deleteScoringEvent", () => {
|
|||
expect(result.deletedTournament).toBe(false);
|
||||
});
|
||||
|
||||
it("does not promote a primary when a golf-style tournament (no primary) loses a window", async () => {
|
||||
// Golf-style shared majors intentionally have no primary window — every
|
||||
// linked event is isPrimary=false. Deleting one must not flip a sibling
|
||||
// into a primary, which would change its scoring/guard behavior.
|
||||
const { db } = makeSharedDb({
|
||||
deletedEvent: { tournamentId: "tournament-1", isPrimary: false },
|
||||
remaining: [
|
||||
{ id: "event-2", isPrimary: false },
|
||||
{ id: "event-3", isPrimary: false },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await deleteScoringEvent("event-1", db);
|
||||
|
||||
expect(result.promotedPrimaryId).toBeNull();
|
||||
expect(result.remainingWindows).toBe(2);
|
||||
});
|
||||
|
||||
it("promotes the earliest remaining window when the primary window is deleted", async () => {
|
||||
const { db } = makeSharedDb({
|
||||
deletedEvent: { tournamentId: "tournament-1", isPrimary: true },
|
||||
|
|
|
|||
|
|
@ -305,11 +305,13 @@ export async function deleteScoringEvent(
|
|||
remainingWindows = remaining.length;
|
||||
|
||||
if (remaining.length > 0) {
|
||||
// Auto-heal: if the deleted event was the primary window (or no sibling is
|
||||
// flagged primary anymore), promote the earliest remaining window so the
|
||||
// shared major stays scorable and fan-out stays deterministic.
|
||||
const hasPrimary = remaining.some((e) => e.isPrimary);
|
||||
if (event.isPrimary || !hasPrimary) {
|
||||
// Auto-heal: if the deleted event was the primary window, promote the
|
||||
// earliest remaining window so the shared major stays scorable and
|
||||
// fan-out stays deterministic. Only heal when we actually removed the
|
||||
// primary — golf-style majors intentionally have no primary (scored on the
|
||||
// canonical tournament page), and event.isPrimary is false for them, so
|
||||
// they are left untouched.
|
||||
if (event.isPrimary) {
|
||||
await setPrimaryEvent(remaining[0].id, db);
|
||||
promotedPrimaryId = remaining[0].id;
|
||||
}
|
||||
|
|
@ -1126,6 +1128,24 @@ export async function getSportsSeasonsByTournament(tournamentId: string) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the distinct sports-season windows linked to a tournament. Cheaper than
|
||||
* getSportsSeasonsByTournament when only the count is needed (no relations).
|
||||
*/
|
||||
export async function countWindowsByTournament(
|
||||
tournamentId: string,
|
||||
providedDb?: ReturnType<typeof database>
|
||||
): Promise<number> {
|
||||
const db = providedDb || database();
|
||||
const [row] = await db
|
||||
.select({
|
||||
count: sql<number>`count(distinct ${schema.scoringEvents.sportsSeasonId})::int`,
|
||||
})
|
||||
.from(schema.scoringEvents)
|
||||
.where(eq(schema.scoringEvents.tournamentId, tournamentId));
|
||||
return row?.count ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The primary scoring event for a tournament — the single window where the admin
|
||||
* builds the bracket/stages and scoring happens; its results fan out to siblings.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
deleteScoringEvent,
|
||||
bulkCreateScoringEvents,
|
||||
ensurePrimaryEvent,
|
||||
getSportsSeasonsByTournament,
|
||||
countWindowsByTournament,
|
||||
type CreateScoringEventData,
|
||||
} from "~/models/scoring-event";
|
||||
import { isBracketMajor } from "~/lib/event-utils";
|
||||
|
|
@ -66,15 +66,14 @@ export async function loader({ params }: Route.LoaderArgs) {
|
|||
const sharedInfo = new Map<string, { name: string; windowCount: number }>();
|
||||
await Promise.all(
|
||||
[...linkedTournamentIds].map(async (tid) => {
|
||||
const [tournament, windows] = await Promise.all([
|
||||
allTournamentsForSport.find((t) => t.id === tid)
|
||||
? Promise.resolve(allTournamentsForSport.find((t) => t.id === tid)!)
|
||||
: getTournamentById(tid),
|
||||
getSportsSeasonsByTournament(tid),
|
||||
const cached = allTournamentsForSport.find((t) => t.id === tid);
|
||||
const [tournament, windowCount] = await Promise.all([
|
||||
cached ?? getTournamentById(tid),
|
||||
countWindowsByTournament(tid),
|
||||
]);
|
||||
sharedInfo.set(tid, {
|
||||
name: tournament?.name ?? "shared tournament",
|
||||
windowCount: windows.length,
|
||||
windowCount,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue