удаления поправлены, сортировка элементов в титре поправлено, обоюдное удаление поправлено
This commit is contained in:
@@ -68,14 +68,14 @@ DEFAULT_CONTEXT_VARIABLES: tuple[dict[str, Any], ...] = (
|
|||||||
{"key": "selected_penalty_team_penalty", "label": "Выбранное удаление командное (1/0)", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection", "value_type": "text"},
|
{"key": "selected_penalty_team_penalty", "label": "Выбранное удаление командное (1/0)", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection", "value_type": "text"},
|
||||||
{"key": "selected_penalty_team_id", "label": "Команда выбранного удаления", "category": "Выделенные", "entity_type": "team", "scope": "match", "source_type": "selection"},
|
{"key": "selected_penalty_team_id", "label": "Команда выбранного удаления", "category": "Выделенные", "entity_type": "team", "scope": "match", "source_type": "selection"},
|
||||||
|
|
||||||
{"key": "selected_home_penalty_id", "label": "Удаление левой команды — ID удаления", "category": "Удаления — левая команда", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
{"key": "selected_home_penalty_id", "label": "Удаление левой команды — ID удаления", "category": "Удаления — левая команда", "entity_type": "penalty", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_home_penalty_player_id", "label": "Удаление левой команды — игрок external ID", "category": "Удаления — левая команда", "entity_type": "player", "scope": "match", "source_type": "selection"},
|
{"key": "selected_home_penalty_player_id", "label": "Удаление левой команды — игрок external ID", "category": "Удаления — левая команда", "entity_type": "player", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_home_penalty_player_db_id", "label": "Удаление левой команды — игрок DB ID", "category": "Удаления — левая команда", "entity_type": "player", "scope": "match", "source_type": "selection"},
|
{"key": "selected_home_penalty_player_db_id", "label": "Удаление левой команды — игрок DB ID", "category": "Удаления — левая команда", "entity_type": "player", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_home_penalty_team_penalty", "label": "Удаление левой команды — командное (1/0)", "category": "Удаления — левая команда", "entity_type": "penalty", "scope": "match", "source_type": "selection", "value_type": "text"},
|
{"key": "selected_home_penalty_team_penalty", "label": "Удаление левой команды — командное (1/0)", "category": "Удаления — левая команда", "entity_type": "penalty", "scope": "match_shared", "source_type": "selection", "value_type": "text"},
|
||||||
{"key": "selected_away_penalty_id", "label": "Удаление правой команды — ID удаления", "category": "Удаления — правая команда", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
{"key": "selected_away_penalty_id", "label": "Удаление правой команды — ID удаления", "category": "Удаления — правая команда", "entity_type": "penalty", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_away_penalty_player_id", "label": "Удаление правой команды — игрок external ID", "category": "Удаления — правая команда", "entity_type": "player", "scope": "match", "source_type": "selection"},
|
{"key": "selected_away_penalty_player_id", "label": "Удаление правой команды — игрок external ID", "category": "Удаления — правая команда", "entity_type": "player", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_away_penalty_player_db_id", "label": "Удаление правой команды — игрок DB ID", "category": "Удаления — правая команда", "entity_type": "player", "scope": "match", "source_type": "selection"},
|
{"key": "selected_away_penalty_player_db_id", "label": "Удаление правой команды — игрок DB ID", "category": "Удаления — правая команда", "entity_type": "player", "scope": "match_shared", "source_type": "selection"},
|
||||||
{"key": "selected_away_penalty_team_penalty", "label": "Удаление правой команды — командное (1/0)", "category": "Удаления — правая команда", "entity_type": "penalty", "scope": "match", "source_type": "selection", "value_type": "text"},
|
{"key": "selected_away_penalty_team_penalty", "label": "Удаление правой команды — командное (1/0)", "category": "Удаления — правая команда", "entity_type": "penalty", "scope": "match_shared", "source_type": "selection", "value_type": "text"},
|
||||||
{"key": "selected_penalty_infraction_id", "label": "Нарушение выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
{"key": "selected_penalty_infraction_id", "label": "Нарушение выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
||||||
{"key": "selected_penalty_preset_id", "label": "Длительность выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
{"key": "selected_penalty_preset_id", "label": "Длительность выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
||||||
{"key": "selected_penalty_side", "label": "Сторона выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
{"key": "selected_penalty_side", "label": "Сторона выбранного удаления", "category": "Выделенные", "entity_type": "penalty", "scope": "match", "source_type": "selection"},
|
||||||
@@ -92,7 +92,22 @@ DEFAULT_CONTEXT_VARIABLES: tuple[dict[str, Any], ...] = (
|
|||||||
{"key": "compare_player2_id", "label": "Игрок сравнения 2", "category": "Сравнение", "entity_type": "player", "scope": "session", "source_type": "manual"},
|
{"key": "compare_player2_id", "label": "Игрок сравнения 2", "category": "Сравнение", "entity_type": "player", "scope": "session", "source_type": "manual"},
|
||||||
{"key": "compare_team1_id", "label": "Команда сравнения 1", "category": "Сравнение", "entity_type": "team", "scope": "session", "source_type": "manual"},
|
{"key": "compare_team1_id", "label": "Команда сравнения 1", "category": "Сравнение", "entity_type": "team", "scope": "session", "source_type": "manual"},
|
||||||
{"key": "compare_team2_id", "label": "Команда сравнения 2", "category": "Сравнение", "entity_type": "team", "scope": "session", "source_type": "manual"},
|
{"key": "compare_team2_id", "label": "Команда сравнения 2", "category": "Сравнение", "entity_type": "team", "scope": "session", "source_type": "manual"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# These values describe the live state of the match itself, not a private UI
|
||||||
|
# selection of one account. They must be visible to Mapping regardless of which
|
||||||
|
# operator/admin account opened the Mapping editor.
|
||||||
|
MATCH_SHARED_CONTEXT_KEYS: frozenset[str] = frozenset({
|
||||||
|
"selected_home_penalty_id",
|
||||||
|
"selected_home_penalty_player_id",
|
||||||
|
"selected_home_penalty_player_db_id",
|
||||||
|
"selected_home_penalty_team_penalty",
|
||||||
|
"selected_away_penalty_id",
|
||||||
|
"selected_away_penalty_player_id",
|
||||||
|
"selected_away_penalty_player_db_id",
|
||||||
|
"selected_away_penalty_team_penalty",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_SQL_SOURCES: tuple[dict[str, Any], ...] = (
|
DEFAULT_SQL_SOURCES: tuple[dict[str, Any], ...] = (
|
||||||
@@ -211,7 +226,17 @@ class MappingDataService:
|
|||||||
def ensure_defaults(self) -> None:
|
def ensure_defaults(self) -> None:
|
||||||
now = _utcnow()
|
now = _utcnow()
|
||||||
with self.database.session() as session:
|
with self.database.session() as session:
|
||||||
existing_vars = {row.key for row in session.scalars(select(MappingContextVariable))}
|
existing_var_rows = {row.key: row for row in session.scalars(select(MappingContextVariable))}
|
||||||
|
existing_vars = set(existing_var_rows)
|
||||||
|
# Build78 initially stored automatic HOME/AWAY penalty identifiers as
|
||||||
|
# account-scoped match values. Migrate existing databases in-place so
|
||||||
|
# an operator can set a penalty and an admin can immediately see it in Mapping.
|
||||||
|
for key in MATCH_SHARED_CONTEXT_KEYS:
|
||||||
|
row = existing_var_rows.get(key)
|
||||||
|
if row is not None and row.scope != "match_shared":
|
||||||
|
row.scope = "match_shared"
|
||||||
|
row.updated_by = "system"
|
||||||
|
row.updated_at = now
|
||||||
for index, item in enumerate(DEFAULT_CONTEXT_VARIABLES):
|
for index, item in enumerate(DEFAULT_CONTEXT_VARIABLES):
|
||||||
if item["key"] in existing_vars:
|
if item["key"] in existing_vars:
|
||||||
continue
|
continue
|
||||||
@@ -505,6 +530,7 @@ class MappingDataService:
|
|||||||
if scope == "global": return "global"
|
if scope == "global": return "global"
|
||||||
if scope == "account": return f"account:{user.id}"
|
if scope == "account": return f"account:{user.id}"
|
||||||
if scope in {"session", "temporary"}: return f"session:{token or user.id}"
|
if scope in {"session", "temporary"}: return f"session:{token or user.id}"
|
||||||
|
if scope in {"match_shared", "shared_match"}: return f"match:{game_id or '-'}"
|
||||||
return f"match:{game_id or '-'}:account:{user.id}"
|
return f"match:{game_id or '-'}:account:{user.id}"
|
||||||
|
|
||||||
def resolve_context(self, user: HockeyUser, supplied: dict[str, Any] | None = None) -> tuple[dict[str, str], list[dict[str, Any]]]:
|
def resolve_context(self, user: HockeyUser, supplied: dict[str, Any] | None = None) -> tuple[dict[str, str], list[dict[str, Any]]]:
|
||||||
|
|||||||
@@ -5537,8 +5537,13 @@ class HockeyDataService:
|
|||||||
elif state_key not in {"5x5", ""}:
|
elif state_key not in {"5x5", ""}:
|
||||||
state_label = state_key.replace("x", " on " if language_key == "en" else " на ")
|
state_label = state_key.replace("x", " on " if language_key == "en" else " на ")
|
||||||
|
|
||||||
home_label = state_label if advantage_side == "home" else ""
|
coincidental_penalties = bool(
|
||||||
away_label = state_label if advantage_side == "away" else ""
|
home_penalties > 0
|
||||||
|
and away_penalties > 0
|
||||||
|
and home_skaters == away_skaters
|
||||||
|
)
|
||||||
|
home_label = state_label if advantage_side == "home" or coincidental_penalties else ""
|
||||||
|
away_label = state_label if advantage_side == "away" or coincidental_penalties else ""
|
||||||
phase_labels = {
|
phase_labels = {
|
||||||
"regulation": ("Основное время", "Regulation"),
|
"regulation": ("Основное время", "Regulation"),
|
||||||
"regular_overtime": ("Овертайм регулярки", "Regular OT"),
|
"regular_overtime": ("Овертайм регулярки", "Regular OT"),
|
||||||
|
|||||||
@@ -1338,10 +1338,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mappingInventoryInputs(profile) {
|
function mappingInventoryInputs(profile) {
|
||||||
// Preserve the exact Input sequence received from the vMix API via Agent.
|
// Mapping picker is operator-facing: show Inputs in vMix numeric order so a
|
||||||
// The array order is presentation-only: stable Mapping identity still uses
|
// title can be found by its familiar # quickly. Stable links still use key/title.
|
||||||
// key -> unique title -> number, so changing Input positions does not break links.
|
const inputs = Array.isArray(profile?.inventory?.inputs) ? [...profile.inventory.inputs] : [];
|
||||||
return Array.isArray(profile?.inventory?.inputs) ? [...profile.inventory.inputs] : [];
|
return inputs.sort((left, right) => {
|
||||||
|
const leftNumber = Number(left?.number);
|
||||||
|
const rightNumber = Number(right?.number);
|
||||||
|
const leftRank = Number.isFinite(leftNumber) ? leftNumber : Number.MAX_SAFE_INTEGER;
|
||||||
|
const rightRank = Number.isFinite(rightNumber) ? rightNumber : Number.MAX_SAFE_INTEGER;
|
||||||
|
return leftRank - rightRank
|
||||||
|
|| String(left?.title || "").localeCompare(String(right?.title || ""), "ru", { numeric: true, sensitivity: "base" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mappingVmixFieldOrder(left, right) {
|
||||||
|
// Natural sort keeps Player2.Text before Player10.Text and makes long GT
|
||||||
|
// templates much easier to scan. Type suffix stays part of the comparison.
|
||||||
|
return String(left?.name || "").localeCompare(String(right?.name || ""), "ru", {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mappingInputByKey(profile, key, title = "") {
|
function mappingInputByKey(profile, key, title = "") {
|
||||||
@@ -1939,7 +1955,7 @@
|
|||||||
const visibleInputs = filteredInputs.some((item) => mappingInputValue(item) === mappingInputValue(currentInput))
|
const visibleInputs = filteredInputs.some((item) => mappingInputValue(item) === mappingInputValue(currentInput))
|
||||||
? filteredInputs
|
? filteredInputs
|
||||||
: [currentInput, ...filteredInputs];
|
: [currentInput, ...filteredInputs];
|
||||||
const allFields = currentInput.fields || [];
|
const allFields = [...(currentInput.fields || [])].sort(mappingVmixFieldOrder);
|
||||||
const filter = ["text", "image", "color"].includes(state.mappingFieldFilter) ? state.mappingFieldFilter : "text";
|
const filter = ["text", "image", "color"].includes(state.mappingFieldFilter) ? state.mappingFieldFilter : "text";
|
||||||
const counts = {
|
const counts = {
|
||||||
text: allFields.filter((field) => mappingFieldFilterMatch(field, "text")).length,
|
text: allFields.filter((field) => mappingFieldFilterMatch(field, "text")).length,
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ def test_preview_selection_is_kept_per_side_and_visible():
|
|||||||
assert 'selectedPreviewEventIds: { home: "", away: "" }' in JS
|
assert 'selectedPreviewEventIds: { home: "", away: "" }' in JS
|
||||||
assert 'board.selectedPreviewEventIds[side] = event.id' in JS
|
assert 'board.selectedPreviewEventIds[side] = event.id' in JS
|
||||||
assert 'hockeyPenaltyIsPreviewSelected(board, event)' in JS
|
assert 'hockeyPenaltyIsPreviewSelected(board, event)' in JS
|
||||||
assert 'Player DB ID' in JS
|
assert 'selected_home_penalty_player_db_id' in JS
|
||||||
assert 'Командное</b>${event.teamPenalty ? "1" : "0"}' in JS
|
assert 'selected_away_penalty_player_db_id' in JS
|
||||||
|
assert 'selected_home_penalty_team_penalty' in JS
|
||||||
|
assert 'selected_away_penalty_team_penalty' in JS
|
||||||
assert '.hpd-team-card-identifiers' in CSS
|
assert '.hpd-team-card-identifiers' in CSS
|
||||||
|
|||||||
@@ -12,13 +12,10 @@ def test_mapping_data_group_count_is_right_aligned_before_fixed_arrow():
|
|||||||
assert ".hockey-map-data-group summary>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}" in CSS
|
assert ".hockey-map-data-group summary>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}" in CSS
|
||||||
|
|
||||||
|
|
||||||
def test_mapping_vmix_input_selector_preserves_inventory_api_order():
|
def test_mapping_inventory_raw_order_remains_available_on_server():
|
||||||
start = JS.index("function mappingInventoryInputs(profile)")
|
# Build81 intentionally sorts the operator-facing picker by vMix number.
|
||||||
end = JS.index("function mappingInputByKey", start)
|
# The Agent/server inventory still preserves the raw API array for identity/rebind work.
|
||||||
block = JS[start:end]
|
assert "function mappingInventoryInputs(profile)" in JS
|
||||||
assert "return Array.isArray(profile?.inventory?.inputs) ? [...profile.inventory.inputs] : [];" in block
|
|
||||||
assert ".sort(" not in block
|
|
||||||
assert "numberValue" not in block
|
|
||||||
|
|
||||||
|
|
||||||
def test_server_preserves_agent_inventory_array_order():
|
def test_server_preserves_agent_inventory_array_order():
|
||||||
|
|||||||
66
tests/test_build81_penalty_mapping_sort_and_mutual_labels.py
Normal file
66
tests/test_build81_penalty_mapping_sort_and_mutual_labels.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from hockey_data.auth_bridge import HockeyUser
|
||||||
|
from hockey_data.mapping_context import MappingDataService
|
||||||
|
from hockey_data.models import Game
|
||||||
|
from hockey_data.service import HockeyDataService
|
||||||
|
from tests.support import LocalTestDatabase
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
APP = (ROOT / "ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||||
|
ADMIN = (ROOT / "hockey_data/static/admin-directories.js").read_text(encoding="utf-8")
|
||||||
|
MAPPING = (ROOT / "hockey_data/mapping_context.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_penalty_card_no_longer_renders_technical_id_block():
|
||||||
|
start = APP.index("function hockeyPenaltyCard")
|
||||||
|
end = APP.index("function hockeyPenaltyContext", start) if "function hockeyPenaltyContext" in APP[start:] else start + 12000
|
||||||
|
snippet = APP[start:end]
|
||||||
|
assert "hpd-team-card-identifiers" not in snippet
|
||||||
|
assert "Player external ID" not in snippet
|
||||||
|
assert "Player DB ID" not in snippet
|
||||||
|
|
||||||
|
|
||||||
|
def test_side_penalty_context_is_shared_between_accounts(tmp_path: Path):
|
||||||
|
database = LocalTestDatabase(tmp_path / "shared-penalty-context.sqlite3")
|
||||||
|
database.create_all()
|
||||||
|
with database.session() as session:
|
||||||
|
session.add(Game(external_id="G1", tournament_external_id="T1", home_team_external_id="H", away_team_external_id="A"))
|
||||||
|
service = MappingDataService(database) # type: ignore[arg-type]
|
||||||
|
operator = HockeyUser(id="10", login="operator", display_name="Operator", is_admin=False, role="operator")
|
||||||
|
admin = HockeyUser(id="99", login="admin", display_name="Admin", is_admin=True, role="admin")
|
||||||
|
|
||||||
|
service.set_context_value("selected_home_penalty_id", "PEN-17", operator, {"game_id": "G1", "session_token": "OP"})
|
||||||
|
service.set_context_value("selected_home_penalty_player_db_id", "321", operator, {"game_id": "G1", "session_token": "OP"})
|
||||||
|
context, variables = service.resolve_context(admin, {"game_id": "G1", "session_token": "ADM"})
|
||||||
|
assert context["selected_home_penalty_id"] == "PEN-17"
|
||||||
|
assert context["selected_home_penalty_player_db_id"] == "321"
|
||||||
|
variable = next(item for item in variables if item["key"] == "selected_home_penalty_id")
|
||||||
|
assert variable["scope"] == "match_shared"
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapping_input_picker_sorts_by_number_and_fields_naturally():
|
||||||
|
start = ADMIN.index("function mappingInventoryInputs(profile)")
|
||||||
|
end = ADMIN.index("function mappingInputByKey", start)
|
||||||
|
block = ADMIN[start:end]
|
||||||
|
assert "leftRank - rightRank" in block
|
||||||
|
assert "Number(left?.number)" in block
|
||||||
|
assert "function mappingVmixFieldOrder" in block
|
||||||
|
assert "numeric: true" in block
|
||||||
|
assert "const allFields = [...(currentInput.fields || [])].sort(mappingVmixFieldOrder);" in ADMIN
|
||||||
|
|
||||||
|
|
||||||
|
def test_coincidental_penalties_fill_both_home_and_away_labels():
|
||||||
|
settings = {
|
||||||
|
"strength_regulation_skaters": 5,
|
||||||
|
"strength_min_skaters": 3,
|
||||||
|
"strength_state_labels": {"regulation": {"4x4": {"ru": "4 на 4", "en": "4 on 4"}}},
|
||||||
|
}
|
||||||
|
def penalty(side: str):
|
||||||
|
return {"side": side, "infraction": {"code": "TEST"}, "preset": "2", "durationMs": 120000, "remainingMs": 90000, "finished": False}
|
||||||
|
timer_state = {"penalty_board": {"penalties": [penalty("home"), penalty("away")]}}
|
||||||
|
value = HockeyDataService._strength_payload(settings, stage="regular", current_period="1", timer_state=timer_state, language="ru")
|
||||||
|
assert value["advantage_side"] == ""
|
||||||
|
assert value["state_key"] == "4x4"
|
||||||
|
assert value["home_label"] == "4 на 4"
|
||||||
|
assert value["away_label"] == "4 на 4"
|
||||||
@@ -17,13 +17,15 @@ def test_mapping_load_is_resilient_per_endpoint():
|
|||||||
assert 'Часть Mapping API недоступна' in JS
|
assert 'Часть Mapping API недоступна' in JS
|
||||||
|
|
||||||
|
|
||||||
def test_visual_mapping_has_sql_table_cell_picker_and_searchable_api_order_inputs():
|
def test_visual_mapping_has_sql_table_cell_picker_and_searchable_number_sorted_inputs():
|
||||||
assert 'data-map-table-source' in JS
|
assert 'data-map-table-source' in JS
|
||||||
assert 'data-map-table-row-search' in JS
|
assert 'data-map-table-row-search' in JS
|
||||||
assert '.row.${row.index}.' in JS
|
assert '.row.${row.index}.' in JS
|
||||||
assert 'data-map-input-search' in JS
|
assert 'data-map-input-search' in JS
|
||||||
assert 'mappingFilteredInputs' in JS
|
assert 'mappingFilteredInputs' in JS
|
||||||
assert 'return Array.isArray(profile?.inventory?.inputs) ? [...profile.inventory.inputs] : [];' in JS
|
assert 'const inputs = Array.isArray(profile?.inventory?.inputs) ? [...profile.inventory.inputs] : [];' in JS
|
||||||
|
assert 'Number(left?.number)' in JS
|
||||||
|
assert 'leftRank - rightRank' in JS
|
||||||
|
|
||||||
|
|
||||||
def test_visual_mapping_has_explicit_sql_cell_dropdowns_and_apply_input():
|
def test_visual_mapping_has_explicit_sql_cell_dropdowns_and_apply_input():
|
||||||
|
|||||||
@@ -7262,7 +7262,6 @@ async function hockeySelectPenaltyForPreview(component, event) {
|
|||||||
: side === "away"
|
: side === "away"
|
||||||
? String(getByPath(state.data, "hockey.selected_game.away.id") || getByPath(state.data, "hockey.selected_game.away.external_id") || "")
|
? String(getByPath(state.data, "hockey.selected_game.away.id") || getByPath(state.data, "hockey.selected_game.away.external_id") || "")
|
||||||
: "";
|
: "";
|
||||||
const playerIds = hockeyPenaltyPlayerIdentifiers(component, event);
|
|
||||||
const detail = {
|
const detail = {
|
||||||
item_id: String(event.external_id || event.id || ""),
|
item_id: String(event.external_id || event.id || ""),
|
||||||
value: String(event.external_id || event.id || ""),
|
value: String(event.external_id || event.id || ""),
|
||||||
@@ -7411,15 +7410,6 @@ function hockeyPenaltyCard(component, event, runtime) {
|
|||||||
<span>Командное удаление — без игрока</span>
|
<span>Командное удаление — без игрока</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
${previewSelected ? `
|
|
||||||
<div class="hpd-team-card-identifiers" title="Идентификаторы выбранного удаления для Mapping">
|
|
||||||
<span><b>ID удаления</b>${escapeHtml(String(event.external_id || event.id || "—"))}</span>
|
|
||||||
<span><b>Player external ID</b>${escapeHtml(playerIds.externalId || "—")}</span>
|
|
||||||
<span><b>Player DB ID</b>${escapeHtml(playerIds.dbId || "—")}</span>
|
|
||||||
<span><b>Командное</b>${event.teamPenalty ? "1" : "0"}</span>
|
|
||||||
</div>
|
|
||||||
` : ""}
|
|
||||||
|
|
||||||
<div class="hpd-team-card-footer">
|
<div class="hpd-team-card-footer">
|
||||||
<span class="hpd-team-card-status">${
|
<span class="hpd-team-card-status">${
|
||||||
previewSelected
|
previewSelected
|
||||||
|
|||||||
Reference in New Issue
Block a user