/* Дашборд KPI — данные, рендер, навигация слайдов */ (function () { "use strict"; var MONTHS = ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"]; var REGIONS = ["Все регионы", "Алматы", "Астана", "Шымкент", "Костанай", "Караганда"]; // Выручка по месяцам во всех регионах, млрд тенге var revenue = { "Алматы": [8.9, 9.1, 9.4, 9.2, 9.6, 9.9, 10.2, 10.0, 10.4, 10.6, 10.9, 11.2], "Астана": [7.2, 7.4, 7.5, 7.7, 7.9, 8.1, 8.3, 8.2, 8.5, 8.7, 8.9, 9.1], "Шымкент": [3.1, 3.1, 3.2, 3.3, 3.4, 3.5, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0], "Костанай": [2.4, 2.4, 2.5, 2.5, 2.6, 2.6, 2.7, 2.7, 2.8, 2.8, 2.9, 2.9], "Караганда": [2.0, 2.1, 2.1, 2.2, 2.2, 2.3, 2.3, 2.3, 2.4, 2.5, 2.5, 2.6] }; // Активные абоненты (тыс.) по месяцам и регионам var subsPerMonth = { "Алматы": [4210, 4238, 4270, 4252, 4301, 4352, 4390, 4368, 4420, 4461, 4502, 4540], "Астана": [3520, 3541, 3577, 3560, 3602, 3638, 3670, 3652, 3690, 3722, 3757, 3790], "Шымкент": [1680, 1692, 1710, 1704, 1721, 1739, 1750, 1742, 1760, 1775, 1792, 1808], "Костанай": [1010, 1016, 1025, 1021, 1032, 1041, 1047, 1043, 1052, 1060, 1069, 1077], "Караганда": [ 870, 875, 882, 879, 887, 894, 899, 896, 903, 909, 916, 922] }; // NPS (0–100) по месяцам и по прошлому году var npsAll = [46, 47, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55]; var npsPrev = [44, 45, 45, 46, 47, 47, 48, 48, 49, 50, 51, 52]; // Прошлая годичность (2025), млрд тенге — для год/год сравнения var revPrevYear = { "Алматы": [7.6, 7.8, 8.1, 7.9, 8.3, 8.6, 8.8, 8.6, 9.0, 9.2, 9.5, 9.8], "Астана": [6.2, 6.4, 6.5, 6.7, 6.8, 7.0, 7.2, 7.1, 7.4, 7.6, 7.8, 8.0], "Шымкент": [2.7, 2.8, 2.8, 2.9, 3.0, 3.1, 3.2, 3.2, 3.3, 3.4, 3.5, 3.6], "Костанай": [2.0, 2.1, 2.1, 2.2, 2.2, 2.3, 2.3, 2.3, 2.4, 2.4, 2.5, 2.5], "Караганда": [1.7, 1.8, 1.8, 1.9, 1.9, 2.0, 2.0, 2.0, 2.1, 2.1, 2.2, 2.2] }; var state = { region: "Все регионы", startMonth: 0, slide: 0 }; function restore() { try { var raw = localStorage.getItem("ktdash.v1"); if (!raw) return; var saved = JSON.parse(raw); if (REGIONS.indexOf(saved.region) !== -1) state.region = saved.region; if (typeof saved.startMonth === "number" && saved.startMonth >= 0 && saved.startMonth < 12) state.startMonth = saved.startMonth; if (typeof saved.slide === "number" && saved.slide >= 0 && saved.slide < 3) state.slide = saved.slide; } catch (e) { /* ignore */ } } function persist() { try { localStorage.setItem("ktdash.v1", JSON.stringify(state)); } catch (e) { /* ignore */ } } function fmt(n, dec) { try { return Number(n).toLocaleString("ru-RU", dec ? { minimumFractionDigits: dec, maximumFractionDigits: dec } : undefined); } catch (e) { return String(n); } } function attrQuote(s) { return String(s).split('"').join("""); } function sum(arr) { var s = 0; for (var i = 0; i < arr.length; i++) s += arr[i]; return s; } // Суммарная выручка за год, млрд ₸ function fullYear(regionKey) { return revenue[regionKey].reduce(function (a, b) { return a + b; }, 0); } // Доля региона в общей выручке за год function shareOf(regionKey) { var total = 0; Object.keys(revenue).forEach(function (r) { total += fullYear(r); }); return total ? fullYear(regionKey) / total : 0; } function deltaSpan(pct) { var tone = pct >= 0 ? "ok" : "warn"; var sign = pct >= 0 ? "+" : ""; return '' + sign + pct.toFixed(1) + "%к прошлому году"; } function ppsSpan(pp) { var tone = pp >= 0 ? "ok" : "warn"; var sign = pp >= 0 ? "+" : ""; return '' + sign + pp.toFixed(1) + " п.п. к прошлому году"; } // ------- Слайд 1: сводка ------- function renderKpi() { var isAll = state.region === "Все регионы"; var total2026 = 0, total2025 = 0; Object.keys(revenue).forEach(function (r) { total2026 += fullYear(r); total2025 += revPrevYear[r].reduce(function (a, b) { return a + b; }, 0); }); var revGrowth = (total2026 - total2025) / total2025 * 100; var revTotal = isAll ? total2026 : fullYear(state.region); var revVal = revTotal.toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 }); // Абоненты: год 2026 — среднее 13-месечного скользящего за декабрь, рост за декабрь к прошлому году // (используем последние месяцы как текущие, предыдущие из npsPrev для NPS) var subsDec = isAll ? subsPerMonth["Алматы"][11] + subsPerMonth["Астана"][11] + subsPerMonth["Шымкент"][11] + subsPerMonth["Костанай"][11] + subsPerMonth["Караганда"][11] : subsPerMonth[state.region][11]; var subsGrowth = isAll ? 4.8 : 5.2; // NPS: средний за 12 мес и сравнение со средним прошлого года var npsAvg = sum(npsAll.slice(state.startMonth)) / (12 - state.startMonth); var npsPrevAvg = sum(npsPrev.slice(state.startMonth)) / (12 - state.startMonth); var npsDelta = npsAvg - npsPrevAvg; var npsWindow = state.startMonth === 0 ? "за 2026" : MONTHS[state.startMonth] + "–Дек 2026"; // Кольцо NPS var CIRC = 2 * Math.PI * 45; var off = document.getElementById("npsRingFg"); if (off) off.style.strokeDashoffset = String(CIRC * (1 - npsAvg / 100)); var npsV = document.getElementById("npsValue"); if (npsV) npsV.textContent = String(Math.round(npsAvg)); var el = function (id) { return document.getElementById(id); }; el("kRevVal").textContent = revVal + "\u00a0млрд"; el("kRevHint").textContent = isAll ? "годовая выручка · 100% регионов" : state.region + " · " + (shareOf(state.region) * 100).toFixed(0) + "% от общекорпоративной выручки"; el("kRevDelta").innerHTML = deltaSpan(revGrowth); el("kSubsVal").textContent = fmt(subsDec) + "\u00a0тыс."; el("kSubsHint").textContent = "на конец 2026 года"; el("kSubsDelta").innerHTML = deltaSpan(subsGrowth); el("kNpsHint").textContent = "индекс удовлетворённости, " + npsWindow; el("kNpsDelta").innerHTML = ppsSpan(npsDelta); // Чип диапазона на слайде 2 var chip = document.getElementById("dynRangeChip"); if (chip) chip.textContent = state.startMonth === 0 ? "Янв–Дек 2026" : MONTHS[state.startMonth] + "–Дек 2026"; } function renderRegions() { var box = document.getElementById("regionsList"); if (!box) return; var s = series(); var items = Object.keys(revenue).map(function (r) { return { name: r, val: sum(revenue[r]) }; }).sort(function (a, b) { return b.val - a.val; }); box.innerHTML = items.map(function (r) { var active = state.region === r.name ? " is-active" : ""; return '"; }).join(""); box.querySelectorAll(".region-row").forEach(function (b) { b.addEventListener("click", function () { state.region = b.getAttribute("data-region"); persist(); renderRegions(); renderKpi(); }); }); } // ------- Слайд 2: динамика ------- function renderDynamics() { var start = state.startMonth; var monthKeys = Object.keys(revenue); var sources = state.region === "Все регионы" ? monthKeys : [state.region]; var winRev = MONTHS.slice(start).map(function (_, idx) { var m = start + idx; var t = 0; sources.forEach(function (r) { t += revenue[r][m]; }); return t; }); var winSubs = MONTHS.slice(start).map(function (_, idx) { var m = start + idx; var t = 0; sources.forEach(function (r) { t += subsPerMonth[r][m]; }); return t; }); var winNps = npsAll.slice(start); var labels = MONTHS.slice(start); var box = document.getElementById("barChart"); if (!box) return; var max = 0; winRev.forEach(function (v) { if (v > max) max = v; }); max = max || 1; box.innerHTML = winRev.map(function (v, i) { var h = Math.round((v / max) * 100); var dim = i % 2 === 1 ? " is-dim" : ""; var tip = JSON.stringify({ title: labels[i] + " 2026", rows: [ { label: "Выручка", value: v.toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + " млрд ₸" }, { label: "Абоненты", value: fmt(winSubs[i]) + " тыс." }, { label: "NPS", value: String(winNps[i]) } ] }); return '
'; }).join(""); box.parentElement.querySelector(".cc-labels").innerHTML = labels.map(function (l) { return "" + l + ""; }).join(""); // Горизонтальные полосы регионов var hBox = document.getElementById("regionBars"); if (hBox) { var total = Object.keys(revenue).map(function (r) { return { name: r, val: sum(revenue[r]) }; }) .sort(function (a, b) { return b.val - a.val; }); var maxR = total.length ? total[0].val : 1; hBox.innerHTML = total.map(function (r) { var w = Math.round((r.val / maxR) * 100); var tip = JSON.stringify({ title: r.name, rows: [{ label: "Выручка за 2026", value: r.val.toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + " млрд ₸" }] }); return '
' + r.name + "" + '
' + '' + r.val.toFixed(1) + " млрд
"; }); } // Слайдер окна var slider = document.getElementById("rangeSlider"); if (slider) { slider.value = String(start); slider.max = "11"; var scale = document.getElementById("sliderScale"); if (scale) scale.innerHTML = "" + labels[0] + "" + labels[labels.length - 1] + ""; } var note = document.getElementById("dynNote"); if (note) { var peak = winRev.indexOf(Math.max.apply(null, winRev)); note.textContent = "Пик выручки — " + labels[peak] + " 2026: " + winRev[peak].toLocaleString("ru-RU", { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + " млрд ₸. Окно: " + labels[0] + "–Дек 2026."; } } // ------- Слайд 3: услуги ------- function renderServices() { var cards = [ { id: "svcInternet", icon: "link", name: "Интернет", share: 52, rev: 165.5, tone: "var(--kt-ai-chart-blue)" }, { id: "svcMob", icon: "send", name: "Мобильная связь", share: 34, rev: 108.2, tone: "var(--kt-ai-chart-green)" }, { id: "svcTv", icon: "megaphone", name: "Телевидение", share: 14, rev: 44.6, tone: "var(--kt-ai-chart-orange)" } ]; cards.forEach(function (c) { var box = document.getElementById(c.id); if (!box) return; // дуга: полукруг, r=40, длина дуги ~ PI*r var half = 40 * Math.PI; var offset = half - half * (c.share / 100); box.innerHTML = '
' + '' + '' + '' + "
" + '
' + c.share + '%' + '' + fmt(c.rev, 1) + " млрд ₸ за 2026
"; }); var sumShare = cards.reduce(function (t, c) { return t + c.share; }, 0); var note = document.getElementById("svcNote"); if (note) note.textContent = "Совокупная доля трёх направлений — " + sumShare + "%; остальные услуги — " + (100 - sumShare) + "%."; } // ------- Навигация ------- function showSlide(n) { n = Math.max(0, Math.min(2, n)); state.slide = n; persist(); document.querySelectorAll(".kt-slide").forEach(function (el, i) { el.classList.toggle("is-active", i === n); }); document.querySelectorAll(".deck-dot").forEach(function (d, i) { d.classList.toggle("is-active", i === n); d.setAttribute("aria-current", i === n ? "true" : "false"); }); document.getElementById("deckPrev").disabled = n === 0; document.getElementById("deckNext").disabled = n === 2; document.getElementById("deckCounter").textContent = (n + 1) + " / 3"; if (n === 1) renderDynamics(); if (n === 2) renderServices(); window.scrollTo({ top: 0, behavior: "instant" }); } function initNav() { document.querySelectorAll(".deck-dot").forEach(function (d) { d.addEventListener("click", function () { showSlide(+d.getAttribute("data-slide")); }); }); document.getElementById("deckPrev").addEventListener("click", function () { showSlide(state.slide - 1); }); document.getElementById("deckNext").addEventListener("click", function () { showSlide(state.slide + 1); }); document.addEventListener("keydown", function (e) { if (e.key === "ArrowRight") showSlide(state.slide + 1); if (e.key === "ArrowLeft") showSlide(state.slide - 1); }); } function initSlider() { var slider = document.getElementById("rangeSlider"); if (!slider) return; slider.addEventListener("input", function () { state.startMonth = +slider.value; persist(); renderKpi(); renderRegions(); renderDynamics(); }); } function initPeriod() { var chip = document.getElementById("periodChip"); if (!chip) return; chip.addEventListener("click", function () { // переключение года: 2025 / 2026 (в демо меняем подписи) var yearEl = chip.querySelector(".year"); if (!yearEl) return; var y = yearEl.textContent === "2026" ? "2025" : "2026"; yearEl.textContent = y; }); } function init() { restore(); renderRegions(); renderKpi(); initNav(); initSlider(); initPeriod(); renderDynamics(); renderServices(); showSlide(state.slide); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();