Fix Rosters page.

This commit is contained in:
Chris Parsons 2026-04-18 21:20:38 -07:00
parent 09b5193f18
commit 269ba05316
3 changed files with 108 additions and 31 deletions

View file

@ -1,5 +1,4 @@
import { memo, useEffect, useMemo, useState } from "react";
import { Badge } from "~/components/ui/badge";
import {
Select,
SelectContent,
@ -7,6 +6,7 @@ import {
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { DraftPickCell } from "./DraftPickCell";
import { type DraftPick, groupPicksByTeamAndSport } from "./picks-utils";
interface TeamRosterViewProps {
@ -14,6 +14,7 @@ interface TeamRosterViewProps {
ownerMap?: Record<string, string>;
picks: DraftPick[];
sports: Array<{ id: string; name: string }>;
totalRounds: number;
}
export const TeamRosterView = memo(function TeamRosterView({
@ -21,12 +22,12 @@ export const TeamRosterView = memo(function TeamRosterView({
ownerMap = {},
picks,
sports,
totalRounds,
}: TeamRosterViewProps) {
const [selectedTeamId, setSelectedTeamId] = useState(
draftSlots[0]?.team.id ?? ""
);
// Reset selection if the selected team is no longer present in the slot list
useEffect(() => {
if (
draftSlots.length > 0 &&
@ -46,16 +47,42 @@ export const TeamRosterView = memo(function TeamRosterView({
[picksByTeamAndSport, selectedTeamId]
);
const sportsWithPicks = useMemo(
const flexPicksAvailable = useMemo(
() => Math.max(0, totalRounds - sports.length),
[totalRounds, sports.length]
);
const flexPicksUsed = useMemo(() => {
let used = 0;
teamPicks?.forEach((sportPicks) => {
if (sportPicks.length > 1) used += sportPicks.length - 1;
});
return used;
}, [teamPicks]);
const flexPicksRemaining = useMemo(
() => flexPicksAvailable - flexPicksUsed,
[flexPicksAvailable, flexPicksUsed]
);
const missingSports = useMemo(
() => sports.filter((s) => (teamPicks?.get(s.id)?.length ?? 0) === 0),
[sports, teamPicks]
);
const draftedSports = useMemo(
() => sports.filter((s) => (teamPicks?.get(s.id)?.length ?? 0) > 0),
[sports, teamPicks]
);
const totalPicks = useMemo(() => {
let count = 0;
teamPicks?.forEach((sp) => (count += sp.length));
return count;
}, [teamPicks]);
const flexColor =
flexPicksAvailable === 0
? "text-muted-foreground"
: flexPicksRemaining <= 0
? "text-destructive"
: flexPicksRemaining <= Math.ceil(flexPicksAvailable * 0.25)
? "text-amber-500 dark:text-amber-400"
: "text-emerald-500 dark:text-emerald-400";
if (draftSlots.length === 0) {
return (
@ -68,6 +95,7 @@ export const TeamRosterView = memo(function TeamRosterView({
return (
<div className="h-full overflow-y-auto">
<div className="p-4 space-y-4">
{/* Team selector */}
<div className="space-y-1">
<label
htmlFor="roster-team-select"
@ -89,31 +117,76 @@ export const TeamRosterView = memo(function TeamRosterView({
</Select>
</div>
{totalPicks === 0 ? (
<p className="text-sm text-muted-foreground text-center py-6">
No picks yet
</p>
{/* Mini dashboard */}
<div className="grid grid-cols-2 gap-3">
<div className="rounded-lg border bg-card px-3 py-2.5 space-y-0.5">
<p className="text-xs text-muted-foreground font-medium">Flex Picks</p>
{flexPicksAvailable === 0 ? (
<p className="text-sm font-semibold text-muted-foreground">N/A</p>
) : (
sportsWithPicks.map((sport) => {
<>
<p className={`text-xl font-bold tabular-nums leading-none ${flexColor}`}>
{flexPicksRemaining}
<span className="text-sm font-normal text-muted-foreground ml-1">
/ {flexPicksAvailable}
</span>
</p>
<p className="text-xs text-muted-foreground">remaining</p>
</>
)}
</div>
<div className="rounded-lg border bg-card px-3 py-2.5 space-y-0.5">
<p className="text-xs text-muted-foreground font-medium">Sports Remaining</p>
<p className={`text-xl font-bold tabular-nums leading-none ${missingSports.length > 0 ? "text-foreground" : "text-emerald-500 dark:text-emerald-400"}`}>
{missingSports.length}
<span className="text-sm font-normal text-muted-foreground ml-1">
/ {sports.length}
</span>
</p>
<p className="text-xs text-muted-foreground">to draft</p>
</div>
</div>
{/* Missing sports list */}
{missingSports.length > 0 && (
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-1.5">Still Needed</p>
<p className="text-sm text-muted-foreground">
{missingSports.map((s) => s.name).join(" · ")}
</p>
</div>
)}
{/* Per-sport pick cells */}
{draftedSports.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-6">No picks yet</p>
) : (
<div className="space-y-3">
{draftedSports.map((sport) => {
const sportPicks = teamPicks?.get(sport.id) ?? [];
return (
<div key={sport.id}>
<div className="flex items-center gap-2 mb-2">
<h3 className="text-sm font-semibold">{sport.name}</h3>
<Badge variant="secondary" className="text-xs px-1.5 py-0">
{sportPicks.length}
</Badge>
</div>
<ul className="space-y-1 pl-2">
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-1.5">
{sport.name}
</p>
<div className="flex flex-wrap gap-1">
{sportPicks.map((pick) => (
<li key={pick.id} className="text-sm text-foreground">
{pick.participant.name}
</li>
<DraftPickCell
key={pick.id}
pickNumber={pick.pickNumber}
round={pick.round}
pickInRound={pick.pickInRound}
state="picked"
pick={pick}
corona="gradient"
className="w-44 flex-none"
/>
))}
</ul>
</div>
</div>
);
})
})}
</div>
)}
</div>
</div>

View file

@ -1,5 +1,8 @@
export type DraftPick = {
id: string;
pickNumber: number;
round: number;
pickInRound: number;
team: { id: string; name: string };
participant: { id: string; name: string };
sport: { id: string; name: string };

View file

@ -1577,6 +1577,7 @@ export default function DraftRoom() {
picks,
sports: seasonSportsData,
ownerMap,
totalRounds: season.draftRounds,
};
const handleAutodraftUpdate = useCallback(