* Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
206 lines
5.3 KiB
TypeScript
206 lines
5.3 KiB
TypeScript
import { PlayoffBracket } from "./PlayoffBracket";
|
|
import { SeasonStandings } from "./SeasonStandings";
|
|
import { QualifyingPointsStandings } from "./QualifyingPointsStandings";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
|
import { AlertCircle } from "lucide-react";
|
|
|
|
/**
|
|
* SportSeasonDisplay - Pattern detection and display component
|
|
*
|
|
* Detects the scoring pattern of a sports season and displays the appropriate
|
|
* component (PlayoffBracket, SeasonStandings, or QualifyingPointsStandings).
|
|
*
|
|
* This is the main integration point for displaying sport-specific results
|
|
* to league members.
|
|
*/
|
|
|
|
type ScoringPattern =
|
|
| "playoff_bracket"
|
|
| "season_standings"
|
|
| "qualifying_points";
|
|
|
|
interface Match {
|
|
id: string;
|
|
round: string;
|
|
matchNumber: number;
|
|
participant1Id: string | null;
|
|
participant2Id: string | null;
|
|
winnerId: string | null;
|
|
loserId: string | null;
|
|
isComplete: boolean;
|
|
participant1Score: string | null;
|
|
participant2Score: string | null;
|
|
participant1?: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
participant2?: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
winner?: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
loser?: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
}
|
|
|
|
interface SeasonStanding {
|
|
id: string;
|
|
championshipPoints: string;
|
|
position: number;
|
|
previousPosition?: number | null;
|
|
participant: {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
interface QPStanding {
|
|
id: string;
|
|
totalQualifyingPoints: string;
|
|
eventsScored: number;
|
|
finalRanking: number | null;
|
|
participant: {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
interface TeamOwnership {
|
|
participantId: string;
|
|
teamName: string;
|
|
teamId: string;
|
|
ownerName?: string;
|
|
}
|
|
|
|
interface ScoringRules {
|
|
pointsFor1st: number;
|
|
pointsFor2nd: number;
|
|
pointsFor3rd: number;
|
|
pointsFor4th: number;
|
|
pointsFor5th: number;
|
|
pointsFor6th: number;
|
|
pointsFor7th: number;
|
|
pointsFor8th: number;
|
|
}
|
|
|
|
interface SportSeasonDisplayProps {
|
|
scoringPattern: ScoringPattern;
|
|
sportSeasonName: string;
|
|
sportName: string;
|
|
|
|
// Playoff data
|
|
playoffMatches?: Match[];
|
|
playoffRounds?: string[];
|
|
preEliminatedParticipants?: { id: string; name: string }[];
|
|
participantPoints?: { participantId: string; points: number }[];
|
|
partialScoreParticipantIds?: string[];
|
|
|
|
// Season standings data (F1)
|
|
seasonStandings?: SeasonStanding[];
|
|
seasonIsFinalized?: boolean;
|
|
|
|
// Qualifying points data (Golf/Tennis)
|
|
qpStandings?: QPStanding[];
|
|
qpIsFinalized?: boolean;
|
|
totalMajors?: number | null;
|
|
majorsCompleted?: number;
|
|
canFinalize?: boolean;
|
|
|
|
// Shared data
|
|
teamOwnerships?: TeamOwnership[];
|
|
userParticipantIds?: string[];
|
|
scoringRules?: ScoringRules | null;
|
|
showOwnership?: boolean;
|
|
}
|
|
|
|
export function SportSeasonDisplay({
|
|
scoringPattern,
|
|
sportSeasonName,
|
|
sportName: _sportName,
|
|
playoffMatches = [],
|
|
playoffRounds = [],
|
|
preEliminatedParticipants = [],
|
|
participantPoints = [],
|
|
partialScoreParticipantIds = [],
|
|
seasonStandings = [],
|
|
seasonIsFinalized = false,
|
|
qpStandings = [],
|
|
qpIsFinalized = false,
|
|
totalMajors,
|
|
majorsCompleted = 0,
|
|
canFinalize = false,
|
|
teamOwnerships = [],
|
|
userParticipantIds = [],
|
|
scoringRules = null,
|
|
showOwnership = true,
|
|
}: SportSeasonDisplayProps) {
|
|
// Pattern detection and component selection
|
|
switch (scoringPattern) {
|
|
case "playoff_bracket":
|
|
// Display playoff bracket
|
|
return (
|
|
<PlayoffBracket
|
|
matches={playoffMatches}
|
|
rounds={playoffRounds}
|
|
preEliminatedParticipants={preEliminatedParticipants}
|
|
participantPoints={participantPoints}
|
|
partialScoreParticipantIds={partialScoreParticipantIds}
|
|
teamOwnerships={teamOwnerships}
|
|
userParticipantIds={userParticipantIds}
|
|
showOwnership={showOwnership}
|
|
title="Playoff Bracket"
|
|
/>
|
|
);
|
|
|
|
case "season_standings":
|
|
// Display F1-style championship standings
|
|
return (
|
|
<SeasonStandings
|
|
standings={seasonStandings}
|
|
teamOwnerships={teamOwnerships}
|
|
userParticipantIds={userParticipantIds}
|
|
showOwnership={showOwnership}
|
|
isFinalized={seasonIsFinalized}
|
|
title={sportSeasonName}
|
|
description="Championship standings - positions calculated from points"
|
|
/>
|
|
);
|
|
|
|
case "qualifying_points":
|
|
// Display qualifying points standings (Golf/Tennis)
|
|
return (
|
|
<QualifyingPointsStandings
|
|
standings={qpStandings}
|
|
scoringRules={scoringRules}
|
|
isFinalized={qpIsFinalized}
|
|
totalMajors={totalMajors}
|
|
majorsCompleted={majorsCompleted}
|
|
canFinalize={canFinalize}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
// Unknown pattern - show error
|
|
return (
|
|
<Card className="border-destructive/50">
|
|
<CardHeader>
|
|
<CardTitle className="text-destructive flex items-center gap-2">
|
|
<AlertCircle className="h-5 w-5" />
|
|
Unknown Scoring Pattern
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-muted-foreground">
|
|
This sports season uses an unsupported scoring pattern: "
|
|
{scoringPattern}". Please contact support.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
}
|