From a4b9fe906566f495b4485cb180fc4d19314724ef Mon Sep 17 00:00:00 2001 From: yohlo Date: Mon, 29 Sep 2025 10:50:18 -0500 Subject: [PATCH] updated bracket --- src/features/bracket/components/bracket.tsx | 155 ++++++++++++++---- .../bracket/components/match-card.tsx | 17 +- .../bracket/components/match-slot.tsx | 11 +- .../bracket/components/seed-badge.tsx | 4 +- 4 files changed, 152 insertions(+), 35 deletions(-) diff --git a/src/features/bracket/components/bracket.tsx b/src/features/bracket/components/bracket.tsx index f0477eb..a3689f4 100644 --- a/src/features/bracket/components/bracket.tsx +++ b/src/features/bracket/components/bracket.tsx @@ -1,6 +1,7 @@ -import { Flex } from "@mantine/core"; +import { Flex, Box } from "@mantine/core"; import { Match } from "@/features/matches/types"; import { MatchCard } from "./match-card"; +import { useEffect, useRef } from "react"; interface BracketProps { rounds: Match[][]; @@ -13,33 +14,131 @@ export const Bracket: React.FC = ({ orders, showControls, }) => { - return ( - - {rounds.map((round, roundIndex) => ( - - {round.map((match) => - match.bye ? ( -
- ) : ( -
- -
+ const containerRef = useRef(null); + const svgRef = useRef(null); + + useEffect(() => { + const updateConnectorLines = () => { + if (!containerRef.current || !svgRef.current) return; + + const svg = svgRef.current; + const container = containerRef.current; + const flexContainer = container.querySelector('.bracket-flex-container') as HTMLElement; + if (!flexContainer) return; + + svg.innerHTML = ''; + + const flexRect = flexContainer.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + + svg.style.width = `${flexContainer.scrollWidth}px`; + svg.style.height = `${flexContainer.scrollHeight}px`; + + rounds.forEach((round, roundIndex) => { + if (roundIndex === rounds.length - 1) return; + + const nextRound = rounds[roundIndex + 1]; + + round.forEach((match, matchIndex) => { + if (match.bye) return; + + const matchElement = container.querySelector(`[data-match-lid="${match.lid}"]`) as HTMLElement; + if (!matchElement) return; + + const nextMatches = nextRound.filter(nextMatch => + !nextMatch.bye && ( + orders[nextMatch.home_from_lid] === match.order || + orders[nextMatch.away_from_lid] === match.order ) - )} -
- ))} -
+ ); + + nextMatches.forEach(nextMatch => { + const nextMatchElement = container.querySelector(`[data-match-lid="${nextMatch.lid}"]`) as HTMLElement; + if (!nextMatchElement) return; + + const matchRect = matchElement.getBoundingClientRect(); + const nextMatchRect = nextMatchElement.getBoundingClientRect(); + + const startX = matchRect.right - flexRect.left; + const startY = matchRect.top + matchRect.height / 2 - flexRect.top; + const endX = nextMatchRect.left - flexRect.left; + const endY = nextMatchRect.top + nextMatchRect.height / 2 - flexRect.top; + + const midX = startX + (endX - startX) * 0.5; + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + const pathData = `M ${startX} ${startY} L ${midX} ${startY} L ${midX} ${endY} L ${endX} ${endY}`; + + path.setAttribute('d', pathData); + path.setAttribute('stroke', 'var(--mantine-color-default-border)'); + path.setAttribute('stroke-width', '2'); + path.setAttribute('fill', 'none'); + path.setAttribute('stroke-linecap', 'round'); + path.setAttribute('stroke-linejoin', 'round'); + + svg.appendChild(path); + }); + }); + }); + }; + + updateConnectorLines(); + + const handleUpdate = () => { + requestAnimationFrame(updateConnectorLines); + }; + + const scrollContainer = containerRef.current?.closest('.mantine-ScrollArea-viewport'); + scrollContainer?.addEventListener('scroll', handleUpdate); + window.addEventListener('resize', handleUpdate); + + return () => { + scrollContainer?.removeEventListener('scroll', handleUpdate); + window.removeEventListener('resize', handleUpdate); + }; + }, [rounds, orders]); + + return ( + + + + {rounds.map((round, roundIndex) => ( + + {round.map((match) => + match.bye ? ( +
+ ) : ( +
+ +
+ ) + )} +
+ ))} +
+
); }; diff --git a/src/features/bracket/components/match-card.tsx b/src/features/bracket/components/match-card.tsx index 14d3b40..3695513 100644 --- a/src/features/bracket/components/match-card.tsx +++ b/src/features/bracket/components/match-card.tsx @@ -199,7 +199,15 @@ export const MatchCard: React.FC = ({ return ( - + {match.order} @@ -214,7 +222,12 @@ export const MatchCard: React.FC = ({ w={showToolbar || showEditButton ? 200 : 220} withBorder pos="relative" - style={{ overflow: "visible" }} + style={{ + overflow: "visible", + backgroundColor: 'var(--mantine-color-body)', + borderColor: 'var(--mantine-color-default-border)', + boxShadow: 'var(--mantine-shadow-sm)', + }} data-match-lid={match.lid} > diff --git a/src/features/bracket/components/match-slot.tsx b/src/features/bracket/components/match-slot.tsx index 4783804..eada4c4 100644 --- a/src/features/bracket/components/match-slot.tsx +++ b/src/features/bracket/components/match-slot.tsx @@ -21,9 +21,16 @@ export const MatchSlot: React.FC = ({ cups, isWinner }) => ( - + {(seed && seed > 0) ? : undefined} - + {team ? ( <> diff --git a/src/features/bracket/components/seed-badge.tsx b/src/features/bracket/components/seed-badge.tsx index 52eb316..96ff993 100644 --- a/src/features/bracket/components/seed-badge.tsx +++ b/src/features/bracket/components/seed-badge.tsx @@ -18,9 +18,7 @@ export const SeedBadge: React.FC = ({ seed }) => { color: "var(--mantine-color-text)", display: "flex", alignItems: "center", - justifyContent: "center", - borderTopLeftRadius: "var(--mantine-radius-default)", - borderBottomLeftRadius: "var(--mantine-radius-default)", + justifyContent: "center" }} > {seed}