279 lines
23 KiB
HTML
279 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<meta name="color-scheme" content="light">
|
||
<title>DRB Казахтелеком B2C</title>
|
||
<link rel="icon" href="data:,">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-page.css">
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box}
|
||
[hidden]{display:none!important}
|
||
html,body{margin:0;background:var(--kt-ai-bg);color:var(--kt-ai-fg);font-family:var(--kt-ai-font-sans)}
|
||
.wrap{max-width:var(--kt-ai-content-wide,1280px);margin:0 auto;padding:0 var(--kt-ai-space-5) var(--kt-ai-space-10)}
|
||
.top{position:sticky;top:0;z-index:var(--kt-ai-z-sticky,20);background:var(--kt-ai-bg);border-bottom:1px solid var(--kt-ai-border)}
|
||
.top-in{display:flex;align-items:center;gap:var(--kt-ai-space-4);min-height:56px;padding:var(--kt-ai-space-2) 0}
|
||
.top img{width:36px;height:36px;flex-shrink:0}
|
||
.top .head{min-width:0;flex:1}
|
||
.top h1{margin:0;font-size:var(--kt-ai-text-lg);font-weight:var(--kt-ai-weight-semibold);line-height:var(--kt-ai-leading-tight);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.top .sub{margin:2px 0 0;font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.top .tools{display:flex;align-items:center;gap:var(--kt-ai-space-3);flex-shrink:0}
|
||
.iconbtn{width:32px;height:32px;display:inline-grid;place-items:center;border-radius:var(--kt-ai-radius-full);border:1px solid var(--kt-ai-border);background:var(--kt-ai-bg-elevated);color:var(--kt-ai-fg);cursor:pointer;padding:0}
|
||
.iconbtn:hover{background:var(--kt-ai-bg-hover)}
|
||
.iconbtn svg{width:16px;height:16px}
|
||
[data-theme="light"] .ic-sun,[data-theme="dark"] .ic-moon{display:none}
|
||
.section{margin-top:var(--kt-ai-space-6)}
|
||
.section-h{display:flex;align-items:baseline;justify-content:space-between;gap:var(--kt-ai-space-4);margin:0 0 var(--kt-ai-space-3)}
|
||
.section-h h2{margin:0;font-size:var(--kt-ai-text-md);font-weight:var(--kt-ai-weight-semibold)}
|
||
.section-h .muted{font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted)}
|
||
.charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--kt-ai-stack-block,16px)}
|
||
.chart-card h3{margin:0 0 var(--kt-ai-space-4);font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-semibold);display:flex;justify-content:space-between;align-items:baseline;gap:var(--kt-ai-space-3);flex-wrap:wrap}
|
||
.chart-card h3 .ttl{white-space:nowrap}
|
||
.chart-card h3 .muted{font-weight:var(--kt-ai-weight-regular);color:var(--kt-ai-fg-muted);font-size:var(--kt-ai-text-xs)}
|
||
.bars{display:flex;flex-direction:column;gap:6px}
|
||
.brow{display:grid;grid-template-columns:minmax(96px,34%) minmax(0,1fr) auto;align-items:center;gap:var(--kt-ai-space-3);min-height:24px;padding:2px 6px;margin:0 -6px;border-radius:var(--kt-ai-radius-sm);background:none;border:0;color:inherit;font:inherit;text-align:left;cursor:default}
|
||
.brow[data-pick="true"]{cursor:pointer}
|
||
.brow[data-pick="true"]:hover{background:var(--kt-ai-bg-hover)}
|
||
.brow[data-active="true"]{background:var(--kt-ai-bg-active)}
|
||
.brow .lbl{font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.brow .track{display:block;height:10px;border-radius:2px;background:var(--kt-ai-bg-sunken);overflow:hidden}
|
||
.brow .fill{display:block;height:100%;border-radius:2px;background:var(--kt-ai-chart-blue);transition:width var(--kt-ai-dur-base) var(--kt-ai-ease-standard)}
|
||
.chart-card[data-tone="green"] .fill{background:var(--kt-ai-chart-green)}
|
||
.brow .val{font-family:var(--kt-ai-font-mono);font-size:var(--kt-ai-text-2xs);color:var(--kt-ai-fg-muted);font-variant-numeric:tabular-nums;white-space:nowrap}
|
||
.tb{display:flex;align-items:center;gap:var(--kt-ai-space-3);flex-wrap:wrap;min-height:40px;margin-bottom:var(--kt-ai-space-3)}
|
||
.tb .grow{flex:1 1 220px;min-width:0}
|
||
.tb .kt-ai-select-wrap{flex:0 1 220px}
|
||
.tb .count{font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted);white-space:nowrap;margin-left:auto}
|
||
.chips{display:flex;gap:var(--kt-ai-space-2);flex-wrap:wrap;margin-bottom:var(--kt-ai-space-3)}
|
||
.chips:empty{display:none}
|
||
.tfoot{display:flex;align-items:center;justify-content:space-between;gap:var(--kt-ai-space-3);flex-wrap:wrap;padding:var(--kt-ai-space-3) 0;font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted)}
|
||
@media(max-width:639.98px){
|
||
.wrap{padding-left:var(--kt-ai-space-4);padding-right:var(--kt-ai-space-4)}
|
||
.top .sub{display:none}
|
||
.kt-ai-kpi-strip[data-cards="true"]{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--kt-ai-space-3)}
|
||
.kt-ai-kpi-strip[data-cards="true"] .kt-ai-kpi:last-child{grid-column:1/-1}
|
||
.kt-ai-kpi-strip[data-cards="true"] .value{font-size:var(--kt-ai-text-lg)}
|
||
.chart-card h3 .muted{flex:1 1 100%}
|
||
.tb .kt-ai-search-field,.tb .kt-ai-search{display:block}
|
||
.tb .kt-ai-select-wrap{flex-basis:100%}
|
||
.tb .count{margin-left:0;width:100%}
|
||
.brow{grid-template-columns:minmax(80px,40%) minmax(0,1fr) auto}
|
||
}
|
||
@media(prefers-reduced-motion:reduce){.brow .fill{transition:none}}
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app" data-kt-data-mode="demo">
|
||
<header class="top">
|
||
<div class="wrap top-in">
|
||
<img src="./kt-logo.svg" alt="Казахтелеком">
|
||
<div class="head">
|
||
<h1>DRB Казахтелеком B2C</h1>
|
||
<p class="sub" id="sub">Июль 2026 · покупки и выручка по регионам</p>
|
||
</div>
|
||
<div class="tools">
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
<div class="kt-ai-segmented" role="tablist" aria-label="Метрика">
|
||
<button class="seg" type="button" role="tab" data-metric="qty" data-active="true">Покупки</button>
|
||
<button class="seg" type="button" role="tab" data-metric="sum">Выручка</button>
|
||
</div>
|
||
<button class="iconbtn" id="theme" type="button" aria-label="Переключить тему">
|
||
<svg class="ic-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
|
||
<svg class="ic-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="wrap">
|
||
<section class="section" aria-label="Ключевые показатели">
|
||
<div class="kt-ai-kpi-strip" data-cards="true">
|
||
<div class="kt-ai-kpi"><div class="value" id="k-qty">—</div><div class="label">Покупок</div><div class="hint">за июль 2026</div></div>
|
||
<div class="kt-ai-kpi"><div class="value" id="k-sum">—</div><div class="label">Выручка</div><div class="hint">по 50 позициям</div></div>
|
||
<div class="kt-ai-kpi"><div class="value" id="k-regions">—</div><div class="label">Регионов</div><div class="hint">в выборке</div></div>
|
||
<div class="kt-ai-kpi"><div class="value" id="k-lead" data-long="true">—</div><div class="label">Услуга-лидер</div><div class="hint" id="k-lead-hint">по покупкам</div></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section charts" aria-label="Графики">
|
||
<div class="kt-ai-card chart-card" data-tone="blue">
|
||
<h3><span class="ttl">Регионы</span> <span class="muted" id="c-regions-hint">по покупкам · нажмите, чтобы отфильтровать</span></h3>
|
||
<div class="bars" id="c-regions"></div>
|
||
</div>
|
||
<div class="kt-ai-card chart-card" data-tone="green">
|
||
<h3><span class="ttl">Услуги</span> <span class="muted" id="c-services-hint">по покупкам · топ-8</span></h3>
|
||
<div class="bars" id="c-services"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section" aria-label="Реестр позиций">
|
||
<div class="section-h"><h2>Позиции</h2><span class="muted">услуга × регион</span></div>
|
||
<div class="tb">
|
||
<div class="kt-ai-search-field grow" data-compact="true"><input class="kt-ai-search kt-ai-input" id="q" type="search" placeholder="Поиск по услуге…" autocomplete="off" aria-label="Поиск по услуге"></div>
|
||
<div class="kt-ai-select-wrap"><select class="kt-ai-select" id="region" aria-label="Регион"><option value="">Все регионы</option></select></div>
|
||
<span class="count" id="count"></span>
|
||
</div>
|
||
<div class="chips" id="chips"></div>
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table" id="tbl">
|
||
<thead><tr>
|
||
<th data-sort="service">Услуга</th>
|
||
<th class="col-region" data-sort="region">Регион</th>
|
||
<th class="num" data-sort="qty" data-dir="desc">Покупок</th>
|
||
<th class="num" data-sort="sum">Сумма, ₸</th>
|
||
</tr></thead>
|
||
<tbody id="rows"></tbody>
|
||
</table>
|
||
<div class="kt-ai-empty" id="empty" hidden><div class="title">Ничего не найдено</div><div>Измените запрос или снимите фильтр по региону</div></div>
|
||
</div>
|
||
<div class="tfoot">
|
||
<span id="foot"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" id="more" type="button" hidden>Показать ещё</button>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<script>
|
||
const FILTERS=[{id:"region",label:"Регион",affects:["table","chart","kpi"]},{id:"q",label:"Поиск по услуге",affects:["table"]}];
|
||
const DATA = [
|
||
{"region": "Астана", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 37372, "sum": 238463786.09},
|
||
{"region": "г.Алматы", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 39671, "sum": 237005698.16},
|
||
{"region": "г.Алматы", "service": "Абонплата ШПД по FTTx для B2C", "qty": 35588, "sum": 152893561.28},
|
||
{"region": "Астана", "service": "2025 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 35637, "sum": 136187938.99},
|
||
{"region": "Астана", "service": "B2C TV Plus Абонплата Full ГТС", "qty": 47661, "sum": 132060372.93},
|
||
{"region": "Астана", "service": "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", "qty": 61700, "sum": 123403164.43},
|
||
{"region": "г.Алматы", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 57378, "sum": 110497209.55},
|
||
{"region": "г.Алматы", "service": "2025 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 29997, "sum": 106749675.59},
|
||
{"region": "Астана", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 52504, "sum": 99235780.41},
|
||
{"region": "Астана", "service": "2022 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 36946, "sum": 96430127.27},
|
||
{"region": "Астана", "service": "Абонплата ШПД по FTTx для B2C", "qty": 19565, "sum": 88834659.73},
|
||
{"region": "Астана", "service": "2024 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 22167, "sum": 83549325.34},
|
||
{"region": "г.Алматы", "service": "B2C TV Plus Абонплата Full ГТС", "qty": 31221, "sum": 83511013.05},
|
||
{"region": "г.Алматы", "service": "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", "qty": 40838, "sum": 82748215.46},
|
||
{"region": "г.Семей", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 41486, "sum": 76400272.02},
|
||
{"region": "Астана", "service": "TV Plus Абонплата за Премиальный пакет ГТС", "qty": 29210, "sum": 71278165.09},
|
||
{"region": "г.Алматы", "service": "2022 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 24001, "sum": 62521274.3},
|
||
{"region": "Астана", "service": "2021 B2C Абонплата за мобильный номер Kcell GSM откр.", "qty": 27864, "sum": 59635624.39},
|
||
{"region": "г.Алматы", "service": "2024 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 15678, "sum": 58970815.48},
|
||
{"region": "г.Актобе", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 30641, "sum": 55819936.98},
|
||
{"region": "г.Алматы", "service": "B2C Абонплата за основной телефон ГТС", "qty": 52732, "sum": 49636818.5},
|
||
{"region": "г.Актобе", "service": "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", "qty": 25178, "sum": 48000483.43},
|
||
{"region": "г.Алматы", "service": "TV Plus Абонплата за Премиальный пакет ГТС", "qty": 19055, "sum": 45298192.89},
|
||
{"region": "г.Павлодар", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 23334, "sum": 44857518.84},
|
||
{"region": "г.Алматы", "service": "B2C TV Plus Аренда STB оператора новое", "qty": 96183, "sum": 43252519.71},
|
||
{"region": "г.Караганда", "service": "Абонплата ШПД по FTTx для B2C", "qty": 8916, "sum": 41624727.66},
|
||
{"region": "Астана", "service": "2026 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 13685, "sum": 41618770.34},
|
||
{"region": "г.Алматы", "service": "2018 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 11568, "sum": 40199964.35},
|
||
{"region": "г.Тараз", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 21880, "sum": 39564813.34},
|
||
{"region": "г.Алматы", "service": "Абонплата ШПД по ADSL для B2C", "qty": 10710, "sum": 36238454.72},
|
||
{"region": "г.Алматы", "service": "2020 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 12148, "sum": 36010481.68},
|
||
{"region": "г.Усть-Каменогорск", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 18362, "sum": 35883527.39},
|
||
{"region": "г. Шымкент", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 19180, "sum": 35338634.51},
|
||
{"region": "г. Шымкент", "service": "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", "qty": 18945, "sum": 35138664.91},
|
||
{"region": "Астана", "service": "B2C TV Plus Аренда STB оператора новое", "qty": 76655, "sum": 34746783.92},
|
||
{"region": "Астана", "service": "2018 B2C Абонплата за Домашний интернет FTTx ГТС", "qty": 8705, "sum": 32905725.03},
|
||
{"region": "г.Актобе", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 6742, "sum": 32313021.91},
|
||
{"region": "г.Усть-Каменогорск", "service": "Абонплата ШПД по FTTx для B2C", "qty": 7269, "sum": 31834355.15},
|
||
{"region": "г.Атырау", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 5495, "sum": 31688118.61},
|
||
{"region": "г.Усть-Каменогорск", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 5600, "sum": 31520413.53},
|
||
{"region": "Астана", "service": "B2C Аренда оборудования ONT оператора - новое", "qty": 87071, "sum": 31220264.09},
|
||
{"region": "г.Павлодар", "service": "B2C Абонплата ШПД по FTTx ГТС (New)", "qty": 6104, "sum": 30831455.08},
|
||
{"region": "г.Кокшетау", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 16818, "sum": 30654924.73},
|
||
{"region": "г.Актобе", "service": "2025 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 8613, "sum": 30562705.78},
|
||
{"region": "г.Костанай", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 16473, "sum": 30539180.35},
|
||
{"region": "Енбекшиказахский район", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 17347, "sum": 30429552.38},
|
||
{"region": "г.Актобе", "service": "2022 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 11709, "sum": 30303557.0},
|
||
{"region": "г.Кызылорда", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 16539, "sum": 29772241.67},
|
||
{"region": "г.Алматы", "service": "2026 B2C Абонплата за Домашний Интернет FTTx ГТС", "qty": 10206, "sum": 29200316.21},
|
||
{"region": "г.Караганда", "service": "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", "qty": 15714, "sum": 28837871.98}
|
||
];
|
||
const $ = id => document.getElementById(id);
|
||
const nf = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 0 });
|
||
const fmt = n => nf.format(Math.round(n));
|
||
const short = n => n >= 1e9 ? (n / 1e9).toFixed(2).replace(".", ",") + " млрд" : n >= 1e6 ? (n / 1e6).toFixed(1).replace(".", ",") + " млн" : n >= 1e3 ? (n / 1e3).toFixed(0) + " тыс." : fmt(n);
|
||
const esc = s => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||
const PAGE = 25;
|
||
const state = { metric: "qty", region: "", q: "", sort: "qty", dir: "desc", shown: PAGE };
|
||
|
||
$("theme").onclick = () => {
|
||
const t = document.documentElement.getAttribute("data-theme") === "dark" ? "light" : "dark";
|
||
document.documentElement.setAttribute("data-theme", t);
|
||
try { localStorage.setItem("demo.theme", t); } catch (e) {}
|
||
};
|
||
|
||
const totalQty = DATA.reduce((s, r) => s + r.qty, 0), totalSum = DATA.reduce((s, r) => s + r.sum, 0);
|
||
const regions = [...new Set(DATA.map(r => r.region))].sort((a, b) => a.localeCompare(b, "ru"));
|
||
$("k-qty").textContent = fmt(totalQty);
|
||
$("k-sum").textContent = short(totalSum) + " ₸";
|
||
$("k-regions").textContent = regions.length;
|
||
for (const r of regions) { const o = document.createElement("option"); o.value = r; o.textContent = r; $("region").appendChild(o); }
|
||
|
||
function agg(key) {
|
||
const m = new Map();
|
||
for (const r of DATA) m.set(r[key], (m.get(r[key]) || 0) + r[state.metric]);
|
||
return [...m.entries()].sort((a, b) => b[1] - a[1]);
|
||
}
|
||
function bars(el, items, pick) {
|
||
const max = items[0]?.[1] || 1;
|
||
el.innerHTML = items.map(([label, v]) => `<${pick ? "button type=\"button\"" : "div"} class="brow" ${pick ? `data-pick="true" data-region="${esc(label)}" data-active="${state.region === label}"` : ""} title="${esc(label)}: ${fmt(v)}">
|
||
<span class="lbl">${esc(label)}</span><span class="track"><span class="fill" style="width:${Math.max(2, v / max * 100).toFixed(1)}%"></span></span><span class="val">${state.metric === "sum" ? short(v) : fmt(v)}</span></${pick ? "button" : "div"}>`).join("");
|
||
}
|
||
function renderTop() {
|
||
const byRegion = agg("region"), byService = agg("service");
|
||
const mlabel = state.metric === "sum" ? "по выручке" : "по покупкам";
|
||
const lead = byService[0];
|
||
$("k-lead").textContent = lead[0]; $("k-lead").title = lead[0]; $("k-lead-hint").textContent = mlabel + " · " + (state.metric === "sum" ? short(lead[1]) + " ₸" : fmt(lead[1]));
|
||
$("c-regions-hint").textContent = mlabel + " · нажмите, чтобы отфильтровать";
|
||
$("c-services-hint").textContent = mlabel + " · топ-8";
|
||
bars($("c-regions"), byRegion.slice(0, 10), true);
|
||
bars($("c-services"), byService.slice(0, 8), false);
|
||
}
|
||
$("c-regions").addEventListener("click", e => {
|
||
const b = e.target.closest("[data-region]"); if (!b) return;
|
||
setRegion(state.region === b.dataset.region ? "" : b.dataset.region);
|
||
});
|
||
document.querySelectorAll(".kt-ai-segmented .seg").forEach(b => b.onclick = () => {
|
||
state.metric = b.dataset.metric;
|
||
document.querySelectorAll(".kt-ai-segmented .seg").forEach(x => x.dataset.active = String(x === b));
|
||
state.sort = state.metric; state.dir = "desc"; state.shown = PAGE;
|
||
renderTop(); renderTable();
|
||
});
|
||
|
||
function setRegion(r) { state.region = r; $("region").value = r; state.shown = PAGE; renderTop(); renderTable(); }
|
||
$("region").onchange = () => setRegion($("region").value);
|
||
$("q").oninput = () => { state.q = $("q").value.trim().toLowerCase(); state.shown = PAGE; renderTable(); };
|
||
document.querySelectorAll("th[data-sort]").forEach(th => th.onclick = () => {
|
||
const k = th.dataset.sort;
|
||
state.dir = state.sort === k && state.dir === "desc" ? "asc" : (state.sort === k ? "desc" : (k === "qty" || k === "sum" ? "desc" : "asc"));
|
||
state.sort = k; state.shown = PAGE; renderTable();
|
||
});
|
||
$("more").onclick = () => { state.shown += PAGE; renderTable(); };
|
||
const hl = (s, q) => { if (!q) return esc(s); const i = s.toLowerCase().indexOf(q); return i < 0 ? esc(s) : esc(s.slice(0, i)) + "<mark>" + esc(s.slice(i, i + q.length)) + "</mark>" + esc(s.slice(i + q.length)); };
|
||
function renderTable() {
|
||
const rows = DATA.filter(r => (!state.region || r.region === state.region) && (!state.q || r.service.toLowerCase().includes(state.q)));
|
||
const k = state.sort, d = state.dir === "asc" ? 1 : -1;
|
||
rows.sort((a, b) => (typeof a[k] === "number" ? (a[k] - b[k]) : String(a[k]).localeCompare(String(b[k]), "ru")) * d);
|
||
document.querySelectorAll("th[data-sort]").forEach(th => { if (th.dataset.sort === k) th.dataset.dir = state.dir; else th.removeAttribute("data-dir"); });
|
||
const shown = rows.slice(0, state.shown);
|
||
$("rows").innerHTML = shown.map(r => `<tr>
|
||
<td class="svc"><span class="t" title="${esc(r.service)}">${hl(r.service, state.q)}</span><span class="m">${esc(r.region)}</span></td>
|
||
<td class="col-region" data-label="Регион">${esc(r.region)}</td>
|
||
<td class="num" data-label="Покупок"><span class="v">${fmt(r.qty)}</span></td>
|
||
<td class="num" data-label="Сумма, ₸"><span class="v">${fmt(r.sum)}</span></td></tr>`).join("");
|
||
$("empty").hidden = rows.length > 0; $("tbl").hidden = rows.length === 0;
|
||
const q = rows.reduce((s, r) => s + r.qty, 0), sum = rows.reduce((s, r) => s + r.sum, 0);
|
||
$("count").textContent = rows.length === DATA.length ? `${rows.length} позиций` : `${rows.length} из ${DATA.length}`;
|
||
$("foot").textContent = rows.length ? `Показано ${shown.length} из ${rows.length} · итого ${fmt(q)} покупок · ${short(sum)} ₸` : "";
|
||
$("more").hidden = shown.length >= rows.length;
|
||
$("more").textContent = `Показать ещё ${Math.min(PAGE, rows.length - shown.length)}`;
|
||
$("chips").innerHTML = state.region ? `<button class="kt-ai-chip" data-tone="blue" data-removable type="button" id="chip-r">${esc(state.region)} <span class="remove" aria-label="Снять фильтр">×</span></button>` : "";
|
||
const c = $("chip-r"); if (c) c.onclick = () => setRegion("");
|
||
}
|
||
renderTop(); renderTable();
|
||
</script>
|
||
</body>
|
||
</html>
|