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 {
|
interface TeamsDraftedGridProps {
|
||||||
draftSlots: Array<{
|
draftSlots: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
draftOrder: number;
|
|
||||||
team: {
|
team: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
seasonId: string;
|
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
picks: Array<{
|
picks: Array<{
|
||||||
|
|
@ -26,13 +24,9 @@ interface TeamsDraftedGridProps {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
availableParticipants: Array<{
|
sports: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
sport: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}>;
|
}>;
|
||||||
season: {
|
season: {
|
||||||
numFlexPicks: number;
|
numFlexPicks: number;
|
||||||
|
|
@ -42,25 +36,9 @@ interface TeamsDraftedGridProps {
|
||||||
export function TeamsDraftedGrid({
|
export function TeamsDraftedGrid({
|
||||||
draftSlots,
|
draftSlots,
|
||||||
picks,
|
picks,
|
||||||
availableParticipants,
|
sports,
|
||||||
season,
|
season,
|
||||||
}: TeamsDraftedGridProps) {
|
}: 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
|
// Calculate picks by team and sport
|
||||||
const picksByTeamAndSport = useMemo(() => {
|
const picksByTeamAndSport = useMemo(() => {
|
||||||
const map = new Map<string, Map<string, typeof picks>>();
|
const map = new Map<string, Map<string, typeof picks>>();
|
||||||
|
|
@ -85,17 +63,18 @@ export function TeamsDraftedGrid({
|
||||||
|
|
||||||
draftSlots.forEach((slot) => {
|
draftSlots.forEach((slot) => {
|
||||||
const teamPicks = picks.filter((p) => p.team.id === slot.team.id);
|
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);
|
map.set(slot.team.id, flexUsed);
|
||||||
});
|
});
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}, [picks, draftSlots, sports]);
|
}, [picks, draftSlots]);
|
||||||
|
|
||||||
if (sports.length === 0) {
|
if (sports.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full p-8 text-muted-foreground">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +135,7 @@ export function TeamsDraftedGrid({
|
||||||
>
|
>
|
||||||
{teamSportPicks.length > 0 ? (
|
{teamSportPicks.length > 0 ? (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{teamSportPicks.map((pick) => (
|
{teamSportPicks.map((pick, i) => (
|
||||||
<div
|
<div
|
||||||
key={pick.id}
|
key={pick.id}
|
||||||
className="text-sm flex items-center gap-1"
|
className="text-sm flex items-center gap-1"
|
||||||
|
|
@ -167,7 +146,7 @@ export function TeamsDraftedGrid({
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="text-xs px-1 py-0"
|
className="text-xs px-1 py-0"
|
||||||
>
|
>
|
||||||
{teamSportPicks.indexOf(pick) + 1}
|
{i + 1}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -119,35 +119,32 @@ export async function loader(args: any) {
|
||||||
|
|
||||||
// Get ALL participants (both drafted and undrafted) - sorted by EV desc, then name
|
// 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
|
// Filtering will be done on the client side based on the "Show Drafted" toggle
|
||||||
let availableParticipants: any[] = [];
|
const availableParticipants = sportsSeasonIds.length > 0
|
||||||
|
? await db
|
||||||
if (sportsSeasonIds.length > 0) {
|
.select({
|
||||||
availableParticipants = await db
|
id: schema.participants.id,
|
||||||
.select({
|
name: schema.participants.name,
|
||||||
id: schema.participants.id,
|
sport: schema.sports,
|
||||||
name: schema.participants.name,
|
})
|
||||||
sport: schema.sports,
|
.from(schema.participants)
|
||||||
})
|
.innerJoin(
|
||||||
.from(schema.participants)
|
schema.sportsSeasons,
|
||||||
.innerJoin(
|
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
|
||||||
schema.sportsSeasons,
|
)
|
||||||
eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)
|
.innerJoin(
|
||||||
)
|
schema.sports,
|
||||||
.innerJoin(
|
eq(schema.sportsSeasons.sportId, schema.sports.id)
|
||||||
schema.sports,
|
)
|
||||||
eq(schema.sportsSeasons.sportId, schema.sports.id)
|
.where(
|
||||||
)
|
sportsSeasonIds.length === 1
|
||||||
.where(
|
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
|
||||||
sportsSeasonIds.length === 1
|
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds)
|
||||||
? eq(schema.participants.sportsSeasonId, sportsSeasonIds[0])
|
)
|
||||||
: inArray(schema.participants.sportsSeasonId, sportsSeasonIds)
|
.orderBy(
|
||||||
)
|
desc(schema.participants.expectedValue),
|
||||||
.orderBy(
|
asc(schema.participants.name)
|
||||||
desc(schema.participants.expectedValue),
|
)
|
||||||
asc(schema.participants.name)
|
: [];
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load user's team queue if they have a team
|
// Load user's team queue if they have a team
|
||||||
const userQueue = userTeam ? await getTeamQueue(userTeam.id) : [];
|
const userQueue = userTeam ? await getTeamQueue(userTeam.id) : [];
|
||||||
|
|
@ -305,12 +302,14 @@ export default function DraftRoom() {
|
||||||
|
|
||||||
const seasonSportsData = useMemo(() => {
|
const seasonSportsData = useMemo(() => {
|
||||||
const sportsMap = new Map<string, { id: string; name: string }>();
|
const sportsMap = new Map<string, { id: string; name: string }>();
|
||||||
availableParticipants.forEach((p: any) => {
|
availableParticipants.forEach((p) => {
|
||||||
if (!sportsMap.has(p.sport.id)) {
|
if (!sportsMap.has(p.sport.id)) {
|
||||||
sportsMap.set(p.sport.id, { id: p.sport.id, name: p.sport.name });
|
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]);
|
}, [availableParticipants]);
|
||||||
|
|
||||||
// Calculate draft eligibility for current user's team
|
// Calculate draft eligibility for current user's team
|
||||||
|
|
@ -979,7 +978,7 @@ export default function DraftRoom() {
|
||||||
<TeamsDraftedGrid
|
<TeamsDraftedGrid
|
||||||
draftSlots={draftSlots}
|
draftSlots={draftSlots}
|
||||||
picks={picks}
|
picks={picks}
|
||||||
availableParticipants={availableParticipants}
|
sports={seasonSportsData}
|
||||||
season={{ numFlexPicks }}
|
season={{ numFlexPicks }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue