diff --git a/app/components/sport-season/UpcomingCalendarPanel.tsx b/app/components/sport-season/UpcomingCalendarPanel.tsx index f881ffa..663c32d 100644 --- a/app/components/sport-season/UpcomingCalendarPanel.tsx +++ b/app/components/sport-season/UpcomingCalendarPanel.tsx @@ -11,6 +11,7 @@ import { } from "~/components/ui/card"; import { formatEventDate } from "~/lib/date-utils"; import type { UpcomingParticipantEvent } from "~/models/scoring-event"; +import { isBracketMajor } from "~/lib/event-utils"; export interface CalendarPanelEvent extends UpcomingParticipantEvent { sportName: string; @@ -61,7 +62,7 @@ function EventRow({ event, showLeague }: { event: CalendarPanelEvent; showLeague ? format(gameDate, "MMM d") : formatEventDate(event.eventDate); const participantCount = event.relevantParticipants.length; - const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match"; + const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match" && !isBracketMajor(event.simulatorType); const displayName = event.matchLabel ? `${event.name} — ${event.matchLabel}` : event.name; diff --git a/app/components/sport-season/UpcomingEventsCard.tsx b/app/components/sport-season/UpcomingEventsCard.tsx index fb93d79..77db30f 100644 --- a/app/components/sport-season/UpcomingEventsCard.tsx +++ b/app/components/sport-season/UpcomingEventsCard.tsx @@ -8,6 +8,7 @@ import { GradientIcon } from "~/components/ui/GradientIcon"; import { formatEventDate } from "~/lib/date-utils"; import { BRACKT_GRADIENT } from "~/lib/brand"; import type { CalendarPanelEvent } from "./UpcomingCalendarPanel"; +import { isBracketMajor } from "~/lib/event-utils"; interface Props { events: CalendarPanelEvent[]; @@ -45,7 +46,7 @@ function groupEvents(events: CalendarPanelEvent[]): GroupedEvent[] { for (const event of events) { const existing = map.get(event.id); - const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match"; + const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match" && !isBracketMajor(event.simulatorType); const leagueEntry: LeagueParticipants = { leagueId: event.leagueId ?? "unknown", diff --git a/app/components/sports/SportSeasonCard.tsx b/app/components/sports/SportSeasonCard.tsx index b9898f9..71d571f 100644 --- a/app/components/sports/SportSeasonCard.tsx +++ b/app/components/sports/SportSeasonCard.tsx @@ -11,6 +11,7 @@ import { import { Trophy, Target, Flag, Calendar, Users } from "lucide-react"; import { formatEventDate } from "~/lib/date-utils"; import type { UpcomingParticipantEvent } from "~/models/scoring-event"; +import { isBracketMajor } from "~/lib/event-utils"; import { SportIcon } from "~/components/SportIcon"; interface SportSeasonCardProps { @@ -31,7 +32,7 @@ function EventLine({ event }: { event: UpcomingParticipantEvent }) { const dateStr = gameDate ? format(gameDate, "MMM d") : formatEventDate(event.eventDate); - const isAllCompete = event.eventType !== "playoff_game"; + const isAllCompete = event.eventType !== "playoff_game" && !isBracketMajor(event.simulatorType); const count = event.relevantParticipants.length; const displayName = event.matchLabel ? `${event.name} — ${event.matchLabel}` diff --git a/app/lib/event-utils.ts b/app/lib/event-utils.ts new file mode 100644 index 0000000..e602eab --- /dev/null +++ b/app/lib/event-utils.ts @@ -0,0 +1,12 @@ +const BRACKET_MAJOR_SIMULATOR_TYPES = [ + "cs2_major_qualifying_points", + "tennis_qualifying_points", +] as const; + +/** + * Returns true for major_tournament events that use a bracket (CS2, Tennis). + * Golf (golf_qualifying_points) uses manual result entry and returns false. + */ +export function isBracketMajor(simulatorType: string | null | undefined): boolean { + return BRACKET_MAJOR_SIMULATOR_TYPES.includes(simulatorType as typeof BRACKET_MAJOR_SIMULATOR_TYPES[number]); +} diff --git a/app/models/scoring-event.ts b/app/models/scoring-event.ts index 162ddc8..8e474af 100644 --- a/app/models/scoring-event.ts +++ b/app/models/scoring-event.ts @@ -232,7 +232,7 @@ export async function deleteScoringEvent( } await db.transaction(async (tx) => { - if (event.eventType === "playoff_game") { + if (event.eventType === "playoff_game" || event.eventType === "major_tournament") { await tx .delete(schema.seasonParticipantResults) .where(eq(schema.seasonParticipantResults.sportsSeasonId, event.sportsSeasonId)); @@ -241,7 +241,7 @@ export async function deleteScoringEvent( await tx.delete(schema.scoringEvents).where(eq(schema.scoringEvents.id, eventId)); }); - if (event.eventType === "playoff_game") { + if (event.eventType === "playoff_game" || event.eventType === "major_tournament") { await recalculateAffectedLeagues(event.sportsSeasonId, db); } @@ -355,6 +355,8 @@ export interface UpcomingParticipantEvent { matchLabel: string | null; eventType: string; sportsSeasonId: string; + /** simulatorType from the parent sport — used to distinguish bracket-majors from manual-entry majors */ + simulatorType?: string | null; /** Only the current user's drafted participants involved in this event */ relevantParticipants: Array<{ id: string; name: string }>; } diff --git a/app/models/season.ts b/app/models/season.ts index d573f48..e969a00 100644 --- a/app/models/season.ts +++ b/app/models/season.ts @@ -26,6 +26,7 @@ export interface SeasonWithSportsSeasons extends Season { type: string; slug: string; iconUrl: string | null; + simulatorType: string | null; }; }; }>; 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 1dab669..1134eb4 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 @@ -68,7 +68,7 @@ export async function loader({ params }: Route.LoaderArgs) { throw new Response("Event not found", { status: 404 }); } - if (event.eventType !== "playoff_game") { + if (event.eventType !== "playoff_game" && event.eventType !== "major_tournament") { throw new Response("This event is not a playoff event", { status: 400 }); } diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.tsx index 5fbb0c0..a529cd3 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.tsx +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.tsx @@ -22,6 +22,7 @@ import { import { Badge } from "~/components/ui/badge"; import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2, Brackets, Save } from "lucide-react"; import { getEventTypeLabel } from "~/models/scoring-event"; +import { isBracketMajor } from "~/lib/event-utils"; import { Table, TableBody, @@ -97,6 +98,8 @@ export default function EventResults({ const { sportsSeason, event, participants, results, participantResults, seasonResults, notParticipatingIds } = loaderData; const [hasChanges, setHasChanges] = useState(false); + const isBracketEvent = event.eventType === "playoff_game" || isBracketMajor(sportsSeason.sport?.simulatorType); + // Create a map of participants with results for easy lookup const participantResultsMap = new Map( results.map((r: { seasonParticipant: { id: string } }) => [r.seasonParticipant.id, r]) @@ -232,7 +235,7 @@ export default function EventResults({