brackt/app/components/draft/TeamRosterView.tsx
Chris Parsons 599bba8949
refactor: address code review findings in Roster/Summary draft views (#57)
- Extract groupPicksByTeamAndSport into shared picks-utils.ts to remove
  duplication between TeamRosterView and DraftSummaryView
- Fix TeamRosterView: make teamPicks a proper useMemo dep on selectedTeamId,
  add useEffect to reset stale selection if draftSlots changes
- Add accessible label/htmlFor to TeamRosterView team Select
- Remove dead `season` prop from DraftSummaryView (was inherited from
  TeamsDraftedGrid but never used)
- Fix DraftSummaryView: sport name cells are now <th scope="row">, column
  headers have scope="col", Total header uses solid bg-muted to match Sport
- Replace || null with explicit > 0 check in Total cell for clarity
- Consolidate to summaryViewProps/rosterViewProps in route for consistency

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 16:46:35 -08:00

121 lines
3.5 KiB
TypeScript

import { memo, useEffect, useMemo, useState } from "react";
import { Badge } from "~/components/ui/badge";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
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 }>;
}
export const TeamRosterView = memo(function TeamRosterView({
draftSlots,
ownerMap = {},
picks,
sports,
}: 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 &&
!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 sportsWithPicks = 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]);
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">
<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>
{totalPicks === 0 ? (
<p className="text-sm text-muted-foreground text-center py-6">
No picks yet
</p>
) : (
sportsWithPicks.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">
{sportPicks.map((pick) => (
<li key={pick.id} className="text-sm text-foreground">
{pick.participant.name}
</li>
))}
</ul>
</div>
);
})
)}
</div>
</div>
);
});