diff --git a/app/rankings/ncaa/actions.ts b/app/rankings/ncaa/actions.ts index 0352dd5..71a6310 100644 --- a/app/rankings/ncaa/actions.ts +++ b/app/rankings/ncaa/actions.ts @@ -36,6 +36,48 @@ export async function savePlayerRankings( return { success: true }; } +export async function deletePlayerRankings(): Promise { + const supabase = await createClient(); + + const { data: { user }, error: authError } = await supabase.auth.getUser(); + if (authError || !user) { + return { success: false, error: 'Not authenticated' }; + } + + const { error } = await supabase + .from('user_ncaa_rankings') + .delete() + .eq('user_id', user.id) + .eq('season_year', 2026); + + if (error) { + return { success: false, error: error.message }; + } + + return { success: true }; +} + +export async function deleteExpectedGames(): Promise { + const supabase = await createClient(); + + const { data: { user }, error: authError } = await supabase.auth.getUser(); + if (authError || !user) { + return { success: false, error: 'Not authenticated' }; + } + + const { error } = await supabase + .from('user_ncaa_team_settings') + .delete() + .eq('user_id', user.id) + .eq('season_year', 2026); + + if (error) { + return { success: false, error: error.message }; + } + + return { success: true }; +} + export async function saveExpectedGames( settings: { team_name: string; expected_games: number }[] ): Promise { diff --git a/app/rankings/ncaa/player-ranking-card.tsx b/app/rankings/ncaa/player-ranking-card.tsx index ceacfa7..b298c5d 100644 --- a/app/rankings/ncaa/player-ranking-card.tsx +++ b/app/rankings/ncaa/player-ranking-card.tsx @@ -11,6 +11,7 @@ interface PlayerRankingCardProps { expectedGames: number; provided: DraggableProvided; isDragging: boolean; + isEditing: boolean; } export default function PlayerRankingCard({ @@ -19,6 +20,7 @@ export default function PlayerRankingCard({ expectedGames, provided, isDragging, + isEditing, }: PlayerRankingCardProps) { const borderColor = getPositionBorderColor(player.position, 'NCAAM'); const posTextColor = getPositionTextColor(player.position, 'NCAAM'); @@ -39,16 +41,20 @@ export default function PlayerRankingCard({ }} > {/* Drag handle */} -
-
-
-
-
+ {isEditing ? ( +
+
+
+
+
+
-
+ ) : ( +
+ )} {/* Rank number */}
diff --git a/app/rankings/ncaa/player-rankings-list.tsx b/app/rankings/ncaa/player-rankings-list.tsx index 77db0fa..b96140e 100644 --- a/app/rankings/ncaa/player-rankings-list.tsx +++ b/app/rankings/ncaa/player-rankings-list.tsx @@ -9,6 +9,7 @@ interface PlayerRankingsListProps { onReorder: (newOrder: PlayerWithStats[]) => void; expectedGames: Record; rankMap?: Map; + isEditing: boolean; } export default function PlayerRankingsList({ @@ -16,6 +17,7 @@ export default function PlayerRankingsList({ onReorder, expectedGames, rankMap, + isEditing, }: PlayerRankingsListProps) { const handleDragEnd = (result: DropResult) => { if (!result.destination) return; @@ -37,7 +39,7 @@ export default function PlayerRankingsList({ return ( - + {(provided) => (
{players.map((player, index) => ( - + {(provided, snapshot) => ( )} diff --git a/app/rankings/ncaa/rankings-page.tsx b/app/rankings/ncaa/rankings-page.tsx index cf7160d..3f045de 100644 --- a/app/rankings/ncaa/rankings-page.tsx +++ b/app/rankings/ncaa/rankings-page.tsx @@ -6,8 +6,8 @@ import RankingsFilters from './rankings-filters'; import PlayerRankingsList from './player-rankings-list'; import ExpectedGamesSection from './expected-games-section'; import TeamsTable from './teams-table'; -import { savePlayerRankings, saveExpectedGames } from './actions'; -import { computeProjectedTotal } from './utils'; +import { savePlayerRankings, saveExpectedGames, deletePlayerRankings, deleteExpectedGames } from './actions'; +import { computeProjectedTotal, getDefaultExpectedGames } from './utils'; import type { PlayerWithStats, NcaaTeamInfo, UserRanking, UserTeamSetting } from './utils'; type SortBy = 'projection' | 'expectedGames' | 'custom'; @@ -60,8 +60,14 @@ export default function RankingsPage({ }, [orderedPlayers]); const [hasUnsavedRankings, setHasUnsavedRankings] = useState(false); + const [hasCustomRankings, setHasCustomRankings] = useState(userRankings.length > 0); + const [isEditing, setIsEditing] = useState(false); const [isSaving, setIsSaving] = useState(false); + // Snapshot of player order before editing, for cancel + const preEditOrderRef = useRef(orderedPlayers); + const preEditSortByRef = useRef(sortBy); + // Debounce ref for expected games saving const gamesTimeoutRef = useRef(null); @@ -184,9 +190,45 @@ export default function RankingsPage({ })); await savePlayerRankings(rankings); setHasUnsavedRankings(false); + setHasCustomRankings(true); + setIsEditing(false); setIsSaving(false); }, [orderedPlayers]); + // Reset rankings to default projection order + const handleResetRankings = useCallback(async () => { + setOrderedPlayers((prev) => + [...prev].sort((a, b) => b.projectedTotal - a.projectedTotal) + ); + setSortBy('projection'); + setHasUnsavedRankings(false); + setHasCustomRankings(false); + setIsEditing(false); + await deletePlayerRankings(); + }, []); + + // Build default expected games map from team seeds + const defaultExpectedGames = useMemo(() => { + const teamInfoMap = new Map(teamInfo.map((ti) => [ti.team_name, ti])); + const defaults: Record = {}; + for (const team of allTeams) { + defaults[team] = getDefaultExpectedGames(teamInfoMap.get(team)?.seed); + } + return defaults; + }, [teamInfo, allTeams]); + + const hasCustomExpectedGames = useMemo(() => { + return allTeams.some((team) => expectedGames[team] !== defaultExpectedGames[team]); + }, [expectedGames, defaultExpectedGames, allTeams]); + + // Reset expected games to seed-based defaults + const handleResetExpectedGames = useCallback(async () => { + setExpectedGames(defaultExpectedGames); + updateProjectedTotals(defaultExpectedGames); + if (gamesTimeoutRef.current) clearTimeout(gamesTimeoutRef.current); + await deleteExpectedGames(); + }, [defaultExpectedGames, updateProjectedTotals]); + // Cleanup timeouts useEffect(() => { return () => { @@ -252,17 +294,49 @@ export default function RankingsPage({ ); })}
-
- +
+ {filteredPlayers.length} player{filteredPlayers.length !== 1 ? 's' : ''} - {hasUnsavedRankings && ( + {(hasUnsavedRankings || hasCustomRankings) && ( + + )} + {isEditing ? ( + <> + + + + ) : ( )}
@@ -273,15 +347,25 @@ export default function RankingsPage({ onReorder={handleReorder} expectedGames={expectedGames} rankMap={rankMap} + isEditing={isEditing} /> + {hasCustomExpectedGames && ( +
+ +
+ )}
diff --git a/app/rankings/ncaa/teams-table.tsx b/app/rankings/ncaa/teams-table.tsx index 2b10e70..e64fa7d 100644 --- a/app/rankings/ncaa/teams-table.tsx +++ b/app/rankings/ncaa/teams-table.tsx @@ -9,13 +9,12 @@ import { TableRow, } from '@/components/ui/table'; import { ScrollArea } from '@/components/ui/scroll-area'; -import type { NcaaTeamInfo, PlayerWithStats } from './utils'; +import type { NcaaTeamInfo } from './utils'; interface TeamsTableProps { teams: string[]; teamInfo: NcaaTeamInfo[]; expectedGames: Record; - players: PlayerWithStats[]; onExpectedGamesChange: (teamName: string, games: number) => void; } @@ -23,7 +22,6 @@ export default function TeamsTable({ teams, teamInfo, expectedGames, - players, onExpectedGamesChange, }: TeamsTableProps) { const teamInfoMap = new Map(); @@ -31,24 +29,15 @@ export default function TeamsTable({ teamInfoMap.set(ti.team_name, ti); } - // Compute team-level aggregates + // Compute team-level data const teamData = teams.map((teamName) => { const info = teamInfoMap.get(teamName); - const teamPlayers = players.filter((p) => p.team_name === teamName); - const avgPpg = - teamPlayers.length > 0 - ? Math.round( - (teamPlayers.reduce((sum, p) => sum + p.averages.ppg, 0) / teamPlayers.length) * 10 - ) / 10 - : 0; return { teamName, seed: info?.seed ?? null, region: info?.region ?? null, expectedGames: expectedGames[teamName] || 1, - playerCount: teamPlayers.length, - avgPpg, }; }); @@ -66,21 +55,19 @@ export default function TeamsTable({ - Team Seed + Team Region Games - Players - Avg PPG {teamData.map((team) => ( - {team.teamName} {team.seed ? `#${team.seed}` : '-'} + {team.teamName} {team.region || '-'} @@ -100,8 +87,6 @@ export default function TeamsTable({ className="bg-background border border-border rounded px-1 py-0.5 text-sm w-14 text-foreground" /> - {team.playerCount} - {team.avgPpg} ))}