From 0eeb1b6245744c634377ca8f601dcbae71a60a13 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Fri, 14 Nov 2025 20:39:51 -0800 Subject: [PATCH] feat: Enhance league home page with sports seasons section and scoring pattern display --- app/components/sports/SportSeasonCard.tsx | 104 ++++++++++++++++++++++ app/routes/leagues/$leagueId.tsx | 49 ++++++++-- plans/scoring-system.md | 32 ++++--- 3 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 app/components/sports/SportSeasonCard.tsx diff --git a/app/components/sports/SportSeasonCard.tsx b/app/components/sports/SportSeasonCard.tsx new file mode 100644 index 0000000..061dcf8 --- /dev/null +++ b/app/components/sports/SportSeasonCard.tsx @@ -0,0 +1,104 @@ +import { Link } from "react-router"; +import { Badge } from "~/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { Trophy, Target, Flag } from "lucide-react"; + +interface SportSeasonCardProps { + leagueId: string; + sportSeasonId: string; + sportName: string; + seasonName: string; + status: "upcoming" | "active" | "completed"; + scoringPattern?: string | null; +} + +export function SportSeasonCard({ + leagueId, + sportSeasonId, + sportName, + seasonName, + status, + scoringPattern, +}: SportSeasonCardProps) { + const getStatusBadge = () => { + switch (status) { + case "upcoming": + return ( + + + Upcoming + + ); + case "active": + return ( + + + Active + + ); + case "completed": + return ( + + + Completed + + ); + } + }; + + const getScoringPatternLabel = () => { + if (!scoringPattern) return null; + + switch (scoringPattern) { + case "single_elimination_playoff": + return "Playoff Bracket"; + case "page_playoff": + return "AFL Finals"; + case "season_standings": + return "Season Standings"; + case "qualifying_points": + return "Qualifying Points"; + default: + return null; + } + }; + + const scoringLabel = getScoringPatternLabel(); + const isPlayoffType = scoringPattern === "single_elimination_playoff" || scoringPattern === "page_playoff"; + + return ( + + + +
+
+ {sportName} + {seasonName} +
+ {getStatusBadge()} +
+
+ +
+ {scoringLabel && ( +
+ {isPlayoffType ? ( + + ) : ( + + )} + {scoringLabel} +
+ )} +
+
+
+ + ); +} diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx index b547b39..4dd9412 100644 --- a/app/routes/leagues/$leagueId.tsx +++ b/app/routes/leagues/$leagueId.tsx @@ -13,7 +13,7 @@ import { findUserByClerkId, findDraftSlotsBySeasonId, } from "~/models"; -import { findSeasonWithSportsSeasons } from "~/models/season"; +import { findCurrentSeasonWithSports } from "~/models/season"; import type { Route } from "./+types/$leagueId"; import { Button } from "~/components/ui/button"; import { @@ -23,6 +23,7 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; +import { SportSeasonCard } from "~/components/sports/SportSeasonCard"; export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); @@ -37,10 +38,8 @@ export async function loader(args: Route.LoaderArgs) { } // Fetch current season with sports - const season = await findCurrentSeason(leagueId); - const seasonWithSports = season - ? await findSeasonWithSportsSeasons(season.id) - : null; + const seasonWithSports = await findCurrentSeasonWithSports(leagueId); + const season = seasonWithSports || null; // Fetch commissioners const commissioners = await findCommissionersByLeagueId(leagueId); @@ -119,8 +118,15 @@ export async function loader(args: Route.LoaderArgs) { // Count teams with owners const teamsWithOwners = teams.filter((t) => t.ownerId !== null).length; - // Count sports in season - const sportsCount = seasonWithSports?.seasonSports?.length || 0; + // Get sports seasons data + const sportsSeasons = seasonWithSports?.seasonSports?.map((ss) => ({ + id: ss.sportsSeason.id, + name: ss.sportsSeason.name, + status: ss.sportsSeason.status, + scoringPattern: ss.sportsSeason.scoringPattern, + sport: ss.sportsSeason.sport, + })) || []; + const sportsCount = sportsSeasons.length; // Check if draft order is set const isDraftOrderSet = draftSlots.length > 0; @@ -139,6 +145,7 @@ export async function loader(args: Route.LoaderArgs) { sportsCount, teamsWithOwners, isDraftOrderSet, + sportsSeasons, }; } @@ -157,6 +164,7 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) { sportsCount, teamsWithOwners, isDraftOrderSet, + sportsSeasons, } = loaderData; const [searchParams, setSearchParams] = useSearchParams(); const [copied, setCopied] = useState(false); @@ -227,6 +235,33 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
{/* Left Column - 2/3 width on desktop */}
+ {/* Sports Seasons Section */} + {season && sportsSeasons.length > 0 && ( + + + Sports Seasons + + {sportsSeasons.length} sport{sportsSeasons.length !== 1 ? "s" : ""} in this season + + + +
+ {sportsSeasons.map((sportSeason) => ( + + ))} +
+
+
+ )} + {isUserCommissioner && season && availableTeamCount > 0 && ( diff --git a/plans/scoring-system.md b/plans/scoring-system.md index e66de49..2fd438d 100644 --- a/plans/scoring-system.md +++ b/plans/scoring-system.md @@ -1358,18 +1358,24 @@ scoring_events { **Note**: Route exists and displays correctly. Navigation cards on league home page pending (see 4.5) -- [ ] **4.5** League home page integration (partially complete) +- [x] **4.5** League home page integration ✅ *Completed* - [x] Add navigation link to standings page from league home - - [ ] Display team standings table on league home page (or keep separate as currently implemented) - - [ ] Add "Sports Seasons" section showing all sports in current season - - [ ] Link each sport season card to detail page (Q11 - ownership hints on each sport's page) - - [ ] Show sport status badges (upcoming, active, completed) - - [ ] Quick access to brackets from league home + - [x] Display team standings table on league home page (kept separate as standalone page) + - [x] Add "Sports Seasons" section showing all sports in current season + - [x] Link each sport season card to detail page (Q11 - ownership hints on each sport's page) + - [x] Show sport status badges (upcoming, active, completed) + - [x] Quick access to brackets from league home (via sport season cards) **Implementation Notes**: - - "View Standings" link added in League Info section (app/routes/leagues/$leagueId.tsx:433-440) - - Standalone standings page preferred over embedding in league home for cleaner UX - - Sport season detail pages already exist (Phase 3.4) - need navigation cards on league home + - "View Standings" link in League Info section (app/routes/leagues/$leagueId.tsx) + - Standalone standings page for cleaner UX + - Sport season detail pages exist from Phase 3.4 + - Created `SportSeasonCard` component with status badges (app/components/sports/SportSeasonCard.tsx) + - Sports Seasons section displays 2-column grid on desktop, single column on mobile + - Each card shows: sport name, season name, status badge, scoring pattern icon + - Status badges use color coding: blue=upcoming, green=active, gray=completed + - Cards link directly to sport season detail pages where brackets/standings are displayed + - Updated loader to use `findCurrentSeasonWithSports` for efficient data fetching - [ ] **4.6** Historical views - [ ] Season completion detection @@ -1468,10 +1474,14 @@ scoring_events { - ⏳ **Phase 4**: Standings & Display - IN PROGRESS - ✅ **4.1** Enhanced Standings Table - COMPLETE (tiebreaker logic, placement breakdowns, standalone page, navigation) - ✅ **4.2** Movement Tracking & Snapshots - COMPLETE (daily snapshots, 7-day comparison, admin interface) - - ⏳ **4.5** League Home Integration - PARTIAL (standings link added, sports season cards pending) + - ✅ **4.3** Team Breakdown Pages - COMPLETE (detailed score breakdown, sport grouping, navigation) + - ✅ **4.4** Sport Season Pages with Ownership - COMPLETE (pattern-specific displays, ownership hints, brackets) + - ✅ **4.5** League Home Integration - COMPLETE (sports season cards, status badges, direct navigation) - ⏳ **Phase 5**: Expected Value - TODO - ⏳ **Phase 6**: Polish & Optimization - TODO -**Current Focus**: Phase 4 - Standings & Display (4.1, 4.2, and 4.3 complete, 4.5 partially complete) +**Current Focus**: Phase 4 - Standings & Display (4.1-4.5 complete, only 4.6 Historical Views remaining) **Total Test Count**: 404 tests passing ✅ + +**Next Task**: Phase 4.6 - Historical Views (season completion detection, point progression charts)