/* Дашборд по продажам B2B: статические данные + имитация в localStorage. */ (function () { "use strict"; /* ---------- Периоды: 8 месяцев, 2 квартала, 2 года ---------- */ var PERIODS = [ { id: "year", label: "2026", period: "Год" }, { id: "q2", label: "II кв. 2026", period: "Квартал" }, { id: "q1", label: "I кв. 2026", period: "Квартал" }, { id: "sep", label: "Сентябрь", period: "Месяц" }, { id: "aug", label: "Август", period: "Месяц" }, { id: "jul", label: "Июль", period: "Месяц" }, { id: "jun", label: "Июнь", period: "Месяц" }, { id: "may", label: "Май", period: "Месяц" }, { id: "apr", label: "Апрель", period: "Месяц" }, { id: "q4", label: "IV кв. 2025", period: "Квартал" }, { id: "q3", label: "III кв. 2025", period: "Квартал" }, { id: "q2p", label: "II кв. 2025", period: "Квартал" }, { id: "q1p", label: "I кв. 2025", period: "Квартал" }, { id: "dec", label: "Декабрь", period: "Месяц" }, { id: "nov", label: "Ноябрь", period: "Месяц" }, { id: "oct", label: "Октябрь", period: "Месяц" }, { id: "sep25", label: "Сентябрь", period: "Месяц" }, { id: "aug25", label: "Август", period: "Месяц" }, { id: "jul25", label: "Июль", period: "Месяц" }, { id: "jun25", label: "Июнь", period: "Месяц" } ]; var PERIOD_BY_ID = {}; for (var p = 0; p < PERIODS.length; p++) PERIOD_BY_ID[PERIODS[p].id] = PERIODS[p]; var MONTH_LABELS = ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"]; /* ---------- Направления услуг ---------- */ var CATEGORIES = ["Доступ в интернет", "IP-телефония", "Транзит", "Колл-центр", "Видеонаблюдение"]; var CAT_COLORS = [ "var(--kt-ai-chart-blue)", "var(--kt-ai-chart-green)", "var(--kt-ai-chart-orange)", "var(--kt-ai-chart-pink)", "var(--kt-ai-chart-salmon)" ]; /* ---------- Клиенты (нейтральные примеры) ---------- */ var CLIENTS = [ { name: "СтройМонтаж", dir: "Алматы", dirId: "almaty" }, { name: "АгроТрейд Костанай", dir: "Костанай", dirId: "kostanay" }, { name: "Логистик Плюс", dir: "Астана", dirId: "astana" }, { name: "ПродМир", dir: "Шымкент", dirId: "shymkent" }, { name: "ТехноПолимер", dir: "Караганда", dirId: "karaganda" }, { name: "МедФарм К", dir: "Кокшетау", dirId: "kokshetau" }, { name: "Ритейл Гур", dir: "Алматы", dirId: "almaty" }, { name: "ЭкоЭнерго", dir: "Актау", dirId: "aktau" } ]; /* ---------- Услуга по направлению ---------- */ function serviceFor(category) { switch (category) { case "Доступ в интернет": return "Волоконная линия, 500 Мбит/с"; case "IP-телефония": return "Абоненты, 48 номеров"; case "Транзит": return "Транзит, 10 Гбит/с"; case "Колл-центр": return "Виртуальные операторы, 12 мест"; case "Видеонаблюдение": return "Камеры, 8 точек"; } return "Услуги связи"; } /* ---------- Менеджеры ---------- */ var MANAGERS = ["А. Сегизбаев", "Ж. Нурлан", "М. Ибраимова", "Д. Калетаев"]; /* ---------- Сумма: базовая + детерминированная поправка ---------- */ function hashStr(s) { var h = 2166136261; for (var i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } function dealSum(seed) { var h = hashStr(seed) % 1000; var base = 320000 + (h % 40) * 27500 + Math.floor(h / 40) * 80000; return base; } /* ---------- Генерация сделок на 20 периодов ---------- */ var ALL_DEALS = []; function MONTH_ZERO(m) { return m < 10 ? "0" + m : "" + m; } function periodMonthLabel(per) { if (per.period === "Месяц") return MONTH_BY_LABEL[per.label] || "09"; return "09"; } var MONTH_BY_LABEL = { "Январь": "01", "Февраль": "02", "Март": "03", "Апрель": "04", "Май": "05", "Июнь": "06", "Июль": "07", "Август": "08", "Сентябрь": "09", "Октябрь": "10", "Ноябрь": "11", "Декабрь": "12" }; function quarterMonths(qid) { if (qid === "q1") return [1, 2, 3]; if (qid === "q2") return [4, 5, 6]; if (qid === "q3") return [7, 8, 9]; if (qid === "q4") return [10, 11, 12]; if (qid === "q1p") return [1, 2, 3]; if (qid === "q2p") return [4, 5, 6]; if (qid === "q3p") return [7, 8, 9]; if (qid === "q4p") return [10, 11, 12]; return [9]; } (function generate() { for (var pi = 0; pi < PERIODS.length; pi++) { var per = PERIODS[pi]; var year = per.id.indexOf("25") > -1 ? 2025 : 2026; var count; if (per.period === "Год") count = 76; else if (per.period === "Квартал") count = 23; else count = 9; for (var k = 0; k < count; k++) { var hc = hashStr(per.id + "#" + k); var client = CLIENTS[hc % CLIENTS.length]; var category = CATEGORIES[Math.floor(hc / 7) % CATEGORIES.length]; var manager = MANAGERS[Math.floor(hc / 13) % MANAGERS.length]; var roll = (hc >>> 4) % 20; var status = roll < 12 ? "Закрыта" : (roll < 16 ? "На согласовании" : "В работе"); var seqN = ALL_DEALS.length + 1; var date; if (status === "Закрыта") { if (per.period === "Год") { var m2 = (hc % 12) + 1; var d = ((hc >>> 2) % 26) + 1; date = year + "-" + MONTH_ZERO(m2) + "-" + MONTH_ZERO(d); } else if (per.period === "Квартал") { var qMonths = quarterMonths(per.id); var m3 = qMonths[hc % 3]; var d3 = ((hc >>> 2) % 26) + 1; date = year + "-" + MONTH_ZERO(m3) + "-" + MONTH_ZERO(d3); } } else { /* Открытые сделки показываются на срезе: дата — срез конца периода */ var y2 = yearOfPeriod(per.id); var mOpen; if (per.period === "Месяц") mOpen = periodMonthLabel(per); else mOpen = "09"; /* срез квартала: конец третьего месяца */ date = y2 + "-" + mOpen + "-25"; } ALL_DEALS.push({ id: seqN, number: "B2B-26-" + (1000 + seqN), client: client.name, dirId: client.dirId, dir: client.dir, category: category, service: serviceFor(category), manager: manager, amount: dealSum(per.id + "#" + k), status: status, date: date, period: per.id }); } } })(); function yearOfPeriod(pid) { return pid.indexOf("25") !== -1 ? 2025 : 2026; } /* ---------- Форматирование ---------- */ function fmtMoney(v) { var s = Math.round(v / 1000).toString().replace(/\B(?=(\d{3})+(?!\d))/g, "\u202f"); return s + "\u202f₸"; } function fmtMoneyShort(v) { if (v >= 1000000) { var m = v / 1000000; var s = (Math.round(m * 100) / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, "\u202f"); return s + "\u202fmn ₸"; } return fmtMoney(v); } function fmtDate(iso) { var p = iso.split("-"); return p[2] + "." + p[1] + "." + p[0]; } function statusInfo(status) { if (status === "Закрыта") return { status: "ok", label: "Закрыта" }; if (status === "На согласовании") return { status: "warn", label: "На согласовании" }; return { status: "info", label: "В работе" }; } /* ---------- Состояние (localStorage) ---------- */ var LS_PERIOD = "b2b.period"; var LS_VIEW = "b2b.view"; var LS_DIR = "b2b.dir"; function lsGet(key, fallback) { try { var v = localStorage.getItem(key); return (v === null || v === "") ? fallback : v; } catch (e) { return fallback; } } function lsSet(key, value) { try { localStorage.setItem(key, value); } catch (e) {} } var state = { period: lsGet(LS_PERIOD, "q2"), view: lsGet(LS_VIEW, "overview"), dir: lsGet(LS_DIR, ""), search: "" }; if (!PERIOD_BY_ID[state.period]) state.period = "q2"; /* ---------- Выборка ---------- */ function dealsOf(periodId) { var out = []; for (var i = 0; i < ALL_DEALS.length; i++) { if (ALL_DEALS[i].period === periodId) out.push(ALL_DEALS[i]); } return out; } function sumDeals(list) { var s = 0; for (var i = 0; i < list.length; i++) s += list[i].amount; return s; } function closedList(list) { var out = []; for (var i = 0; i < list.length; i++) if (list[i].status === "Закрыта") out.push(list[i]); return out; } function openList(list) { var out = []; for (var i = 0; i < list.length; i++) if (list[i].status !== "Закрыта") out.push(list[i]); return out; } function applyDirFilter(list) { if (!state.dir) return list; var out = []; for (var i = 0; i < list.length; i++) if (list[i].dirId === state.dir) out.push(list[i]); return out; } /* ---------- Обзор: KPI и блоки ---------- */ function renderOverview() { var closed = closedList(dealsOf(state.period)); var deals = applyDirFilter(closed); var dealsSum = sumDeals(deals); var active = applyDirFilter(openList(dealsOf(state.period))); var avg = deals.length ? Math.round(dealsSum / deals.length) : 0; var dTexts = []; var dVals = [0, 0, 0]; for (var k = 0; k < 3; k++) { var cur = k === 0 ? dealsSum : (k === 1 ? deals.length : avg); var prevV = prevPeriodFor(k); if (prevV > 0 && prevV !== cur) { var pct = Math.round(((cur - prevV) / prevV) * 100); dVals[k] = pct; dTexts[k] = (pct >= 0 ? "+" : "−") + Math.abs(pct) + "%"; } else { dTexts[k] = ""; } } var kpis = [ { label: "Выручка, закрытые сделки", value: fmtMoney(dealsSum), hint: periodHint(), delta: dTexts[0], deltaVal: dVals[0] }, { label: "Сделок закрыто", value: String(deals.length), hint: "из " + closedList(dealsOf(state.period)).length, delta: dTexts[1], deltaVal: dVals[1] }, { label: "Средний чек", value: avg > 0 ? fmtMoney(avg) : "—", hint: "по закрытым", delta: dTexts[2], deltaVal: dVals[2] }, { label: "Открытый портфель", value: fmtMoney(sumDeals(active)), hint: active.length + " активных сделок" } ]; var html = ""; for (var i = 0; i < kpis.length; i++) { html += '