diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 71571c4..329fab6 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]) diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 7d740bc..0115a33 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 @@ -138,6 +138,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..2a69060 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1362,7 +1362,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 +1370,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 +1391,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..6e006b6 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -869,3 +869,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..6632ca8 --- /dev/null +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -0,0 +1,231 @@ +.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, +.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 */ +.butterfly-match::after { + content: ""; + position: absolute; + left: 100%; /* Start from end of match cell */ + top: 50%; + width: 20px; + height: 2px; + background: #707070; +} + +.semi-final::after { + content: ""; + position: absolute; + left: calc(100% + 20px); /* After horizontal line */ + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +/* Vertical line connecting pair of matches */ +.butterfly-match:nth-child(2n)::before { + content: ""; + position: absolute; + left: calc(100% + 20px); /* After horizontal line */ + top: 50%; + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +.butterfly-match:nth-child(2n + 1)::before { + content: ""; + position: absolute; + left: calc(100% + 20px); + bottom: calc(50% - 2px); /* Account for half of horizontal line height */ + width: 2px; + height: calc( + ((var(--next-match-distance)) / 2) + ); /* Add half of horizontal line height */ + background: #707070; +} + +/* Vertical line connecting pair of matches */ +.butterfly-match.reverse-bracket:nth-child(2n)::before { + content: ""; + position: absolute; + left: calc(0% - 20px); /* After horizontal line */ + top: 50%; + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +.butterfly-match.reverse-bracket:nth-child(2n + 1)::before { + content: ""; + position: absolute; + left: calc(0% - 20px); + bottom: 50%; /* Account for half of horizontal line height */ + width: 2px; + height: calc( + (var(--next-match-distance)) / 2 + ); /* Add half of horizontal line height */ + background: #707070; +} + +/* Horizontal line to next round match */ +.butterfly-match .incoming-line { + position: absolute; + left: -20px; + top: 50%; + width: 20px; + height: 2px; + background: #707070; +} + +.inward .incoming-line { + display: none; +} + +.butterfly-match.outward::after { + display: none; +} + +.butterfly-round:last-child .butterfly-match::after, +.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; +} +/* Broadcast mode styling for all lines */ +.broadcast-mode .butterfly-match::after, +.broadcast-mode .butterfly-match:nth-child(2n)::before, +.broadcast-mode .butterfly-match:nth-child(2n + 1)::before, +.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n)::before, +.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n + 1)::before, +.broadcast-mode .butterfly-match .incoming-line, +.broadcast-mode .semi-final::after { + background-color: black; /* 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..159c70b --- /dev/null +++ b/tournaments/static/tournaments/js/tournament_bracket.js @@ -0,0 +1,333 @@ +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 = `
+