diff --git a/app.py b/app.py index f35b3c2..eac304a 100644 --- a/app.py +++ b/app.py @@ -29,7 +29,7 @@ from ui_builder import install_ui_builder from khl_site.khl_data_center import APP as khl_site_app BASE_DIR = Path(__file__).resolve().parent -BUILD_VERSION = "2026.09.10.1" +BUILD_VERSION = "2026.09.10.2" # compatibility: BUILD_VERSION = "2026.08.26.1" # compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.24.15" diff --git a/hockey_data/mapping_context.py b/hockey_data/mapping_context.py index 18f8c38..ae5f57b 100644 --- a/hockey_data/mapping_context.py +++ b/hockey_data/mapping_context.py @@ -886,7 +886,7 @@ class MappingDataService: ) result: list[dict[str, Any]] = [] - # BUILD129: operator-managed team colours. Enabled is deliberately a + # BUILD130: operator-managed team colours. Enabled is deliberately a # separate value from the HEX string so a colour may remain configured # while being disabled for the current graphics logic. for side, team, side_label in (("home", home_team, "Хозяева"), ("away", away_team, "Гости")): @@ -894,6 +894,7 @@ class MappingDataService: (1, "color_hex", "color_enabled"), (2, "color_2_hex", "color_2_enabled"), (3, "color_3_hex", "color_3_enabled"), + (4, "color_4_hex", "color_4_enabled"), ) for index, color_field, enabled_field in color_specs: raw_color = str(getattr(team, color_field, "") or "") if team is not None else "" diff --git a/hockey_data/models.py b/hockey_data/models.py index 805cfdc..7946d7f 100644 --- a/hockey_data/models.py +++ b/hockey_data/models.py @@ -254,7 +254,7 @@ class Team(Base): short_name_en: Mapped[str] = mapped_column(String(255), nullable=False, default="") city_ru: Mapped[str] = mapped_column(String(255), nullable=False, default="") city_en: Mapped[str] = mapped_column(String(255), nullable=False, default="") - # BUILD129: three operator-editable team colours. The legacy ``color_hex`` + # BUILD130: four operator-editable team colours. The legacy ``color_hex`` # column remains colour #1 for backwards compatibility with existing SQL. color_hex: Mapped[str] = mapped_column(String(9), nullable=False, default="") color_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) @@ -262,6 +262,8 @@ class Team(Base): color_2_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) color_3_hex: Mapped[str] = mapped_column(String(9), nullable=False, default="") color_3_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + color_4_hex: Mapped[str] = mapped_column(String(9), nullable=False, default="") + color_4_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) logo_url: Mapped[str] = mapped_column(Text, nullable=False, default="") raw_payload: Mapped[str] = mapped_column(Text, nullable=False, default="") synced_at: Mapped[datetime] = mapped_column( diff --git a/hockey_data/router.py b/hockey_data/router.py index 804eb39..7a6e618 100644 --- a/hockey_data/router.py +++ b/hockey_data/router.py @@ -171,6 +171,8 @@ class TeamDirectoryPayload(BaseModel): color_2_enabled: bool = False color_3_hex: str = Field(default="", max_length=9) color_3_enabled: bool = False + color_4_hex: str = Field(default="", max_length=9) + color_4_enabled: bool = False logo_url: str = Field(default="", max_length=2000) active: bool = True diff --git a/hockey_data/service.py b/hockey_data/service.py index fdafe6f..3de7914 100644 --- a/hockey_data/service.py +++ b/hockey_data/service.py @@ -614,10 +614,13 @@ class HockeyDataService: "color_2_enabled": bool(team.color_2_enabled), "color_3_hex": team.color_3_hex, "color_3_enabled": bool(team.color_3_enabled), + "color_4_hex": team.color_4_hex, + "color_4_enabled": bool(team.color_4_enabled), "colors": [ {"index": 1, "hex": team.color_hex, "enabled": bool(team.color_enabled)}, {"index": 2, "hex": team.color_2_hex, "enabled": bool(team.color_2_enabled)}, {"index": 3, "hex": team.color_3_hex, "enabled": bool(team.color_3_enabled)}, + {"index": 4, "hex": team.color_4_hex, "enabled": bool(team.color_4_enabled)}, ], "logo_url": team.logo_url, "source": membership.source, @@ -770,6 +773,7 @@ class HockeyDataService: ("color_hex", "color_enabled"), ("color_2_hex", "color_2_enabled"), ("color_3_hex", "color_3_enabled"), + ("color_4_hex", "color_4_enabled"), ) for color_field, enabled_field in color_fields: if color_field in payload and payload[color_field] is not None: diff --git a/hockey_data/static/admin-directories.css b/hockey_data/static/admin-directories.css index 93b3dfd..1cc2886 100644 --- a/hockey_data/static/admin-directories.css +++ b/hockey_data/static/admin-directories.css @@ -1613,7 +1613,7 @@ .hockey-team-color-control { display: grid; - grid-template-columns: 42px minmax(0, 1fr) 34px; + grid-template-columns: 42px minmax(0, 1fr) minmax(0, 1fr) 34px; gap: 7px; align-items: center; } @@ -1657,12 +1657,17 @@ cursor: pointer; } -.hockey-team-color-control input[data-team-color-text] { +.hockey-team-color-value{display:grid!important;gap:3px!important;margin:0!important;min-width:0} +.hockey-team-color-value>small{font-size:7px!important;font-weight:900;letter-spacing:.5px;color:#71869a!important} +.hockey-team-color-value>input{width:100%;min-width:0} +.hockey-team-color-control input[data-team-color-text], +.hockey-team-color-control input[data-team-color-rgb] { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; } +.hockey-team-color-control input[data-team-color-rgb]{text-transform:none;letter-spacing:0} .hockey-team-color-clear { color: #879bb1; @@ -1746,11 +1751,11 @@ .hockey-map-sql-table-ref code{grid-column:1/-1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:8px Consolas,monospace;color:#738da6} @media(max-width:1050px){.hockey-map-sql-table-list{grid-template-columns:1fr}} -/* BUILD129 — three optional team colours with explicit enable flags */ +/* BUILD130 — four optional team colours with explicit HEX/RGB entry */ .hockey-team-color-field-head{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:3px} .hockey-team-color-field-head>span{color:#c8d7e6;font-size:10px;font-weight:900} .hockey-team-color-field-head>small{max-width:520px;text-align:right;line-height:1.35} -.hockey-team-colors-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px} +.hockey-team-colors-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px} .hockey-team-color-item{min-width:0;padding:8px;border:1px solid #2b4057;border-radius:10px;background:#0b1725;opacity:.72;transition:border-color .15s ease,opacity .15s ease,background .15s ease} .hockey-team-color-item.is-enabled{border-color:#347662;background:#0d201f;opacity:1} .hockey-team-color-enabled{display:grid!important;grid-template-columns:auto minmax(0,1fr) auto!important;align-items:center!important;gap:6px!important;margin:0 0 6px!important} @@ -1767,3 +1772,4 @@ .hockey-team-color-chip.is-enabled>b{background:#183a32;color:#71e2c7} .hockey-team-color-chip.is-enabled>code{color:#bed2e2} @media(max-width:1180px){.hockey-team-colors-grid{grid-template-columns:1fr}.hockey-team-color-field-head{align-items:flex-start;flex-direction:column}.hockey-team-color-field-head>small{text-align:left}} +@media(max-width:720px){.hockey-team-color-control{grid-template-columns:42px minmax(0,1fr) 34px}.hockey-team-color-value:nth-of-type(2){grid-column:2/3}} diff --git a/hockey_data/static/admin-directories.js b/hockey_data/static/admin-directories.js index 9e7d559..34da989 100644 --- a/hockey_data/static/admin-directories.js +++ b/hockey_data/static/admin-directories.js @@ -80,6 +80,23 @@ return color || "#FFFFFF"; }; + const teamColorHexToRgb = (value) => { + const color = normalizeTeamColorHex(value); + if (!color) return ""; + return [1, 3, 5].map((offset) => parseInt(color.slice(offset, offset + 2), 16)).join(", "); + }; + + const teamColorRgbToHex = (value) => { + let raw = String(value ?? "").trim(); + if (!raw) return ""; + raw = raw.replace(/^rgb\s*\(/i, "").replace(/\)$/, "").trim(); + const parts = raw.includes(",") ? raw.split(",") : raw.split(/\s+/); + if (parts.length !== 3) return ""; + const rgb = parts.map((part) => Number(String(part).trim())); + if (rgb.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return ""; + return `#${rgb.map((part) => part.toString(16).padStart(2, "0")).join("").toUpperCase()}`; + }; + const teamColorFieldName = (index) => index === 1 ? "color_hex" : `color_${index}_hex`; const teamColorEnabledName = (index) => index === 1 ? "color_enabled" : `color_${index}_enabled`; @@ -102,14 +119,15 @@ - + + `; }; - const teamColorsCellMarkup = (item) => [1, 2, 3].map((index) => { + const teamColorsCellMarkup = (item) => [1, 2, 3, 4].map((index) => { const field = teamColorFieldName(index); const enabledField = teamColorEnabledName(index); const value = normalizeTeamColorHex(item?.[field]); @@ -420,12 +438,13 @@