// Дашборд по продажам — статика, состояние в localStorage. var DATA = { months: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен"], monthsFull: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь"], cats: [ { id: "internet", name: "Интернет" }, { id: "tv", name: "Телевидение" }, { id: "mobile", name: "Мобильные услуги" }, { id: "corporate", name: "Корпоративные услуги" }, { id: "digital", name: "Цифровые сервисы" } ], unit: "млн тг", // 5 категорий × 9 месяцев (факт 2026 и сравнение 2025), млн тг v: { internet: [3520, 3580, 3610, 3590, 3720, 3800, 3890, 3940, 4010], tv: [1810, 1830, 1850, 1840, 1870, 1890, 1910, 1920, 1950], mobile: [2950, 2990, 3020, 3060, 3040, 3110, 3180, 3260, 3310], corporate: [1620, 1650, 1660, 1720, 1740, 1770, 1810, 1840, 1900], digital: [940, 1010, 1080, 1160, 1280, 1410, 1560, 1710, 1880] }, pv: { internet: [3310, 3350, 3380, 3360, 3420, 3470, 3520, 3550, 3590], tv: [1860, 1850, 1840, 1830, 1820, 1810, 1800, 1790, 1780], mobile: [2720, 2760, 2800, 2810, 2790, 2840, 2890, 2950, 3010], corporate: [1520, 1540, 1550, 1580, 1600, 1620, 1650, 1670, 1700], digital: [690, 740, 800, 870, 960, 1050, 1170, 1280, 1400] } }; var PERIODS = [ { id: "m1_9", label: "Янв – Сен", from: 0, to: 8 }, { id: "q1", label: "I квартал", from: 0, to: 2 }, { id: "q2", label: "II квартал", from: 3, to: 5 }, { id: "q3", label: "III квартал", from: 6, to: 8 } ]; var PALETTE = ["--kt-ai-chart-blue", "--kt-ai-chart-green", "--kt-ai-chart-orange", "--kt-ai-chart-salmon", "--kt-ai-chart-pink"]; var LS_KEY = "dash-sales-v1"; var state = load(); function load() { try { var raw = localStorage.getItem(LS_KEY); if (raw) { var s = JSON.parse(raw); if (s && typeof s === "object") return { screen: s.screen || "overview", period: s.period || "m1_9", cat: s.cat || "all" }; } } catch (e) {} return { screen: "overview", period: "m1_9", cat: "all" }; } function save() { try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) {} } // ---------- данные ---------- function period() { for (var i = 0; i < PERIODS.length; i++) if (PERIODS[i].id === state.period) return PERIODS[i]; return PERIODS[0]; } function monthRange() { var p = period(); var r = []; for (var i = p.from; i <= p.to; i++) r.push(i); return r; } function seriesOf(catId, monthIdx) { var y = DATA.v[catId] || [], p = DATA.pv[catId] || []; return { cur: y[monthIdx] || 0, prev: p[monthIdx] || 0 }; } function sumSeries(catId) { var r = monthRange(), cur = 0, prev = 0; for (var i = 0; i < r.length; i++) { var s = seriesOf(catId, r[i]); cur += s.cur; prev += s.prev; } return { cur: cur, prev: prev }; } function activeCats() { if (state.cat === "all") return DATA.cats.slice(); for (var i = 0; i < DATA.cats.length; i++) if (DATA.cats[i].id === state.cat) return [DATA.cats[i]]; return DATA.cats.slice(); } function totals() { var cur = 0, prev = 0, cats = activeCats(); for (var i = 0; i < cats.length; i++) { var t = sumSeries(cats[i].id); cur += t.cur; prev += t.prev; } return { cur: cur, prev: prev }; } function deltaPct(cur, prev) { if (!prev) return null; return (cur - prev) / prev * 100; } // ---------- форматирование ---------- function fmtInt(n) { var s = String(Math.round(n)); var out = "", c = 0; for (var i = s.length - 1; i >= 0; i--) { out = s.charAt(i) + out; c++; if (c % 3 === 0 && i > 0) out = " " + out; } return out; } function fmtMoney(v) { return fmtInt(v) + " " + DATA.unit; } function fmtPct(n) { var sign = n >= 0 ? "+" : "−"; return sign + Math.abs(n).toFixed(1).replace(".", ",") + "%"; } // ---------- рендер: сводка ---------- function kpiCard(label, value, hint, dataLong) { var hl = dataLong ? ' data-long="true"' : ""; return '
' + value + '' + label + '' + hint + '
'; } function barChart() { var r = monthRange(), cats = activeCats(); var max = 0; var sums = []; for (var i = 0; i < r.length; i++) { var m = 0; for (var c = 0; c < cats.length; c++) m += (DATA.v[cats[c].id] || [])[r[i]] || 0; sums.push(m); if (m > max) max = m; } var bars = ""; var axis = ""; for (var i = 0; i < r.length; i++) { var h = max ? Math.max(2, Math.round((sums[i] / max) * 100)) : 2; var tip = JSON.stringify(DATA.monthsFull[r[i]] + " " + (state.cat === "all" ? "— все категории" : DATA.cats[state.cat] ? DATA.cats[state.cat].name : "") + ": " + fmtMoney(sums[i])); bars += '
'; axis += '' + DATA.months[r[i]] + ''; } var p = period(); var label = (state.cat === "all" ? "Все категории" : catName()) + ", " + p.label.toLowerCase() + " 2026"; return '
Выручка по месяцам, ' + label + '
' + '
' + fmtInt(Math.max.apply(null, sums.concat([1]))) + '' + DATA.unit + "
" + '
' + bars + '
' + axis + "
"; } function catName() { for (var i = 0; i < DATA.cats.length; i++) if (DATA.cats[i].id === state.cat) return DATA.cats[i].name; return ""; } function donut() { var cats = DATA.cats, vals = [], total = 0; for (var i = 0; i < cats.length; i++) { var t = sumSeries(cats[i].id); vals.push(t.cur); total += t.cur; } if (!total) return ""; var segs = "", off = 25; for (var i = 0; i < cats.length; i++) { var share = (vals[i] / total) * 100; var c = "var(" + PALETTE[i % PALETTE.length] + ")"; segs += '"; off -= share; } var rows = ""; for (var i = 0; i < cats.length; i++) { var c = "var(" + PALETTE[i % PALETTE.length] + ")"; var dim = (state.cat !== "all" && cats[i].id !== state.cat) ? ' style="opacity:.4"' : ""; rows += '
' + '' + cats[i].name + '' + '' + fmtInt(vals[i]) + '' + '' + Math.round((vals[i] / total) * 100) + "%
"; } return '
' + segs + '' + "∑
" + rows + "
"; } function yoyChart() { var cats = DATA.cats; var max = 0, rows = []; for (var i = 0; i < cats.length; i++) { var t = sumSeries(cats[i].id); if (t.cur > max) max = t.cur; rows.push({ cat: cats[i], t: t }); } if (!max) return ""; var body = ""; for (var i = 0; i < rows.length; i++) { var rr = rows[i]; var w = Math.max(2, Math.round((rr.t.cur / max) * 100)); var c = "var(" + PALETTE[i % PALETTE.length] + ")"; var dim = (state.cat !== "all" && rr.cat.id !== state.cat) ? ' style="opacity:.4"' : ""; body += '' + '' + rr.cat.name + "" + '' + '' + '' + fmtInt(rr.t.cur) + ''; } return '
Структура по категориям, ' + period().label.toLowerCase() + " 2026
" + '
' + fmtInt(max) + '' + DATA.unit + "
" + body + "
"; } function summaryText() { var t = totals(), cats = DATA.cats; var best = null, worst = null; for (var i = 0; i < cats.length; i++) { var tt = sumSeries(cats[i].id); var d = deltaPct(tt.cur, tt.prev); if (d === null) continue; if (best === null || d > best.d) best = { c: cats[i], d: d, t: tt }; if (worst === null || d < worst.d) worst = { c: cats[i], d: d, t: tt }; } var p = period(); var h = '
Что показывают эти числа'; h += 'Выручка за ' + p.label.toLowerCase() + " 2026 — " + fmtMoney(t.cur) + (deltaPct(t.cur, t.prev) !== null ? ", к прошлому году " + fmtPct(deltaPct(t.cur, t.prev)) : "") + (state.cat === "all" ? " по всем категориям." : " в категории «" + catName() + "».") + ""; if (best && worst && best.c.id !== worst.c.id) { h += 'Быстрее всего растёт «' + best.c.name + "» (" + fmtPct(best.d) + "), а отстает «" + worst.c.name + "» (" + fmtPct(worst.d) + ")." + ""; } else if (best) { h += 'Лучший результат по динамике — «' + best.c.name + "» (" + fmtPct(best.d) + ")." + ""; } return h + "
"; } function renderOverview() { var t = totals(), d = deltaPct(t.cur, t.prev); var h = '
'; h += kpiCard("Выручка", fmtMoney(t.cur), period().label + " 2026", true); h += kpiCard("К прошлому году", d !== null ? fmtPct(d) : "—", period().label + " 2025", false); var cats = DATA.cats, maxT = 0, lead = null; for (var i = 0; i < cats.length; i++) { var tt = sumSeries(cats[i].id); if (tt.cur > maxT) { maxT = tt.cur; lead = cats[i]; } } var shareLead = maxT && t.cur ? Math.round(maxT / t.cur * 100) : 0; h += kpiCard("Ведущая категория", lead ? lead.name : "—", shareLead + "% от выручки", true); h += kpiCard("Средний месяц", fmtMoney(Math.round(t.cur / monthRange().length)), "за " + monthRange().length + " мес.", true); h += "
"; h += '
' + barChart() + yoyChart() + "
"; if (state.cat === "all") { h += '
Структура выручки по категориям
' + '
' + donut() + "
"; } h += summaryText(); return h; } // ---------- рендер: детали ---------- function detailsToolbar() { var pOpts = ""; for (var i = 0; i < PERIODS.length; i++) { pOpts += '"; } var cOpts = '"; for (var i = 0; i < DATA.cats.length; i++) { cOpts += '"; } return '
' + '" + '" + '
'; } function detailsKpis() { var t = totals(); var curCat = activeCats().length === 1 ? activeCats()[0] : null; var h = '
'; h += kpiCard(curCat ? curCat.name : "Все категории", fmtMoney(t.cur), period().label + " 2026", true); var d = deltaPct(t.cur, t.prev); h += kpiCard("К прошлому году", d !== null ? fmtPct(d) : "—", period().label + " 2025", false); h += kpiCard("Средний месяц", fmtMoney(Math.round(t.cur / monthRange().length)), "за " + monthRange().length + " мес.", true); if (state.cat === "all") { var lead = null, maxT2 = 0; for (var i = 0; i < DATA.cats.length; i++) { var tt = sumSeries(DATA.cats[i].id); if (tt.cur > maxT2) { maxT2 = tt.cur; lead = DATA.cats[i]; } } h += kpiCard("Ведущая категория", lead ? lead.name : "—", Math.round(maxT2 / (t.cur || 1) * 100) + "% от выручки", true); } else { var totalAll = 0; for (var j = 0; j < DATA.cats.length; j++) totalAll += sumSeries(DATA.cats[j].id).cur; h += kpiCard("Доля в выручке", Math.round(t.cur / (totalAll || 1) * 100) + "%", "за период", false); } return h + "
"; } function detailsTable() { var r = monthRange(); var cats = activeCats(); var totalCur = 0, totalPrev = 0, totalDelta = 0; var rows = ""; for (var i = 0; i < r.length; i++) { var mi = r[i]; var mCur = 0, mPrev = 0; for (var c = 0; c < cats.length; c++) { var s = seriesOf(cats[c].id, mi); mCur += s.cur; mPrev += s.prev; } totalCur += mCur; totalPrev += mPrev; totalDelta += mCur - mPrev; rows += '' + DATA.monthsFull[mi] + '' + '' + fmtMoney(mCur) + "" + '' + fmtMoney(mPrev) + "" + '' + (mCur - mPrev >= 0 ? "+" : "−") + fmtInt(Math.abs(mCur - mPrev)) + "" + '' + (mPrev ? fmtPct((mCur - mPrev) / mPrev * 100) : "—") + ""; } var footDelta = totalDelta; return '
' + "" + '" + '' + "" + rows + '' + '" + '" + '" + '" + "
МесяцВыручка 2026' + period().label + " 2025Δ, млн тгΔ, %
Итого' + fmtMoney(totalCur) + "' + fmtMoney(totalPrev) + "' + (footDelta >= 0 ? "+" : "−") + fmtInt(Math.abs(footDelta)) + "' + (totalPrev ? fmtPct((totalCur - totalPrev) / totalPrev * 100) : "—") + "
"; } function renderDetails() { var h = detailsToolbar(); h += detailsKpis(); h += '
По месяцам, ' + period().label.toLowerCase(); h += state.cat === "all" ? " — все категории" : " — " + catName(); h += "
"; h += '
' + detailsBars() + "
" + '
' + detailsAxis() + "
"; h += detailsTable(); if (state.cat === "all") { h += '
Разбивка по категориям
' + categoryBreakdown(); } return h; } function detailsBars() { var r = monthRange(), cats = activeCats(); var max = 0, sums = []; for (var i = 0; i < r.length; i++) { var m = 0; for (var c = 0; c < cats.length; c++) m += (DATA.v[cats[c].id] || [])[r[i]] || 0; sums.push(m); if (m > max) max = m; } var h = ""; for (var i = 0; i < r.length; i++) { var pct = max ? Math.max(2, Math.round((sums[i] / max) * 100)) : 2; var tip = JSON.stringify(DATA.monthsFull[r[i]] + ": " + fmtMoney(sums[i])); h += '
'; } return h; } function detailsAxis() { var r = monthRange(), h = ""; for (var i = 0; i < r.length; i++) h += '' + DATA.months[r[i]] + ""; return h; } function categoryBreakdown() { var totalAll = 0; var sums = []; for (var i = 0; i < DATA.cats.length; i++) { var t = sumSeries(DATA.cats[i].id); sums.push(t); totalAll += t.cur; } var rows = ""; for (var i = 0; i < DATA.cats.length; i++) { var t = sums[i]; var d = deltaPct(t.cur, t.prev); var tone = d === null ? "gray" : (d >= 0 ? "green" : "red"); rows += '' + DATA.cats[i].name + "" + '' + fmtMoney(t.cur) + "" + fmtMoney(t.prev) + "" + '' + Math.round((t.cur / (totalAll || 1)) * 100) + "%" + '' + (d !== null ? fmtPct(d) : "—") + ""; } return '
' + "" + rows + "
Категория2026, " + period().label + '2025, ' + period().label + "ДоляΔ, %
"; } // ---------- главный рендер ---------- function render() { var view = document.getElementById("view"); if (!view) return; var p = period(); var title = document.getElementById("pageTitle"); var sub = document.getElementById("pageSub"); if (title) title.textContent = state.screen === "overview" ? "Сводка" : "Детали"; if (sub) sub.textContent = p.label + " · " + (state.cat === "all" ? "все категории" : catName()); view.innerHTML = '
" + renderOverview() + "
" + '
" + renderDetails() + "
"; bindDetails(); bindNav(); } function bindNav() { var items = document.querySelectorAll(".ds-nav .kt-ai-nav-item"); for (var i = 0; i < items.length; i++) { items[i].setAttribute("data-active", String(items[i].getAttribute("data-screen") === state.screen)); } } function go(screen) { if (state.screen === screen) return; state.screen = screen; save(); render(); var view = document.getElementById("view"); if (view) view.scrollTop = 0; window.scrollTo(0, 0); } function bindDetails() { var fp = document.getElementById("fPeriod"); var fc = document.getElementById("fCat"); var fr = document.getElementById("fReset"); if (fp) fp.addEventListener("change", function () { state.period = fp.value; save(); render(); }); if (fc) fc.addEventListener("change", function () { state.cat = fc.value; save(); render(); }); if (fr) fr.addEventListener("click", function () { state.period = "m1_9"; state.cat = "all"; save(); render(); }); } document.addEventListener("DOMContentLoaded", function () { render(); var nav = document.querySelectorAll(".ds-nav .kt-ai-nav-item"); for (var i = 0; i < nav.length; i++) { (function (el) { el.addEventListener("click", function () { var s = el.getAttribute("data-screen"); if (s) go(s); }); })(nav[i]); } var burger = document.querySelector(".ds-burger"); if (burger) burger.addEventListener("click", function () { var sb = document.getElementById("sidebar"); if (sb) sb.toggleAttribute("data-open"); }); });