feat: Enhance league home page with sports seasons section and scoring pattern display
This commit is contained in:
parent
f17699e4c5
commit
0eeb1b6245
3 changed files with 167 additions and 18 deletions
104
app/components/sports/SportSeasonCard.tsx
Normal file
104
app/components/sports/SportSeasonCard.tsx
Normal file
|
|
@ -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 (
|
||||
<Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-200">
|
||||
<Flag className="mr-1 h-3 w-3" />
|
||||
Upcoming
|
||||
</Badge>
|
||||
);
|
||||
case "active":
|
||||
return (
|
||||
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
|
||||
<Target className="mr-1 h-3 w-3" />
|
||||
Active
|
||||
</Badge>
|
||||
);
|
||||
case "completed":
|
||||
return (
|
||||
<Badge variant="outline" className="bg-gray-50 text-gray-700 border-gray-200">
|
||||
<Trophy className="mr-1 h-3 w-3" />
|
||||
Completed
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<Link to={`/leagues/${leagueId}/sports-seasons/${sportSeasonId}`}>
|
||||
<Card className="hover:shadow-lg transition-shadow cursor-pointer h-full">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-lg mb-1">{sportName}</CardTitle>
|
||||
<CardDescription className="truncate">{seasonName}</CardDescription>
|
||||
</div>
|
||||
{getStatusBadge()}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{scoringLabel && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
{isPlayoffType ? (
|
||||
<Trophy className="h-4 w-4" />
|
||||
) : (
|
||||
<Target className="h-4 w-4" />
|
||||
)}
|
||||
<span>{scoringLabel}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
@ -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) {
|
|||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{/* Left Column - 2/3 width on desktop */}
|
||||
<div className="md:col-span-2 space-y-6">
|
||||
{/* Sports Seasons Section */}
|
||||
{season && sportsSeasons.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Sports Seasons</CardTitle>
|
||||
<CardDescription>
|
||||
{sportsSeasons.length} sport{sportsSeasons.length !== 1 ? "s" : ""} in this season
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{sportsSeasons.map((sportSeason) => (
|
||||
<SportSeasonCard
|
||||
key={sportSeason.id}
|
||||
leagueId={league.id}
|
||||
sportSeasonId={sportSeason.id}
|
||||
sportName={sportSeason.sport.name}
|
||||
seasonName={sportSeason.name}
|
||||
status={sportSeason.status as "upcoming" | "active" | "completed"}
|
||||
scoringPattern={sportSeason.scoringPattern}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{isUserCommissioner && season && availableTeamCount > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue