import { useLoaderData, Link } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { database } from "~/database/context"; import { eq, and } from "drizzle-orm"; import * as schema from "~/database/schema"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { StandingsTable } from "~/components/standings/StandingsTable"; import { getSevenDayStandingsChange } from "~/models/standings"; export async function loader(args: any) { try { const auth = await getAuth(args); const userId = (auth as any).userId as string | null; const { params } = args; const { leagueId, seasonId } = params; const db = database(); // Fetch league const league = await db.query.leagues.findFirst({ where: eq(schema.leagues.id, leagueId), }); if (!league) { throw new Response("League not found", { status: 404 }); } // Fetch season const season = await db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId), }); if (!season || season.leagueId !== leagueId) { throw new Response("Season not found", { status: 404 }); } // Check access if (!userId) { throw new Response("You must be logged in to view standings", { status: 401, }); } // Check if user is a commissioner const isUserCommissioner = await db.query.commissioners.findFirst({ where: and( eq(schema.commissioners.leagueId, leagueId), eq(schema.commissioners.userId, userId) ), }); // Check if user has a team in this season const hasTeam = await db.query.teams.findFirst({ where: and( eq(schema.teams.seasonId, seasonId), eq(schema.teams.ownerId, userId) ), }); if (!isUserCommissioner && !hasTeam) { throw new Response("You do not have access to this league", { status: 403, }); } // Fetch standings with 7-day comparison const standingsWithComparison = await getSevenDayStandingsChange(seasonId, db); // Map to format expected by component (use sevenDayRankChange instead of previousRank) const formattedStandings = standingsWithComparison.map((standing) => ({ ...standing, rankChange: standing.sevenDayRankChange, })); return { league, season, standings: formattedStandings, }; } catch (error) { console.error("Error loading standings:", error); console.error("Error details:", { message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); throw error; } } export default function LeagueStandings() { const { league, season, standings } = useLoaderData(); return (
{/* Header */}

{league.name}

{season.year} Season Standings

{/* Standings Card */} Team Standings {standings.length === 0 ? (

No standings data yet.

Standings will appear once participants have results.

) : ( )}
{/* Info Card */}

Tiebreaker Rules: Teams are ranked by total points. If tied, the team with more 1st place finishes ranks higher. If still tied, 2nd place finishes are compared, then 3rd, and so on through 8th place.

Movement Indicators: Arrows show rank changes compared to 7 days ago. Green arrows (↑) indicate improvement in rank, red arrows (↓) indicate decline.

Placement Breakdown: Shows how many times each team's participants finished in each position (1stΓ—2 means 2 first-place finishes).

); }