Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 114 additions & 14 deletions app/draft/[id]/draft-room.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState, useTransition } from 'react';
import { useEffect, useState, useMemo, useTransition } from 'react';
import { createClient } from '@/app/utils/supabase/client';
import DraftQueue from './draft-queue';
import PlayerSearch from './player-search';
Expand All @@ -10,15 +10,21 @@ import DraftInfoPanel from './draft-info-panel';
import { makePick, startDraft, togglePause, toggleAutoPick, triggerAutoPick } from './actions';
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { getDefaultExpectedGames, parsePlayerSummary, computeProjectedTotal } from '@/app/rankings/ncaa/utils';

type PlayerSortBy = 'rank' | 'projection' | 'name';

interface DraftRoomProps {
draftSettings: any;
currentTeam: any;
isCommissioner: boolean;
leagueTeams?: any[];
userRankings?: any[];
userTeamSettings?: any[];
ncaaTeamInfo?: any[];
}

export default function DraftRoom({ draftSettings, currentTeam, isCommissioner, leagueTeams = [] }: DraftRoomProps) {
export default function DraftRoom({ draftSettings, currentTeam, isCommissioner, leagueTeams = [], userRankings = [], userTeamSettings = [], ncaaTeamInfo = [] }: DraftRoomProps) {
const supabase = createClient();

// Draft state
Expand All @@ -33,6 +39,63 @@ export default function DraftRoom({ draftSettings, currentTeam, isCommissioner,
const [isPaused, setIsPaused] = useState<boolean>(draftSettings.is_paused || false);
const [pickError, setPickError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const [isQueueOpen, setIsQueueOpen] = useState(true);

const isNcaam = draftSettings.leagues.league === 'NCAAM';
const [playerSortBy, setPlayerSortBy] = useState<PlayerSortBy>(isNcaam ? 'rank' : 'name');

// Build NCAAM ranking/projection maps
const expectedGamesMap = useMemo(() => {
if (!isNcaam) return {};
const map: Record<string, number> = {};
for (const ti of ncaaTeamInfo) {
map[ti.team_name] = getDefaultExpectedGames(ti.seed);
}
for (const s of userTeamSettings) {
map[s.team_name] = s.expected_games;
}
return map;
}, [isNcaam, ncaaTeamInfo, userTeamSettings]);

const rankMap = useMemo(() => {
const m = new Map<string, number>();
for (const r of userRankings) {
m.set(r.player_id, r.rank_position);
}
return m;
}, [userRankings]);

// Enrich players with NCAAM rank/projection and sort
const enrichAndSort = (players: any[]): any[] => {
if (!isNcaam) return players;
return players.map(p => {
const { ppg } = parsePlayerSummary(p.summary);
const expGames = expectedGamesMap[p.team_name] || 1;
return {
...p,
rank: rankMap.get(p.id) ?? null,
projectedTotal: computeProjectedTotal(ppg, expGames),
};
});
};

const sortPlayers = (players: any[], sort: PlayerSortBy): any[] => {
const sorted = [...players];
if (sort === 'rank') {
sorted.sort((a, b) => {
const aRank = a.rank;
const bRank = b.rank;
if (aRank != null && bRank != null) return aRank - bRank;
if (aRank != null) return -1;
if (bRank != null) return 1;
return (b.projectedTotal || 0) - (a.projectedTotal || 0);
});
} else if (sort === 'projection') {
sorted.sort((a, b) => (b.projectedTotal || 0) - (a.projectedTotal || 0));
}
// 'name' keeps the original alphabetical order from the query
return sorted;
};

// Get current pick team information
useEffect(() => {
Expand Down Expand Up @@ -149,8 +212,10 @@ export default function DraftRoom({ draftSettings, currentTeam, isCommissioner,

if (playersError) throw playersError;

setAvailablePlayers(players || []);
setSearchResults(players || []);
const enriched = enrichAndSort(players || []);
const sorted = sortPlayers(enriched, playerSortBy);
setAvailablePlayers(sorted);
setSearchResults(sorted);
} catch (error) {
console.error('Error fetching available players:', error);
} finally {
Expand Down Expand Up @@ -274,6 +339,13 @@ export default function DraftRoom({ draftSettings, currentTeam, isCommissioner,
setSearchResults(filtered);
};

const handleSortChange = (sort: PlayerSortBy) => {
setPlayerSortBy(sort);
const sorted = sortPlayers(availablePlayers, sort);
setAvailablePlayers(sorted);
setSearchResults(sorted);
};

const isDraftActive = draftState.draft_status === 'in_progress';
const isDraftCompleted = draftState.draft_status === 'completed';
const isMyTurn = currentPickTeam?.id === currentTeam.id && isDraftActive;
Expand Down Expand Up @@ -349,7 +421,7 @@ export default function DraftRoom({ draftSettings, currentTeam, isCommissioner,
<TabsContent value="players">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4">
{/* Player Search - main area */}
<div className="lg:col-span-5">
<div className={isQueueOpen ? 'lg:col-span-5' : 'lg:col-span-9'}>
<Card>
<CardContent className="p-4">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">Available Players</h3>
Expand Down Expand Up @@ -403,20 +475,48 @@ export default function DraftRoom({ draftSettings, currentTeam, isCommissioner,
isDraftActive={isDraftActive}
selectedPlayer={selectedPlayer}
leagueType={draftSettings.leagues.league}
isNcaam={isNcaam}
sortBy={playerSortBy}
onSortChange={handleSortChange}
/>
</CardContent>
</Card>
</div>

{/* Draft Queue */}
<div className="lg:col-span-4">
<Card>
<CardContent className="p-4">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">Your Draft Queue</h3>
<DraftQueue teamId={currentTeam.id} draftId={draftSettings.id} />
</CardContent>
</Card>
</div>
{/* Draft Queue - collapsible */}
{isQueueOpen ? (
<div className="lg:col-span-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">Your Draft Queue</h3>
<button
onClick={() => setIsQueueOpen(false)}
className="text-muted-foreground hover:text-foreground transition-colors"
title="Collapse queue"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="15 18 9 12 15 6" />
</svg>
</button>
</div>
<DraftQueue teamId={currentTeam.id} draftId={draftSettings.id} />
</CardContent>
</Card>
</div>
) : (
<div className="hidden lg:flex items-start">
<button
onClick={() => setIsQueueOpen(true)}
className="p-2 bg-card border border-border rounded-lg text-muted-foreground hover:text-foreground transition-colors"
title="Show queue"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
</div>
)}

{/* Draft Info Panel */}
<div className="lg:col-span-3">
Expand Down
39 changes: 35 additions & 4 deletions app/draft/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,40 @@ export default async function DraftPage({
return <div>Error loading teams</div>;
}

const currentTeam = userTeam || {
const currentTeam = userTeam || {
id: 'commissioner',
name: 'Commissioner View',
league_id: draftSettings.league_id
};

// Fetch NCAA rankings data if this is an NCAAM league
let userRankings: any[] = [];
let userTeamSettings: any[] = [];
let ncaaTeamInfo: any[] = [];

if (draftSettings.leagues.league === 'NCAAM') {
const [rankingsRes, teamSettingsRes, teamInfoRes] = await Promise.all([
supabase
.from('user_ncaa_rankings')
.select('*')
.eq('user_id', user.id)
.eq('season_year', 2026),
supabase
.from('user_ncaa_team_settings')
.select('*')
.eq('user_id', user.id)
.eq('season_year', 2026),
supabase
.from('ncaa_team_info')
.select('*')
.eq('season_year', 2026),
]);

userRankings = rankingsRes.data || [];
userTeamSettings = teamSettingsRes.data || [];
ncaaTeamInfo = teamInfoRes.data || [];
}

return (
<div className="min-h-screen bg-background">
<div className="w-full max-w-[1600px] mx-auto px-4">
Expand All @@ -91,11 +119,14 @@ export default async function DraftPage({
</div>

<main>
<DraftRoom
draftSettings={draftSettings}
currentTeam={currentTeam}
<DraftRoom
draftSettings={draftSettings}
currentTeam={currentTeam}
isCommissioner={isCommissioner}
leagueTeams={leagueTeams}
userRankings={userRankings}
userTeamSettings={userTeamSettings}
ncaaTeamInfo={ncaaTeamInfo}
/>
</main>
</div>
Expand Down
74 changes: 72 additions & 2 deletions app/draft/[id]/picks-carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useRef, useEffect, useMemo } from 'react';
import { getPositionColor, getPositionBorderColor, computePickInfo, getTeamIdForPick } from './utils';
import { getPositionColor, getPositionBorderColor, getPositionsForLeague, computePickInfo, getTeamIdForPick } from './utils';

interface PicksCarouselProps {
leagueTeams: any[];
Expand Down Expand Up @@ -52,6 +52,41 @@ export default function PicksCarousel({
return map;
}, [leagueTeams]);

const positions = getPositionsForLeague(leagueType);

// Build roster counts per team: teamId -> { G: 2, F: 1, ... }
const teamRosters = useMemo(() => {
const rosters: Record<string, Record<string, number>> = {};
for (const pick of draftPicks) {
const teamId = pick.team?.id || pick.team_id;
const position = pick.player?.position;
if (teamId && position) {
if (!rosters[teamId]) rosters[teamId] = {};
rosters[teamId][position] = (rosters[teamId][position] || 0) + 1;
}
}
return rosters;
}, [draftPicks]);

// Cumulative roster at each pick number: pickNumber -> { G: x, F: y, ... }
const rosterAtPick = useMemo(() => {
const map: Record<number, Record<string, number>> = {};
const running: Record<string, Record<string, number>> = {};
// Sort picks by pick_number to build cumulative state
const sorted = [...draftPicks].sort((a, b) => (a.pick_number || 0) - (b.pick_number || 0));
for (const pick of sorted) {
const teamId = pick.team?.id || pick.team_id;
const position = pick.player?.position;
if (teamId && position && pick.pick_number != null) {
if (!running[teamId]) running[teamId] = {};
running[teamId][position] = (running[teamId][position] || 0) + 1;
// Snapshot the team's roster state at this pick
map[pick.pick_number] = { ...running[teamId] };
}
}
return map;
}, [draftPicks]);

const currentOverallPick = (currentRound - 1) * totalTeams + currentPick;

// Auto-scroll to current pick
Expand Down Expand Up @@ -109,6 +144,7 @@ export default function PicksCarousel({
const playerTeam = pick.player?.team || '';
const posColor = getPositionColor(position, leagueType);
const borderColor = getPositionBorderColor(position, leagueType);
const roster = rosterAtPick[overallPick] || {};

return (
<div
Expand All @@ -134,11 +170,29 @@ export default function PicksCarousel({
</div>
{/* Team name */}
<p className="text-[10px] text-muted-foreground truncate mt-1">{teamName}</p>
{/* Roster breakdown */}
<div className="flex gap-1 mt-1 flex-wrap">
{positions.map((pos) => {
const count = roster[pos] || 0;
if (count === 0) return null;
return (
<span
key={pos}
className={`text-[9px] font-medium px-1 rounded ${getPositionColor(pos, leagueType)} text-white`}
>
{pos}{count}
</span>
);
})}
</div>
</div>
);
}

// Empty / future pick
// Empty / future pick — show current team roster
const currentRoster = teamRosters[teamId] || {};
const hasRoster = Object.values(currentRoster).some(c => c > 0);

return (
<div
key={overallPick}
Expand All @@ -151,6 +205,22 @@ export default function PicksCarousel({
>
<p className="text-[10px] text-muted-foreground mb-0.5">{notation}</p>
<p className="text-sm font-medium truncate text-muted-foreground">{teamName}</p>
{hasRoster && (
<div className="flex gap-1 mt-1 flex-wrap">
{positions.map((pos) => {
const count = currentRoster[pos] || 0;
if (count === 0) return null;
return (
<span
key={pos}
className={`text-[9px] font-medium px-1 rounded ${getPositionColor(pos, leagueType)} text-white`}
>
{pos}{count}
</span>
);
})}
</div>
)}
</div>
);
})}
Expand Down
Loading
Loading