Refactor draft grid sports calculation and improve type safety (#16)
* Fix flex pick count using unique sports drafted, not total season sports The flex count was calculated as `totalPicks - totalSportsInSeason`, which always returned 0 when a team drafted multiple participants from the same sport (e.g. 2 picks from UEFA Champions League with 2 sports in season = 2 - 2 = 0). Now uses the number of unique sports the team has actually drafted from, so 2 picks in 1 sport correctly shows 1 flex used. https://claude.ai/code/session_01DZ6jqgZTDEmJucanBtRCtM * Code review fixes for TeamsDraftedGrid and draft route - Fix misleading early-return message: "No picks have been made yet" only fires when there are no sports configured, so update to say "No sports have been configured for this season." - Remove unused draftOrder and team.seasonId from TeamsDraftedGrid props - Replace availableParticipants prop with sports prop to eliminate duplicate sports derivation; route already computes seasonSportsData - Add alphabetical sort to seasonSportsData so ordering is consistent - Fix availableParticipants: any[] in loader — restructure to ternary so TypeScript infers the Drizzle select type directly - Remove any annotation from seasonSportsData forEach callback (now typed) - Fix teamSportPicks.indexOf(pick) O(n) re-scan — use map callback index i https://claude.ai/code/session_01DZ6jqgZTDEmJucanBtRCtM --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
edc4eb75e8
commit
7294084c13
2 changed files with 39 additions and 61 deletions
|
|
@ -4,11 +4,9 @@ import { useMemo } from "react";
|
|||
interface TeamsDraftedGridProps {
|
||||
draftSlots: Array<{
|
||||
id: string;
|
||||
draftOrder: number;
|
||||
team: {
|
||||
id: string;
|
||||
name: string;
|
||||
seasonId: string;
|
||||
};
|
||||
}>;
|
||||
picks: Array<{
|
||||
|
|
@ -26,13 +24,9 @@ interface TeamsDraftedGridProps {
|
|||
name: string;
|
||||
};
|
||||
}>;
|
||||
availableParticipants: Array<{
|
||||
sports: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
sport: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
}>;
|
||||
season: {
|
||||
numFlexPicks: number;
|
||||
|
|
@ -42,25 +36,9 @@ interface TeamsDraftedGridProps {
|
|||
export function TeamsDraftedGrid({
|
||||
draftSlots,
|
||||
picks,
|
||||
availableParticipants,
|
||||
sports,
|
||||
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>>();
|
||||
|
|
@ -85,17 +63,18 @@ export function TeamsDraftedGrid({
|
|||
|
||||
draftSlots.forEach((slot) => {
|
||||
const teamPicks = picks.filter((p) => p.team.id === slot.team.id);
|
||||
const flexUsed = Math.max(0, teamPicks.length - sports.length);
|
||||
const uniqueSportsPicked = new Set(teamPicks.map((p) => p.sport.id)).size;
|
||||
const flexUsed = Math.max(0, teamPicks.length - uniqueSportsPicked);
|
||||
map.set(slot.team.id, flexUsed);
|
||||
});
|
||||
|
||||
return map;
|
||||
}, [picks, draftSlots, sports]);
|
||||
}, [picks, draftSlots]);
|
||||
|
||||
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>
|
||||
<p>No sports have been configured for this season.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -156,7 +135,7 @@ export function TeamsDraftedGrid({
|
|||
>
|
||||
{teamSportPicks.length > 0 ? (
|
||||
<div className="flex flex-col gap-1">
|
||||
{teamSportPicks.map((pick) => (
|
||||
{teamSportPicks.map((pick, i) => (
|
||||
<div
|
||||
key={pick.id}
|
||||
className="text-sm flex items-center gap-1"
|
||||
|
|
@ -167,7 +146,7 @@ export function TeamsDraftedGrid({
|
|||
variant="secondary"
|
||||
className="text-xs px-1 py-0"
|
||||
>
|
||||
{teamSportPicks.indexOf(pick) + 1}
|
||||
{i + 1}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -119,35 +119,32 @@ export async function loader(args: any) {
|
|||
|
||||
// Get ALL participants (both drafted and undrafted) - sorted by EV desc, then name
|
||||
// Filtering will be done on the client side based on the "Show Drafted" toggle
|
||||
let availableParticipants: any[] = [];
|
||||
|
||||
if (sportsSeasonIds.length > 0) {
|
||||
availableParticipants = await db
|
||||
.select({
|
||||
id: schema.participants.id,
|
||||
name: schema.participants.name,
|
||||
sport: schema.sports,
|
||||
})
|
||||
.from(schema.participants)
|
||||
.innerJoin(
|
||||
schema.sportsSeasons,
|
||||
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
|
||||
)
|
||||
.innerJoin(
|
||||
schema.sports,
|
||||
eq(schema.sportsSeasons.sportId, schema.sports.id)
|
||||
)
|
||||
.where(
|
||||
sportsSeasonIds.length === 1
|
||||
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
|
||||
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds)
|
||||
)
|
||||
.orderBy(
|
||||
desc(schema.participants.expectedValue),
|
||||
asc(schema.participants.name)
|
||||
);
|
||||
|
||||
}
|
||||
const availableParticipants = sportsSeasonIds.length > 0
|
||||
? await db
|
||||
.select({
|
||||
id: schema.participants.id,
|
||||
name: schema.participants.name,
|
||||
sport: schema.sports,
|
||||
})
|
||||
.from(schema.participants)
|
||||
.innerJoin(
|
||||
schema.sportsSeasons,
|
||||
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
|
||||
)
|
||||
.innerJoin(
|
||||
schema.sports,
|
||||
eq(schema.sportsSeasons.sportId, schema.sports.id)
|
||||
)
|
||||
.where(
|
||||
sportsSeasonIds.length === 1
|
||||
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
|
||||
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds)
|
||||
)
|
||||
.orderBy(
|
||||
desc(schema.participants.expectedValue),
|
||||
asc(schema.participants.name)
|
||||
)
|
||||
: [];
|
||||
|
||||
// Load user's team queue if they have a team
|
||||
const userQueue = userTeam ? await getTeamQueue(userTeam.id) : [];
|
||||
|
|
@ -305,12 +302,14 @@ export default function DraftRoom() {
|
|||
|
||||
const seasonSportsData = useMemo(() => {
|
||||
const sportsMap = new Map<string, { id: string; name: string }>();
|
||||
availableParticipants.forEach((p: any) => {
|
||||
availableParticipants.forEach((p) => {
|
||||
if (!sportsMap.has(p.sport.id)) {
|
||||
sportsMap.set(p.sport.id, { id: p.sport.id, name: p.sport.name });
|
||||
}
|
||||
});
|
||||
return Array.from(sportsMap.values());
|
||||
return Array.from(sportsMap.values()).sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
}, [availableParticipants]);
|
||||
|
||||
// Calculate draft eligibility for current user's team
|
||||
|
|
@ -979,7 +978,7 @@ export default function DraftRoom() {
|
|||
<TeamsDraftedGrid
|
||||
draftSlots={draftSlots}
|
||||
picks={picks}
|
||||
availableParticipants={availableParticipants}
|
||||
sports={seasonSportsData}
|
||||
season={{ numFlexPicks }}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue