1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-04-17 05:15:36 +00:00

New stats page, scoreboard, and live scores pages.

This commit is contained in:
2026-04-13 18:11:21 -04:00
parent 7526148ba5
commit c210b70c95
62 changed files with 10006 additions and 154 deletions

View File

@@ -27,9 +27,10 @@
<% end %>
<li><%= link_to "All Brackets (Printable)", "/tournaments/#{@tournament.id}/all_brackets?print=true", target: :_blank %></li>
</ul>
</li>
<li><%= link_to " Bout Board" , "/tournaments/#{@tournament.id}/up_matches", class: "fas fa-list-alt" %></li>
<% end %>
</li>
<li><%= link_to " Bout Board" , "/tournaments/#{@tournament.id}/up_matches", class: "fas fa-list-alt" %></li>
<li><%= link_to " Live Scores" , "/tournaments/#{@tournament.id}/live_scores", class: "fas fa-tv" %></li>
<% end %>
<% if can? :manage, @tournament %>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#director"><i class="fas fa-tools"> Director Links</i>

View File

@@ -0,0 +1,70 @@
<% submit_label = local_assigns.fetch(:submit_label, "Update Match") %>
<% redirect_path = local_assigns[:redirect_path] %>
<h4>Match Results</h4>
<br>
<div data-controller="match-score" data-match-score-finished-value="<%= @match.finished == 1 %>">
<div class="field">
<%= f.label "Win type" %><br>
<%= f.select :win_type, Match::WIN_TYPES, { include_blank: false }, {
data: {
match_score_target: "winType",
action: "change->match-score#winTypeChanged"
}
} %>
</div>
<br>
<div class="field">
<%= f.label "Overtime Type" %> Leave blank if not overtime. For High School the 1st overtime is SV-1, second overtime is TB-1, third overtime is UTB.<br>
<%= f.select(:overtime_type, Match::OVERTIME_TYPES, {}, {
data: {
match_score_target: "overtimeSelect"
}
}) %>
</div>
<br>
<div class="field">
<%= f.label "Winner" %> Please choose the winner<br>
<%= f.collection_select :winner_id, @wrestlers, :id, :name_with_school,
{ include_blank: true },
{
data: {
match_score_target: "winnerSelect",
action: "change->match-score#winnerChanged"
}
}
%>
</div>
<br>
<div class="field">
<%= f.label "Final Score" %>
<br>
<% if @match.finished == 1 %>
<%= f.text_field :score, id: "final-score-field", data: { match_score_target: "finalScoreField" }, class: "form-control", style: "max-width: 220px;" %>
<% else %>
<span id="score-help-text">
The input will adjust based on the selected win type.
</span>
<br>
<div id="dynamic-score-input" data-match-score-target="dynamicScoreInput"></div>
<p id="pin-time-tip" class="text-muted mt-2" style="display: none;" data-match-score-target="pinTimeTip">
<strong>Tip:</strong> Pin time is an accumulation over the match, not how much time was left in the current period.
<br>For example, if all 3 periods are 2 minutes and a pin happened with 1:27 left in the second period,
the pin time would be <strong>2:33</strong> (2 minutes for the first period + 33 seconds elapsed in the second period).
</p>
<%= f.hidden_field :score, id: "final-score-field", data: { match_score_target: "finalScoreField" } %>
<% end %>
<div id="validation-alerts" class="text-danger mt-2" data-match-score-target="validationAlerts"></div>
<%= hidden_field_tag :redirect_to, redirect_path if redirect_path.present? %>
<%= f.hidden_field :finished, value: 1 %>
<%= f.hidden_field :round, value: @match.round %>
<br>
<%= f.submit submit_label, id: "update-match-btn",
data: {
match_score_target: "submitButton",
action: "click->match-score#confirmWinner"
},
class: "btn btn-success" %>
</div>
</div>

View File

@@ -130,65 +130,6 @@
<br>
<br>
<br>
<h4>Match Results</h4>
<br>
<div data-controller="match-score">
<div class="field">
<%= f.label "Win type" %><br>
<%= f.select :win_type, Match::WIN_TYPES, { include_blank: false }, {
data: {
match_score_target: "winType",
action: "change->match-score#winTypeChanged"
}
} %>
</div>
<br>
<div class="field">
<%= f.label "Overtime Type" %> Leave blank if not overtime. For High School the 1st overtime is SV-1, second overtime is TB-1, third overtime is UTB.<br>
<%= f.select(:overtime_type, Match::OVERTIME_TYPES) %>
</div>
<br>
<div class="field">
<%= f.label "Winner" %> Please choose the winner<br>
<%= f.collection_select :winner_id, @wrestlers, :id, :name_with_school,
{ include_blank: true },
{
data: {
match_score_target: "winnerSelect",
action: "change->match-score#winnerChanged"
}
}
%>
</div>
<br>
<div class="field">
<%= f.label "Final Score" %>
<br>
<span id="score-help-text">
The input will adjust based on the selected win type.
</span>
<br>
<div id="dynamic-score-input" data-match-score-target="dynamicScoreInput"></div>
<p id="pin-time-tip" class="text-muted mt-2" style="display: none;" data-match-score-target="pinTimeTip">
<strong>Tip:</strong> Pin time is an accumulation over the match, not how much time was left in the current period.
<br>For example, if all 3 periods are 2 minutes and a pin happened with 1:27 left in the second period,
the pin time would be <strong>2:33</strong> (2 minutes for the first period + 33 seconds elapsed in the second period).
</p>
<div id="validation-alerts" class="text-danger mt-2" data-match-score-target="validationAlerts"></div>
<%= f.hidden_field :score, id: "final-score-field", data: { match_score_target: "finalScoreField" } %>
<br>
<%= f.submit "Update Match", id: "update-match-btn",
data: {
match_score_target: "submitButton",
action: "click->match-score#confirmWinner"
},
class: "btn btn-success" %>
</div>
</div><!-- End of match-score controller -->
<%= render "matches/match_results_fields", f: f, redirect_path: @match_results_redirect_path %>
</div><!-- End of match-data controller div -->
<br>
<%= f.hidden_field :finished, :value => 1 %>
<%= f.hidden_field :round, :value => @match.round %>
<% end %><!-- End of form_for -->

View File

@@ -0,0 +1,113 @@
<%
source_mode = local_assigns.fetch(:source_mode, "localstorage")
display_mode = local_assigns.fetch(:display_mode, "fullscreen")
show_mat_banner = local_assigns.fetch(:show_mat_banner, false)
show_stats = local_assigns.fetch(:show_stats, false)
mat = local_assigns[:mat]
match = local_assigns[:match]
tournament = local_assigns[:tournament] || match&.tournament
fullscreen = display_mode == "fullscreen"
root_style = if fullscreen
"min-height: 100vh; background: #000; color: #fff; display: flex; align-items: stretch; justify-content: stretch; padding: 0; margin: 0; position: relative;"
elsif show_stats
"background: #000; color: #fff; padding: 0; margin: 0; position: relative; width: 100%; border: 1px solid #222;"
else
"background: #000; color: #fff; display: flex; align-items: stretch; justify-content: stretch; padding: 0; margin: 0; position: relative; width: 100%; min-height: 260px; border: 1px solid #222;"
end
inner_style = if fullscreen
"display: grid; grid-template-rows: auto 1fr; width: 100vw; min-height: 100vh;"
elsif show_stats
"display: grid; grid-template-rows: auto auto auto; width: 100%;"
else
"display: grid; grid-template-rows: auto 1fr; width: 100%; min-height: 260px;"
end
board_style = fullscreen ? "display: grid; grid-template-columns: 1fr 1.3fr 1fr; width: 100%; min-height: 0;" : "display: grid; grid-template-columns: 1fr 1.2fr 1fr; width: 100%; min-height: 0; min-height: 260px;"
panel_padding = fullscreen ? "2vw" : "1rem"
name_size = fullscreen ? "clamp(2rem, 4vw, 4rem)" : "clamp(1rem, 2vw, 1.8rem)"
school_size = fullscreen ? "clamp(1rem, 2vw, 2rem)" : "clamp(0.85rem, 1.3vw, 1.1rem)"
score_size = fullscreen ? "clamp(8rem, 18vw, 16rem)" : "clamp(3rem, 8vw, 6rem)"
period_size = fullscreen ? "clamp(1.5rem, 3vw, 2.5rem)" : "clamp(1rem, 2vw, 1.5rem)"
clock_size = fullscreen ? "clamp(6rem, 16vw, 14rem)" : "clamp(3rem, 10vw, 5.5rem)"
banner_offset = fullscreen ? "6vw" : "2rem"
banner_border = fullscreen ? "0.45vw" : "4px"
center_border = fullscreen ? "1vw" : "6px"
%>
<div
data-controller="match-scoreboard"
data-match-scoreboard-source-mode-value="<%= source_mode %>"
data-match-scoreboard-display-mode-value="<%= display_mode %>"
data-match-scoreboard-match-id-value="<%= match&.id || 0 %>"
data-match-scoreboard-mat-id-value="<%= mat&.id || 0 %>"
data-match-scoreboard-tournament-id-value="<%= tournament&.id || 0 %>"
data-match-scoreboard-initial-bout-number-value="<%= match&.bout_number || 0 %>"
style="<%= root_style %>">
<div style="<%= inner_style %>">
<% if show_mat_banner %>
<div style="background: #111; color: #fff; text-align: center; padding: 1vh 2vw; border-bottom: 0.5vw solid #fff;">
<div style="font-size: clamp(1.5rem, 3vw, 3rem); font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;">
Mat <%= mat&.name %>
</div>
<div data-match-scoreboard-target="boutLabel" style="font-size: clamp(1rem, 2vw, 1.75rem); letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.95; margin-top: 0.3rem;">
Bout <%= match&.bout_number || "" %>
</div>
</div>
<% else %>
<div style="background: #111; color: #fff; text-align: center; padding: 0.35rem 1rem; border-bottom: 4px solid #fff;">
<div data-match-scoreboard-target="boutLabel" style="font-size: clamp(0.85rem, 1.3vw, 1.1rem); letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.95;">
Bout <%= match&.bout_number || "" %>
</div>
</div>
<% end %>
<div style="<%= board_style %>">
<section data-match-scoreboard-target="redSection" style="background: #c91f1f; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: <%= panel_padding %>; text-align: center;">
<div data-match-scoreboard-target="redName" style="font-size: <%= name_size %>; font-weight: 700; line-height: 1;">NO MATCH</div>
<div data-match-scoreboard-target="redSchool" style="font-size: <%= school_size %>; margin-top: 0.75rem; opacity: 0.95;"></div>
<div data-match-scoreboard-target="redTimerIndicator" style="font-size: <%= school_size %>; margin-top: 0.75rem;"></div>
<div data-match-scoreboard-target="redScore" style="font-size: <%= score_size %>; font-weight: 800; line-height: 0.9; margin-top: 3vh;">0</div>
</section>
<section data-match-scoreboard-target="centerSection" style="background: #050505; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: <%= panel_padding %>; text-align: center; border-left: <%= center_border %> solid #fff; border-right: <%= center_border %> solid #fff;">
<div data-match-scoreboard-target="periodLabel" style="font-size: <%= period_size %>; font-weight: 700; margin-top: 2vh; text-transform: uppercase;">No Match</div>
<div data-match-scoreboard-target="clock" style="font-size: <%= clock_size %>; font-weight: 800; line-height: 0.9; margin-top: 3vh;">-</div>
<div data-match-scoreboard-target="emptyState" style="display: none; margin-top: 4vh; font-size: clamp(1.5rem, 3vw, 4rem); font-weight: 700; color: #fff;">No Match</div>
</section>
<section data-match-scoreboard-target="greenSection" style="background: #1cab2d; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: <%= panel_padding %>; text-align: center;">
<div data-match-scoreboard-target="greenName" style="font-size: <%= name_size %>; font-weight: 700; line-height: 1;">NO MATCH</div>
<div data-match-scoreboard-target="greenSchool" style="font-size: <%= school_size %>; margin-top: 0.75rem; opacity: 0.95;"></div>
<div data-match-scoreboard-target="greenTimerIndicator" style="font-size: <%= school_size %>; margin-top: 0.75rem;"></div>
<div data-match-scoreboard-target="greenScore" style="font-size: <%= score_size %>; font-weight: 800; line-height: 0.9; margin-top: 3vh;">0</div>
</section>
</div>
<% if show_stats %>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; background: #fff; color: #111; padding: 12px;">
<section style="border: 1px solid #d9d9d9; padding: 12px; min-width: 0;">
<div style="font-weight: 700; margin-bottom: 8px;">Red Stats</div>
<pre data-match-scoreboard-target="redStats" style="margin: 0; background: #f7f7f7; border: 1px solid #ececec; padding: 10px; min-height: 120px; white-space: pre-wrap; overflow-wrap: anywhere;"></pre>
</section>
<section style="border: 1px solid #d9d9d9; padding: 12px; min-width: 0;">
<div style="font-weight: 700; margin-bottom: 8px;">Green Stats</div>
<pre data-match-scoreboard-target="greenStats" style="margin: 0; background: #f7f7f7; border: 1px solid #ececec; padding: 10px; min-height: 120px; white-space: pre-wrap; overflow-wrap: anywhere;"></pre>
</section>
</div>
<div style="background: #fff; color: #111; padding: 0 12px 12px;">
<section style="border: 1px solid #d9d9d9; padding: 12px;">
<div style="font-weight: 700; margin-bottom: 8px;">Last Match Result</div>
<div data-match-scoreboard-target="lastMatchResult">-</div>
</section>
</div>
<% end %>
</div>
<div
data-match-scoreboard-target="timerBanner"
style="display: none; position: absolute; left: <%= banner_offset %>; right: <%= banner_offset %>; top: 50%; transform: translateY(-50%); background: rgba(15, 15, 15, 0.96); border: <%= banner_border %> solid #fff; padding: 1.5vh 2vw; text-align: center; z-index: 20;">
<div data-match-scoreboard-target="timerBannerLabel" style="font-size: <%= fullscreen ? "clamp(1.3rem, 2.6vw, 2.6rem)" : "clamp(0.9rem, 1.6vw, 1.25rem)" %>; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;"></div>
<div data-match-scoreboard-target="timerBannerClock" style="font-size: <%= fullscreen ? "clamp(2.5rem, 6vw, 6rem)" : "clamp(1.6rem, 4vw, 3rem)" %>; font-weight: 800; line-height: 1; margin-top: 0.5vh;"></div>
</div>
</div>

View File

@@ -10,6 +10,18 @@
class="alert alert-secondary"
style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
<% unless @match.finished %>
<div data-match-spectate-target="scoreboardContainer" style="margin-bottom: 20px;">
<%= render "tournaments/live_score_card",
source_mode: "websocket",
show_mat_header: false,
show_details: false,
mat: @match.mat,
match: @match,
tournament: @tournament %>
</div>
<% end %>
<div class="match-details">
<div class="wrestler-info wrestler1">
<h4><%= @wrestler1_name %> (<%= @wrestler1_school_name %>)</h4>
@@ -83,4 +95,4 @@
color: white;
}
*/
</style>
</style>

View File

@@ -0,0 +1,108 @@
<h1><%= @wrestler1_name %> VS. <%= @wrestler2_name %></h1>
<div
data-controller="match-state"
data-match-state-match-id-value="<%= @match.id %>"
data-match-state-tournament-id-value="<%= @match.tournament.id %>"
data-match-state-bout-number-value="<%= @match.bout_number %>"
data-match-state-weight-label-value="<%= @match.weight&.max %>"
data-match-state-bracket-position-value="<%= @match.bracket_position %>"
data-match-state-ruleset-value="<%= @match_state_ruleset %>"
data-match-state-w1-id-value="<%= @match.w1 || 0 %>"
data-match-state-w2-id-value="<%= @match.w2 || 0 %>"
data-match-state-w1-name-value="<%= @wrestler1_name %>"
data-match-state-w2-name-value="<%= @wrestler2_name %>"
data-match-state-w1-school-value="<%= @wrestler1_school_name %>"
data-match-state-w2-school-value="<%= @wrestler2_school_name %>">
<div class="row">
<div class="col-md-4">
<div class="panel panel-success" data-match-state-target="greenPanel">
<div class="panel-heading">
<strong data-match-state-target="greenLabel">Green</strong> <span data-match-state-target="greenName"><%= @wrestler1_name %></span>
</div>
<div class="panel-body">
<p class="text-muted" data-match-state-target="greenSchool"><%= @wrestler1_school_name %></p>
<h2 data-match-state-target="greenScore">0</h2>
<div data-match-state-target="greenControls"></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Bout <%= @match.bout_number %></strong>
</div>
<div class="panel-body">
<p><strong>Bracket Position:</strong> <%= @match.bracket_position %></p>
<p><strong>Period:</strong> <span data-match-state-target="periodLabel"></span></p>
<p><strong>Clock:</strong> <span data-match-state-target="clock">2:00</span></p>
<p><strong>Status:</strong> <span data-match-state-target="clockStatus">Stopped</span></p>
<p><strong>Match Position:</strong> <span data-match-state-target="matchPosition"></span></p>
<p><strong>Format:</strong> <span data-match-state-target="formatName"></span></p>
<div class="btn-group" style="margin-top: 10px;">
<button type="button" class="btn btn-success btn-sm" data-action="click->match-state#startClock">Start</button>
<button type="button" class="btn btn-danger btn-sm" data-action="click->match-state#stopClock">Stop</button>
</div>
<div style="margin-top: 10px;">
<strong>Adjust Match Clock</strong>
<div class="btn-group" style="margin-top: 8px;">
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#subtractMinute">-1m</button>
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#subtractSecond">-1s</button>
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#addSecond">+1s</button>
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#addMinute">+1m</button>
</div>
</div>
<div style="margin-top: 12px;">
<button type="button" class="btn btn-primary btn-sm" data-action="click->match-state#swapColors">Swap Red/Green</button>
</div>
<div style="margin-top: 12px;">
<strong>Match Period Navigation</strong>
<div class="text-muted">Use these to move between periods and choice periods.</div>
<div class="btn-group" style="margin-top: 8px;">
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#previousPhase">Previous Period</button>
<button type="button" class="btn btn-default btn-sm" data-action="click->match-state#nextPhase">Next Period</button>
</div>
<div style="margin-top: 8px;">
<strong>Match Time Accumulation:</strong>
<span data-match-state-target="accumulationClock">0:00</span>
</div>
<div style="margin-top: 12px;">
<button type="button" class="btn btn-warning btn-sm" data-action="click->match-state#resetMatch">Reset Match</button>
</div>
</div>
<div data-match-state-target="choiceActions" style="margin-top: 12px;"></div>
<hr>
<h4>Event Log</h4>
<div data-match-state-target="eventLog"></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-danger" data-match-state-target="redPanel">
<div class="panel-heading">
<strong data-match-state-target="redLabel">Red</strong> <span data-match-state-target="redName"><%= @wrestler2_name %></span>
</div>
<div class="panel-body">
<p class="text-muted" data-match-state-target="redSchool"><%= @wrestler2_school_name %></p>
<h2 data-match-state-target="redScore">0</h2>
<div data-match-state-target="redControls"></div>
</div>
</div>
</div>
</div>
<div class="panel panel-default" data-match-state-target="matchResultsPanel">
<div class="panel-heading">
<strong>Submit Match Results</strong>
</div>
<div class="panel-body">
<%= form_for(@match) do |f| %>
<%= f.hidden_field :w1_stat, data: { match_state_target: "w1StatField" } %>
<%= f.hidden_field :w2_stat, data: { match_state_target: "w2StatField" } %>
<%= render "matches/match_results_fields", f: f, redirect_path: @match_results_redirect_path, submit_label: "Update Match" %>
<% end %>
</div>
</div>
</div>

View File

@@ -0,0 +1,7 @@
<%= render "matches/scoreboard",
source_mode: "localstorage",
display_mode: "fullscreen",
show_mat_banner: true,
mat: @mat,
match: @match,
tournament: @tournament %>

View File

@@ -0,0 +1,33 @@
<% if @match %>
<div
data-controller="mat-state"
data-mat-state-tournament-id-value="<%= @tournament.id %>"
data-mat-state-mat-id-value="<%= @mat.id %>"
data-mat-state-bout-number-value="<%= @match.bout_number %>"
data-mat-state-match-id-value="<%= @match.id %>"
data-mat-state-select-match-url-value="<%= select_match_mat_path(@mat) %>"
data-mat-state-weight-label-value="<%= @match.weight&.max %>"
data-mat-state-w1-id-value="<%= @match.w1 || 0 %>"
data-mat-state-w2-id-value="<%= @match.w2 || 0 %>"
data-mat-state-w1-name-value="<%= @wrestler1_name %>"
data-mat-state-w2-name-value="<%= @wrestler2_name %>"
data-mat-state-w1-school-value="<%= @wrestler1_school_name %>"
data-mat-state-w2-school-value="<%= @wrestler2_school_name %>">
<h3>Mat <%= @mat.name %></h3>
<div style="margin-bottom: 10px;">
<% @queue_matches.each_with_index do |queue_match, index| %>
<% queue_label = "Queue #{index + 1}" %>
<% if queue_match %>
<% button_class = queue_match.id == @match.id ? "btn btn-success btn-sm" : "btn btn-primary btn-sm" %>
<%= link_to "#{queue_label}: Bout #{queue_match.bout_number}", state_mat_path(@mat, bout_number: queue_match.bout_number), class: button_class %>
<% else %>
<button type="button" class="btn btn-default btn-sm" disabled><%= "#{queue_label}: Not assigned" %></button>
<% end %>
<% end %>
</div>
<%= render template: "matches/state" %>
</div>
<% else %>
<h3>Mat <%= @mat.name %></h3>
<p>No matches assigned to this mat.</p>
<% end %>

View File

@@ -0,0 +1,111 @@
<%
match = local_assigns[:match]
mat = local_assigns[:mat]
tournament = local_assigns[:tournament]
source_mode = local_assigns.fetch(:source_mode, "mat_websocket")
show_mat_header = local_assigns.fetch(:show_mat_header, true)
show_details = local_assigns.fetch(:show_details, true)
dom_id_suffix = mat&.id || match&.id || "none"
%>
<div
data-controller="match-scoreboard"
data-match-scoreboard-source-mode-value="<%= source_mode %>"
data-match-scoreboard-display-mode-value="embedded"
data-match-scoreboard-match-id-value="<%= match&.id || 0 %>"
data-match-scoreboard-mat-id-value="<%= mat&.id || 0 %>"
data-match-scoreboard-tournament-id-value="<%= tournament.id %>"
data-match-scoreboard-initial-bout-number-value="<%= match&.bout_number || 0 %>">
<div class="panel panel-default" style="margin-bottom: 0;">
<% if show_mat_header %>
<div class="panel-heading">
<strong>Mat <%= mat&.name %></strong>
</div>
<% end %>
<table class="table table-bordered table-condensed" style="margin-bottom: 0; table-layout: fixed;">
<tr class="active">
<td>
<strong data-match-scoreboard-target="boutLabel">Bout <%= match&.bout_number || "" %></strong>
<span style="margin-left: 12px;" data-match-scoreboard-target="weightLabel">Weight <%= match&.weight&.max || "-" %></span>
</td>
<td class="text-right" style="white-space: nowrap;">
<span class="label label-default" data-match-scoreboard-target="clock">-</span>
<span class="label label-primary" data-match-scoreboard-target="periodLabel">No Match</span>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<div data-match-scoreboard-target="greenName" style="font-weight: 700; font-size: 1.15em;">NO MATCH</div>
<div class="text-muted" data-match-scoreboard-target="greenSchool"></div>
<div data-match-scoreboard-target="greenTimerIndicator" style="margin-top: 6px;"></div>
</td>
<td data-match-scoreboard-target="greenSection" class="text-center" style="background: #1cab2d; color: #fff; font-size: 2rem; font-weight: 700; vertical-align: middle; width: 110px;">
<span data-match-scoreboard-target="greenScore">0</span>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<div data-match-scoreboard-target="redName" style="font-weight: 700; font-size: 1.15em;">NO MATCH</div>
<div class="text-muted" data-match-scoreboard-target="redSchool"></div>
<div data-match-scoreboard-target="redTimerIndicator" style="margin-top: 6px;"></div>
</td>
<td data-match-scoreboard-target="redSection" class="text-center" style="background: #c91f1f; color: #fff; font-size: 2rem; font-weight: 700; vertical-align: middle; width: 110px;">
<span data-match-scoreboard-target="redScore">0</span>
</td>
</tr>
<% if show_details %>
<tr>
<td colspan="2" style="padding: 0;">
<div class="panel panel-default" style="margin: 10px;">
<div class="panel-heading">
<a data-toggle="collapse" href="#live-score-stats-<%= dom_id_suffix %>" aria-expanded="false" aria-controls="live-score-stats-<%= dom_id_suffix %>" style="display: flex; justify-content: space-between; align-items: center; color: #333; text-decoration: none; background: transparent; outline: none;">
<strong>Stats</strong>
<span class="text-muted" style="font-size: 0.9em;">Show/Hide</span>
</a>
</div>
<div id="live-score-stats-<%= dom_id_suffix %>" class="panel-collapse collapse">
<div class="panel-body" style="padding-bottom: 0;">
<div class="row">
<div class="col-sm-6">
<div class="label label-success" style="display: inline-block; margin-bottom: 8px;">Green</div>
<pre data-match-scoreboard-target="greenStats" class="well well-sm" style="min-height: 100px; white-space: pre-wrap; overflow-wrap: anywhere; background: #fff;"></pre>
</div>
<div class="col-sm-6">
<div class="label label-danger" style="display: inline-block; margin-bottom: 8px;">Red</div>
<pre data-match-scoreboard-target="redStats" class="well well-sm" style="min-height: 100px; white-space: pre-wrap; overflow-wrap: anywhere; background: #fff;"></pre>
</div>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td colspan="2" style="padding: 0;">
<div class="panel panel-default" style="margin: 10px;">
<div class="panel-heading">
<a data-toggle="collapse" href="#live-score-last-result-<%= dom_id_suffix %>" aria-expanded="false" aria-controls="live-score-last-result-<%= dom_id_suffix %>" style="display: flex; justify-content: space-between; align-items: center; color: #333; text-decoration: none; background: transparent; outline: none;">
<strong>Last Match Result</strong>
<span class="text-muted" style="font-size: 0.9em;">Show/Hide</span>
</a>
</div>
<div id="live-score-last-result-<%= dom_id_suffix %>" class="panel-collapse collapse">
<div class="panel-body">
<div data-match-scoreboard-target="lastMatchResult">-</div>
</div>
</div>
</div>
</td>
</tr>
<% end %>
</table>
</div>
<div data-match-scoreboard-target="emptyState" style="display: none;"></div>
<div data-match-scoreboard-target="centerSection" style="display: none;"></div>
<div data-match-scoreboard-target="timerBanner" style="display: none;"></div>
<div data-match-scoreboard-target="timerBannerLabel" style="display: none;"></div>
<div data-match-scoreboard-target="timerBannerClock" style="display: none;"></div>
</div>

View File

@@ -0,0 +1,12 @@
<h1><%= @tournament.name %> Live Scores</h1>
<% if @mats.any? %>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(520px, 1fr)); gap: 20px; align-items: start;">
<% @mats.each do |mat| %>
<% match = mat.selected_scoreboard_match || mat.queue1_match %>
<%= render "tournaments/live_score_card", mat: mat, match: match, tournament: @tournament %>
<% end %>
</div>
<% else %>
<p>No mats have been created for this tournament.</p>
<% end %>

View File

@@ -129,13 +129,24 @@
<thead>
<tr>
<th>Name</th>
<th>Current Match</th>
<th><%= link_to " New Mat" , "/mats/new?tournament=#{@tournament.id}", :class=>"fas fa-plus" %></th>
</tr>
</thead>
<tbody>
<% @mats.each do |mat| %>
<% current_match = mat.queue1_match %>
<tr>
<td><%= link_to "Mat #{mat.name}", mat %></td>
<td>
<% if current_match %>
<%= link_to "Stat Match", stat_match_path(current_match), class: "btn btn-primary btn-sm" %>
<%= link_to "State Match", state_mat_path(mat), class: "btn btn-success btn-sm" %>
<%= link_to "Scoreboard", scoreboard_mat_path(mat, print: true), class: "btn btn-warning btn-sm", target: "_blank", rel: "noopener" %>
<% else %>
<%= link_to "Scoreboard", scoreboard_mat_path(mat, print: true), class: "btn btn-warning btn-sm", target: "_blank", rel: "noopener" %>
<% end %>
</td>
<% if can? :manage, @tournament %>
<td>
<%= link_to mat, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete Mat #{mat.name}?" }, class: "text-decoration-none" do %>