2026-03-02 16:46:35 -08:00
|
|
|
import { memo, useMemo } from "react";
|
|
|
|
|
import { type DraftPick, groupPicksByTeamAndSport } from "./picks-utils";
|
|
|
|
|
|
|
|
|
|
interface DraftSummaryViewProps {
|
|
|
|
|
draftSlots: Array<{ id: string; team: { id: string; name: string } }>;
|
|
|
|
|
ownerMap?: Record<string, string>;
|
|
|
|
|
picks: DraftPick[];
|
|
|
|
|
sports: Array<{ id: string; name: string }>;
|
2026-03-02 22:19:19 -08:00
|
|
|
totalRounds: number;
|
2026-03-02 16:46:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const DraftSummaryView = memo(function DraftSummaryView({
|
|
|
|
|
draftSlots,
|
|
|
|
|
ownerMap = {},
|
|
|
|
|
picks,
|
|
|
|
|
sports,
|
2026-03-02 22:19:19 -08:00
|
|
|
totalRounds,
|
2026-03-02 16:46:35 -08:00
|
|
|
}: DraftSummaryViewProps) {
|
|
|
|
|
const picksByTeamAndSport = useMemo(
|
|
|
|
|
() => groupPicksByTeamAndSport(picks),
|
|
|
|
|
[picks]
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-02 22:19:19 -08:00
|
|
|
const sportStats = useMemo(() => {
|
|
|
|
|
const map = new Map<string, { total: number; teams: number }>();
|
|
|
|
|
sports.forEach((sport) => map.set(sport.id, { total: 0, teams: 0 }));
|
|
|
|
|
picksByTeamAndSport.forEach((teamMap) => {
|
|
|
|
|
teamMap.forEach((teamSportPicks, sportId) => {
|
|
|
|
|
const stat = map.get(sportId);
|
|
|
|
|
if (stat && teamSportPicks.length > 0) {
|
|
|
|
|
stat.total += teamSportPicks.length;
|
|
|
|
|
stat.teams++;
|
|
|
|
|
}
|
2026-03-02 16:46:35 -08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return map;
|
|
|
|
|
}, [picksByTeamAndSport, sports]);
|
|
|
|
|
|
2026-03-02 22:19:19 -08:00
|
|
|
const flexPicksAvailable = Math.max(0, totalRounds - sports.length);
|
|
|
|
|
|
|
|
|
|
const flexPicksUsedByTeam = useMemo(() => {
|
|
|
|
|
const map = new Map<string, number>();
|
|
|
|
|
picksByTeamAndSport.forEach((teamMap, teamId) => {
|
|
|
|
|
let used = 0;
|
|
|
|
|
teamMap.forEach((teamSportPicks) => {
|
|
|
|
|
if (teamSportPicks.length > 1) used += teamSportPicks.length - 1;
|
|
|
|
|
});
|
|
|
|
|
map.set(teamId, used);
|
|
|
|
|
});
|
|
|
|
|
return map;
|
|
|
|
|
}, [picksByTeamAndSport]);
|
|
|
|
|
|
2026-03-02 16:46:35 -08:00
|
|
|
if (sports.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center h-full p-8 text-muted-foreground">
|
|
|
|
|
<p>No sports have been configured for this season.</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="w-full h-full overflow-auto">
|
|
|
|
|
<table className="w-full border-collapse">
|
|
|
|
|
<thead className="sticky top-0 bg-background z-10">
|
|
|
|
|
<tr>
|
|
|
|
|
<th
|
|
|
|
|
scope="col"
|
|
|
|
|
className="border-r border-b border-border p-2 text-left font-semibold bg-muted sticky left-0 z-20 min-w-[120px]"
|
|
|
|
|
>
|
|
|
|
|
Sport
|
|
|
|
|
</th>
|
|
|
|
|
<th
|
|
|
|
|
scope="col"
|
|
|
|
|
className="border-r border-b border-border p-2 text-center font-semibold bg-muted min-w-[48px]"
|
|
|
|
|
>
|
|
|
|
|
Total
|
|
|
|
|
</th>
|
|
|
|
|
{draftSlots.map((slot, index) => {
|
|
|
|
|
const displayName = ownerMap[slot.team.id] || slot.team.name;
|
|
|
|
|
const isLast = index === draftSlots.length - 1;
|
2026-03-02 22:19:19 -08:00
|
|
|
const used = flexPicksUsedByTeam.get(slot.team.id) ?? 0;
|
|
|
|
|
const flexesExhausted = flexPicksAvailable > 0 && used >= flexPicksAvailable;
|
2026-03-02 16:46:35 -08:00
|
|
|
return (
|
|
|
|
|
<th
|
|
|
|
|
key={slot.id}
|
|
|
|
|
scope="col"
|
|
|
|
|
className={`border-b border-border p-2 text-center font-semibold bg-muted/50 min-w-[60px] ${!isLast ? "border-r" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
<span className="text-sm">{displayName}</span>
|
2026-03-02 22:19:19 -08:00
|
|
|
{flexPicksAvailable > 0 && (
|
|
|
|
|
<div className={`text-xs mt-0.5 ${flexesExhausted ? "text-destructive font-medium" : "font-normal text-muted-foreground"}`}>
|
|
|
|
|
{used} of {flexPicksAvailable} flexes
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-02 16:46:35 -08:00
|
|
|
</th>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{sports.map((sport, sportIndex) => {
|
|
|
|
|
const isLastRow = sportIndex === sports.length - 1;
|
2026-03-02 22:19:19 -08:00
|
|
|
const { total: sportTotal, teams: sportTeamCount } = sportStats.get(sport.id) ?? { total: 0, teams: 0 };
|
2026-03-02 16:46:35 -08:00
|
|
|
return (
|
|
|
|
|
<tr key={sport.id}>
|
|
|
|
|
<th
|
|
|
|
|
scope="row"
|
|
|
|
|
className={`border-r border-border p-2 font-medium text-sm text-left bg-muted sticky left-0 z-10 ${!isLastRow ? "border-b" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
{sport.name}
|
|
|
|
|
</th>
|
|
|
|
|
<td
|
|
|
|
|
className={`border-r border-border p-2 text-center text-sm font-semibold bg-muted/30 ${!isLastRow ? "border-b" : ""}`}
|
|
|
|
|
>
|
2026-03-02 22:19:19 -08:00
|
|
|
{sportTotal > 0 ? (
|
|
|
|
|
<>
|
|
|
|
|
<div>{sportTotal}</div>
|
|
|
|
|
<div className="text-xs font-normal text-muted-foreground mt-0.5">
|
|
|
|
|
{sportTeamCount} team{sportTeamCount !== 1 ? "s" : ""}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
) : null}
|
2026-03-02 16:46:35 -08:00
|
|
|
</td>
|
|
|
|
|
{draftSlots.map((slot, slotIndex) => {
|
|
|
|
|
const count =
|
|
|
|
|
picksByTeamAndSport.get(slot.team.id)?.get(sport.id)
|
|
|
|
|
?.length ?? 0;
|
|
|
|
|
const isLast = slotIndex === draftSlots.length - 1;
|
|
|
|
|
return (
|
|
|
|
|
<td
|
|
|
|
|
key={`${slot.team.id}-${sport.id}`}
|
|
|
|
|
className={`border-border p-2 text-center text-sm ${
|
|
|
|
|
count >= 2 ? "bg-accent/30 font-semibold" : "bg-background"
|
|
|
|
|
} ${!isLastRow ? "border-b" : ""} ${!isLast ? "border-r" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
{count > 0 ? count : null}
|
|
|
|
|
</td>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|