${escapeHtml(home.name || "—")}${hockeyShootoutSlots(homeAttempts, allowed, language)}
@@ -14923,9 +14925,51 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
});
state.formValues[selectedKey] = null;
});
- node.querySelector("[data-shootout-round]")?.addEventListener("click", async () => {
- if (!runtime) return;
- await hockeyUpdateGameControl(gameId, `/shootout/round?language=${language}`, { method: "POST" });
+ node.dataset.shootoutGame = gameId;
+ const roundButton = node.querySelector("[data-shootout-round]");
+ const sameGame = () => {
+ const current = getByPath(state.data, props.gamePath || "hockey.selected_game") || {};
+ return String(current.external_id || current.id || "").trim() === gameId;
+ };
+ const refreshRoundAction = () => {
+ const current = state.hockeyGameControl[gameId]?.shootout || {};
+ const busy = state.hockeyShootoutRoundBusy.has(gameId);
+ const canControl = runtime && hockeyCanControl() && sameGame();
+ const extraRounds = Math.max(0, Number(current.extra_rounds || 0));
+ const seriesState = extraRounds > 0
+ ? `${language === "en" ? "Extra series" : "Дополнительная серия"} №${extraRounds}`
+ : language === "en" ? "Main series" : "Основная серия";
+ const status = busy ? (language === "en" ? "Adding one attempt for each team…" : "Добавляем по одному буллиту каждой команде…")
+ : !canControl ? (language === "en" ? "Next series is available to the match operator." : "Следующая серия доступна оператору матча.")
+ : !current.started ? (language === "en" ? "Start the main series first." : "Сначала начните основную серию.")
+ : !current.both_complete ? (language === "en" ? "Complete the current series for both teams." : "Завершите текущую серию обеих команд.")
+ : !current.tied ? (language === "en" ? "An extra series is available when the score is tied." : "Дополнительная серия доступна при равном счёте.")
+ : current.can_add_round === true ? (language === "en" ? "The score is tied. Add one attempt for each team." : "Счёт равный: можно добавить по одному буллиту каждой команде.")
+ : language === "en" ? "Wait for the shootout data to update." : "Дождитесь обновления данных буллитов.";
+ roundButton.disabled = busy || !canControl || current.can_add_round !== true;
+ roundButton.textContent = busy ? (language === "en" ? "Adding…" : "Добавляем…") : (language === "en" ? "Next series" : "Следующая серия");
+ roundButton.title = status;
+ roundButton.setAttribute("aria-busy", String(busy));
+ node.querySelector("[data-shootout-series-state]").textContent = seriesState;
+ node.querySelector("[data-shootout-round-status]").textContent = status;
+ };
+ node.refreshShootoutRoundAction = refreshRoundAction;
+ refreshRoundAction();
+ roundButton.addEventListener("click", async () => {
+ if (!runtime || !hockeyCanControl() || !sameGame() || state.hockeyShootoutRoundBusy.has(gameId)
+ || state.hockeyGameControl[gameId]?.shootout?.can_add_round !== true) return;
+ state.hockeyShootoutRoundBusy.add(gameId);
+ refreshRoundAction();
+ try {
+ await hockeyUpdateGameControl(gameId, `/shootout/round?language=${language}`, { method: "POST" });
+ } catch (error) {
+ console.error("Shootout next series failed", error);
+ } finally {
+ state.hockeyShootoutRoundBusy.delete(gameId);
+ document.querySelectorAll(".hockey-shootout-control").forEach((current) => {
+ if (current.dataset.shootoutGame === gameId) current.refreshShootoutRoundAction?.();
+ });
+ }
});
const applyShootoutSearch = (side, rawValue) => {
const query = normalizePlayerSearch(rawValue);
diff --git a/ui_builder/static/styles.css b/ui_builder/static/styles.css
index 010665f..218d12a 100644
--- a/ui_builder/static/styles.css
+++ b/ui_builder/static/styles.css
@@ -5687,7 +5687,7 @@ body.hockey-navigation-open .runtime-viewport > .runtime-sizer { margin-inline:
.hso-score { display: flex; align-items: center; gap: 12px; }
.hso-score span { max-width: 160px; overflow: hidden; color: #8ca0b6; font-size: 10px; font-weight: 850; text-overflow: ellipsis; white-space: nowrap; }
.hso-score strong { min-width: 92px; padding: 7px 12px; color: #fff; border: 1px solid #334b65; border-radius: 11px; background: #0e1d2d; font-size: 23px; font-weight: 950; text-align: center; font-variant-numeric: tabular-nums; }
-.hso-setup { min-height: 52px; display: flex; align-items: center; gap: 10px; padding: 8px 16px; border-bottom: 1px solid #1d3147; background: rgba(255,255,255,.018); }
+.hso-setup { min-height: 52px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px 16px; border-bottom: 1px solid #1d3147; background: rgba(255,255,255,.018); }
.hso-series-size { display: flex; align-items: center; gap: 5px; }
.hso-series-size>span { margin-right: 4px; color: #8296ad; font-size: 9px; font-weight: 850; }
.hso-series-size button,
@@ -5697,6 +5697,10 @@ body.hockey-navigation-open .runtime-viewport > .runtime-sizer { margin-inline:
.hso-series-size button.active { color: #07150f; border-color: #48dfbd; background: #48dfbd; }
.hso-start { color: #dbe8f5; }
.hso-next-series { color: #ffe2a3; border-color: rgba(240,189,101,.55); background: rgba(240,189,101,.09); }
+.hso-round-action { display: grid; gap: 3px; text-align: center; }
+.hso-round-action small { color: #8296ad; font-size: 8px; }
+.hso-round-state { flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px 12px; color: #8296ad; font-size: 9px; }
+.hso-round-state strong { color: #dce8f4; font-size: 10px; }
.hso-series-size button:disabled,
.hso-start:disabled,
.hso-next-series:disabled { opacity: .45; cursor: default; }