({
+ draftOrder: i + 1,
+ teamId: `team-${i + 1}`,
+ }));
+}
+
+describe("getTeamForPick", () => {
+ const slots = makeSlots(4);
+
+ it("returns correct team for round 1 (forward)", () => {
+ expect(getTeamForPick(1, slots)?.teamId).toBe("team-1");
+ expect(getTeamForPick(2, slots)?.teamId).toBe("team-2");
+ expect(getTeamForPick(3, slots)?.teamId).toBe("team-3");
+ expect(getTeamForPick(4, slots)?.teamId).toBe("team-4");
+ });
+
+ it("returns correct team for round 2 (snake reversed)", () => {
+ expect(getTeamForPick(5, slots)?.teamId).toBe("team-4");
+ expect(getTeamForPick(6, slots)?.teamId).toBe("team-3");
+ expect(getTeamForPick(7, slots)?.teamId).toBe("team-2");
+ expect(getTeamForPick(8, slots)?.teamId).toBe("team-1");
+ });
+
+ it("returns correct team for round 3 (forward again)", () => {
+ expect(getTeamForPick(9, slots)?.teamId).toBe("team-1");
+ expect(getTeamForPick(10, slots)?.teamId).toBe("team-2");
+ expect(getTeamForPick(11, slots)?.teamId).toBe("team-3");
+ expect(getTeamForPick(12, slots)?.teamId).toBe("team-4");
+ });
+
+ it("returns undefined for empty slots", () => {
+ expect(getTeamForPick(1, [])).toBeUndefined();
+ });
+});
+
+describe("getProjectedPicks", () => {
+ it("returns empty array when no draft slots", () => {
+ const result = getProjectedPicks({
+ draftSlots: [],
+ userTeamId: "team-1",
+ currentPick: 1,
+ totalRounds: 10,
+ existingPicks: [],
+ });
+ expect(result).toEqual([]);
+ });
+
+ it("returns empty array when user has no team in slots", () => {
+ const result = getProjectedPicks({
+ draftSlots: makeSlots(4),
+ userTeamId: "team-999",
+ currentPick: 1,
+ totalRounds: 10,
+ existingPicks: [],
+ });
+ expect(result).toEqual([]);
+ });
+
+ describe("4-team snake draft, user at position 1", () => {
+ const slots = makeSlots(4);
+
+ it("computes picks from start of draft", () => {
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-1",
+ currentPick: 1,
+ totalRounds: 4,
+ existingPicks: [],
+ });
+
+ expect(result).toEqual([
+ { round: 1, pickNumber: 1, picksFromNow: 0 },
+ { round: 2, pickNumber: 8, picksFromNow: 7 },
+ { round: 3, pickNumber: 9, picksFromNow: 8 },
+ { round: 4, pickNumber: 16, picksFromNow: 15 },
+ ]);
+ });
+
+ it("computes picks from mid-draft, skipping past picks", () => {
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-1",
+ currentPick: 5,
+ totalRounds: 4,
+ existingPicks: [
+ { pickNumber: 1, teamId: "team-1" },
+ ],
+ });
+
+ expect(result).toEqual([
+ { round: 2, pickNumber: 8, picksFromNow: 3 },
+ { round: 3, pickNumber: 9, picksFromNow: 4 },
+ { round: 4, pickNumber: 16, picksFromNow: 11 },
+ ]);
+ });
+
+ it("skips rounds already picked even if current pick is before them", () => {
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-1",
+ currentPick: 1,
+ totalRounds: 4,
+ existingPicks: [
+ { pickNumber: 1, teamId: "team-1" },
+ { pickNumber: 8, teamId: "team-1" },
+ ],
+ });
+
+ expect(result).toEqual([
+ { round: 3, pickNumber: 9, picksFromNow: 8 },
+ { round: 4, pickNumber: 16, picksFromNow: 15 },
+ ]);
+ });
+ });
+
+ describe("4-team snake draft, user at position 3", () => {
+ const slots = makeSlots(4);
+
+ it("computes correct snake positions", () => {
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-3",
+ currentPick: 1,
+ totalRounds: 4,
+ existingPicks: [],
+ });
+
+ expect(result).toEqual([
+ { round: 1, pickNumber: 3, picksFromNow: 2 },
+ { round: 2, pickNumber: 6, picksFromNow: 5 },
+ { round: 3, pickNumber: 11, picksFromNow: 10 },
+ { round: 4, pickNumber: 14, picksFromNow: 13 },
+ ]);
+ });
+ });
+
+ describe("4-team snake draft, user at position 4 (last)", () => {
+ const slots = makeSlots(4);
+
+ it("picks first in round 2 due to snake reversal", () => {
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-4",
+ currentPick: 1,
+ totalRounds: 4,
+ existingPicks: [],
+ });
+
+ expect(result).toEqual([
+ { round: 1, pickNumber: 4, picksFromNow: 3 },
+ { round: 2, pickNumber: 5, picksFromNow: 4 },
+ { round: 3, pickNumber: 12, picksFromNow: 11 },
+ { round: 4, pickNumber: 13, picksFromNow: 12 },
+ ]);
+ });
+ });
+
+ it("returns empty when all picks are in the past", () => {
+ const result = getProjectedPicks({
+ draftSlots: makeSlots(4),
+ userTeamId: "team-1",
+ currentPick: 17,
+ totalRounds: 4,
+ existingPicks: [
+ { pickNumber: 1, teamId: "team-1" },
+ { pickNumber: 8, teamId: "team-1" },
+ { pickNumber: 9, teamId: "team-1" },
+ { pickNumber: 16, teamId: "team-1" },
+ ],
+ });
+ expect(result).toEqual([]);
+ });
+
+ it("handles 8-team snake draft correctly", () => {
+ const slots = makeSlots(8);
+
+ const result = getProjectedPicks({
+ draftSlots: slots,
+ userTeamId: "team-5",
+ currentPick: 1,
+ totalRounds: 3,
+ existingPicks: [],
+ });
+
+ expect(result).toEqual([
+ { round: 1, pickNumber: 5, picksFromNow: 4 },
+ { round: 2, pickNumber: 12, picksFromNow: 11 },
+ { round: 3, pickNumber: 21, picksFromNow: 20 },
+ ]);
+ });
+
+ it("excludes picks before currentPick", () => {
+ const result = getProjectedPicks({
+ draftSlots: makeSlots(4),
+ userTeamId: "team-1",
+ currentPick: 9,
+ totalRounds: 4,
+ existingPicks: [],
+ });
+
+ expect(result).toEqual([
+ { round: 3, pickNumber: 9, picksFromNow: 0 },
+ { round: 4, pickNumber: 16, picksFromNow: 7 },
+ ]);
+ });
+});
diff --git a/app/lib/draft-order.ts b/app/lib/draft-order.ts
index e474bca..7503245 100644
--- a/app/lib/draft-order.ts
+++ b/app/lib/draft-order.ts
@@ -16,3 +16,52 @@ export function getTeamForPick(
}
return draftSlots.find((slot) => slot.draftOrder === pickInRound);
}
+
+export interface ProjectedPick {
+ round: number;
+ pickNumber: number;
+ picksFromNow: number;
+}
+
+export function getProjectedPicks(options: {
+ draftSlots: Array<{ draftOrder: number; teamId: string }>;
+ userTeamId: string;
+ currentPick: number;
+ totalRounds: number;
+ existingPicks: Array<{ pickNumber: number; teamId: string }>;
+}): ProjectedPick[] {
+ const { draftSlots, userTeamId, currentPick, totalRounds, existingPicks } = options;
+ const totalTeams = draftSlots.length;
+ if (totalTeams === 0) return [];
+
+ const userSlot = draftSlots.find((slot) => slot.teamId === userTeamId);
+ if (!userSlot) return [];
+
+ const userDraftOrder = userSlot.draftOrder;
+ const pickedNumbersForTeam = new Set(
+ existingPicks
+ .filter((p) => p.teamId === userTeamId)
+ .map((p) => p.pickNumber)
+ );
+
+ const result: ProjectedPick[] = [];
+
+ for (let round = 1; round <= totalRounds; round++) {
+ const isEvenRound = round % 2 === 0;
+ const pickInRound = isEvenRound
+ ? totalTeams - userDraftOrder + 1
+ : userDraftOrder;
+ const pickNumber = (round - 1) * totalTeams + pickInRound;
+
+ if (pickNumber < currentPick) continue;
+ if (pickedNumbersForTeam.has(pickNumber)) continue;
+
+ result.push({
+ round,
+ pickNumber,
+ picksFromNow: pickNumber - currentPick,
+ });
+ }
+
+ return result;
+}
diff --git a/app/routes/leagues/$leagueId.draft.$seasonId.tsx b/app/routes/leagues/$leagueId.draft.$seasonId.tsx
index af39dac..5633dbc 100644
--- a/app/routes/leagues/$leagueId.draft.$seasonId.tsx
+++ b/app/routes/leagues/$leagueId.draft.$seasonId.tsx
@@ -35,7 +35,7 @@ import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay";
import { AuthRecoveryOverlay } from "~/components/draft/AuthRecoveryOverlay";
import { ParticipantSelectionDialog } from "~/components/draft/ParticipantSelectionDialog";
import { calculateDraftEligibility } from "~/lib/draft-eligibility";
-import { getTeamForPick } from "~/lib/draft-order";
+import { getTeamForPick, getProjectedPicks } from "~/lib/draft-order";
import { useDraftNotifications } from "~/hooks/useDraftNotifications";
import { NotificationSettings } from "~/components/NotificationSettings";
import { AutodraftBadgeWithPopover } from "~/components/AutodraftSettings";
@@ -857,6 +857,20 @@ export default function DraftRoom() {
return grid;
}, [picks, draftSlots, season.draftRounds]);
+ const projectedPicks = useMemo(() => {
+ if (!userTeam || season.status !== "draft" || isDraftComplete) return [];
+ return getProjectedPicks({
+ draftSlots,
+ userTeamId: userTeam.id,
+ currentPick,
+ totalRounds: season.draftRounds,
+ existingPicks: picks.map((p) => ({
+ pickNumber: p.pickNumber,
+ teamId: p.team.id,
+ })),
+ });
+ }, [userTeam, season.status, isDraftComplete, draftSlots, currentPick, season.draftRounds, picks]);
+
// Get drafted participant IDs for filtering
const draftedParticipantIds = useMemo(
() => new Set(picks.map((p) => p.participant.id)),
@@ -974,7 +988,8 @@ export default function DraftRoom() {
onMakePick: handleMakePick,
onAddToQueue: handleAddToQueue,
onRemoveFromQueue: handleRemoveFromQueue,
- }), [filteredParticipants, participantRanks, miniDraftGrid, searchQuery, sportFilters, hideDrafted, hideIneligible, uniqueSports, draftedParticipantIds, queue, eligibility, canPick, userTeam, hideCompletedSports, userDraftedSportNames, handleHideCompletedSportsChange, handleMakePick, handleAddToQueue, handleRemoveFromQueue, setSearchQuery, setSportFilters, setHideDrafted, setHideIneligible]);
+ projectedPicks,
+ }), [filteredParticipants, participantRanks, miniDraftGrid, searchQuery, sportFilters, hideDrafted, hideIneligible, uniqueSports, draftedParticipantIds, queue, eligibility, canPick, userTeam, hideCompletedSports, userDraftedSportNames, handleHideCompletedSportsChange, handleMakePick, handleAddToQueue, handleRemoveFromQueue, setSearchQuery, setSportFilters, setHideDrafted, setHideIneligible, projectedPicks]);
const draftGridSectionProps = {
draftSlots,