diff --git a/src/app/routes/_authed/admin/preview.tsx b/src/app/routes/_authed/admin/preview.tsx index d1af781..b2c40d5 100644 --- a/src/app/routes/_authed/admin/preview.tsx +++ b/src/app/routes/_authed/admin/preview.tsx @@ -1,4 +1,4 @@ -import { PreviewBracketPage } from '@/features/bracket/components/bracket-page' +import { PreviewBracket } from '@/features/bracket/components/preview' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/_authed/admin/preview')({ @@ -6,5 +6,5 @@ export const Route = createFileRoute('/_authed/admin/preview')({ }) function RouteComponent() { - return + return } diff --git a/src/features/bracket/components/bracket-page.tsx b/src/features/bracket/components/bracket-page.tsx deleted file mode 100644 index 81d2b7c..0000000 --- a/src/features/bracket/components/bracket-page.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { Text, Container, Flex, ScrollArea, NumberInput, Group } from "@mantine/core"; -import { SeedList } from "./seed-list"; -import BracketView from "./bracket-view"; -import { useEffect, useState } from "react"; -import { bracketQueries } from "../queries"; -import { useQuery } from "@tanstack/react-query"; -import './styles.module.css'; -import useAppShellHeight from "@/hooks/use-appshell-height"; -import { createBracketMaps, BracketMaps } from "../utils/bracket-maps"; - -interface Team { - id: string; - name: string; -} - -interface Match { - lid: number; - round: number; - order: number | null; - type: string; - home: any; - away?: any; - reset?: boolean; -} - -interface BracketData { - winners: Match[][]; - losers: Match[][]; -} - - -export const PreviewBracketPage: React.FC = () => { - const height = useAppShellHeight(); - - const [teamCount, setTeamCount] = useState(20); - const { data, isLoading, error } = useQuery(bracketQueries.preview(teamCount)); - - // Create teams with proper structure - const [teams, setTeams] = useState([]); - - // Update teams when teamCount changes - useEffect(() => { - setTeams(Array.from({ length: teamCount }, (_, i) => ({ - id: `team-${i + 1}`, - name: `Team ${i + 1}` - }))); - }, [teamCount]); - - const [seededWinnersBracket, setSeededWinnersBracket] = useState([]); - const [seededLosersBracket, setSeededLosersBracket] = useState([]); - const [bracketMaps, setBracketMaps] = useState(null); - - useEffect(() => { - if (!data || teams.length === 0) return; - - // Create bracket maps for easy lookups - const maps = createBracketMaps(data); - setBracketMaps(maps); - - // Map brackets with team names - const mapBracket = (bracket: Match[][]) => { - return bracket.map(round => - round.map(match => { - const mappedMatch = { ...match }; - - // Map home slot if it has a seed - if (match.home?.seed && match.home.seed > 0) { - const teamIndex = match.home.seed - 1; - if (teams[teamIndex]) { - mappedMatch.home = { - ...match.home, - team: teams[teamIndex] - }; - } - } - - // Map away slot if it has a seed - if (match.away?.seed && match.away.seed > 0) { - const teamIndex = match.away.seed - 1; - if (teams[teamIndex]) { - mappedMatch.away = { - ...match.away, - team: teams[teamIndex] - }; - } - } - - return mappedMatch; - }) - ); - }; - - setSeededWinnersBracket(mapBracket(data.winners)); - setSeededLosersBracket(mapBracket(data.losers)); - }, [teams, data]); - - const handleSeedChange = (teamIndex: number, newSeedIndex: number) => { - const newTeams = [...teams]; - const movingTeam = newTeams[teamIndex]; - - // Remove the team from its current position - newTeams.splice(teamIndex, 1); - - // Insert it at the new position - newTeams.splice(newSeedIndex, 0, movingTeam); - - setTeams(newTeams); - }; - - if (isLoading) return

Loading...

; - if (error) return

Error loading bracket

; - if (!data || !bracketMaps || teams.length === 0) return

No data available

; - - return ( - - - - Preview Bracket (Double Elimination) - - - Teams: - setTeamCount(Number(value) || 12)} - min={12} - max={20} - size="sm" - w={80} - allowDecimal={false} - clampBehavior="strict" - /> - - - -
- - Seed Teams - - -
- -
- - Winners Bracket - - -
-
- - Losers Bracket - - -
-
-
-
- ); -}; diff --git a/src/features/bracket/components/bracket-view.tsx b/src/features/bracket/components/bracket-view.tsx index e736546..4108f51 100644 --- a/src/features/bracket/components/bracket-view.tsx +++ b/src/features/bracket/components/bracket-view.tsx @@ -1,7 +1,7 @@ -import { ActionIcon, Card, Flex, Text } from '@mantine/core'; -import { PlayIcon } from '@phosphor-icons/react'; -import React from 'react'; -import { BracketMaps } from '../utils/bracket-maps'; +import { ActionIcon, Card, Flex, Text } from "@mantine/core"; +import { PlayIcon } from "@phosphor-icons/react"; +import React from "react"; +import { BracketMaps } from "../utils/bracket-maps"; interface Match { lid: number; @@ -19,77 +19,106 @@ interface BracketViewProps { onAnnounce?: (teamOne: any, teamTwo: any) => void; } -const BracketView: React.FC = ({ bracket, bracketMaps, onAnnounce }) => { - // Helper to check match type +const BracketView: React.FC = ({ + bracket, + bracketMaps, + onAnnounce, +}) => { const isMatchType = (type: string, expected: string) => { return type?.toLowerCase() === expected.toLowerCase(); }; - // Helper to get parent match order number using the new bracket maps const getParentMatchOrder = (parentLid: number): number | string => { const parentMatch = bracketMaps.matchByLid.get(parentLid); - if (parentMatch && parentMatch.order !== null && parentMatch.order !== undefined) { + if ( + parentMatch && + parentMatch.order !== null && + parentMatch.order !== undefined + ) { return parentMatch.order; } - // If no order (like for byes), return the parentLid with a different prefix return `Match ${parentLid}`; }; return ( - - {bracket.map((round, roundIndex) => ( - + + {bracket.map((round, roundIndex) => ( + {round.map((match, matchIndex) => { if (!match) return null; - // Handle bye matches (no away slot) - check both 'TBye' and 'bye' - if (isMatchType(match.type, 'bye') || isMatchType(match.type, 'tbye')) { - return ( - - - ); + if ( + isMatchType(match.type, "bye") || + isMatchType(match.type, "tbye") + ) { + return ; } - // Regular matches with both home and away return ( - - {match.order} - + + + {match.order} + + {match.home?.seed && ( - {match.home.seed} )} -
+
{match.home?.seed ? ( match.home.team ? ( - {match.home.team.name} + {match.home.team.name} ) : ( - Team {match.home.seed} + + Team {match.home.seed} + ) - ) : (match.home?.parent_lid !== null && match.home?.parent_lid !== undefined) ? ( - - {match.home.loser ? 'Loser' : 'Winner'} of Match {getParentMatchOrder(match.home.parent_lid)} + ) : match.home?.parent_lid !== null && + match.home?.parent_lid !== undefined ? ( + + {match.home.loser ? "Loser" : "Winner"} of Match{" "} + {getParentMatchOrder(match.home.parent_lid)} ) : ( - TBD + + TBD + )}
@@ -97,67 +126,75 @@ const BracketView: React.FC = ({ bracket, bracketMaps, onAnnou {match.away?.seed && ( - {match.away.seed} )} -
+
{match.away?.seed ? ( match.away.team ? ( - {match.away.team.name} + {match.away.team.name} ) : ( - Team {match.away.seed} + + Team {match.away.seed} + ) - ) : (match.away?.parent_lid !== null && match.away?.parent_lid !== undefined) ? ( - - {match.away.loser ? 'Loser' : 'Winner'} of Match {getParentMatchOrder(match.away.parent_lid)} + ) : match.away?.parent_lid !== null && + match.away?.parent_lid !== undefined ? ( + + {match.away.loser ? "Loser" : "Winner"} of Match{" "} + {getParentMatchOrder(match.away.parent_lid)} ) : match.away ? ( - TBD + + TBD + ) : null}
{match.reset && ( - IF NECESSARY )} {onAnnounce && match.home?.team && match.away?.team && ( { onAnnounce(match.home.team, match.away.team); }} - bd='none' - style={{ boxShadow: 'none' }} - size='xs' + bd="none" + style={{ boxShadow: "none" }} + size="xs" > @@ -172,4 +209,4 @@ const BracketView: React.FC = ({ bracket, bracketMaps, onAnnou ); }; -export default BracketView; \ No newline at end of file +export default BracketView; diff --git a/src/features/bracket/components/bracket.tsx b/src/features/bracket/components/bracket.tsx new file mode 100644 index 0000000..a19b6d1 --- /dev/null +++ b/src/features/bracket/components/bracket.tsx @@ -0,0 +1,46 @@ +import { ScrollArea, Text } from "@mantine/core"; +import BracketView from "./bracket-view"; +import { Match } from "../types"; +import useAppShellHeight from "@/hooks/use-appshell-height"; +import { BracketMaps } from "../utils/bracket-maps"; + +interface BracketProps { + winners: Match[][], + losers?: Match[][], + bracketMaps: BracketMaps | null +} + +const Bracket: React.FC = ({ winners, losers, bracketMaps }) => { + const height = useAppShellHeight(); + + if (!bracketMaps) return

Data not available.

+ + return ( + +
+ + Winners Bracket + + +
+ { + losers &&
+ + Losers Bracket + + +
+ } +
+ ); +}; + +export default Bracket; diff --git a/src/features/bracket/components/preview.tsx b/src/features/bracket/components/preview.tsx new file mode 100644 index 0000000..7674a29 --- /dev/null +++ b/src/features/bracket/components/preview.tsx @@ -0,0 +1,119 @@ +import { + Text, + Container, + Flex, + NumberInput, + Group, + Loader, +} from "@mantine/core"; +import { useEffect, useState } from "react"; +import { bracketQueries } from "../queries"; +import { useQuery } from "@tanstack/react-query"; +import { createBracketMaps, BracketMaps } from "../utils/bracket-maps"; +import { BracketData, Match, Team } from "../types"; +import Bracket from "./bracket"; +import "./styles.module.css"; + +export const PreviewBracket: React.FC = () => { + const [teamCount, setTeamCount] = useState(20); + const { data, isLoading, error } = useQuery( + bracketQueries.preview(teamCount) + ); + + const [teams, setTeams] = useState([]); + + useEffect(() => { + setTeams( + Array.from({ length: teamCount }, (_, i) => ({ + id: `team-${i + 1}`, + name: `Team ${i + 1}`, + })) + ); + }, [teamCount]); + + const [seededWinnersBracket, setSeededWinnersBracket] = useState( + [] + ); + const [seededLosersBracket, setSeededLosersBracket] = useState([]); + const [bracketMaps, setBracketMaps] = useState(null); + + useEffect(() => { + if (!data || teams.length === 0) return; + + const maps = createBracketMaps(data); + setBracketMaps(maps); + + const mapBracket = (bracket: Match[][]) => { + return bracket.map((round) => + round.map((match) => { + const mappedMatch = { ...match }; + + if (match.home?.seed && match.home.seed > 0) { + const teamIndex = match.home.seed - 1; + if (teams[teamIndex]) { + mappedMatch.home = { + ...match.home, + team: teams[teamIndex], + }; + } + } + + if (match.away?.seed && match.away.seed > 0) { + const teamIndex = match.away.seed - 1; + if (teams[teamIndex]) { + mappedMatch.away = { + ...match.away, + team: teams[teamIndex], + }; + } + } + + return mappedMatch; + }) + ); + }; + + setSeededWinnersBracket(mapBracket(data.winners)); + setSeededLosersBracket(mapBracket(data.losers)); + }, [teams, data]); + + if (error) return

Error loading bracket

; + + return ( + + + + Preview Bracket (Double Elimination) + + + + Teams: + + setTeamCount(Number(value) || 12)} + min={12} + max={20} + size="sm" + w={80} + allowDecimal={false} + clampBehavior="strict" + /> + + + + {isLoading ? ( + + + + ) : ( + + )} + + + ); +}; diff --git a/src/features/bracket/types.ts b/src/features/bracket/types.ts new file mode 100644 index 0000000..d821ee0 --- /dev/null +++ b/src/features/bracket/types.ts @@ -0,0 +1,19 @@ +export interface Team { + id: string; + name: string; +} + +export interface Match { + lid: number; + round: number; + order: number | null; + type: string; + home: any; + away?: any; + reset?: boolean; +} + +export interface BracketData { + winners: Match[][]; + losers: Match[][]; +}