2026-03-02 16:46:35 -08:00
|
|
|
import { memo, useEffect, useMemo, useState } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "~/components/ui/select";
|
2026-04-18 21:20:38 -07:00
|
|
|
import { DraftPickCell } from "./DraftPickCell";
|
2026-03-02 16:46:35 -08:00
|
|
|
import { type DraftPick, groupPicksByTeamAndSport } from "./picks-utils";
|
|
|
|
|
|
|
|
|
|
interface TeamRosterViewProps {
|
|
|
|
|
draftSlots: Array<{ id: string; team: { id: string; name: string } }>;
|
|
|
|
|
ownerMap?: Record<string, string>;
|
|
|
|
|
picks: DraftPick[];
|
|
|
|
|
sports: Array<{ id: string; name: string }>;
|
2026-04-18 21:20:38 -07:00
|
|
|
totalRounds: number;
|
2026-03-02 16:46:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const TeamRosterView = memo(function TeamRosterView({
|
|
|
|
|
draftSlots,
|
|
|
|
|
ownerMap = {},
|
|
|
|
|
picks,
|
|
|
|
|
sports,
|
2026-04-18 21:20:38 -07:00
|
|
|
totalRounds,
|
2026-03-02 16:46:35 -08:00
|
|
|
}: TeamRosterViewProps) {
|
|
|
|
|
const [selectedTeamId, setSelectedTeamId] = useState(
|
|
|
|
|
draftSlots[0]?.team.id ?? ""
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (
|
|
|
|
|
draftSlots.length > 0 &&
|
|
|
|
|
!draftSlots.some((s) => s.team.id === selectedTeamId)
|
|
|
|
|
) {
|
|
|
|
|
setSelectedTeamId(draftSlots[0].team.id);
|
|
|
|
|
}
|
|
|
|
|
}, [draftSlots, selectedTeamId]);
|
|
|
|
|
|
|
|
|
|
const picksByTeamAndSport = useMemo(
|
|
|
|
|
() => groupPicksByTeamAndSport(picks),
|
|
|
|
|
[picks]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const teamPicks = useMemo(
|
|
|
|
|
() => picksByTeamAndSport.get(selectedTeamId),
|
|
|
|
|
[picksByTeamAndSport, selectedTeamId]
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-18 21:20:38 -07:00
|
|
|
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(
|
2026-03-02 16:46:35 -08:00
|
|
|
() => sports.filter((s) => (teamPicks?.get(s.id)?.length ?? 0) > 0),
|
|
|
|
|
[sports, teamPicks]
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-18 21:20:38 -07:00
|
|
|
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";
|
2026-03-02 16:46:35 -08:00
|
|
|
|
|
|
|
|
if (draftSlots.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center h-full p-8 text-muted-foreground">
|
|
|
|
|
<p>No teams in this draft.</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="h-full overflow-y-auto">
|
|
|
|
|
<div className="p-4 space-y-4">
|
2026-04-18 21:20:38 -07:00
|
|
|
{/* Team selector */}
|
2026-03-02 16:46:35 -08:00
|
|
|
<div className="space-y-1">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="roster-team-select"
|
|
|
|
|
className="text-xs font-medium text-muted-foreground"
|
|
|
|
|
>
|
|
|
|
|
Team
|
|
|
|
|
</label>
|
|
|
|
|
<Select value={selectedTeamId} onValueChange={setSelectedTeamId}>
|
|
|
|
|
<SelectTrigger id="roster-team-select" className="w-full">
|
|
|
|
|
<SelectValue placeholder="Select a team" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{draftSlots.map((slot) => (
|
|
|
|
|
<SelectItem key={slot.id} value={slot.team.id}>
|
|
|
|
|
{ownerMap[slot.team.id] || slot.team.name}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-18 21:20:38 -07:00
|
|
|
{/* 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>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<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>
|
2026-03-02 16:46:35 -08:00
|
|
|
) : (
|
2026-04-18 21:20:38 -07:00
|
|
|
<div className="space-y-3">
|
|
|
|
|
{draftedSports.map((sport) => {
|
|
|
|
|
const sportPicks = teamPicks?.get(sport.id) ?? [];
|
|
|
|
|
return (
|
|
|
|
|
<div key={sport.id}>
|
|
|
|
|
<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) => (
|
|
|
|
|
<DraftPickCell
|
|
|
|
|
key={pick.id}
|
|
|
|
|
pickNumber={pick.pickNumber}
|
|
|
|
|
round={pick.round}
|
|
|
|
|
pickInRound={pick.pickInRound}
|
|
|
|
|
state="picked"
|
|
|
|
|
pick={pick}
|
|
|
|
|
corona="gradient"
|
|
|
|
|
className="w-44 flex-none"
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-03-02 16:46:35 -08:00
|
|
|
</div>
|
2026-04-18 21:20:38 -07:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
2026-03-02 16:46:35 -08:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|