// Дашборд по продажам — статика, состояние в 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 += '
";
}
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 += '
";
}
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 += '
Структура по категориям, ' + 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 += '
";
}
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 += '
";
}
// ---------- главный рендер ----------
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");
});
});