diff --git a/app/components/tournament/EliminationBracket.tsx b/app/components/tournament/EliminationBracket.tsx
index ee76a18c4..84187a0d1 100644
--- a/app/components/tournament/EliminationBracket.tsx
+++ b/app/components/tournament/EliminationBracket.tsx
@@ -1,126 +1,96 @@
import classNames from "classnames";
+import type { BracketModified } from "~/services/tournament";
+import { Unpacked } from "~/utils";
-const rounds = [
- {
- title: "Round 1",
- bestOf: 3,
- status: "DONE",
- },
- {
- title: "Round 2",
- bestOf: 5,
- status: "INPROGRESS",
- },
- {
- title: "Semifinals",
- bestOf: 5,
- status: "UPCOMING",
- },
- {
- title: "Finals",
- bestOf: 7,
- status: "UPCOMING",
- },
-] as const;
-
-export function EliminationBracket() {
+export function EliminationBracket({
+ bracketSide,
+}: {
+ bracketSide: BracketModified["winners"];
+}) {
return (
- {rounds.map((round, i) => (
+ {bracketSide.map((round, i) => (
))}
-
-
-
-
-
-
+ {bracketSide.map((round, i) => {
+ return (
+
+
+ {round.matches.map((match) => {
+ return ;
+ })}
+
+
+ {i !== bracketSide.length - 1 &&
+ new Array(Math.ceil(round.matches.length / 2))
+ .fill(null)
+ .map((_, i) =>
)}
+
+
+ );
+ })}
);
}
-function RoundInfo(props: {
- round: {
- title: string;
- bestOf: number;
- status: "DONE" | "INPROGRESS" | "UPCOMING";
- };
+function RoundInfo({
+ title,
+ bestOf,
+ status,
+ isLast,
+}: {
+ title: string;
+ bestOf: number;
+ status: "DONE" | "INPROGRESS" | "UPCOMING";
isLast?: boolean;
}) {
return (
-
- {props.round.title}
-
- {props.round.status !== "DONE" && (
-
- Bo{props.round.bestOf}
-
+
{title}
+ {status !== "DONE" && (
+
Bo{bestOf}
)}
);
}
-export function Match() {
+export function Match({
+ match,
+}: {
+ match: Unpacked["matches"]>;
+}) {
return (
1
@@ -130,7 +100,14 @@ export function Match() {
"tournament-bracket__elim__teamOne"
)}
>
- Team Olive
1
+ {match.participants?.[0]}{" "}
+
+ {match.score?.[0] ?? 0}
+
- Chimera 1
+ {match.participants?.[1]}{" "}
+
+ {match.score?.[1] ?? 0}
+
);
diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts
index 24807842f..bc3e226a9 100644
--- a/app/core/tournament/bracket.ts
+++ b/app/core/tournament/bracket.ts
@@ -89,26 +89,33 @@ export function getRoundsDefaultBestOf(
};
}
+export function winnersRoundNames(count: number, isSE: boolean) {
+ return new Array(count).fill(null).map((_, i) => {
+ if (i === count - 4 + Number(isSE)) {
+ return "Winners' Semifinals";
+ }
+ if (i === count - 3 + Number(isSE)) return "Winners' Finals";
+ if (i === count - 2 + Number(isSE)) return "Grand Finals";
+ if (!isSE && i === count - 1) return "Bracket Reset";
+ return `Winners' Round ${i + 1}`;
+ });
+}
+
+export function losersRoundNames(count: number) {
+ return new Array(count)
+ .fill(null)
+ .map((_, i) =>
+ i === count - 1 ? "Losers' Finals" : `Losers' Round ${i + 1}`
+ );
+}
+
export function getRoundNames(bracket: Bracket): EliminationBracket {
const { winners: winnersRoundCount, losers: losersRoundCount } =
countRounds(bracket);
return {
- winners: new Array(winnersRoundCount).fill(null).map((_, i) => {
- const isSE = bracket.losers.length === 0;
- if (i === winnersRoundCount - 4 + Number(isSE)) {
- return "Winners' Semifinals";
- }
- if (i === winnersRoundCount - 3 + Number(isSE)) return "Winners' Finals";
- if (i === winnersRoundCount - 2 + Number(isSE)) return "Grand Finals";
- if (!isSE && i === winnersRoundCount - 1) return "Bracket Reset";
- return `Winners' Round ${i + 1}`;
- }),
- losers: new Array(losersRoundCount)
- .fill(null)
- .map((_, i) =>
- i === losersRoundCount - 1 ? "Losers' Finals" : `Losers' Round ${i + 1}`
- ),
+ winners: winnersRoundNames(winnersRoundCount, losersRoundCount === 0),
+ losers: losersRoundNames(losersRoundCount),
};
}
diff --git a/app/models/TournamentBracket.ts b/app/models/TournamentBracket.ts
index 2362130bc..bf3da9d05 100644
--- a/app/models/TournamentBracket.ts
+++ b/app/models/TournamentBracket.ts
@@ -8,6 +8,7 @@ export function findById(bracketId: string) {
select: {
rounds: {
select: {
+ id: true,
position: true,
_count: {
select: {
@@ -16,6 +17,7 @@ export function findById(bracketId: string) {
},
matches: {
select: {
+ id: true,
participants: {
select: {
team: {
diff --git a/app/routes/to/$organization.$tournament.tsx b/app/routes/to/$organization.$tournament.tsx
index 70ed5b1fc..f8696ff1e 100644
--- a/app/routes/to/$organization.$tournament.tsx
+++ b/app/routes/to/$organization.$tournament.tsx
@@ -64,6 +64,7 @@ export default function TournamentPage() {
const tournamentIsOver = false;
if (tournamentHasStarted(data.brackets)) {
+ // TODO: link broken
result.push({ code: "bracket", text: "Bracket" });
if (!tournamentIsOver) {
result.push({ code: "streams", text: "Streams (4)" });
diff --git a/app/routes/to/$organization.$tournament/bracket.$id.tsx b/app/routes/to/$organization.$tournament/bracket.$id.tsx
index c7c5fca7f..df42d4040 100644
--- a/app/routes/to/$organization.$tournament/bracket.$id.tsx
+++ b/app/routes/to/$organization.$tournament/bracket.$id.tsx
@@ -19,14 +19,17 @@ export const loader: LoaderFunction = async ({ params }) => {
return typedJson(bracket);
};
+// TODO: top seed is at the bottom of the tournament
+// TODO: bottom bracket lines are wrong
+// TODO: match numbers are off
+// TODO: make bracket a bit smaller
export default function BracketTabWrapper() {
const data = useLoaderData();
- console.log("data", data);
-
return (
-
+
+
);
}
diff --git a/app/services/tournament.ts b/app/services/tournament.ts
index f1bbeaeb4..f6e3fd52c 100644
--- a/app/services/tournament.ts
+++ b/app/services/tournament.ts
@@ -5,16 +5,19 @@ import {
} from "~/constants";
import {
EliminationBracket,
+ EliminationBracketSide,
+ losersRoundNames,
MapListIds,
tournamentRoundsForDB,
+ winnersRoundNames,
} from "~/core/tournament/bracket";
import { isTournamentAdmin } from "~/core/tournament/permissions";
import { sortTeamsBySeed } from "~/core/tournament/utils";
import * as Tournament from "~/models/Tournament";
+import * as TournamentBracket from "~/models/TournamentBracket";
import * as TournamentTeam from "~/models/TournamentTeam";
import * as TournamentTeamMember from "~/models/TournamentTeamMember";
import * as TrustRelationship from "~/models/TrustRelationship";
-import * as TournamentBracket from "~/models/TournamentBracket";
import { Serialized, Unpacked } from "~/utils";
import { db } from "~/utils/db.server";
@@ -127,8 +130,11 @@ export async function findTournamentWithInviteCodes({
export type BracketModified = EliminationBracket;
type BracketModifiedSide = {
+ id: string;
+ name: string;
bestOf: number;
matches: {
+ id: string;
score?: [upperTeamScore: number, lowerTeamScore: number];
participants?: [upperTeamName: string | null, lowerTeamName: string | null];
}[];
@@ -139,29 +145,39 @@ export async function bracketById(bracketId: string): Promise {
if (!bracket) throw new Response("No bracket found", { status: 404 });
+ const winnersRounds = bracket.rounds
+ .filter((round) => round.position > 0)
+ .sort((a, b) => a.position - b.position);
+
+ const losersRounds = bracket.rounds
+ .filter((round) => round.position < 0)
+ .sort((a, b) => b.position - a.position);
+
+ console.log("losersRounds.length", losersRounds.length);
+
return {
- winners: modifyRounds(
- bracket.rounds
- .filter((round) => round.position > 0)
- .sort((a, b) => a.position - b.position)
- ),
- losers: modifyRounds(
- bracket.rounds
- .filter((round) => round.position < 0)
- .sort((a, b) => b.position - a.position)
- ),
+ winners: modifyRounds(winnersRounds, "winners", losersRounds.length === 0),
+ losers: modifyRounds(losersRounds, "losers", false),
};
}
function modifyRounds(
- rounds: NonNullable["rounds"]
+ rounds: NonNullable["rounds"],
+ side: EliminationBracketSide,
+ isSE: boolean
): BracketModifiedSide {
- return rounds.map((round) => {
+ const roundNames =
+ side === "winners"
+ ? winnersRoundNames(rounds.length, isSE)
+ : losersRoundNames(rounds.length);
+ return rounds.map((round, i) => {
return {
+ id: round.id,
+ name: roundNames[i],
bestOf: round._count.stages,
matches: round.matches.map((match) => {
const score =
- match.results.length > 0
+ match.participants.length === 2
? match.results.reduce(
(scores: [number, number], result) => {
if (result.winner === "UPPER") scores[0]++;
@@ -182,6 +198,7 @@ function modifyRounds(
]
: undefined;
return {
+ id: match.id,
score,
participants,
};
diff --git a/app/styles/tournament-bracket.css b/app/styles/tournament-bracket.css
index f775a06c2..3585734ba 100644
--- a/app/styles/tournament-bracket.css
+++ b/app/styles/tournament-bracket.css
@@ -1,6 +1,9 @@
/* TODO: should be able to go wider than container https://stackoverflow.com/questions/5581034/is-there-are-way-to-make-a-child-divs-width-wider-than-the-parent-div-using-css */
.tournament-bracket__container {
+ display: flex;
width: 100%;
+ flex-direction: column;
+ gap: var(--s-6);
}
.tournament-bracket__elim__container {
@@ -95,7 +98,7 @@
.tournament-bracket__elim__matches {
display: flex;
- height: calc(var(--brackets-match-height) * var(--brackets-matches));
+ height: calc(var(--brackets-match-height) * var(--brackets-max-matches));
flex-direction: column;
justify-content: space-around;
}
@@ -111,7 +114,7 @@
width: calc(var(--brackets-match-height) / 4);
height: calc(
2px + var(--brackets-match-height) *
- (var(--brackets-matches) / var(--brackets-column-matches))
+ (var(--brackets-max-matches) / var(--brackets-column-matches))
);
border-top: 2px solid var(--theme-transparent);
border-bottom: var(--brackets-bottom-border-length, 2px) solid