diff --git a/tournaments/models/match.py b/tournaments/models/match.py
index 71571c4..0414ae1 100644
--- a/tournaments/models/match.py
+++ b/tournaments/models/match.py
@@ -268,6 +268,16 @@ class Match(models.Model):
elif len(team_scores) == 2:
# Both team scores present
teams.extend([team_score.live_team(self) for team_score in team_scores])
+
+ if self.round is not None and self.round.parent is None:
+ pos1 = team_scores[0].team_registration.bracket_position if hasattr(team_scores[0], 'team_registration') and team_scores[0].team_registration else None
+ pos2 = team_scores[1].team_registration.bracket_position if hasattr(team_scores[1], 'team_registration') and team_scores[1].team_registration else None
+ if pos1 is not None and pos2 is not None and pos1 // 2 == self.index and pos2 // 2 == self.index:
+ if pos1 > pos2:
+ teams = [team_scores[1].live_team(self), team_scores[0].live_team(self)]
+ else:
+ teams = [team_scores[0].live_team(self), team_scores[1].live_team(self)]
+
else:
teams.extend([team_score.live_team(self) for team_score in team_scores if team_score.walk_out != 1])
@@ -402,7 +412,7 @@ class Match(models.Model):
ended = self.end_date is not None
live_format = "Format " + FederalMatchCategory(self.format).format_label_short
- livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled)
+ livematch = LiveMatch(self.index, title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled)
for team in self.live_teams():
livematch.add_team(team)
@@ -463,7 +473,8 @@ class Team:
}
class LiveMatch:
- def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled):
+ def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled):
+ self.index = index
self.title = title
self.date = date
self.teams = []
@@ -485,6 +496,7 @@ class LiveMatch:
def to_dict(self):
return {
+ "index": self.index,
"title": self.title,
"date": self.date,
"teams": [team.to_dict() for team in self.teams],
diff --git a/tournaments/models/round.py b/tournaments/models/round.py
index 7d740bc..1e23535 100644
--- a/tournaments/models/round.py
+++ b/tournaments/models/round.py
@@ -95,7 +95,7 @@ class Round(models.Model):
return True
- def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode):
+ def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode, secondHalf):
matches = self.match_set.filter(disabled=False).order_by('index')
if len(matches) == 0:
return None
@@ -118,7 +118,9 @@ class Round(models.Model):
# Only filter out the pair if both matches are disabled
if current_match.disabled and pair_match and pair_match.disabled:
# Skip one of the matches in the pair
- filtered_matches.append(current_match)
+ if next_round_matches.filter(index=current_match.index // 2).exists():
+ filtered_matches.append(current_match)
+ filtered_matches.append(pair_match)
pass
else:
# Keep the current match
@@ -138,6 +140,8 @@ class Round(models.Model):
if len(matches) > 1 and double_butterfly_mode:
midpoint = int(len(matches) / 2)
first_half_matches = matches[:midpoint]
+ if secondHalf:
+ first_half_matches = matches[midpoint:]
else:
first_half_matches = list(matches) # Convert QuerySet to a list
if self.index == 0 and loser_final:
diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py
index 270530a..b30a4f6 100644
--- a/tournaments/models/tournament.py
+++ b/tournaments/models/tournament.py
@@ -661,8 +661,8 @@ class Tournament(models.Model):
matches.extend(first_round.get_matches_recursive(True))
else:
current_round = self.round_to_show()
- # print(f'current_round = {current_round.index} / parent = {current_round.parent}')
if current_round:
+ print(f'current_round = {current_round.index} / parent = {current_round.parent}')
all_upper_matches_are_over = current_round.all_matches_are_over()
if all_upper_matches_are_over is False:
matches.extend(current_round.get_matches_recursive(True))
@@ -689,8 +689,12 @@ class Tournament(models.Model):
previous_previous_matches = [m for m in previous_previous_matches if m.end_date is None]
matches.extend(previous_previous_matches)
else:
- # print('group_stages')
+ print('group_stages')
group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()]
+ else:
+ first_round = self.first_round()
+ if first_round:
+ matches.extend(first_round.get_matches_recursive(True))
return matches, group_stages
@@ -1362,7 +1366,7 @@ class Tournament(models.Model):
# Add first half of each round (from last to semi-finals)
for round in main_rounds:
next_round = main_rounds.filter(index=round.index - 1).first()
- match_group = round.prepare_match_group(next_round, parent_round, loser_final, double_butterfly_mode)
+ match_group = round.prepare_match_group(next_round, parent_round, loser_final, double_butterfly_mode, False)
if match_group:
serializable_match_groups.append(match_group)
@@ -1370,10 +1374,11 @@ class Tournament(models.Model):
main_rounds_reversed = list(main_rounds)
main_rounds_reversed.reverse()
for round in main_rounds_reversed:
- next_round = main_rounds.filter(index=round.index - 1).first()
- match_group = round.prepare_match_group(next_round, parent_round, None, double_butterfly_mode)
- if match_group:
- serializable_match_groups.append(match_group)
+ if round.index > 0:
+ next_round = main_rounds.filter(index=round.index - 1).first()
+ match_group = round.prepare_match_group(next_round, parent_round, None, double_butterfly_mode, True)
+ if match_group:
+ serializable_match_groups.append(match_group)
return serializable_match_groups
@@ -1390,6 +1395,13 @@ class MatchGroup:
def add_matches(self, matches):
self.matches = matches
+ def to_dict(self):
+ return {
+ 'name': self.name,
+ 'round_id': self.round_id,
+ 'matches': [match.to_dict() for match in self.matches]
+ }
+
class TeamSummon:
def __init__(self, id, names, date, weight, stage, court, image, day_duration):
self.id = str(id)
diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css
index 30c0c3d..8498c51 100644
--- a/tournaments/static/tournaments/css/style.css
+++ b/tournaments/static/tournaments/css/style.css
@@ -339,10 +339,13 @@ tr {
/* For single player teams */
.player.single-player .semibold {
- line-height: 1.4em;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
overflow: hidden;
- position: relative;
+ line-height: 1.4em;
text-overflow: ellipsis;
+ max-height: 2.8em; /* 2 lines × 1.4em line-height */
}
/* For two player teams */
@@ -661,8 +664,9 @@ h-margin {
/* Summons */
.table-row-3-colums-summons {
- display: flex;
+ grid-template-columns: 50% 25% 25%;
align-items: center;
+ display: grid;
}
.summons-left,
@@ -869,3 +873,11 @@ h-margin {
transform: translate(-50%, -50%);
white-space: nowrap;
}
+
+.even-row {
+ background-color: #ffffff; /* White */
+}
+
+.odd-row {
+ background-color: #e6f2ff; /* Light blue */
+}
diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css
new file mode 100644
index 0000000..1be8f50
--- /dev/null
+++ b/tournaments/static/tournaments/css/tournament_bracket.css
@@ -0,0 +1,240 @@
+.round-logo img {
+ width: 50px;
+ height: auto;
+ display: block;
+ margin: 0 auto;
+ position: relative;
+ top: -100px; /* Increased negative value to move it higher up */
+}
+
+.round-logo img {
+ width: 100px; /* Adjust size as needed */
+ height: auto;
+ display: block;
+ margin: 0 auto;
+}
+.butterfly-match.same-level::before {
+ display: none;
+}
+
+/* Adjust styling for matches with single parent */
+.match-content.disabled {
+ visibility: hidden;
+}
+
+.incoming-line.disabled,
+.outgoing-line.disabled,
+.butterfly-match:has(.match-content.disabled)::after,
+.butterfly-match:has(.match-content.disabled)::before {
+ visibility: hidden;
+}
+
+.butterfly-bracket {
+ display: flex;
+ gap: 40px; /* Increased to account for horizontal lines (20px on each side) */
+ position: relative;
+ margin-bottom: 80px;
+}
+
+.round-title {
+ position: absolute;
+ top: 0px; /* Adjust this value to position the title where you want it */
+ left: 50%; /* Center horizontally */
+ transform: translateX(-50%); /* Center it exactly */
+ text-align: center;
+ font-weight: bold;
+ width: auto; /* Change from 100% to auto */
+ padding: 5px 10px;
+
+ white-space: nowrap; /* Prevent text from wrapping */
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.round-name {
+ color: #707070;
+ font-size: 1.5em;
+ padding: 8px 12px;
+ white-space: nowrap; /* Prevent text wrapping */
+ display: block; /* Ensure proper centering */
+}
+
+.round-format {
+ font-size: 0.9em;
+ color: #707070;
+ margin-top: -5px; /* Reduced from -10px to bring it closer */
+ white-space: nowrap; /* Prevent text wrapping */
+ display: block; /* Ensure proper centering */
+}
+
+.round-name.button {
+ border-radius: 16px;
+ width: 100%;
+ display: inline-block;
+ background-color: #fae7ce;
+}
+
+.button:hover {
+ color: white;
+ background-color: #f39200;
+}
+
+.matches-container {
+ position: relative;
+ width: 100%;
+ flex-grow: 1;
+}
+
+.butterfly-round {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px; /* Space between title and matches */
+ position: relative;
+ width: var(--match-width);
+ flex-shrink: 0;
+ margin-top: 100px; /* Add padding to account for absolute positioned title */
+}
+
+.butterfly-match {
+ position: absolute;
+ width: 100%;
+ padding: 10px 10px;
+}
+
+/* Horizontal line after match */
+.outgoing-line {
+ content: "";
+ position: absolute;
+ left: 100%; /* Start from end of match cell */
+ top: 50%;
+ width: 20px;
+ height: 2px;
+ background: orange;
+}
+
+.butterfly-match::before {
+ content: "";
+ position: absolute;
+ left: calc(0% - 20px);
+ width: 2px;
+ top: calc(50% - (var(--next-match-distance) / 2));
+ height: calc(var(--next-match-distance) + 2px);
+ background: pink;
+}
+
+/* Vertical line connecting pair of matches */
+.butterfly-match.reverse-bracket::before {
+ content: "";
+ position: absolute;
+ left: calc(100% + 20px);
+ width: 2px;
+ top: calc(50% - (var(--next-match-distance) / 2));
+ height: calc(var(--next-match-distance) + 2px);
+ background: red;
+}
+
+/* Horizontal line to next round match */
+.incoming-line {
+ position: absolute;
+ left: -20px;
+ top: 50%;
+ width: 20px;
+ height: 2px;
+ background: blue;
+}
+
+/* Horizontal line to next round match */
+.butterfly-match .outgoing-line-downward {
+ position: absolute;
+ right: -20px;
+ top: 50%; /* Start from middle of match */
+ width: 2px;
+ height: var(--semi-final-distance);
+ background: black;
+}
+
+/* Horizontal line to next round match */
+.butterfly-match .outgoing-line-upward {
+ position: absolute;
+ right: -20px;
+ bottom: 50%;
+ width: 2px;
+ height: var(--semi-final-distance);
+ background: black;
+}
+
+/* Horizontal line to next round match */
+.butterfly-match.reverse-bracket .outgoing-line-downward {
+ position: absolute;
+ left: -20px;
+ top: 50%; /* Start from middle of match */
+ width: 2px;
+ height: var(--semi-final-distance);
+ background: black;
+}
+
+/* Horizontal line to next round match */
+.butterfly-match.reverse-bracket .outgoing-line-upward {
+ position: absolute;
+ left: -20px;
+ bottom: 50%;
+ width: 2px;
+ height: var(--semi-final-distance);
+ background: black;
+}
+
+.butterfly-round:last-child .butterfly-match.reverse-bracket::before,
+.butterfly-round:last-child .outgoing-line,
+.butterfly-round:first-child .incoming-line {
+ display: none;
+}
+
+.broadcast-mode .round-name,
+.broadcast-mode .round-format {
+ padding: 0px;
+ color: #707070;
+}
+
+.broadcast-mode .round-title {
+ padding: 8px 20px; /* Slightly more horizontal padding */
+ background-color: white;
+ align-content: center;
+ border-radius: 24px;
+}
+
+.butterfly-match::before,
+.butterfly-match.reverse-bracket::before,
+.incoming-line,
+.outgoing-line,
+.outgoing-line-upward,
+.outgoing-line-downward {
+ background-color: #707070 !important; /* Bright yellow - change to your preferred color */
+}
+
+/* Broadcast mode styling for all lines */
+.broadcast-mode .butterfly-match::before,
+.broadcast-mode .butterfly-match.reverse-bracket::before,
+.broadcast-mode .incoming-line,
+.broadcast-mode .outgoing-line,
+.broadcast-mode .outgoing-line-upward,
+.broadcast-mode .outgoing-line-downward {
+ background-color: black !important; /* Bright yellow - change to your preferred color */
+}
+
+.bubble.match-running {
+ position: relative;
+}
+
+.bubble.match-running::after {
+ content: "";
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 20px; /* Height of the green indicator */
+ background-color: #90ee90; /* Light green color */
+ border-radius: 0 0 24px 24px; /* Match the bubble's bottom corners */
+}
diff --git a/tournaments/static/tournaments/js/tournament_bracket.js b/tournaments/static/tournaments/js/tournament_bracket.js
new file mode 100644
index 0000000..3f610f1
--- /dev/null
+++ b/tournaments/static/tournaments/js/tournament_bracket.js
@@ -0,0 +1,424 @@
+function renderBracket(options) {
+ const bracket = document.getElementById("bracket");
+ const matchTemplates = document.getElementById("match-templates").children;
+ const rounds = [];
+ const matchPositions = [];
+ const matchDisabled = [];
+ const doubleButterflyMode = options.doubleButterflyMode;
+ const displayLoserFinal = options.displayLoserFinal;
+ const tournamentId = options.tournamentId;
+ const isBroadcast = options.isBroadcast;
+ // Group matches by round
+ Array.from(matchTemplates).forEach((template) => {
+ const roundIndex = parseInt(template.dataset.matchRound);
+ if (!rounds[roundIndex]) {
+ rounds[roundIndex] = [];
+ }
+ rounds[roundIndex].push(template);
+ });
+
+ // First create a test match to get natural height
+ const firstMatch = document.createElement("div");
+ firstMatch.className = "butterfly-match";
+ firstMatch.innerHTML = `
${rounds[0][0].innerHTML}
`;
+ bracket.appendChild(firstMatch);
+ const matchHeight = firstMatch.offsetHeight;
+ const matchSpacing = 10;
+ const baseDistance = matchHeight + matchSpacing;
+ bracket.innerHTML = "";
+ const roundCount = rounds.length;
+ let finalRoundIndex = roundCount - 1;
+ if (doubleButterflyMode == true) {
+ finalRoundIndex = finalRoundIndex / 2;
+ }
+ let nextMatchDistance = baseDistance;
+ let minimumMatchDistance = 1;
+
+ const totalRounds = document.querySelectorAll(".butterfly-round").length;
+ const screenWidth = window.innerWidth;
+ let roundTotalCount = roundCount;
+ if (doubleButterflyMode == true && roundCount > 1) {
+ roundTotalCount = roundCount - 1;
+ }
+ const padding = 50 * roundTotalCount; // Account for some padding/margin
+ const availableWidth = screenWidth - padding;
+ responsiveMatchWidth = Math.min(
+ 365,
+ Math.max(365, Math.floor(availableWidth / roundTotalCount)),
+ );
+
+ if (isBroadcast) {
+ responsiveMatchWidth = Math.min(
+ 365,
+ Math.floor(availableWidth / roundTotalCount),
+ );
+ }
+
+ rounds.forEach((roundMatches, roundIndex) => {
+ if (rounds[0].length <= 2 && doubleButterflyMode) {
+ minimumMatchDistance = 2;
+ nextMatchDistance = baseDistance * 2;
+ }
+ const roundDiv = document.createElement("div");
+ roundDiv.className = "butterfly-round";
+ roundDiv.style.setProperty("--match-width", `${responsiveMatchWidth}px`);
+
+ // Create title
+ const titleDiv = document.createElement("div");
+ titleDiv.className = "round-title";
+
+ // Get the match group name and format
+ const firstMatchTemplate = roundMatches[0].closest(".match-template");
+ const matchGroupName = firstMatchTemplate.dataset.matchGroupName;
+ const matchFormat = firstMatchTemplate.dataset.matchFormat;
+ const roundId = firstMatchTemplate.dataset.roundId; // Add this line
+
+ let nameSpan = document.createElement("div");
+ nameSpan.className = "round-name";
+ nameSpan.textContent = matchGroupName;
+ if (
+ roundIndex == finalRoundIndex ||
+ (roundIndex == finalRoundIndex - 1 && displayLoserFinal) ||
+ (roundIndex == finalRoundIndex + 1 && displayLoserFinal) ||
+ isBroadcast
+ ) {
+ } else {
+ nameSpan = document.createElement("a");
+ nameSpan.className = "round-name";
+ nameSpan.classList.add("button");
+ nameSpan.textContent = matchGroupName;
+ if (roundId) {
+ nameSpan.href = `/tournament/${tournamentId}/round/${roundId}/bracket/`;
+ nameSpan.style.cursor = "pointer";
+ }
+ }
+
+ const formatSpan = document.createElement("div");
+ formatSpan.className = "round-format";
+ formatSpan.textContent = matchFormat;
+
+ titleDiv.appendChild(nameSpan);
+ titleDiv.appendChild(formatSpan);
+
+ // Create matches container
+ const matchesContainer = document.createElement("div");
+ matchesContainer.className = "matches-container";
+ if (roundCount > 5 && doubleButterflyMode == true) {
+ if (roundIndex >= finalRoundIndex - 1) {
+ matchesContainer.style.transform = `translateX(-50%)`;
+ if (roundIndex >= finalRoundIndex + 2) {
+ matchesContainer.style.transform = `translateX(-100%)`;
+ }
+ }
+ }
+ roundDiv.appendChild(matchesContainer);
+ if (matchPositions[roundIndex] == undefined) {
+ matchPositions[roundIndex] = {};
+ }
+ matchDisabled[roundIndex] = []; // Initialize array for this round
+ roundMatches.forEach((matchTemplate, matchIndex) => {
+ const matchTitle = matchTemplate.dataset.matchTitle;
+ const matchRealIndex = matchTemplate.dataset.matchRealIndex;
+ const matchDiv = document.createElement("div");
+ matchDiv.className = "butterfly-match";
+
+ matchDiv.style.position = "absolute";
+ const isDisabled = matchTemplate.dataset.disabled === "true";
+ matchDisabled[roundIndex][matchRealIndex] = isDisabled;
+ let isIncomingLineIsDisabled = isDisabled;
+ let isOutgoingLineIsDisabled = isDisabled;
+ let top;
+ const currentMatchesCount = roundMatches.length;
+ if (roundIndex > finalRoundIndex) {
+ matchDiv.classList.add("reverse-bracket");
+
+ if (roundIndex <= finalRoundIndex + 2) {
+ const values = Object.values(
+ matchPositions[roundCount - roundIndex - 1],
+ );
+ top = values[matchIndex];
+ } else {
+ top = matchPositions[roundIndex][matchRealIndex];
+ console.log(matchTitle, top);
+ }
+ }
+
+ if (roundIndex === 0) {
+ nextMatchDistance = 0;
+ if (roundCount > 1) {
+ const nextMatchesCount = rounds[roundIndex + 1].length;
+
+ if (currentMatchesCount == nextMatchesCount && roundCount > 2) {
+ nextMatchDistance = 0;
+ }
+ } else {
+ nextMatchDistance = 0;
+ }
+
+ top = matchIndex * (matchHeight + matchSpacing) * minimumMatchDistance;
+
+ if (roundCount == 3 && doubleButterflyMode) {
+ top = top + (matchHeight + matchSpacing) / 2;
+ }
+ } else if (roundIndex === roundCount - 1 && doubleButterflyMode == true) {
+ nextMatchDistance = 0;
+ } else if (roundIndex == finalRoundIndex) {
+ const values = Object.values(matchPositions[roundIndex - 1]);
+ const parentPos1 = values[0];
+ const parentPos2 = values[1];
+
+ if (doubleButterflyMode == true) {
+ let lgth = matchPositions[0].length / 2;
+ let index = lgth + matchIndex - 1;
+ // If index goes negative, use 0 instead
+ if (displayLoserFinal == true) {
+ if (matchIndex == 0) {
+ top = parentPos1 - baseDistance / 2;
+ } else {
+ top = parentPos1 + baseDistance / 2;
+ }
+ nextMatchDistance = 0;
+ } else {
+ top = parentPos1;
+ nextMatchDistance = 0;
+ }
+ } else {
+ top = (parentPos1 + parentPos2) / 2;
+ if (matchIndex == 0) {
+ nextMatchDistance = parentPos2 - parentPos1;
+ } else {
+ nextMatchDistance = 0;
+ }
+
+ if (displayLoserFinal == true) {
+ if (matchIndex == 1) {
+ top = matchPositions[roundIndex][0] + baseDistance + 80;
+ isIncomingLineIsDisabled = true;
+ }
+ }
+ }
+ } else if (roundIndex < finalRoundIndex) {
+ const parentIndex1 = matchRealIndex * 2 + 1;
+ const parentIndex2 = matchRealIndex * 2 + 2;
+ const parentPos1 = matchPositions[roundIndex - 1][parentIndex1];
+ const parentPos2 = matchPositions[roundIndex - 1][parentIndex2];
+ const parent1Disable = matchDisabled[roundIndex - 1][parentIndex1];
+ const parent2Disable = matchDisabled[roundIndex - 1][parentIndex2];
+ if (
+ (parent1Disable == undefined || parent1Disable == true) &&
+ (parent2Disable == undefined || parent2Disable == true)
+ ) {
+ isIncomingLineIsDisabled = true;
+ }
+ if (
+ matchPositions[roundIndex - 1][parentIndex1] != undefined &&
+ matchPositions[roundIndex - 1][parentIndex2] != undefined
+ ) {
+ top = (parentPos1 + parentPos2) / 2;
+ if (parent1Disable && parent2Disable) {
+ nextMatchDistance = 0;
+ } else {
+ nextMatchDistance = parentPos2 - parentPos1;
+ }
+ } else if (matchPositions[roundIndex - 1][parentIndex1] != undefined) {
+ nextMatchDistance = 0;
+ top = matchPositions[roundIndex - 1][parentIndex1];
+ } else if (matchPositions[roundIndex - 1][parentIndex2] != undefined) {
+ nextMatchDistance = 0;
+ top = matchPositions[roundIndex - 1][parentIndex2];
+ } else {
+ nextMatchDistance = 0;
+ top = 0;
+ }
+ } else if (roundIndex < roundCount) {
+ const parentIndex1 = matchRealIndex * 2 + 1;
+ const parentIndex2 = matchRealIndex * 2 + 2;
+ const parentMatch1 = rounds[roundIndex + 1].find(
+ (match) => parseInt(match.dataset.matchRealIndex) === parentIndex1,
+ );
+ const parentMatch2 = rounds[roundIndex + 1].find(
+ (match) => parseInt(match.dataset.matchRealIndex) === parentIndex2,
+ );
+
+ if (matchPositions[roundIndex + 1] == undefined) {
+ matchPositions[roundIndex + 1] = {};
+ }
+ if (
+ parentMatch1 != undefined &&
+ parentMatch2 != undefined &&
+ parentMatch1.dataset.disabled == "false" &&
+ parentMatch2.dataset.disabled == "false"
+ ) {
+ console.log(
+ roundIndex,
+ matchTitle,
+ parentMatch1.dataset.matchTitle,
+ parentMatch2.dataset.matchTitle,
+ parentMatch1.dataset.disabled,
+ parentMatch2.dataset.disabled,
+ top,
+ );
+ nextMatchDistance = baseDistance;
+ matchPositions[roundIndex + 1][parentIndex1] = top - baseDistance / 2;
+ matchPositions[roundIndex + 1][parentIndex2] = top + baseDistance / 2;
+ console.log(matchPositions[roundIndex + 1]);
+ // } else if (parentMatch1 != undefined) {
+ // matchPositions[roundIndex + 1][parentIndex1] = top;
+ // nextMatchDistance = 0;
+ // } else if (parentMatch2 != undefined) {
+ // matchPositions[roundIndex + 1][parentIndex1] = top;
+ // nextMatchDistance = 0;
+ } else if (
+ parentMatch2 != undefined &&
+ parentMatch2.dataset.disabled == "false"
+ ) {
+ nextMatchDistance = 0;
+ matchPositions[roundIndex + 1][parentIndex2] = top;
+ } else if (
+ parentMatch1 != undefined &&
+ parentMatch1.dataset.disabled == "false"
+ ) {
+ nextMatchDistance = 0;
+ matchPositions[roundIndex + 1][parentIndex1] = top;
+ } else {
+ isOutgoingLineIsDisabled = true;
+ }
+ }
+
+ if (roundCount > 5 && doubleButterflyMode == true) {
+ if (roundIndex >= finalRoundIndex - 2) {
+ if (roundIndex == finalRoundIndex - 1) {
+ matchDiv.classList.add("reverse-bracket");
+ isIncomingLineIsDisabled = true;
+ nextMatchDistance = nextMatchDistance / 2;
+ }
+ if (roundIndex == finalRoundIndex + 1) {
+ matchDiv.classList.remove("reverse-bracket");
+ isOutgoingLineIsDisabled = true;
+ nextMatchDistance = nextMatchDistance;
+ }
+ }
+ }
+ matchDiv.style.setProperty(
+ "--semi-final-distance",
+ `${baseDistance / 2}px`,
+ );
+
+ matchDiv.style.setProperty(
+ "--next-match-distance",
+ `${nextMatchDistance}px`,
+ );
+ matchDiv.style.top = `${top}px`;
+ matchPositions[roundIndex][matchRealIndex] = top;
+
+ if (matchIndex === 0) {
+ // // Add logo for final round
+ // if (roundIndex == finalRoundIndex) {
+ // const logoDiv = document.createElement('div');
+ // logoDiv.className = 'round-logo';
+ // const logoImg = document.createElement('img');
+ // logoImg.src = '/static/tournaments/images/PadelClub_logo_512.png';
+ // logoImg.alt = 'PadelClub Logo';
+ // logoDiv.appendChild(logoImg);
+ // logoDiv.style.transform = `translateX(-50%)`;
+ // matchesContainer.appendChild(logoDiv);
+ // }
+
+ // Position title above the first match
+ if (
+ roundIndex < finalRoundIndex - 1 ||
+ roundIndex > finalRoundIndex + 1
+ ) {
+ titleDiv.style.top = `${-80}px`; // Adjust the 60px offset as needed
+ } else {
+ titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed
+ }
+ titleDiv.style.position = "absolute";
+ if (roundCount >= 5 && doubleButterflyMode == true) {
+ if (roundIndex == finalRoundIndex - 1) {
+ titleDiv.style.marginLeft = "60px";
+ } else if (roundIndex == finalRoundIndex + 1) {
+ titleDiv.style.marginLeft = "-60px";
+ }
+ }
+ matchesContainer.appendChild(titleDiv);
+ }
+
+ if (
+ roundIndex == finalRoundIndex &&
+ matchIndex === 1 &&
+ matchDisabled[roundIndex][matchRealIndex] == false &&
+ displayLoserFinal == true &&
+ doubleButterflyMode == false
+ ) {
+ let nameSpan = document.createElement("div");
+ nameSpan.className = "round-name";
+ nameSpan.textContent = matchTitle;
+ const formatSpan = document.createElement("div");
+ formatSpan.className = "round-format";
+ formatSpan.textContent = matchFormat;
+ const titleDiv = document.createElement("div");
+ titleDiv.className = "round-title";
+ titleDiv.appendChild(nameSpan);
+ titleDiv.appendChild(formatSpan);
+ titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed
+ titleDiv.style.position = "absolute";
+ matchesContainer.appendChild(titleDiv);
+ }
+
+ matchDiv.innerHTML = `
+
+ ${matchTemplate.innerHTML}
+
+ `;
+
+ if (roundCount > 5 && doubleButterflyMode == true) {
+ if (roundIndex >= finalRoundIndex - 2) {
+ if (roundIndex === finalRoundIndex - 2) {
+ if (matchIndex === 0) {
+ const outgoingLine = document.createElement("div");
+ outgoingLine.className = "outgoing-line-downward";
+ matchDiv.appendChild(outgoingLine);
+ } else {
+ const outgoingLine = document.createElement("div");
+ outgoingLine.className = "outgoing-line-upward";
+ matchDiv.appendChild(outgoingLine);
+ }
+ }
+ if (roundIndex === finalRoundIndex + 2) {
+ if (matchIndex === 0) {
+ const outgoingLine = document.createElement("div");
+ outgoingLine.className = "outgoing-line-downward";
+ matchDiv.appendChild(outgoingLine);
+ } else {
+ const outgoingLine = document.createElement("div");
+ outgoingLine.className = "outgoing-line-upward";
+ matchDiv.appendChild(outgoingLine);
+ }
+ }
+ }
+ }
+
+ // if (
+ // roundIndex == finalRoundIndex - 1 &&
+ // displayLoserFinal == true &&
+ // doubleButterflyMode == true
+ // ) {
+ // const matchDiv2 = document.createElement("div");
+ // matchDiv2.className = "butterfly-match";
+ // matchDiv2.classList.add("inward");
+ // matchDiv2.classList.add("semi-final");
+ // matchDiv2.style.setProperty(
+ // "--next-match-distance",
+ // `${baseDistance}px`,
+ // );
+ // matchDiv2.style.top = `${top}px`;
+ // matchDiv2.innerHTML = `${rounds[0][0].innerHTML}
`;
+ // matchesContainer.appendChild(matchDiv2); // Append to matchesContainer instead of roundDiv
+ // }
+ matchesContainer.appendChild(matchDiv); // Append to matchesContainer instead of roundDiv
+ });
+
+ bracket.appendChild(roundDiv);
+ });
+}
diff --git a/tournaments/templates/tournaments/base.html b/tournaments/templates/tournaments/base.html
index ee1dec2..a7d2b24 100644
--- a/tournaments/templates/tournaments/base.html
+++ b/tournaments/templates/tournaments/base.html
@@ -13,6 +13,7 @@
/>
+ {% block extra_css %}{% endblock %}
{% for team in match.teams %}
-
+
{% if team.id %}
{% endif %}
diff --git a/tournaments/templates/tournaments/broadcast/broadcast.html b/tournaments/templates/tournaments/broadcast/broadcast.html
index 61b7dea..e090ec9 100644
--- a/tournaments/templates/tournaments/broadcast/broadcast.html
+++ b/tournaments/templates/tournaments/broadcast/broadcast.html
@@ -12,11 +12,12 @@
diff --git a/tournaments/templates/tournaments/broadcast/broadcast_club.html b/tournaments/templates/tournaments/broadcast/broadcast_club.html
index 6b3d617..77196a4 100644
--- a/tournaments/templates/tournaments/broadcast/broadcast_club.html
+++ b/tournaments/templates/tournaments/broadcast/broadcast_club.html
@@ -13,7 +13,7 @@
{% for tournament in tournaments %}
-
+
{{ tournament.formatted_start_date }}
@@ -31,7 +31,8 @@
Automatic |
-
Programmation |
+
(beta) Tableau |
+
(beta) Programmation |
Matchs |
Poules |
Convocations |
diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html
new file mode 100644
index 0000000..4c6e842
--- /dev/null
+++ b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html
@@ -0,0 +1,161 @@
+
+{% load static %}
+{% load qr_code %}
+
+
+
+
+
+
+
+
+
+
+
+
Tableau
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_summon.html b/tournaments/templates/tournaments/broadcast/broadcasted_summon.html
index f3272fa..de2d2d8 100644
--- a/tournaments/templates/tournaments/broadcast/broadcasted_summon.html
+++ b/tournaments/templates/tournaments/broadcast/broadcasted_summon.html
@@ -9,13 +9,11 @@
-
diff --git a/tournaments/templates/tournaments/navigation_tournament.html b/tournaments/templates/tournaments/navigation_tournament.html
index e144d9a..fd5995f 100644
--- a/tournaments/templates/tournaments/navigation_tournament.html
+++ b/tournaments/templates/tournaments/navigation_tournament.html
@@ -2,9 +2,9 @@
Informations
-
+ {% endif %}
{% if tournament.display_matches or tournament.display_group_stages %}
Matches
diff --git a/tournaments/templates/tournaments/tournament_bracket.html b/tournaments/templates/tournaments/tournament_bracket.html
index b334bfd..daf44df 100644
--- a/tournaments/templates/tournaments/tournament_bracket.html
+++ b/tournaments/templates/tournaments/tournament_bracket.html
@@ -1,4 +1,9 @@
{% extends 'tournaments/base.html' %}
+{% load static %}
+
+{% block extra_css %}
+
+{% endblock %}
{% block head_title %}Matchs du {{ tournament.display_name }}{% endblock %}
{% block first_title %}{{ tournament.event.display_name }}{% endblock %}
@@ -21,6 +26,7 @@
data-match-group-name="{{ match_group.name }}"
data-match-format="{{ match.format }}"
data-match-title="{{ match.title }}"
+ data-match-real-index="{{ match.index }}"
data-round-id="{{ match_group.round_id }}"
class="match-template">
{% include 'tournaments/bracket_match_cell.html' %}
@@ -30,479 +36,15 @@
{% endfor %}
+
-
-
{% endif %}
{% endblock %}
diff --git a/tournaments/urls.py b/tournaments/urls.py
index 2bede6a..4f315c2 100644
--- a/tournaments/urls.py
+++ b/tournaments/urls.py
@@ -31,6 +31,8 @@ urlpatterns = [
path('broadcast/json/', views.broadcast_json, name='broadcast-json'),
path('broadcast/group-stages/', views.tournament_broadcasted_group_stages, name='broadcasted-group-stages'),
path('broadcast/prog/', views.tournament_broadcasted_prog, name='broadcasted-prog'),
+ path('broadcast/bracket/', views.tournament_broadcasted_bracket, name='broadcasted-bracket'),
+ path('bracket/json/', views.tournament_bracket_json, name='tournament-bracket-json'),
path('group-stages/', views.tournament_group_stages, name='group-stages'),
path('group-stages/json/', views.tournament_live_group_stage_json, name='group-stages-json'),
path('rankings/', views.tournament_rankings, name='tournament-rankings'),
diff --git a/tournaments/views.py b/tournaments/views.py
index 3de6532..1a5df8a 100644
--- a/tournaments/views.py
+++ b/tournaments/views.py
@@ -449,6 +449,31 @@ def tournament_broadcasted_prog(request, tournament_id):
'qr_code_options': qr_code_options(),
})
+def tournament_broadcasted_bracket(request, tournament_id):
+ tournament = get_object_or_404(Tournament, pk=tournament_id)
+ return render(request, 'tournaments/broadcast/broadcasted_bracket.html', {
+ 'tournament': tournament,
+ 'qr_code_url': qr_code_url(request, tournament_id),
+ 'qr_code_options': qr_code_options(),
+ })
+
+
+def tournament_bracket_json(request, tournament_id):
+ """
+ JSON endpoint for tournament bracket data.
+ """
+ tournament = get_object_or_404(Tournament, pk=tournament_id)
+ context = get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=True, display_loser_final=True)
+
+ data = {
+ 'match_groups': [match_group.to_dict() for match_group in context['match_groups']],
+ 'double_butterfly_mode': context['double_butterfly_mode'],
+ 'display_loser_final': context['display_loser_final']
+ }
+
+ return JsonResponse(data)
+
+
def tournament_live_group_stage_json(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
@@ -578,8 +603,20 @@ def xls_to_csv(request):
file_path = os.path.join(directory, uploaded_file.name)
file_name = default_storage.save(file_path, ContentFile(uploaded_file.read()))
- # convert to csv and save
- data_xls = pd.read_excel(file_name, sheet_name=0, index_col=None)
+ # Check available sheets and look for 'inscriptions'
+ xls = pd.ExcelFile(file_name)
+ sheet_names = xls.sheet_names
+
+ # Determine which sheet to use
+ target_sheet = 0 # Default to first sheet
+ if 'inscriptions' in [name.lower() for name in sheet_names]:
+ for i, name in enumerate(sheet_names):
+ if name.lower() == 'inscriptions':
+ target_sheet = i # or use the name directly: target_sheet = name
+ break
+
+ # Convert to csv and save
+ data_xls = pd.read_excel(file_name, sheet_name=target_sheet, index_col=None)
csv_file_name = create_random_filename('players', 'csv')
output_path = os.path.join(directory, csv_file_name)
data_xls.to_csv(output_path, sep=';', index=False, encoding='utf-8')
@@ -1018,7 +1055,7 @@ def round_bracket(request, tournament_id, round_id):
context = get_butterfly_bracket_view_context(tournament, round, double_butterfly_mode=False, display_loser_final=False)
return render(request, 'tournaments/tournament_bracket.html', context)
-def get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=False):
+def get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=True):
if parent_round:
double_butterfly_mode = False
display_loser_final = True