Fix avanzamento quarto basket e regia senza cronometro.

Normalizza il periodo nel backend quando l'app invia "Q1" via cable, così advance_period aggiorna regia e overlay; rimuove i controlli cronometro dalla regia e rende più tollerante il decode iOS al resume sessione.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Emiliano Frascaro
2026-06-20 16:15:44 +02:00
parent c92099a41b
commit 793cfaaaf1
18 changed files with 861 additions and 452 deletions

View File

@@ -34,6 +34,7 @@ module Public
def score
cmd = params[:cmd].presence || params[:score_action].presence
result = Scoring::ApplyAction.new(session: @session, action: cmd).call
@session.reload
render json: status_payload.merge(
set_won: result.set_won,
match_won: result.match_won,

View File

@@ -55,7 +55,7 @@ class ScoreState < ApplicationRecord
end
def current_period
(data || {})["period"].to_i.positive? ? (data || {})["period"].to_i : 1
Scoring::PeriodData.number_from((data || {})["period"]) || 1
end
def current_period_label

View File

@@ -28,6 +28,7 @@ module Scoring
def ensure_period_data(score)
data = period_data(score)
default_data.each { |k, v| data[k] = v unless data.key?(k) }
PeriodData.normalize_hash!(data)
data
end
@@ -43,7 +44,7 @@ module Scoring
end
def period_label(data)
period = data["period"].to_i
period = PeriodData.number_from(data["period"]) || 1
return "OT" if data["overtime"]
board = @match.effective_board_type
@@ -63,15 +64,17 @@ module Scoring
rules = period_rules
max_periods = rules.periods
current = PeriodData.number_from(data["period"]) || 1
if data["overtime"]
data["period"] = data["period"].to_i + 1
data["period"] = current + 1
data["clock_secs"] = rules.overtime_duration_secs
elsif data["period"].to_i >= max_periods
elsif current >= max_periods
data["overtime"] = true
data["period"] = max_periods + 1
data["clock_secs"] = rules.overtime_duration_secs
else
data["period"] = data["period"].to_i + 1
data["period"] = current + 1
data["clock_secs"] = rules.period_duration_secs
end
data["clock_running"] = false
@@ -109,7 +112,14 @@ module Scoring
score = ensure_score(session)
data = ensure_period_data(score)
%w[period clock_secs clock_running home_score away_score overtime].each do |key|
data[key] = payload[key] if payload.key?(key)
next unless payload.key?(key)
if key == "period"
period = PeriodData.number_from(payload[key])
data["period"] = period if period&.positive?
else
data[key] = payload[key]
end
end
save_period_score!(score, data)
score

View File

@@ -0,0 +1,32 @@
module Scoring
# Normalizza `score_states.data["period"]` (intero o etichetta "Q2" / "2° tempo").
module PeriodData
module_function
def number_from(raw)
case raw
when Integer, Float
value = raw.to_i
value.positive? ? value : nil
when String
text = raw.strip
return nil if text.empty?
if (match = text.match(/\AQ(\d+)\z/i))
match[1].to_i
elsif (match = text.match(/\A(\d+)\s*°/))
match[1].to_i
elsif (value = text.to_i).positive?
value
end
end
end
def normalize_hash!(data)
number = number_from(data["period"])
data["period"] = number if number&.positive?
data["period"] = 1 unless data["period"].to_i.positive?
data
end
end
end

View File

@@ -2,7 +2,6 @@
<p class="regia-sets" id="sets-line"><%= live_score_period_label(@match, @score) %></p>
<p class="regia-score-line" id="score-line"><%= live_score_points_label(@match, @score) %></p>
<p class="regia-partials" id="partials-line" hidden></p>
<p class="regia-clock-line" id="clock-line">Cronometro: <%= format_clock_secs(data["clock_secs"]) %></p>
<div class="regia-team-row">
<div>
@@ -13,7 +12,7 @@
<button type="button" class="regia-btn regia-btn--point" data-action="home_point_3" style="width:100%;margin-top:4px">+3</button>
<button type="button" class="regia-btn regia-btn--minus" data-action="home_undo" style="width:100%;margin-top:4px"></button>
</div>
<div class="regia-center-hint" id="center-hint"><%= format_clock_secs(data["clock_secs"]) %></div>
<div class="regia-center-hint" id="center-hint"><%= live_score_period_label(@match, @score) %></div>
<div>
<div class="regia-team-name"><%= @match.opponent_name %></div>
<div class="regia-points" id="away-points"><%= data["away_score"] || @score.away_points %></div>
@@ -23,8 +22,4 @@
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:4px"></button>
</div>
</div>
<div class="regia-clock-actions">
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_toggle"><%= data["clock_running"] ? "Pausa" : "Avvia" %> cronometro</button>
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_reset">Reset</button>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period">Prossimo quarto</button>
</div>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period" style="margin-top:12px">Prossimo quarto</button>

View File

@@ -2,7 +2,6 @@
<p class="regia-sets" id="sets-line"><%= live_score_period_label(@match, @score) %></p>
<p class="regia-score-line" id="score-line"><%= live_score_points_label(@match, @score) %></p>
<p class="regia-partials" id="partials-line" hidden></p>
<p class="regia-clock-line" id="clock-line">Cronometro: <%= format_clock_secs(data["clock_secs"]) %></p>
<div class="regia-team-row">
<div>
@@ -11,7 +10,7 @@
<button type="button" class="regia-btn regia-btn--point" data-action="home_point" style="width:100%;margin-top:8px">+1</button>
<button type="button" class="regia-btn regia-btn--minus" data-action="home_undo" style="width:100%;margin-top:6px"></button>
</div>
<div class="regia-center-hint" id="center-hint"><%= format_clock_secs(data["clock_secs"]) %></div>
<div class="regia-center-hint" id="center-hint"><%= live_score_period_label(@match, @score) %></div>
<div>
<div class="regia-team-name"><%= @match.opponent_name %></div>
<div class="regia-points" id="away-points"><%= data["away_score"] || @score.away_points %></div>
@@ -19,8 +18,4 @@
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:6px"></button>
</div>
</div>
<div class="regia-clock-actions">
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_toggle"><%= data["clock_running"] ? "Pausa" : "Avvia" %> cronometro</button>
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_reset">Reset</button>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period">Prossimo tempo</button>
</div>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period" style="margin-top:12px">Prossimo tempo</button>

View File

@@ -29,7 +29,7 @@
<header class="regia-header">
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
<p>Regia · <%= regia_header_subtitle(@match, @score) %></p>
<p id="regia-subtitle">Regia · <%= regia_header_subtitle(@match, @score) %></p>
<span id="regia-badge" class="regia-badge <%= @stream_closed ? 'regia-badge--ended' : (@session.paused? ? 'regia-badge--wait' : (@on_air ? 'regia-badge--live' : 'regia-badge--wait')) %>">
<%= @stream_closed ? "Terminata" : (@session.paused? ? "In pausa" : (@on_air ? "In onda" : "In attesa")) %>
</span>
@@ -93,4 +93,4 @@
</div>
</div>
<script src="/regia.js?v=7"></script>
<script src="/regia.js?v=8"></script>