claude/funny-maxwell-vcyb19 #91

Merged
chrisp merged 3 commits from claude/funny-maxwell-vcyb19 into main 2026-06-15 22:05:25 +00:00
10 changed files with 41 additions and 18 deletions

View file

@ -11,6 +11,7 @@ import {
} from "~/components/ui/card"; } from "~/components/ui/card";
import { formatEventDate } from "~/lib/date-utils"; import { formatEventDate } from "~/lib/date-utils";
import type { UpcomingParticipantEvent } from "~/models/scoring-event"; import type { UpcomingParticipantEvent } from "~/models/scoring-event";
import { isBracketMajor } from "~/lib/event-utils";
export interface CalendarPanelEvent extends UpcomingParticipantEvent { export interface CalendarPanelEvent extends UpcomingParticipantEvent {
sportName: string; sportName: string;
@ -61,7 +62,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" && event.eventType !== "group_stage_match"; const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match" && !isBracketMajor(event.simulatorType);
const displayName = event.matchLabel const displayName = event.matchLabel
? `${event.name}${event.matchLabel}` ? `${event.name}${event.matchLabel}`
: event.name; : event.name;

View file

@ -8,6 +8,7 @@ import { GradientIcon } from "~/components/ui/GradientIcon";
import { formatEventDate } from "~/lib/date-utils"; import { formatEventDate } from "~/lib/date-utils";
import { BRACKT_GRADIENT } from "~/lib/brand"; import { BRACKT_GRADIENT } from "~/lib/brand";
import type { CalendarPanelEvent } from "./UpcomingCalendarPanel"; import type { CalendarPanelEvent } from "./UpcomingCalendarPanel";
import { isBracketMajor } from "~/lib/event-utils";
interface Props { interface Props {
events: CalendarPanelEvent[]; events: CalendarPanelEvent[];
@ -45,7 +46,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" && event.eventType !== "group_stage_match"; const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match" && !isBracketMajor(event.simulatorType);
const leagueEntry: LeagueParticipants = { const leagueEntry: LeagueParticipants = {
leagueId: event.leagueId ?? "unknown", leagueId: event.leagueId ?? "unknown",

View file

@ -11,6 +11,7 @@ import {
import { Trophy, Target, Flag, Calendar, Users } from "lucide-react"; import { Trophy, Target, Flag, Calendar, Users } from "lucide-react";
import { formatEventDate } from "~/lib/date-utils"; import { formatEventDate } from "~/lib/date-utils";
import type { UpcomingParticipantEvent } from "~/models/scoring-event"; import type { UpcomingParticipantEvent } from "~/models/scoring-event";
import { isBracketMajor } from "~/lib/event-utils";
import { SportIcon } from "~/components/SportIcon"; import { SportIcon } from "~/components/SportIcon";
interface SportSeasonCardProps { interface SportSeasonCardProps {
@ -31,7 +32,7 @@ function EventLine({ event }: { event: UpcomingParticipantEvent }) {
const dateStr = gameDate const dateStr = gameDate
? format(gameDate, "MMM d") ? format(gameDate, "MMM d")
: formatEventDate(event.eventDate); : formatEventDate(event.eventDate);
const isAllCompete = event.eventType !== "playoff_game"; const isAllCompete = event.eventType !== "playoff_game" && !isBracketMajor(event.simulatorType);
const count = event.relevantParticipants.length; const count = event.relevantParticipants.length;
const displayName = event.matchLabel const displayName = event.matchLabel
? `${event.name}${event.matchLabel}` ? `${event.name}${event.matchLabel}`

12
app/lib/event-utils.ts Normal file
View file

@ -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]);
}

View file

@ -232,7 +232,7 @@ export async function deleteScoringEvent(
} }
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
if (event.eventType === "playoff_game") { if (event.eventType === "playoff_game" || event.eventType === "major_tournament") {
await tx await tx
.delete(schema.seasonParticipantResults) .delete(schema.seasonParticipantResults)
.where(eq(schema.seasonParticipantResults.sportsSeasonId, event.sportsSeasonId)); .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)); 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); await recalculateAffectedLeagues(event.sportsSeasonId, db);
} }
@ -355,6 +355,8 @@ export interface UpcomingParticipantEvent {
matchLabel: string | null; matchLabel: string | null;
eventType: string; eventType: string;
sportsSeasonId: 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 */ /** Only the current user's drafted participants involved in this event */
relevantParticipants: Array<{ id: string; name: string }>; relevantParticipants: Array<{ id: string; name: string }>;
} }

View file

@ -26,6 +26,7 @@ export interface SeasonWithSportsSeasons extends Season {
type: string; type: string;
slug: string; slug: string;
iconUrl: string | null; iconUrl: string | null;
simulatorType: string | null;
}; };
}; };
}>; }>;

View file

@ -68,7 +68,7 @@ export async function loader({ params }: Route.LoaderArgs) {
throw new Response("Event not found", { status: 404 }); 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 }); throw new Response("This event is not a playoff event", { status: 400 });
} }

View file

@ -22,6 +22,7 @@ import {
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2, Brackets, Save } from "lucide-react"; import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2, Brackets, Save } from "lucide-react";
import { getEventTypeLabel } from "~/models/scoring-event"; import { getEventTypeLabel } from "~/models/scoring-event";
import { isBracketMajor } from "~/lib/event-utils";
import { import {
Table, Table,
TableBody, TableBody,
@ -97,6 +98,8 @@ export default function EventResults({
const { sportsSeason, event, participants, results, participantResults, seasonResults, notParticipatingIds } = loaderData; const { sportsSeason, event, participants, results, participantResults, seasonResults, notParticipatingIds } = loaderData;
const [hasChanges, setHasChanges] = useState(false); 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 // Create a map of participants with results for easy lookup
const participantResultsMap = new Map( const participantResultsMap = new Map(
results.map((r: { seasonParticipant: { id: string } }) => [r.seasonParticipant.id, r]) results.map((r: { seasonParticipant: { id: string } }) => [r.seasonParticipant.id, r])
@ -232,7 +235,7 @@ export default function EventResults({
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{event.eventType === "playoff_game" && ( {isBracketEvent && (
<Button variant="outline" asChild> <Button variant="outline" asChild>
<Link to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}/bracket`}> <Link to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}/bracket`}>
<Brackets className="mr-2 h-4 w-4" /> <Brackets className="mr-2 h-4 w-4" />
@ -490,7 +493,7 @@ export default function EventResults({
)} )}
{/* Batch Paste Results — primary entry for qualifying events */} {/* Batch Paste Results — primary entry for qualifying events */}
{event.isQualifyingEvent && event.eventType !== "final_standings" && event.eventType !== "playoff_game" && !event.isComplete && ( {event.isQualifyingEvent && event.eventType !== "final_standings" && !isBracketEvent && !event.isComplete && (
<BatchResultEntry <BatchResultEntry
participants={participants} participants={participants}
sportsSeasonId={sportsSeason.id} sportsSeasonId={sportsSeason.id}
@ -501,7 +504,7 @@ export default function EventResults({
)} )}
{/* Regular Result Entry for other event types */} {/* Regular Result Entry for other event types */}
{event.eventType !== "final_standings" && event.eventType !== "playoff_game" && !event.isComplete && ( {event.eventType !== "final_standings" && !isBracketEvent && !event.isComplete && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Add Result</CardTitle> <CardTitle>Add Result</CardTitle>
@ -593,8 +596,8 @@ export default function EventResults({
</Card> </Card>
)} )}
{/* Current Results - Hide for playoff events since they use the bracket */} {/* Current Results - Hide for bracket events since they use the bracket */}
{event.eventType !== "playoff_game" && ( {!isBracketEvent && (
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -716,8 +719,8 @@ export default function EventResults({
</Card> </Card>
)} )}
{/* Bracket Explanation Card for Playoff Events */} {/* Bracket Explanation Card for Bracket Events */}
{event.eventType === "playoff_game" && !participantResults?.length && ( {isBracketEvent && !participantResults?.length && (
<Card className="border-electric/30 bg-electric/10"> <Card className="border-electric/30 bg-electric/10">
<CardHeader> <CardHeader>
<CardTitle className="text-electric"> <CardTitle className="text-electric">
@ -751,12 +754,12 @@ export default function EventResults({
{/* Participant Results with Fantasy Points */} {/* Participant Results with Fantasy Points */}
{participantResults && participantResults.length > 0 && ( {participantResults && participantResults.length > 0 && (
<Card className={event.eventType === "playoff_game" ? "border-emerald-500/30" : ""}> <Card className={isBracketEvent ? "border-emerald-500/30" : ""}>
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<CardTitle className={event.eventType === "playoff_game" ? "text-emerald-400" : ""}> <CardTitle className={isBracketEvent ? "text-emerald-400" : ""}>
{event.eventType === "playoff_game" ? ( {isBracketEvent ? (
<> <>
<Trophy className="inline mr-2 h-5 w-5" /> <Trophy className="inline mr-2 h-5 w-5" />
Fantasy Points Awarded (from Bracket) Fantasy Points Awarded (from Bracket)
@ -766,13 +769,13 @@ export default function EventResults({
)} )}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{event.eventType === "playoff_game" {isBracketEvent
? `${participantResults.length} participants assigned placements from bracket results` ? `${participantResults.length} participants assigned placements from bracket results`
: "Points calculated from bracket placements (sorted by position)" : "Points calculated from bracket placements (sorted by position)"
} }
</CardDescription> </CardDescription>
</div> </div>
{event.eventType === "playoff_game" && event.isComplete && ( {isBracketEvent && event.isComplete && (
<Badge variant="default" className="bg-emerald-500"> <Badge variant="default" className="bg-emerald-500">
<CheckCircle2 className="mr-1 h-3 w-3" /> <CheckCircle2 className="mr-1 h-3 w-3" />
Bracket Finalized Bracket Finalized

View file

@ -130,6 +130,7 @@ export async function loader(args: Route.LoaderArgs) {
return events.map((event) => ({ return events.map((event) => ({
...event, ...event,
simulatorType: ss.sport.simulatorType ?? null,
sportName: ss.sport.name, sportName: ss.sport.name,
sportSeasonName: ss.name, sportSeasonName: ss.name,
leagueName: league.name, leagueName: league.name,

View file

@ -62,6 +62,7 @@ export async function loader(args: Route.LoaderArgs) {
return events.map((event) => ({ return events.map((event) => ({
...event, ...event,
simulatorType: ss.sport.simulatorType ?? null,
sportName: ss.sport.name, sportName: ss.sport.name,
sportSeasonName: ss.name, sportSeasonName: ss.name,
leagueName: league.name, leagueName: league.name,