brackt/app/components/draft/TeamRosterView.tsx
2026-04-18 21:20:38 -07:00

194 lines
6.3 KiB
TypeScript

import { memo, useEffect, useMemo, useState } from "react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { DraftPickCell } from "./DraftPickCell";
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 }>;
totalRounds: number;
}
export const TeamRosterView = memo(function TeamRosterView({
draftSlots,
ownerMap = {},
picks,
sports,
totalRounds,
}: 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]
);
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 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 (
<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">
{/* Team selector */}
<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>
{/* 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>
) : (
<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>
</div>
);
})}
</div>
)}
</div>
</div>
);
});