brackt/app/components/draft/DraftSummaryView.tsx
Chris Parsons e2f0cefb5e Fix draft Summary tab showing wrong avatar for teams without custom logos
DraftSummaryView was not declaring or forwarding ownerAvatarData, so
TeamAvatar fell back to a generated flag instead of the owner's avatar.
DraftGrid and DraftGridSection already threaded ownerAvatarData correctly;
this makes the Summary tab consistent. No route changes needed — the loader
already enriches draftSlots with ownerAvatarData.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:55:51 -07:00

175 lines
6.7 KiB
TypeScript

import { memo, useMemo, Fragment } from "react";
import { TeamAvatar } from "~/components/TeamAvatar";
import { type DraftPick, groupPicksByTeamAndSport } from "./picks-utils";
import type { AvatarData, RawFlagConfig } from "~/lib/flag-types";
interface DraftSummaryViewProps {
draftSlots: Array<{
id: string;
team: {
id: string;
name: string;
logoUrl?: string | null;
flagConfig?: RawFlagConfig | null;
avatarType?: string | null;
ownerAvatarData?: AvatarData | null;
};
}>;
ownerMap?: Record<string, string>;
picks: DraftPick[];
sports: Array<{ id: string; name: string }>;
totalRounds: number;
}
export const DraftSummaryView = memo(function DraftSummaryView({
draftSlots,
ownerMap = {},
picks,
sports,
totalRounds,
}: DraftSummaryViewProps) {
const picksByTeamAndSport = useMemo(
() => groupPicksByTeamAndSport(picks),
[picks]
);
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++;
}
});
});
return map;
}, [picksByTeamAndSport, sports]);
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]);
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>
);
}
const gridCols = `140px 96px repeat(${draftSlots.length}, minmax(80px, 1fr))`;
return (
<div className="w-full h-full overflow-auto">
<div role="table" aria-label="Draft summary by sport and team" className="grid w-full" style={{ gridTemplateColumns: gridCols }}>
{/* Header row */}
<div role="rowgroup" className="contents">
<div role="row" className="contents">
<div role="columnheader" className="sticky top-0 left-0 z-30 bg-muted border-r border-b border-border px-3 py-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Sport
</div>
<div role="columnheader" className="sticky top-0 z-20 bg-muted border-r border-b border-border px-2 py-2 text-center text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Total
</div>
{draftSlots.map((slot, index) => {
const ownerName = ownerMap[slot.team.id];
const isLast = index === draftSlots.length - 1;
const used = flexPicksUsedByTeam.get(slot.team.id) ?? 0;
const flexesExhausted = flexPicksAvailable > 0 && used >= flexPicksAvailable;
return (
<div
role="columnheader"
key={slot.id}
className={`sticky top-0 z-20 bg-muted border-b border-border px-1 py-2 min-w-0 ${!isLast ? "border-r" : ""}`}
>
<div className="flex flex-col items-center gap-1">
<TeamAvatar
teamId={slot.team.id}
teamName={ownerName || slot.team.name}
logoUrl={slot.team.logoUrl}
flagConfig={slot.team.flagConfig}
avatarType={slot.team.avatarType}
ownerAvatarData={slot.team.ownerAvatarData}
size="md"
/>
<div className="text-xs font-semibold text-center truncate w-full">
{ownerName || slot.team.name}
</div>
{flexPicksAvailable > 0 && (
<span className={`text-xs ${flexesExhausted ? "text-destructive font-medium" : "text-muted-foreground"}`}>
{used}/{flexPicksAvailable} flex picks
</span>
)}
</div>
</div>
);
})}
</div>{/* end header row */}
</div>{/* end rowgroup */}
{/* Data rows */}
<div role="rowgroup" className="contents">
{sports.map((sport, sportIndex) => {
const isLastRow = sportIndex === sports.length - 1;
const { total: sportTotal, teams: sportTeamCount } =
sportStats.get(sport.id) ?? { total: 0, teams: 0 };
return (
<Fragment key={sport.id}>
<div role="row" className="contents">
<div role="rowheader" className={`sticky left-0 z-10 bg-muted border-r border-border px-3 py-2.5 font-medium text-sm break-words ${!isLastRow ? "border-b" : ""}`}>
{sport.name}
</div>
<div role="cell" className={`border-r border-border px-2 py-2.5 text-center bg-muted/20 ${!isLastRow ? "border-b" : ""}`}>
{sportTotal > 0 ? (
<>
<div className="text-sm font-bold tabular-nums">{sportTotal}</div>
<div className="text-xs text-muted-foreground">{sportTeamCount}/{draftSlots.length} teams</div>
</>
) : (
<span className="text-muted-foreground/40 text-sm"></span>
)}
</div>
{draftSlots.map((slot, slotIndex) => {
const count = picksByTeamAndSport.get(slot.team.id)?.get(sport.id)?.length ?? 0;
const isLast = slotIndex === draftSlots.length - 1;
const isFlex = count >= 2;
return (
<div
role="cell"
key={`${slot.team.id}-${sport.id}`}
className={`border-border px-3 py-2.5 text-center ${isFlex ? "bg-amber-500/10" : "bg-background"} ${!isLastRow ? "border-b" : ""} ${!isLast ? "border-r" : ""}`}
>
{count > 0 ? (
<span className={`text-sm font-bold tabular-nums ${isFlex ? "text-amber-600 dark:text-amber-400" : ""}`}>
{count}
</span>
) : (
<span className="text-muted-foreground/30 text-sm"></span>
)}
</div>
);
})}
</div>{/* end row */}
</Fragment>
);
})}
</div>{/* end rowgroup */}
</div>
</div>
);
});