/* Дашборд розничных продаж по городам: данные, фильтры, два экрана */ (function () { "use strict"; /* ================= ДАННЫЕ ================= */ var MONTHS = ["январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август"]; var MONTHS_SHORT = ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг"]; var CITIES = [ { name: "Алматы", region: "Юг", salesT2: 486, salesT: 512, ticketsT: 78.4, avgT: 6530, growth: 5.3, top: "Мобильные устройства" }, { name: "Астана", region: "Север", salesT2: 342, salesT: 358, ticketsT: 52.1, avgT: 6870, growth: 4.7, top: "Телевизоры" }, { name: "Шымкент", region: "Юг", salesT2: 176, salesT: 189, ticketsT: 34.2, avgT: 5530, growth: 7.4, top: "Бытовая техника" }, { name: "Караганда", region: "Север", salesT2: 148, salesT: 142, ticketsT: 27.9, avgT: 5090, growth: -4.1, top: "Домашний интернет" }, { name: "Актау", region: "Запад", salesT2: 121, salesT: 134, ticketsT: 21.6, avgT: 6200, growth: 10.7, top: "Электроника" }, { name: "Акмола", region: "Север", salesT2: 114, salesT: 121, ticketsT: 23.8, avgT: 5080, growth: 6.1, top: "Домашний интернет" }, { name: "Актобе", region: "Запад", salesT2: 108, salesT: 112, ticketsT: 22.4, avgT: 5000, growth: 3.7, top: "Бытовая техника" }, { name: "Костанай", region: "Север", salesT2: 96, salesT: 101, ticketsT: 20.3, avgT: 4970, growth: 5.2, top: "Телефония" }, { name: "Павлодар", region: "Север", salesT2: 88, salesT: 84, ticketsT: 17.1, avgT: 4910, growth: -4.5, top: "Телевизоры" }, { name: "Усть-Каменогорск", region: "Восток", salesT2: 72, salesT: 78, ticketsT: 14.9, avgT: 5230, growth: 8.3, top: "Бытовая техника" } ]; /* товарные категории и базовые веса долей */ var CATS = [ { key: "phone", label: "Телефония", w: 0.24 }, { key: "mob", label: "Мобильные устройства", w: 0.19 }, { key: "tv", label: "Телевизоры", w: 0.12 }, { key: "bt", label: "Бытовая техника", w: 0.11 }, { key: "elec", label: "Электроника", w: 0.10 }, { key: "home", label: "Домашний интернет", w: 0.08 }, { key: "acc", label: "Аксессуары", w: 0.06 }, { key: "other", label: "Прочее", w: 0.10 } ]; var SEEDS = { "Алматы": [1.05, 1.10, 0.95, 0.80, 0.60, 0.90, 1.00, 1.00], "Астана": [0.90, 1.00, 0.70, 1.15, 0.65, 0.85, 1.05, 1.00], "Шымкент": [1.10, 0.95, 0.90, 0.75, 0.85, 1.05, 0.95, 1.00], "Караганда": [0.85, 0.95, 1.05, 0.90, 0.70, 0.80, 1.00, 1.00], "Актау": [1.00, 0.90, 1.15, 0.85, 0.75, 0.95, 0.90, 1.00], "Акмола": [0.95, 0.80, 1.05, 1.00, 0.70, 0.85, 0.95, 1.00], "Актобе": [0.80, 1.05, 0.90, 0.85, 0.95, 0.75, 1.10, 1.00], "Костанай": [1.00, 0.85, 0.90, 1.05, 0.80, 0.95, 1.00, 1.00], "Павлодар": [0.90, 1.00, 0.75, 0.85, 1.05, 0.70, 0.90, 1.00], "Усть-Каменогорск": [0.95, 1.05, 0.80, 0.90, 1.00, 0.85, 0.70, 1.00] }; var state = { q: "", region: "all", sort: "sales", viewed: {} }; var el = {}; var LS_KEY = "kt-dash-rozn-2"; function saveState() { try { localStorage.setItem(LS_KEY, JSON.stringify({ q: state.q, region: state.region, sort: state.sort, viewed: state.viewed })); } catch (e) { /* приватный режим — молча */ } } function loadState() { try { var raw = localStorage.getItem(LS_KEY); if (!raw) { return; } var saved = JSON.parse(raw); if (typeof saved.q === "string") { state.q = saved.q.slice(0, 40); } if (typeof saved.region === "string") { state.region = saved.region; } if (["sales", "growth", "avg", "share"].indexOf(saved.sort) !== -1) { state.sort = saved.sort; } if (saved.viewed && typeof saved.viewed === "object") { state.viewed = saved.viewed; } } catch (e) { /* битые данные — начинаем с чистого */ } } /* ================= ХЕЛПЕРЫ ================= */ function $(id) { return document.getElementById(id); } function fmtNum(n, dec) { dec = dec || 0; var s = String(Number(n.toFixed(dec))); var parts = s.split("."); var int = parts[0]; var out = ""; while (int.length > 3) { out = " " + int.slice(-3) + out; int = int.slice(0, -3); } out = int + out; return parts.length > 1 ? out + "," + parts[1] : out; } function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function totalSales(list, field) { var t = 0; for (var i = 0; i < list.length; i++) { t += list[i][field]; } return t; } function cityByIdx(name) { for (var i = 0; i < CITIES.length; i++) { if (CITIES[i].name === name) { return CITIES[i]; } } return null; } function growthPill(g) { if (g > 0) { return '+' + g.toFixed(1) + "%"; } if (g < 0) { return '' + g.toFixed(1) + "%"; } return '0,0%'; } /* продажи города по месяцам, млн тг */ function monthlySales(c) { var s = SEEDS[c.name] || []; var arr = []; for (var i = 0; i < MONTHS.length; i++) { arr.push(Math.round(c.salesT * (s[i] || 1) * 10) / 10); } return arr; } /* доли товарных категорий, % */ function cityCats(c) { var s = SEEDS[c.name] || []; var arr = []; for (var i = 0; i < CATS.length; i++) { var m = s[i % s.length] || 1; arr.push({ label: CATS[i].label, share: Math.round(CATS[i].w * m * 100) / 100 }); } return arr; } /* топ-позиции: имя + доля в продажах */ function topItems(c) { var cats = cityCats(c); var names = { "phone": { a: "Абонентский план «Безлимит 50»", b: "Абонентский план «Семейный»" }, "mob": { a: "Смартфон Galaxy S24", b: "Смартфон iPhone 15" }, "tv": { a: "Телевизор 55\" 4K", b: "Телевизор 43\" FHD" }, "bt": { a: "Холодильник No Frost", b: "Микроволновая печь" }, "elec": { a: "Ноутбук 15\"", b: "Монитор 27\"" }, "home": { a: "Домашний интернет 300 Мбит/с", b: "Домашний интернет 100 Мбит/с" }, "acc": { a: "Беспроводные наушники", b: "Зарядное устройство USB-C" }, "other": { a: "Услуга аренды линии", b: "Подписка на видеосервис" } }; var sorted = cats.slice().sort(function (a, b) { return b.share - a.share; }); var out = []; out.push({ name: c.top, share: null }); var max = 0; for (var i = 0; i < sorted.length; i++) { if (sorted[i].label === c.top) { out[0].share = sorted[i].share; max = Math.max(max, sorted[i].share); } } for (var j = 0; j < sorted.length && out.length < 4; j++) { if (sorted[j].label === c.top) { continue; } var nm = names[CATS[j].key]; var pick = (j % 2 === 0) ? nm.a : nm.b; out.push({ name: pick, share: Math.round(sorted[j].share * 0.8 * 10) / 10 }); max = Math.max(max, out[out.length - 1].share); } for (var k = 0; k < out.length; k++) { var s10 = Math.round(out[k].share) * 10; out[k].pct = (out[k].share && s10 > 0) ? Math.max(8, Math.min(100, Math.round(s10 / 2))) : 100; } return out; } /* ================= ГЛАВНЫЙ ЭКРАН ================= */ function filteredCities() { var q = state.q.toLowerCase(); var out = []; for (var i = 0; i < CITIES.length; i++) { var c = CITIES[i]; if (state.region !== "all" && c.region !== state.region) { continue; } if (q && c.name.toLowerCase().indexOf(q) === -1) { continue; } out.push(c); } var key = state.sort; out.sort(function (a, b) { if (key === "growth") { return b.growth - a.growth; } if (key === "avg") { return b.avgT - a.avgT; } if (key === "share") { return b.salesT - a.salesT; } return b.salesT - a.salesT; }); return out; } function renderKpi(all) { var sumT = totalSales(all, "salesT"); var sumT2 = totalSales(all, "salesT2"); var cnt = 0; for (var i = 0; i < all.length; i++) { if (all[i].growth > 0) { cnt++; } } var top = all.length ? all.slice().sort(function (a, b) { return b.salesT - a.salesT; })[0] : null; var avg = sumT ? Math.round(sumT / all.length) : 0; var revisit = null; for (var v = 0; v < all.length; v++) { var seen = state.viewed[all[v].name]; if (seen && seen.sales != null && all[v].salesT !== seen.sales) { if (!revisit || all[v].salesT < revisit.salesT) { revisit = all[v]; } } } html( '
Ничего не найдено
" + '' + "' + esc(c.region) + " · розничные продажи за отчётный период
" + "| Категория | Доля |
|---|