brackt/app/components/draft/TeamsDraftedGrid.tsx
Chris Parsons 2cd4096e70 feat: Implement draft room redesign with collapsible sidebar and tabbed content
- Added AvailableParticipantsSection for participant search and filtering.
- Created DraftGridSection for displaying the draft grid with team timers and context menu.
- Developed QueueSection for managing participant queue and autodraft settings.
- Introduced RecentPicksSection to show recent draft picks.
- Implemented TeamsDraftedGrid to visualize drafted participants by team and sport.
- Added collapsible UI components for better layout management.
- Established tabbed navigation for draft board, recent picks, and teams drafted.
- Enhanced responsiveness for mobile and desktop views.
- Updated styles for a cohesive design across components.
2025-10-25 03:23:41 -07:00

205 lines
6.2 KiB
TypeScript

import { Badge } from "~/components/ui/badge";
import { useMemo } from "react";
interface TeamsDraftedGridProps {
draftSlots: Array<{
id: string;
draftOrder: number;
team: {
id: string;
name: string;
seasonId: string;
};
}>;
picks: Array<{
id: string;
team: {
id: string;
name: string;
};
participant: {
id: string;
name: string;
};
sport: {
id: string;
name: string;
type: string;
};
}>;
availableParticipants: Array<{
id: string;
name: string;
sport: {
id: string;
name: string;
};
}>;
season: {
id: string;
name: string;
year: number;
numFlexPicks: number;
sports?: Array<{
id: string;
name: string;
}>;
};
}
export function TeamsDraftedGrid({
draftSlots,
picks,
availableParticipants,
season,
}: TeamsDraftedGridProps) {
// Get unique sports from all available participants (not just picked ones)
const sports = useMemo(() => {
const sportMap = new Map<string, { id: string; name: string }>();
availableParticipants.forEach((participant) => {
if (!sportMap.has(participant.sport.id)) {
sportMap.set(participant.sport.id, {
id: participant.sport.id,
name: participant.sport.name,
});
}
});
return Array.from(sportMap.values()).sort((a, b) =>
a.name.localeCompare(b.name)
);
}, [availableParticipants]);
// Calculate picks by team and sport
const picksByTeamAndSport = useMemo(() => {
const map = new Map<string, Map<string, typeof picks>>();
picks.forEach((pick) => {
if (!map.has(pick.team.id)) {
map.set(pick.team.id, new Map());
}
const teamMap = map.get(pick.team.id)!;
if (!teamMap.has(pick.sport.id)) {
teamMap.set(pick.sport.id, []);
}
teamMap.get(pick.sport.id)!.push(pick);
});
return map;
}, [picks]);
// Calculate flex picks used by each team
const flexPicksByTeam = useMemo(() => {
const map = new Map<string, number>();
picks.forEach((pick) => {
// Flex picks are counted based on sport type or a different logic
// For now, we'll just count total picks and subtract sport-specific limits
// This is a simplified version - adjust based on your actual flex logic
if (!map.has(pick.team.id)) {
map.set(pick.team.id, 0);
}
});
// Calculate flex usage based on season settings
draftSlots.forEach((slot) => {
const teamPicks = picks.filter((p) => p.team.id === slot.team.id);
const totalPicks = teamPicks.length;
// This is simplified - you may need to implement actual flex calculation
// based on sport-specific limits in your season settings
const flexUsed = Math.max(0, totalPicks - sports.length);
map.set(slot.team.id, flexUsed);
});
return map;
}, [picks, draftSlots, sports]);
if (sports.length === 0) {
return (
<div className="flex items-center justify-center h-full p-8 text-muted-foreground">
<p>No picks have been made yet</p>
</div>
);
}
return (
<div className="w-full h-full overflow-auto">
<div className="inline-block min-w-full">
<table className="border-collapse border border-border">
<thead className="sticky top-0 bg-background z-10">
<tr>
<th className="border border-border p-2 text-left font-semibold min-w-[150px] bg-muted/50">
Sport
</th>
{draftSlots.map((slot) => {
const flexUsed = flexPicksByTeam.get(slot.team.id) || 0;
return (
<th
key={slot.id}
className="border border-border p-2 text-left font-semibold min-w-[180px] bg-muted/50"
>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">
{slot.team.name}
</div>
<div className="text-xs text-muted-foreground font-normal">
{flexUsed} of {season.numFlexPicks} flex
</div>
</div>
</th>
);
})}
</tr>
</thead>
<tbody>
{sports.map((sport) => (
<tr key={sport.id}>
<td className="border border-border p-2 font-medium bg-muted/30">
{sport.name}
</td>
{draftSlots.map((slot) => {
const teamSportPicks =
picksByTeamAndSport
.get(slot.team.id)
?.get(sport.id) || [];
const hasMultiplePicks = teamSportPicks.length > 1;
return (
<td
key={`${slot.team.id}-${sport.id}`}
className={`border border-border p-2 ${
hasMultiplePicks
? "bg-accent/30 font-semibold"
: "bg-background"
}`}
>
{teamSportPicks.length > 0 ? (
<div className="flex flex-col gap-1">
{teamSportPicks.map((pick) => (
<div
key={pick.id}
className="text-sm flex items-center gap-1"
>
<span>{pick.participant.name}</span>
{hasMultiplePicks && (
<Badge
variant="secondary"
className="text-xs px-1 py-0"
>
{teamSportPicks.indexOf(pick) + 1}
</Badge>
)}
</div>
))}
</div>
) : null}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}