From 013edbc661cb1e72c2fe16de79d132ceca85d5e7 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Thu, 16 Oct 2025 01:27:57 -0700 Subject: [PATCH] feat: add participant list to draft room with sport name and filtering --- app/components/draft/PlayerList.tsx | 195 +++++++++++++++++++++++++ app/models/season.ts | 1 + app/routes/leagues/$leagueId.draft.tsx | 19 ++- 3 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 app/components/draft/PlayerList.tsx diff --git a/app/components/draft/PlayerList.tsx b/app/components/draft/PlayerList.tsx new file mode 100644 index 0000000..ce142fe --- /dev/null +++ b/app/components/draft/PlayerList.tsx @@ -0,0 +1,195 @@ +import { useState, useMemo } from "react"; +import { Input } from "~/components/ui/input"; +import { Button } from "~/components/ui/button"; +import { Badge } from "~/components/ui/badge"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select"; +import { Search, ArrowUpDown } from "lucide-react"; + +interface Participant { + id: string; + name: string; + sport: string; + expectedValue: number; +} + +interface DraftPick { + participantId: string; +} + +interface PlayerListProps { + participants: Participant[]; + draftPicks: DraftPick[]; + onAddToQueue?: (participantId: string) => void; + onDraftPlayer?: (participantId: string) => void; + canDraft: boolean; +} + +export function PlayerList({ + participants, + draftPicks, + onAddToQueue, + onDraftPlayer, + canDraft, +}: PlayerListProps) { + const [searchQuery, setSearchQuery] = useState(""); + const [sportFilter, setSportFilter] = useState("all"); + const [sortBy, setSortBy] = useState<"ev" | "name">("ev"); + + // Get unique sports + const sports = useMemo(() => { + const uniqueSports = new Set(participants.map((p) => p.sport)); + return Array.from(uniqueSports).sort(); + }, [participants]); + + // Get drafted participant IDs + const draftedIds = useMemo(() => { + return new Set(draftPicks.map((p) => p.participantId)); + }, [draftPicks]); + + // Filter and sort participants + const filteredParticipants = useMemo(() => { + let filtered = participants.filter((p) => { + // Filter out drafted players + if (draftedIds.has(p.id)) return false; + + // Filter by search query + if (searchQuery && !p.name.toLowerCase().includes(searchQuery.toLowerCase())) { + return false; + } + + // Filter by sport + if (sportFilter !== "all" && p.sport !== sportFilter) { + return false; + } + + return true; + }); + + // Sort + if (sortBy === "ev") { + filtered.sort((a, b) => b.expectedValue - a.expectedValue); + } else { + filtered.sort((a, b) => a.name.localeCompare(b.name)); + } + + return filtered; + }, [participants, draftedIds, searchQuery, sportFilter, sortBy]); + + return ( +
+ {/* Filters */} +
+ {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+ + {/* Sport Filter and Sort */} +
+ + + +
+
+ + {/* Player Count */} +
+ {filteredParticipants.length} player{filteredParticipants.length !== 1 ? "s" : ""} available +
+ + {/* Player Table */} +
+ {filteredParticipants.length === 0 ? ( +
+ No players found +
+ ) : ( + + + + + + + + + + + {filteredParticipants.map((participant) => ( + + + + + + + ))} + +
PlayerSportEVActions
{participant.name} + + {participant.sport} + + + {participant.expectedValue.toFixed(1)} + +
+ {onAddToQueue && ( + + )} + {canDraft && onDraftPlayer && ( + + )} +
+
+ )} +
+
+ ); +} diff --git a/app/models/season.ts b/app/models/season.ts index 46d9a8c..1b4167b 100644 --- a/app/models/season.ts +++ b/app/models/season.ts @@ -139,6 +139,7 @@ export async function findSeasonWithSportsSeasons( sportsSeason: { with: { sport: true, + participants: true, }, }, }, diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx index a79c937..1feb187 100644 --- a/app/routes/leagues/$leagueId.draft.tsx +++ b/app/routes/leagues/$leagueId.draft.tsx @@ -12,6 +12,7 @@ import { findSeasonWithSportsSeasons, } from "~/models"; import { DraftGrid } from "~/components/draft/DraftGrid"; +import { PlayerList } from "~/components/draft/PlayerList"; export async function loader(args: LoaderFunctionArgs) { const { userId } = await getAuth(args); @@ -38,7 +39,13 @@ export async function loader(args: LoaderFunctionArgs) { // Get season with sports and participants const seasonWithSports = await findSeasonWithSportsSeasons(season.id); const participants = seasonWithSports?.seasonSports?.flatMap( - (ss: any) => ss.sportsSeason?.participants || [] + (ss: any) => { + const sportName = ss.sportsSeason?.sport?.name || "Unknown"; + return (ss.sportsSeason?.participants || []).map((p: any) => ({ + ...p, + sport: sportName, + })); + } ) || []; // Get teams and draft order @@ -153,11 +160,13 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited {/* Available Players */} -
+

Available Players

-
- Player list will be implemented in Phase 6 -
+
{/* Your Queue */}