import { ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "~/components/ui/button"; import { useRoundTransition } from "~/hooks/useRoundTransition"; import type { FeederMap } from "~/lib/bracket-layout"; import type { BracketTemplate } from "~/lib/bracket-templates"; import { TreeColumns, BracketMatchSlot, bracketGeometry, windowGeometry, SLOT_WIDTH, LABEL_HEIGHT, DESIRED_CARD_HEIGHT, MAX_CARD_HEIGHT, type BracketMatch, type BracketOwnership, } from "./BracketTreeView"; interface BracketTreePaginatedProps { rounds: string[]; matchesByRound: Map; ownershipMap: Map; userParticipantIds: Set; /** Index of the first scoring round — default page starts here */ firstScoringRoundIdx?: number; thirdPlaceRound?: string; feeders?: FeederMap; template?: BracketTemplate; } export function BracketTreePaginated({ rounds, matchesByRound, ownershipMap, userParticipantIds, firstScoringRoundIdx, thirdPlaceRound, feeders, template, }: BracketTreePaginatedProps) { const mainRounds = thirdPlaceRound ? rounds.filter((r) => r !== thirdPlaceRound) : rounds; const thirdPlaceMatch = thirdPlaceRound ? (matchesByRound.get(thirdPlaceRound) ?? [])[0] : undefined; // Pages are pairs of layout columns, not pairs of rounds: a column can mix rounds // when teams enter the bracket at different points (see computeGroupLayout). const geometry = bracketGeometry( mainRounds, matchesByRound, feeders, template?.rounds.map((r) => r.name) ?? mainRounds ); const columns = geometry.layout.columns; const lastPage = Math.max(columns.length - 2, 0); const defaultPage = Math.max( 0, Math.min( firstScoringRoundIdx !== undefined ? Math.max(0, firstScoringRoundIdx - 1) : lastPage, lastPage, ), ); const { page, anim, stripRef, navigate, handleTransitionEnd } = useRoundTransition( lastPage, defaultPage, ); const pageGeometry = (p: number) => windowGeometry(geometry, p, p + 1); const labelFor = (p: number) => { const [a, b] = [columns[p]?.label, columns[p + 1]?.label]; return b ? `${a} → ${b}` : (a ?? ""); }; const label = labelFor(anim ? anim.toPage : page); const pageG = pageGeometry(page); const animFromG = anim ? pageGeometry(anim.fromPage) : pageG; const animToG = anim ? pageGeometry(anim.toPage) : pageG; let leftPage: number; let rightPage: number | null = null; let leftG = pageG; let rightG = pageG; let settlingTransition = false; if (anim?.phase === "sliding") { leftPage = anim.dir === "right" ? anim.fromPage : anim.toPage; rightPage = anim.dir === "right" ? anim.toPage : anim.fromPage; leftG = anim.dir === "right" ? animFromG : animToG; rightG = anim.dir === "right" ? animToG : animFromG; } else if (anim?.phase === "settling") { leftPage = anim.toPage; leftG = animToG; settlingTransition = true; } else { leftPage = page; } const containerMinHeight = anim?.phase === "settling" ? animToG.bracketHeight : animFromG.bracketHeight; const initialX = anim?.phase === "sliding" && anim.dir === "left" ? -SLOT_WIDTH : 0; return (
{label}
{anim?.phase === "sliding" && rightPage !== null && (
)}
{thirdPlaceMatch && (
3rd Place
)}
); }