558 lines
23 KiB
JavaScript
558 lines
23 KiB
JavaScript
/* Дашборд по продажам 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 += '<div class="kt-ai-kpi">';
|
||
html += '<span class="label">' + kpis[i].label + "</span>";
|
||
html += '<span class="value"' + (kpis[i].value.length > 10 ? ' data-long="true"' : "") + ">" + kpis[i].value + "</span>";
|
||
html += '<span class="hint">' + kpis[i].hint + "</span>";
|
||
if (kpis[i].delta) html += '<span class="delta ' + (kpis[i].deltaVal >= 0 ? "ok" : "down") + '">' + kpis[i].delta + "</span>";
|
||
html += "</div>";
|
||
}
|
||
document.getElementById("kpiStrip").innerHTML = html;
|
||
|
||
renderTrend();
|
||
renderCategories();
|
||
renderTopClients();
|
||
}
|
||
|
||
function periodHint() {
|
||
var per = PERIOD_BY_ID[state.period];
|
||
return per.label + (state.dir ? " · " + dirName(state.dir) : "");
|
||
}
|
||
function dirName(dirId) {
|
||
for (var i = 0; i < CLIENTS.length; i++) if (CLIENTS[i].dirId === dirId) return CLIENTS[i].dir;
|
||
return "";
|
||
}
|
||
|
||
function closedPrevPeriod() {
|
||
var idx = periodIndex();
|
||
if (idx + 1 >= PERIODS.length) return [];
|
||
return closedList(dealsOf(PERIODS[idx + 1].id));
|
||
}
|
||
function periodIndex() {
|
||
for (var i = 0; i < PERIODS.length; i++) if (PERIODS[i].id === state.period) return i;
|
||
return 0;
|
||
}
|
||
function prevPeriodFor(kind) {
|
||
var pl = closedPrevPeriod();
|
||
if (kind === 0) return sumDeals(applyDirFilter(pl));
|
||
if (kind === 1) return applyDirFilter(pl).length;
|
||
var fl = applyDirFilter(pl);
|
||
return fl.length ? sumDeals(fl) / fl.length : 0;
|
||
}
|
||
|
||
/* ---------- График: динамика по месяцам ---------- */
|
||
function trendSlots() {
|
||
var per = PERIOD_BY_ID[state.period];
|
||
if (per.period === "Год") return monthsOf(yearOfState(), 1, 12);
|
||
if (per.period === "Квартал") {
|
||
var qm = quarterMonths(state.period);
|
||
return monthsOf(yearOfState(), qm[0], qm[2]);
|
||
}
|
||
var m = parseInt(periodMonthLabel(per), 10);
|
||
return [{ label: MONTH_LABELS[m - 1], key: yearOfState() + "-" + MONTH_ZERO(m) }];
|
||
}
|
||
function yearOfState() {
|
||
if (state.period.indexOf("25") !== -1) return 2025;
|
||
return 2026;
|
||
}
|
||
|
||
function renderTrend() {
|
||
var slots = trendSlots();
|
||
for (var s = 0; s < slots.length; s++) slots[s].value = 0;
|
||
var closed = closedList(dealsOf(state.period));
|
||
for (var i = 0; i < closed.length; i++) {
|
||
var key = closed[i].date.slice(0, 7);
|
||
for (s = 0; s < slots.length; s++) if (slots[s].key === key) slots[s].value += closed[i].amount;
|
||
}
|
||
var max = 0, total = 0;
|
||
for (s = 0; s < slots.length; s++) {
|
||
if (slots[s].value > max) max = slots[s].value;
|
||
total += slots[s].value;
|
||
}
|
||
var bars = "", axis = "";
|
||
for (s = 0; s < slots.length; s++) {
|
||
var h = max > 0 ? Math.round((slots[s].value / max) * 100) : 0;
|
||
bars += '<div class="cc-bar" tabindex="0" data-kt-tip="' + slots[s].label + ": " + fmtMoneyShort(slots[s].value) + '"><i style="height:' + Math.max(h, 1) + '%"></i></div>';
|
||
axis += "<span>" + slots[s].label + "</span>";
|
||
}
|
||
var per = PERIOD_BY_ID[state.period];
|
||
document.getElementById("trendChart").innerHTML =
|
||
'<div class="cc-head"><div>' +
|
||
'<div class="cc-title">Динамика выручки по месяцам</div>' +
|
||
'<div class="cc-total"><span class="num">' + fmtMoneyShort(total) + "</span><span class="unit">закрытые сделки</span></div></div>" +
|
||
'<span class="kt-ai-chip" data-tone="blue">' + per.label + "</span></div>" +
|
||
'<div class="cc-chart">' + bars + "</div>" +
|
||
'<div class="cc-axis">' + axis + "</div>";
|
||
}
|
||
|
||
/* ---------- По направлениям ---------- */
|
||
function renderCategories() {
|
||
var closed = closedList(applyDirFilter(dealsOf(state.period)));
|
||
var totals = {};
|
||
for (var c = 0; c < CATEGORIES.length; c++) totals[CATEGORIES[c]] = 0;
|
||
for (var i = 0; i < closed.length; i++) totals[closed[i].category] += closed[i].amount;
|
||
var order = CATEGORIES.slice();
|
||
order.sort(function (a, b) { return totals[b] - totals[a]; });
|
||
var max = 0;
|
||
for (c = 0; c < order.length; c++) if (totals[order[c]] > max) max = totals[order[c]];
|
||
|
||
var rows = "";
|
||
for (c = 0; c < order.length; c++) {
|
||
var cat = order[c];
|
||
var w = max > 0 ? Math.round((totals[cat] / max) * 100) : 0;
|
||
rows +=
|
||
'<div class="cat-row">' +
|
||
'<span class="cat-name">' + cat + "</span>" +
|
||
'<span class="cat-val">' + fmtMoneyShort(totals[cat]) + "</span>" +
|
||
'<span class="cat-track"><i style="width:' + Math.max(w, 1) + "%;background:" + catColor(cat) + '"></i></span></div>';
|
||
}
|
||
document.getElementById("categoryChart").innerHTML =
|
||
'<div class="cc-title" style="margin-bottom:var(--kt-ai-space-4)">Выручка по направлениям</div>' +
|
||
'<div class="cat-list">' + rows + "</div>";
|
||
}
|
||
function catColor(cat) {
|
||
for (var c = 0; c < CATEGORIES.length; c++) if (CATEGORIES[c] === cat) return CAT_COLORS[c];
|
||
return "var(--kt-ai-chart-blue)";
|
||
}
|
||
|
||
/* ---------- Топ клиентов ---------- */
|
||
function renderTopClients() {
|
||
var closed = closedList(applyDirFilter(dealsOf(state.period)));
|
||
var byClient = {}, byClientDir = {};
|
||
for (var i = 0; i < closed.length; i++) {
|
||
var d = closed[i];
|
||
byClient[d.client] = (byClient[d.client] || 0) + d.amount;
|
||
byClientDir[d.client] = d.dir;
|
||
}
|
||
var arr = [];
|
||
for (var name in byClient) arr.push({ name: name, sum: byClient[name], dir: byClientDir[name] });
|
||
arr.sort(function (a, b) { return b.sum - a.sum; });
|
||
|
||
var rows = "";
|
||
for (i = 0; i < 5 && i < arr.length; i++) {
|
||
rows +=
|
||
'<div class="top-row">' +
|
||
'<span class="top-rank">' + (i + 1) + "</span>" +
|
||
'<div class="top-info"><div class="top-name">' + arr[i].name + '</div><div class="top-meta">' + arr[i].dir + "</div></div>" +
|
||
'<span class="top-sum">' + fmtMoneyShort(arr[i].sum) + "</span></div>";
|
||
}
|
||
document.getElementById("topClients").innerHTML =
|
||
'<div class="card-cap">Крупнейшие клиенты периода</div>' +
|
||
'<div class="toplist">' + rows + "</div>";
|
||
}
|
||
|
||
/* ---------- Детализация: фильтры и таблица ---------- */
|
||
var DIRS = [
|
||
{ id: "almaty", name: "Алматы" },
|
||
{ id: "astana", name: "Астана" },
|
||
{ id: "karaganda", name: "Караганда" },
|
||
{ id: "kostanay", name: "Костанай" },
|
||
{ id: "aktau", name: "Актау" },
|
||
{ id: "kokshetau", name: "Кокшетау" },
|
||
{ id: "shymkent", name: "Шымкент" }
|
||
];
|
||
|
||
function renderDirChips() {
|
||
var bar = document.getElementById("dirFilters");
|
||
var html =
|
||
'<button class="kt-ai-filter-chip" data-dir="" type="button' +
|
||
(state.dir === "" ? '" data-active="true' : "") + ">Все направления</button>";
|
||
for (var i = 0; i < DIRS.length; i++) {
|
||
html +=
|
||
'<button class="kt-ai-filter-chip" data-dir="' + DIRS[i].id + '" type="button' +
|
||
(state.dir === DIRS[i].id ? '" data-active="true' : "") + ">" + DIRS[i].name + "</button>";
|
||
}
|
||
bar.innerHTML = html;
|
||
}
|
||
|
||
function renderDeals() {
|
||
var all = closedList(dealsOf(state.period));
|
||
var dirList = applyDirFilter(all);
|
||
var q = state.search.trim().toLowerCase();
|
||
var rows = [];
|
||
for (var i = 0; i < dirList.length; i++) {
|
||
var d = dirList[i];
|
||
if (q &&
|
||
d.client.toLowerCase().indexOf(q) === -1 &&
|
||
d.manager.toLowerCase().indexOf(q) === -1 &&
|
||
d.service.toLowerCase().indexOf(q) === -1 &&
|
||
d.category.toLowerCase().indexOf(q) === -1) continue;
|
||
rows.push(d);
|
||
}
|
||
rows.sort(function (a, b) { return a.date < b.date ? 1 : -1; });
|
||
|
||
var body = document.getElementById("dealsBody");
|
||
var html = "";
|
||
var limit = 50;
|
||
for (i = 0; i < rows.length && i < limit; i++) {
|
||
var st = statusInfo(rows[i].status);
|
||
html +=
|
||
"<tr>" +
|
||
'<td class="kt-ai-deal-c"><strong>' + rows[i].client + '</strong><span class="deal-dir">' + rows[i].dir + "</span></td>" +
|
||
"<td>" + rows[i].service + '<span class="deal-dir">' + rows[i].category + "</span></td>" +
|
||
"<td>" + rows[i].manager + "</td>" +
|
||
'<td class="num">' + fmtMoney(rows[i].amount) + "</td>" +
|
||
'<td><span class="kt-ai-status-pill" data-status="' + st.status + '">' + st.label + "</span></td>" +
|
||
"</tr>";
|
||
}
|
||
body.innerHTML = html;
|
||
document.getElementById("dealsEmpty").hidden = rows.length > 0;
|
||
var totalSum = sumDeals(rows);
|
||
document.getElementById("dealsCount").textContent = rows.length + " сделок";
|
||
document.getElementById("dealsSum").textContent = "Сумма: " + fmtMoney(totalSum);
|
||
document.getElementById("dealsTable").style.display = rows.length ? "" : "none";
|
||
}
|
||
|
||
/* ---------- Навигация ---------- */
|
||
function setView(view) {
|
||
state.view = view;
|
||
lsSet(LS_VIEW, view);
|
||
document.getElementById("view-overview").hidden = view !== "overview";
|
||
document.getElementById("view-detail").hidden = view !== "detail";
|
||
var items = document.querySelectorAll(".kt-ai-nav-item[data-view]");
|
||
for (var i = 0; i < items.length; i++) {
|
||
if (items[i].getAttribute("data-view") === view) items[i].classList.add("is-active");
|
||
else items[i].classList.remove("is-active");
|
||
}
|
||
document.getElementById("pageTitle").textContent = view === "overview" ? "Продажи B2B" : "Детализация";
|
||
document.getElementById("crumb").textContent = view === "overview" ? "Обзор" : "Обзор · " + PERIOD_BY_ID[state.period].label;
|
||
renderAll();
|
||
}
|
||
|
||
function setPeriod(kind) {
|
||
/* kind: month | quarter | year */
|
||
var pick = kind === "year" ? "year" : (kind === "quarter" ? "q2" : "sep");
|
||
state.period = pick;
|
||
lsSet(LS_PERIOD, pick);
|
||
renderAll();
|
||
}
|
||
|
||
function renderAll() {
|
||
renderOverview();
|
||
renderDeals();
|
||
document.getElementById("navDealCount").textContent = String(dealsOf(state.period).length);
|
||
}
|
||
|
||
/* ---------- События ---------- */
|
||
function bind() {
|
||
var seg = document.getElementById("periodSeg");
|
||
var segBtns = seg.querySelectorAll(".seg");
|
||
for (var s = 0; s < segBtns.length; s++) {
|
||
segBtns[s].addEventListener("click", function () {
|
||
setPeriod(this.getAttribute("data-period"));
|
||
syncPeriodSeg();
|
||
});
|
||
}
|
||
var navItems = document.querySelectorAll(".kt-ai-nav-item[data-view]");
|
||
for (s = 0; s < navItems.length; s++) {
|
||
navItems[s].addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
setView(this.getAttribute("data-view"));
|
||
if (history.replaceState) history.replaceState(null, "", "#" + state.view);
|
||
});
|
||
}
|
||
var chips = document.querySelectorAll("#dirFilters .kt-ai-filter-chip");
|
||
for (s = 0; s < chips.length; s++) {
|
||
chips[s].addEventListener("click", function () {
|
||
state.dir = this.getAttribute("data-dir") || "";
|
||
lsSet(LS_DIR, state.dir);
|
||
renderDirChips();
|
||
renderAll();
|
||
});
|
||
}
|
||
document.getElementById("dealSearch").addEventListener("input", function () {
|
||
state.search = this.value;
|
||
renderDeals();
|
||
});
|
||
syncPeriodSeg();
|
||
}
|
||
|
||
function syncPeriodSeg() {
|
||
var per = PERIOD_BY_ID[state.period];
|
||
var kind = per.period.toLowerCase() === "год" ? "year" : (per.period === "Квартал" ? "quarter" : "month");
|
||
var btns = document.querySelectorAll("#periodSeg .seg");
|
||
for (var i = 0; i < btns.length; i++) {
|
||
if (btns[i].getAttribute("data-period") === kind) btns[i].classList.add("is-active");
|
||
else btns[i].classList.remove("is-active");
|
||
}
|
||
}
|
||
|
||
/* ---------- Старт ---------- */
|
||
function start() {
|
||
document.getElementById("todayDate").textContent = "Данные на 25.09.2026";
|
||
renderDirChips();
|
||
bind();
|
||
var hash = location.hash.replace("#", "");
|
||
setView(hash === "detail" ? "detail" : state.view);
|
||
}
|
||
|
||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start);
|
||
else start();
|
||
})();
|