"use strict";
/* ---------- Данные ---------- */
var REGIONS = [
{ name: "г. Алматы", amount: 1284.6 },
{ name: "г. Астана", amount: 947.2 },
{ name: "Алматинская область", amount: 318.4 },
{ name: "Жамбылская область", amount: 264.8 },
{ name: "Акмолинская область", amount: 212.5 },
{ name: "Костанайская область", amount: 189.3 },
{ name: "Восточно-Казахстанская область", amount: 176.9 },
{ name: "Кызылординская область", amount: 141.7 },
{ name: "Карагандинская область", amount: 128.4 },
{ name: "Атырауская область", amount: 117.2 },
{ name: "Южно-Казахстанская область", amount: 98.6 },
{ name: "Туркестанская область", amount: 84.3 },
{ name: "Актюбинская область", amount: 76.9 },
{ name: "Мангистауская область", amount: 62.4 },
{ name: "Северо-Казахстанская область", amount: 58.1 },
{ name: "Усть-Каменогорский край", amount: 47.6 },
{ name: "Павлодарская область", amount: 33.8 }
];
var CHART_COLORS = "var(--kt-ai-chart-blue)";
var LS_KEY = "top-sales-by-region-v1";
var PERIOD = "2026 год, 9 месяцев";
/* ---------- Состояние ---------- */
var state = {
sortDesc: true,
search: "",
top3Only: false,
pinned: []
};
function loadState() {
try {
var raw = localStorage.getItem(LS_KEY);
if (raw) {
var saved = JSON.parse(raw);
if (saved && typeof saved === "object") {
if (typeof saved.sortDesc === "boolean") state.sortDesc = saved.sortDesc;
if (typeof saved.search === "string") state.search = saved.search;
if (typeof saved.top3Only === "boolean") state.top3Only = saved.top3Only;
if (saved.pinned && Array.isArray(saved.pinned)) state.pinned = saved.pinned;
}
}
} catch (e) { /* повреждённые данные — начинаем с чистого состояния */ }
}
function saveState() {
try {
localStorage.setItem(LS_KEY, JSON.stringify(state));
} catch (e) { /* приватный режим — молча пропускаем */ }
}
/* ---------- Утилиты ---------- */
function escapeHtml(value) {
return String(value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function formatNumber(value, digits) {
var n = Number(value);
if (!isFinite(n)) return "—";
return n.toLocaleString("ru-RU", {
minimumFractionDigits: digits,
maximumFractionDigits: digits
});
}
function formatFull(value) {
var n = Number(value);
if (!isFinite(n)) return "—";
return n.toLocaleString("ru-RU", { maximumFractionDigits: 1 });
}
function getEl(id) {
return document.getElementById(id);
}
function shortName(name) {
if (name.indexOf("область") !== -1) return name.replace("область", "обл.").replace("Восточно-Казахстанская", "Вост.-Казахст.");
return name;
}
/* ---------- Выборка ---------- */
function totalAmount() {
var t = 0;
for (var i = 0; i < REGIONS.length; i++) t += REGIONS[i].amount;
return t;
}
function currentData() {
var q = state.search.trim().toLowerCase();
var byAmount = REGIONS.slice().sort(function (a, b) { return b.amount - a.amount; });
var top3Names = [];
for (var t = 0; t < 3; t++) top3Names.push(byAmount[t].name);
var max = byAmount[0] ? byAmount[0].amount : 0;
var grandTotal = totalAmount();
var allTotal = 0;
for (var a = 0; a < REGIONS.length; a++) allTotal += REGIONS[a].amount;
var rows = [];
for (var k = 0; k < REGIONS.length; k++) {
var r = REGIONS[k];
var matches = !q || r.name.toLowerCase().indexOf(q) !== -1;
var isPinned = state.pinned.indexOf(r.name) !== -1;
if (state.top3Only && top3Names.indexOf(r.name) === -1 && !isPinned) matches = false;
if (!matches) continue;
var rank = null;
for (var x = 0; x < byAmount.length; x++) {
if (byAmount[x].name === r.name) { rank = x + 1; break; }
}
var share = grandTotal > 0 ? (r.amount / grandTotal) * 100 : 0;
rows.push({
name: r.name,
amount: r.amount,
rank: rank,
share: share,
top: top3Names.indexOf(r.name) !== -1,
pinned: isPinned,
max: max
});
}
rows.sort(function (a, b) {
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
if (a.name !== b.name) {
var an = a.name.toLowerCase();
var bn = b.name.toLowerCase();
if (an < bn) return -1;
if (an > bn) return 1;
return 0;
}
if (state.sortDesc) return b.amount - a.amount;
return a.amount - b.amount;
});
return rows;
}
/* ---------- Рендер ---------- */
function renderMeta(rows) {
var meta = getEl("dash-meta");
if (meta) meta.textContent = PERIOD + " · млн тенге";
}
function renderKpis(rows) {
var strip = getEl("kpi-strip");
if (!strip) return;
var total = 0;
var best = null;
var visibleCount = rows.length;
for (var i = 0; i < rows.length; i++) {
total += rows[i].amount;
if (!best || rows[i].amount > best.amount) best = rows[i];
}
var allTotal = 0;
for (var k = 0; k < REGIONS.length; k++) allTotal += REGIONS[k].amount;
var top3Names = [];
var byAmount = REGIONS.slice().sort(function (a, b) { return b.amount - a.amount; }).slice(0, 3);
var top3Sum = 0;
for (var j = 0; j < byAmount.length; j++) top3Sum += byAmount[j].amount;
var kpis = [];
kpis.push({ label: "Всего областей", value: String(REGIONS.length), hint: "в рейтинге" });
kpis.push({ label: "Общий объём продаж", value: formatNumber(allTotal, 1), hint: "млн тг, все области" });
kpis.push({ label: "Лидер продаж", value: best ? best.name : "—", hint: best ? formatNumber(best.amount, 1) + " млн тг" : "", long: true });
kpis.push({ label: "Объём топ-3", value: formatNumber(top3Sum, 1), hint: "млн тг, 3 области" });
var html = "";
for (var m = 0; m < kpis.length; m++) {
html += '
';
html += '' + escapeHtml(kpis[m].label) + "";
html += '" + escapeHtml(kpis[m].value) + "";
if (kpis[m].hint) html += '' + escapeHtml(kpis[m].hint) + "";
html += "
";
}
strip.innerHTML = html;
}
function renderChart(rows) {
var barsBox = getEl("chart-bars");
var axisBox = getEl("chart-axis");
if (!barsBox || !axisBox) return;
var show = rows.slice().sort(function (a, b) { return b.amount - a.amount; }).slice(0, 10);
var html = "";
var axis = "";
var maxVal = 0;
for (var i = 0; i < show.length; i++) if (show[i].amount > maxVal) maxVal = show[i].amount;
for (var j = 0; j < show.length; j++) {
var r = show[j];
var h = maxVal > 0 ? Math.round((r.amount / maxVal) * 100) : 0;
if (h < 2) h = 2;
var top3 = (j < 3);
var tip = "{"title":"" + r.name + "","rows":[{"label":"Объём продаж","value":"" + formatFull(r.amount) + " млн тг"},{"label":"Доля","value":"" + formatNumber(r.share, 1) + "%"}]}";
html += '
';
axis += '' + escapeHtml(shortName(r.name)) + "";
}
barsBox.innerHTML = html;
axisBox.innerHTML = axis;
}
function renderFilters() {
var chips = getEl("filter-chips");
if (!chips) return;
var html = "";
var count = getEl("chip-count");
if (state.pinned.length > 0) {
for (var i = 0; i < state.pinned.length; i++) {
html += '' + escapeHtml("Закреплено") + " " +
escapeHtml(state.pinned[i]) +
'';
}
}
if (state.top3Only) {
html += 'Фильтр Топ-3';
}
chips.innerHTML = html;
if (count) {
var rows = currentData();
count.textContent = "Показано: " + rows.length + " из " + REGIONS.length;
}
var searchInput = getEl("search");
if (searchInput && searchInput.value !== state.search) searchInput.value = state.search;
var t3 = getEl("btn-top3");
if (t3) t3.textContent = state.top3Only ? "Снять: Топ-3" : "Топ-3";
var sortBtn = getEl("btn-sort");
if (sortBtn) sortBtn.setAttribute("aria-sort", state.sortDesc ? "descending" : "ascending");
}
function renderTable(rows) {
var tbody = getEl("table-body");
var empty = getEl("empty");
if (!tbody) return;
if (rows.length === 0) {
tbody.innerHTML = "";
if (empty) empty.hidden = false;
return;
}
if (empty) empty.hidden = true;
var html = "";
var maxAmount = 0;
for (var i = 0; i < rows.length; i++) if (rows[i].amount > maxAmount) maxAmount = rows[i].amount;
for (var j = 0; j < rows.length; j++) {
var r = rows[j];
var pct = maxAmount > 0 ? Math.round((r.amount / maxAmount) * 100) : 0;
var rankCell = '' + (r.rank ? r.rank : "—") + "";
if (r.pinned) rankCell += ' ';
html += '
';
html += "
" + rankCell + "
";
html += '
' + escapeHtml(r.name) + "
";
html += '
' + formatNumber(r.amount, 1) + "
";
html += '
' + formatNumber(r.share, 1) + "%
";
html += "
";
}
tbody.innerHTML = html;
}
function renderSummary(rows) {
var box = getEl("summary");
if (!box) return;
if (rows.length === 0) {
box.hidden = true;
return;
}
var sorted = rows.slice().sort(function (a, b) { return b.amount - a.amount; });
var best = sorted[0];
var second = sorted[1];
var total = 0;
for (var i = 0; i < rows.length; i++) total += rows[i].amount;
var allTotal = 0;
for (var k = 0; k < REGIONS.length; k++) allTotal += REGIONS[k].amount;
var bestShare = best && allTotal > 0 ? (best.amount / allTotal) * 100 : 0;
var text = "Лидер — " + best.name + ": " + formatNumber(best.amount, 1) + " млн тг, доля " + formatNumber(bestShare, 1) + "%";
if (second && second.name !== best.name) {
text += ", " + second.name + " — " + formatNumber(second.amount, 1) + " млн тг";
}
text += ". Итого по отфильтрованному списку: " + formatNumber(total, 1) + " млн тг.";
box.textContent = text;
box.hidden = false;
}
function renderSortControl() {
var ic = getEl("sort-ic");
if (ic) ic.setAttribute("data-dir", state.sortDesc ? "desc" : "asc");
}
function render() {
var rows = currentData();
renderMeta(rows);
renderKpis(rows);
renderChart(rows);
renderFilters();
renderTable(rows);
renderSummary(rows);
renderSortControl();
saveState();
}
/* ---------- События ---------- */
function toast(message) {
var stack = document.querySelector(".kt-ai-toast-stack");
if (!stack) return;
var el = document.createElement("div");
el.className = "kt-ai-toast";
el.textContent = message;
stack.appendChild(el);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 2500);
}
function resetAll() {
state.sortDesc = true;
state.search = "";
state.top3Only = false;
state.pinned = [];
var s = getEl("search");
if (s) s.value = "";
render();
}
function wireEvents() {
var sortBtn = getEl("btn-sort");
if (sortBtn) {
sortBtn.addEventListener("click", function () {
state.sortDesc = !state.sortDesc;
render();
});
}
var search = getEl("search");
if (search) {
search.addEventListener("input", function () {
state.search = search.value;
render();
});
}
var top3Btn = getEl("btn-top3");
if (top3Btn) {
top3Btn.addEventListener("click", function () {
state.top3Only = !state.top3Only;
render();
});
}
var resetBtn = getEl("btn-refresh");
if (resetBtn) {
resetBtn.addEventListener("click", function () {
try { localStorage.removeItem(LS_KEY); } catch (e) { /* приватный режим */ }
resetAll();
toast("Данные обновлены");
});
}
var emptyReset = getEl("btn-empty-reset");
if (emptyReset) emptyReset.addEventListener("click", resetAll);
var chips = getEl("filter-chips");
if (chips) {
chips.addEventListener("click", function (ev) {
var target = ev.target;
while (target && target !== chips) {
if (target.classList && target.classList.contains("remove")) break;
target = target.parentNode;
}
if (!target || target === chips) return;
var region = target.getAttribute ? target.getAttribute("data-region") : null;
var clearTop3 = target.getAttribute ? target.getAttribute("data-clear-top3") : null;
if (region) {
state.pinned = state.pinned.filter(function (p) { return p !== region; });
render();
} else if (clearTop3) {
state.top3Only = false;
render();
}
});
}
var tbody = getEl("table-body");
if (tbody) {
tbody.addEventListener("click", function (ev) {
var target = ev.target;
while (target && target !== tbody) {
if (target.tagName === "TR") break;
target = target.parentNode;
}
if (!target || target === tbody) return;
var cells = target.querySelectorAll("td");
if (cells.length < 2) return;
var name = cells[1].textContent.trim();
if (!name) return;
var idx = state.pinned.indexOf(name);
if (idx === -1) {
state.pinned.push(name);
toast("Закреплено: " + name);
} else {
state.pinned.splice(idx, 1);
toast("Откреплено: " + name);
}
render();
});
}
}
function init() {
loadState();
var s = getEl("search");
if (s) s.value = state.search;
wireEvents();
render();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}