";
}
function setPeriod(id) {
state.period = id;
saveState();
applyScreen();
}
function setGroup(id) {
state.group = id;
saveState();
applyScreen();
}
function resetState() {
state.period = "9m";
state.group = "all";
saveState();
applyScreen();
}
function switchScreen(id) {
state.screen = id;
saveState();
applyScreen();
}
// ---------- Графики ----------
function chartCard(title, meta, body, note) {
var head = '
' + title + "
" +
(meta ? '
' + meta + "
" : "") + "
";
var foot = note ? '
' + note + "
" : "";
return '' + head + body + foot + "";
}
function groupedBarHtml(pairs, tipRows) {
var max = 0;
for (var i = 0; i < pairs.length; i++) {
for (var j = 1; j < pairs[i].vals.length; j++) {
if (pairs[i].vals[j] > max) max = pairs[i].vals[j];
}
}
if (max < 1) max = 1;
var bars = "";
for (var k = 0; k < pairs.length; k++) {
var p = pairs[k];
var inner = "";
for (var m = 0; m < p.vals.length; m++) {
var h = Math.max(2, Math.round(p.vals[m] / max * 190));
var tip = tipRows[p.tipKey || "" + k];
inner += '";
}
bars += '
';
}
function lineChartHtml(points) {
var W = 520, H = 200, padX = 10, padT = 14, padB = 26, innerH = H - padT - padB;
var max = 0, min = Infinity;
for (var i = 0; i < points.length; i++) {
if (points[i].v > max) max = points[i].v;
if (points[i].v < min) min = points[i].v;
}
if (min <= 0) min = 0;
var top = max * 1.06, bot = Math.max(0, min - (max - min) * 0.35);
var n = points.length;
function X(i) { return n === 1 ? W / 2 : padX + i * (W - 2 * padX) / (n - 1); }
function Y(v) { return padT + (1 - (v - bot) / (top - bot || 1)) * innerH; }
var path = "", dots = "", labels = "";
for (var k = 0; k < n; k++) {
path += (k ? "L" : "M") + X(k).toFixed(1) + " " + Y(points[k].v).toFixed(1);
dots += '";
labels += '' + points[k].label + "";
}
var grid = "";
for (var g = 0; g < 4; g++) {
var gy = padT + g * innerH / 3;
var gv = top - g * (top - bot) / 3;
grid += '' +
'' + fmtInt(gv) + "";
}
return '";
}
function barRowHtml(label, value, maxV, color, tip) {
var w = Math.max(2, Math.round(value / maxV * 100));
return '
' + label + "" +
'' +
'' + fmtInt(value) + "
";
}
function donutHtml(total, pts) {
var acc = 0;
var segs = "";
for (var i = 0; i < pts.length; i++) {
var pp = pts[i];
if (pp.value <= 0) continue;
var share = pp.value / total * 100;
segs += '";
acc += share;
}
var legend = "";
for (var j = 0; j < pts.length; j++) {
var q = pts[j];
if (q.value <= 0) continue;
var pc = Math.round(q.value / total * 100);
legend += '
' +
'' + q.label + '' + pc + "%
";
}
var svg = '';
return '
' + svg + '
' + legend + "
";
}
// ---------- Экран «Обзор» ----------
function overviewKpis() {
var p = periodDef(state.period);
var groups = filteredGroups();
var cur = 0, prev = 0;
for (var i = 0; i < groups.length; i++) {
cur += groupSum(groups[i].id, YEAR, p.from, p.months);
prev += groupSum(groups[i].id, PREV_YEAR, p.from, p.months);
}
var yoy = prev ? (cur / prev - 1) * 100 : 0;
var avg = cur / p.months;
var top = topGroupIn(groups, p.from, p.months, YEAR);
var topLabel = top.group ? top.group.name : "—";
var deltaLabel = yoy >= 0 ? "выше" : "ниже";
var deltaNum = Math.abs(yoy).toFixed(1).replace(".", ",");
return [
{ label: "Объём продаж", value: fmtInt(cur), unit: " млн тг", hint: p.label },
{ label: "К уровню " + PREV_YEAR, value: (yoy >= 0 ? "+" : "") + yoy.toFixed(1).replace(".", ",") + "%", unit: "", hint: deltaLabel + " на " + deltaNum + " п.п." },
{ label: "Средний месяц", value: fmtInt(avg), unit: " млн тг", hint: "за " + p.months + " мес." },
{ label: "Сильнейшая группа", value: topLabel, unit: "", hint: fmtInt(top.value) + " млн тг" }
];
}
function kpiStripHtml(kpis) {
var h = '
';
for (var i = 0; i < kpis.length; i++) {
var k = kpis[i];
h += '
";
h += filterBarHtml();
h += kpiStripHtml(overviewKpis());
h += overviewChartsHtml();
h += conclusionHtml();
return h;
}
// ---------- Экран «Детали» ----------
function detailsMonthlyTable() {
var p = periodDef(state.period);
var groups = filteredGroups();
var h = '
Продажи по месяцам
';
var headCols = "
Месяц
";
for (var c = 0; c < groups.length; c++) headCols += "
" + groups[c].name + "
";
headCols += "
Итого
К " + PREV_YEAR + "
";
var body = "";
var colTot = {};
for (var c2 = 0; c2 < groups.length; c2++) colTot[groups[c2].id] = 0;
var grand = 0, grandPrev = 0;
for (var m = 0; m < p.months; m++) {
var mi = p.from + m;
body += "
" + MONTHS[mi] + "
";
var rowTot = 0, rowPrev = 0;
for (var c3 = 0; c3 < groups.length; c3++) {
var v = GROUP_DATA[groups[c3].id].m2026[mi];
var pv = GROUP_DATA[groups[c3].id].m2025[mi];
colTot[groups[c3].id] += v; rowTot += v; rowPrev += pv;
body += '
' + fmtInt(v) + "
";
}
grand += rowTot; grandPrev += rowPrev;
body += '
";
var totals = [];
var sumAll = 0;
for (var i = 0; i < GROUP_DEFS.length; i++) {
var t = groupSum(GROUP_DEFS[i].id, YEAR, p.from, p.months);
totals.push(t);
sumAll += t;
}
for (var g = 0; g < GROUP_DEFS.length; g++) {
var gd = GROUP_DEFS[g];
var tot = totals[g];
var share = sumAll ? tot / sumAll * 100 : 0;
var bm = bestMonthId(gd.id, p.from, p.months, YEAR);
var y = yoyOf(gd.id, p.from, p.months);
h += '
";
var gTot = groupSum(state.group, YEAR, p.from, p.months);
for (var m = 0; m < p.months; m++) {
var mi = p.from + m;
var mvv = GROUP_DATA[state.group].m2026[mi];
var pvv = GROUP_DATA[state.group].m2025[mi];
var sh = gTot ? mvv / gTot * 100 : 0;
h += '