dashbord-top-uslug-b2c-po-re-2/index.html

530 lines
41 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Топ услуг B2C по регионам</title>
<script data-kt-ai-base-resolver>
(function () {
var корень = document.documentElement;
function норм(значение) {
значение = (значение || "").trim();
if (!значение || значение === "/") return "";
if (значение.charAt(0) !== "/") значение = "/" + значение;
return значение.replace(/\/+$/, "");
}
var база = норм(корень.getAttribute("data-kt-ai-base"));
var папка = норм(корень.getAttribute("data-kt-ai-static")) || "/design-system";
window.ktAiBase = база;
window.ktAiAsset = function (имя) {
return /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(имя) ? имя : база + папка + "/" + имя;
};
})();
</script>
<script data-kt-ai-boot="css">
["kt-ai-tokens.css", "kt-ai-components.css"].forEach(function (файл) {
var ссылка = document.createElement("link");
ссылка.rel = "stylesheet";
ссылка.href = window.ktAiAsset(файл);
document.head.appendChild(ссылка);
});
</script>
<style>
@layer product {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
.shell-topbar-burger { display: none; }
@media (max-width: 831.98px) { .shell-topbar-burger { display: inline-flex; } }
.sb-top { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: 2px 4px 12px; }
.sb-top img { width: 28px; height: 28px; border-radius: var(--kt-ai-radius-xl); flex-shrink: 0; }
.sb-top .nm { font-weight: var(--kt-ai-weight-semibold); font-size: var(--kt-ai-text-sm); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.sb-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 2px; }
.sb-bottom .badge { margin-left: auto; font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-2xs); background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); border-radius: var(--kt-ai-radius-full); padding: 0 6px; }
.sb-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 2px; padding-top: var(--kt-ai-space-4); }
.sb-bottom button { background: none; border: none; font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; }
.sb-bottom .sb-tour { display: flex; align-items: flex-start; gap: var(--kt-ai-space-4); padding: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-3); border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-xl); background: var(--kt-ai-bg-elevated); transition: background var(--kt-ai-dur-fast) var(--kt-ai-ease-standard); }
.sb-bottom .sb-tour:hover { background: var(--kt-ai-bg-soft); }
.sb-tour .ic { display: grid; place-items: center; width: 28px; height: 28px; flex-shrink: 0; border-radius: var(--kt-ai-radius-lg); background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); }
.sb-tour .tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sb-tour .tx strong { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg); }
.sb-tour .tx span { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
@media (min-width: 832px) and (max-width: 1023.98px) {
.sb-bottom .sb-tour { justify-content: center; padding: var(--kt-ai-space-3); border: 0; background: none; }
.sb-tour .tx { display: none; }
}
.sb-util[data-on="true"] { color: var(--kt-ai-primary); background: var(--kt-ai-primary-subtle); }
.kt-ai-shell[data-rail="true"] .sb-tour { justify-content: center; padding: var(--kt-ai-space-3); }
.kt-ai-shell[data-rail="true"] .sb-tour .tx { display: none; }
.onb-card { position: relative; width: 420px; max-width: calc(100vw - 32px); background: var(--kt-ai-bg-elevated); border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-3xl); box-shadow: var(--kt-ai-shadow-lg); padding: var(--kt-ai-space-8); text-align: center; }
.onb-x { position: absolute; top: var(--kt-ai-space-4); right: var(--kt-ai-space-4); }
.onb-ic { width: 56px; height: 56px; margin: 0 auto var(--kt-ai-space-6); display: grid; place-items: center; border-radius: var(--kt-ai-radius-2xl); background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); }
.onb-ic .kt-icon { width: 26px; height: 26px; }
.onb-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
.onb-text { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); line-height: var(--kt-ai-leading-normal); margin-bottom: var(--kt-ai-space-7); }
.onb-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--kt-ai-space-5); }
.onb-dots { display: flex; gap: 6px; }
.onb-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--kt-ai-border-strong); transition: background var(--kt-ai-dur-fast), width var(--kt-ai-dur-fast); }
.onb-dots span.on { background: var(--kt-ai-primary); width: 18px; border-radius: 3px; }
.onb-btns { display: flex; gap: var(--kt-ai-space-3); }
.menu-up .kt-ai-menu { top: auto; bottom: calc(100% + 4px); right: auto; left: 0; }
.kt-ai-shell[data-rail="true"] { grid-template-columns: var(--kt-ai-sidebar-collapsed-width) minmax(0, 1fr); }
.kt-ai-shell[data-rail="true"] .nav-label, .kt-ai-shell[data-rail="true"] .kt-ai-nav-item .count,
.kt-ai-shell[data-rail="true"] .sb-top .nm, .kt-ai-shell[data-rail="true"] .sb-bottom .badge { display: none; }
.kt-ai-shell[data-rail="true"] .kt-ai-nav-item { justify-content: center; padding: 0; }
.kt-ai-shell[data-rail="true"] .sb-top { flex-direction: column; gap: var(--kt-ai-space-3); padding-left: 0; padding-right: 0; }
.topbar-ai {
display: inline-flex; align-items: center; gap: var(--kt-ai-space-2); flex-shrink: 0;
height: 32px; padding: 0 var(--kt-ai-space-4); border-radius: var(--kt-ai-radius-full);
border: 1px solid var(--kt-ai-border-strong); background: var(--kt-ai-bg-elevated); color: var(--kt-ai-fg);
cursor: pointer; font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium);
transition: background var(--kt-ai-dur-fast);
}
.topbar-ai:hover { background: var(--kt-ai-bg-hover); }
.topbar-ai .orb { width: 16px; height: 16px; border-radius: 50%; background: var(--kt-ai-ai-orb); flex-shrink: 0; }
@media (max-width: 639.98px) { .topbar-ai { padding: 0; width: 32px; justify-content: center; } .topbar-ai .ai-label { display: none; } }
.topbar-title { display: flex; align-items: baseline; gap: var(--kt-ai-space-4); min-width: 0; }
.topbar-title .t { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-title .sub { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); white-space: nowrap; }
@media (max-width: 639.98px) { .topbar-title .sub { display: none; } }
.kt-ai-topbar .kt-ai-search-field:not([data-compact="true"]) { max-width: 240px; }
@media (max-width: 767.98px) { .kt-ai-topbar .kt-ai-search-field:not([data-compact="true"]) { max-width: 176px; } }
#view { width: 100%; max-width: var(--kt-ai-content-table); margin-inline: auto; padding: var(--kt-ai-stack-region) var(--kt-ai-stack-region) var(--kt-ai-space-11); }
@media (max-width: 639.98px) { #view { padding: var(--kt-ai-stack-group) var(--kt-ai-space-6) var(--kt-ai-space-10); } }
.page-hero { display: flex; align-items: flex-start; gap: var(--kt-ai-space-6); margin-bottom: var(--kt-ai-stack-group); }
.page-hero .ph-text { flex: 1; min-width: 0; }
.page-hero h1 { margin: 0; font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); letter-spacing: -.01em; line-height: 1.15; }
.page-hero .sub { margin-top: var(--kt-ai-space-3); font-size: var(--kt-ai-text-md); color: var(--kt-ai-fg-muted); }
.page-hero .ph-action { flex-shrink: 0; }
@media (max-width: 639.98px) { .page-hero { flex-direction: column; gap: var(--kt-ai-space-5); } .page-hero h1 { font-size: var(--kt-ai-text-2xl); } }
.topbar-crumb { display: flex; align-items: center; gap: var(--kt-ai-space-3); min-width: 0; font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
.shell-panel-toggle { display: inline-flex; }
@media (max-width: 831.98px) { .shell-panel-toggle { display: none; } }
.sb-new { margin: 0 4px var(--kt-ai-space-5); }
.sb-new .kt-ai-btn { width: 100%; justify-content: center; }
.kt-ai-shell[data-rail="true"] .sb-new .kt-ai-btn { padding: 0; }
.menu-user { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); }
.menu-user .nm { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg); }
.menu-user .role { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-muted); }
.notif-menu { min-width: 300px; max-width: 340px; padding: 0; }
.notif-menu .head { padding: var(--kt-ai-space-4) var(--kt-ai-space-5); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-semibold); border-bottom: 1px solid var(--kt-ai-divider); }
.guide-steps { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); margin: var(--kt-ai-space-5) 0; }
.guide-steps .gs { display: flex; gap: var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); align-items: flex-start; }
.guide-steps .gs .n { width: 20px; height: 20px; border-radius: 50%; background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); font-size: var(--kt-ai-text-2xs); font-weight: var(--kt-ai-weight-bold); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; }
#view > .kt-ai-kpi-strip { margin-bottom: var(--kt-ai-stack-block); }
.toolbar-row { display: flex; align-items: center; gap: var(--kt-ai-space-4); flex-wrap: wrap; padding: 0 0 var(--kt-ai-stack-block); }
.toolbar-row .spacer { flex: 1; }
.drawer-head { display: flex; align-items: flex-start; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
.drawer-head .t { flex: 1; }
.drawer-flags { display: flex; flex-direction: column; gap: var(--kt-ai-space-3); margin: var(--kt-ai-space-6) 0; }
.drawer-actions { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); margin-top: var(--kt-ai-space-8); }
.kt-ai-card .drawer-actions { flex-direction: row-reverse; justify-content: flex-start; }
.kt-ai-card .drawer-actions .kt-ai-btn { width: auto; }
@media (max-width: 639.98px) { .kt-ai-card .drawer-actions { flex-direction: column; } }
.ai-panel { display: flex; flex-direction: column; }
.ai-panel .kt-ai-chat { flex: 1; overflow-y: auto; padding-bottom: var(--kt-ai-space-6); }
.ai-panel-foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--kt-ai-space-4); }
.section-stub { margin-top: var(--kt-ai-space-10); }
.proto-label { margin-left: auto; }
td .kt-ai-checkbox input, th .kt-ai-checkbox input { margin: 0; }
.conf-cell { display: inline-flex; align-items: center; gap: var(--kt-ai-space-3); }
.conf-bar { width: 46px; height: 6px; border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-bg-sunken); overflow: hidden; flex-shrink: 0; }
.conf-bar > span { display: block; height: 100%; border-radius: var(--kt-ai-radius-full); }
.conf-cell[data-tone="ok"] .conf-bar > span { background: var(--kt-ai-status-ok-fg); }
.conf-cell[data-tone="warn"] .conf-bar > span { background: var(--kt-ai-status-warn-fg); }
.conf-cell[data-tone="risk"] .conf-bar > span { background: var(--kt-ai-status-risk-fg); }
.conf-pct { font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
.topbar-dot { position: absolute; top: 4px; right: 4px; width: 7px; height: 7px; border-radius: 50%; background: var(--kt-ai-primary); }
.kt-ai-topbar .kt-ai-menu { right: 0; left: auto; top: calc(100% + 6px); }
.drawer-sec { padding: var(--kt-ai-space-6) 0; border-top: 1px solid var(--kt-ai-divider); }
.drawer-sec:first-of-type { border-top: none; padding-top: 0; }
.drawer-sec.decision { border-top: 2px solid var(--kt-ai-border-strong); }
.agent-summary { margin: var(--kt-ai-space-4) 0; font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); line-height: var(--kt-ai-leading-normal); }
.agent-signal { display: flex; gap: var(--kt-ai-space-4); align-items: flex-start; padding: var(--kt-ai-space-3) 0; font-size: var(--kt-ai-text-sm); }
.agent-signal svg { color: var(--kt-ai-fg-faint); flex-shrink: 0; margin-top: 2px; }
.rec-box { display: flex; flex-direction: column; background: var(--kt-ai-primary-subtle); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-5); font-size: var(--kt-ai-text-sm); margin-top: var(--kt-ai-space-4); }
.cell-title { font-weight: var(--kt-ai-weight-medium); }
.cell-sub { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-act { text-align: right; }
.row-kebab { opacity: 0; transition: opacity var(--kt-ai-dur-fast); }
.kt-ai-table tbody tr:hover .row-kebab { opacity: 1; }
/* Chart placeholder styles */
.chart-wrap { background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-6); margin-bottom: var(--kt-ai-stack-block); }
.chart-title { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
.chart-bars { display: flex; align-items: flex-end; gap: var(--kt-ai-space-2); height: 200px; padding: var(--kt-ai-space-4) 0; }
.chart-bar { flex: 1; background: var(--kt-ai-primary); border-radius: var(--kt-ai-radius-md) var(--kt-ai-radius-md) 0 0; min-width: 24px; transition: opacity 0.2s; cursor: pointer; }
.chart-bar:hover { opacity: 0.8; }
.chart-labels { display: flex; gap: var(--kt-ai-space-2); margin-top: var(--kt-ai-space-3); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
.chart-labels span { flex: 1; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Region filter chips */
.chip-row { display: flex; gap: var(--kt-ai-space-2); flex-wrap: wrap; margin-bottom: var(--kt-ai-stack-block); }
.chip { padding: var(--kt-ai-space-2) var(--kt-ai-space-4); border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); cursor: pointer; border: 1px solid transparent; transition: all 0.2s; }
.chip:hover { background: var(--kt-ai-bg-hover); }
.chip.active { background: var(--kt-ai-primary); color: white; }
/* Summary tiles */
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-stack-block); }
.summary-tile { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-5); }
.summary-tile .label { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2); }
.summary-tile .value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg); }
.summary-tile .sub { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
}
</style>
</head>
<body class="kt-ai-app">
<script id="kt-app-config" type="application/json">
{
"product": {
"name": "Топ услуг B2C по регионам",
"purpose": "Дашборд руководителя · Сегмент B2C",
"entity": "услуга",
"view": "management"
},
"user": {
"name": "Руководитель направления B2C",
"role": "Директор по продуктам"
},
"kpis": [
{ "value": "1 178 651", "label": "всего покупок", "hint": "Июль 2026" },
{ "value": "14", "label": "регионов", "hint": "в топ-50" },
{ "value": "B2C Абонплата ШПД FTTx", "label": "услуга-лидер", "hint": "37 372 покупок в Астане" },
{ "value": "г.Алматы", "label": "регион-лидер", "hint": "237 млн ₸ сумма" }
],
"nav": [
{ "label": "Дашборд", "icon": "dashboard" },
{ "label": "Регионы", "icon": "map" },
{ "label": "Услуги", "icon": "catalog" },
{ "label": "Отчёты", "icon": "spreadsheet" }
],
"table": {
"columns": [
{ "key": "region", "label": "Регион", "type": "text", "source": "ClickHouse" },
{ "key": "service", "label": "Услуга", "type": "text", "source": "ClickHouse" },
{ "key": "qty", "label": "Кол-во покупок", "type": "num", "source": "ClickHouse", "align": "right" },
{ "key": "amount", "label": "Сумма (₸)", "type": "num", "source": "ClickHouse", "align": "right" },
{ "key": "status", "label": "Статус", "type": "status", "source": "агент" }
],
"rows": []
},
"statuses": {
"top": { "label": "Топ-10", "tone": "ok" },
"high": { "label": "Высокий спрос", "tone": "info" },
"medium": { "label": "Средний", "tone": "info" },
"growing": { "label": "Растущий", "tone": "warn" }
},
"filterKeys": ["region"],
"dashboard": {
"series": [
{ "key": "amount", "label": "Сумма", "color": "var(--kt-ai-primary)" }
],
"range": "Июль 2026",
"breakdowns": [
{ "title": "По регионам", "type": "bar", "groupKey": "region" },
{ "title": "Топ-10 услуг", "type": "rank", "limit": 10 }
]
}
}
</script>
<!-- embedded KT AI icon sprite -->
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<symbol id="dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></symbol>
<symbol id="map" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V21l9-4 9 4V3l-9 4z"/><path d="M12 3v18"/><path d="M21 7l-9-4-9 4"/></symbol>
<symbol id="catalog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></symbol>
<symbol id="spreadsheet" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M8 13h2"/><path d="M14 13h2"/><path d="M8 17h2"/><path d="M14 17h2"/></symbol>
<symbol id="search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></symbol>
<symbol id="filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"/></symbol>
<symbol id="download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></symbol>
<symbol id="chevronRight" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></symbol>
<symbol id="settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 5H3"/><path d="M12 19H3"/><path d="M14 3v4"/><path d="M16 17v4"/><path d="M21 12h-9"/><path d="M21 19h-5"/><path d="M21 5h-7"/><path d="M8 10v4"/></symbol>
<symbol id="chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16h8"/><path d="M7 11h12"/><path d="M7 6h3"/></symbol>
<symbol id="table" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"/></symbol>
<symbol id="sort" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m21 16-4 4-4-4"/><path d="M17 20V4"/><path d="m3 8 4-4 4 4"/><path d="M7 4v16"/></symbol>
<symbol id="help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></symbol>
<symbol id="bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></symbol>
<symbol id="user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
<symbol id="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></symbol>
<symbol id="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></symbol>
<symbol id="panelLeft" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></symbol>
<symbol id="menu" viewBox="0 0 24 24"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></symbol>
<symbol id="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></symbol>
<symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></symbol>
<symbol id="trophy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978"/><path d="M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978"/><path d="M18 9h1.5a1 1 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z"/><path d="M6 9H4.5a1 1 0 0 1 0-5H6"/></symbol>
<symbol id="building" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12h4"/><path d="M10 8h4"/><path d="M14 21v-3a2 2 0 0 0-4 0v3"/><path d="M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2"/><path d="M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16"/></symbol>
<symbol id="arrowUpDown" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m21 16-4 4-4-4"/><path d="M17 20V4"/><path d="m3 8 4-4 4 4"/><path d="M7 4v16"/></symbol>
<symbol id="chevronDown" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></symbol>
<symbol id="more" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></symbol>
<symbol id="external" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></symbol>
</svg>
<script>
// Данные из brief.md — все 50 строк
const RAW_DATA = [
{"region":"Астана","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":37372,"amount":238463786.09},
{"region":"г.Алматы","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":39671,"amount":237005698.16},
{"region":"г.Алматы","service":"Абонплата ШПД по FTTx для B2C","qty":35588,"amount":152893561.28},
{"region":"Астана","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":35637,"amount":136187938.99},
{"region":"Астана","service":"B2C TV Plus Абонплата Full ГТС","qty":47661,"amount":132060372.93},
{"region":"Астана","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":61700,"amount":123403164.43},
{"region":"г.Алматы","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":57378,"amount":110497209.55},
{"region":"г.Алматы","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":29997,"amount":106749675.59},
{"region":"Астана","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":52504,"amount":99235780.41},
{"region":"Астана","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":36946,"amount":96430127.27},
{"region":"Астана","service":"Абонплата ШПД по FTTx для B2C","qty":19565,"amount":88834659.73},
{"region":"Астана","service":"2024 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":22167,"amount":83549325.34},
{"region":"г.Алматы","service":"B2C TV Plus Абонплата Full ГТС","qty":31221,"amount":83511013.05},
{"region":"г.Алматы","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":40838,"amount":82748215.46},
{"region":"г.Семей","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":41486,"amount":76400272.02},
{"region":"Астана","service":"TV Plus Абонплата за Премиальный пакет ГТС","qty":29210,"amount":71278165.09},
{"region":"г.Алматы","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":24001,"amount":62521274.30},
{"region":"Астана","service":"2021 B2C Абонплата за мобильный номер Kcell GSM откр.","qty":27864,"amount":59635624.39},
{"region":"г.Алматы","service":"2024 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":15678,"amount":58970815.48},
{"region":"г.Актобе","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":30641,"amount":55819936.98},
{"region":"г.Алматы","service":"B2C Абонплата за основной телефон ГТС","qty":52732,"amount":49636818.50},
{"region":"г.Актобе","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":25178,"amount":48000483.43},
{"region":"г.Алматы","service":"TV Plus Абонплата за Премиальный пакет ГТС","qty":19055,"amount":45298192.89},
{"region":"г.Павлодар","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":23334,"amount":44857518.84},
{"region":"г.Алматы","service":"B2C TV Plus Аренда STB оператора новое","qty":96183,"amount":43252519.71},
{"region":"г.Караганда","service":"Абонплата ШПД по FTTx для B2C","qty":8916,"amount":41624727.66},
{"region":"Астана","service":"2026 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":13685,"amount":41618770.34},
{"region":"г.Алматы","service":"2018 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":11568,"amount":40199964.35},
{"region":"г.Тараз","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":21880,"amount":39564813.34},
{"region":"г.Алматы","service":"Абонплата ШПД по ADSL для B2C","qty":10710,"amount":36238454.72},
{"region":"г.Алматы","service":"2020 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":12148,"amount":36010481.68},
{"region":"г.Усть-Каменогорск","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":18362,"amount":35883527.39},
{"region":"г. Шымкент","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":19180,"amount":35338634.51},
{"region":"г. Шымкент","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":18945,"amount":35138664.91},
{"region":"Астана","service":"B2C TV Plus Аренда STB оператора новое","qty":76655,"amount":34746783.92},
{"region":"Астана","service":"2018 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":8705,"amount":32905725.03},
{"region":"г.Актобе","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":6742,"amount":32313021.91},
{"region":"г.Усть-Каменогорск","service":"Абонплата ШПД по FTTx для B2C","qty":7269,"amount":31834355.15},
{"region":"г.Атырау","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":5495,"amount":31688118.61},
{"region":"г.Усть-Каменогорск","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":5600,"amount":31520413.53},
{"region":"Астана","service":"B2C Аренда оборудования ONT оператора - новое","qty":87071,"amount":31220264.09},
{"region":"г.Павлодар","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":6104,"amount":30831455.08},
{"region":"г.Кокшетау","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16818,"amount":30654924.73},
{"region":"г.Актобе","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":8613,"amount":30562705.78},
{"region":"г.Костанай","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16473,"amount":30539180.35},
{"region":"Енбекшиказахский район","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":17347,"amount":30429552.38},
{"region":"г.Актобе","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":11709,"amount":30303557.00},
{"region":"г.Кызылорда","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16539,"amount":29772241.67},
{"region":"г.Алматы","service":"2026 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":10206,"amount":29200316.21},
{"region":"г.Караганда","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":15714,"amount":28837871.98}
];
// Форматирование чисел
function fmtNum(n) { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " "); }
function fmtMoney(n) { return fmtNum(n.toFixed(2).replace(".", ",")); }
// Статусы для строк
function getStatus(i) {
if (i < 10) return "top";
if (i < 20) return "high";
if (i < 35) return "medium";
return "growing";
}
// Подготовка строк таблицы
const rows = RAW_DATA.map((r, i) => ({
region: r.region,
service: r.service,
qty: fmtNum(r.qty),
amount: fmtMoney(r.amount),
status: getStatus(i),
_raw: r
}));
// Сохранение в localStorage
localStorage.setItem("b2c_dashboard_data", JSON.stringify(rows));
localStorage.setItem("b2c_dashboard_filter", "all");
// Обновление конфига
const cfg = JSON.parse(document.getElementById("kt-app-config").textContent);
cfg.table.rows = rows;
cfg.kpis[0].value = fmtNum(RAW_DATA.reduce((s, r) => s + r.qty, 0));
cfg.kpis[3].value = "г.Алматы";
document.getElementById("kt-app-config").textContent = JSON.stringify(cfg);
</script>
<!-- App shell content will be rendered by KT AI runtime -->
<div id="kt-ai-root"></div>
<script src="design-system/kt-ai-orb.js"></script>
<script src="design-system/kt-ai-feedback.js"></script>
<script src="design-system/kt-ai-chart-tip.js"></script>
<script src="design-system/kt-ai-composer.js"></script>
<script>
// Простой рендер дашборда без полного shell
(function() {
const rows = JSON.parse(localStorage.getItem("b2c_dashboard_data") || "[]");
const filter = localStorage.getItem("b2c_dashboard_filter") || "all";
// Sidebar
const sidebar = document.createElement("aside");
sidebar.className = "kt-ai-sidebar";
sidebar.innerHTML = `
<div class="sb-top">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📊</text></svg>" alt="Logo">
<span class="nm">B2C Дашборд</span>
</div>
<nav class="kt-ai-nav">
<a class="kt-ai-nav-item kt-ai-nav-item--active"><span class="nav-icon"><svg class="kt-icon"><use href="#dashboard"/></span><span class="nav-label">Дашборд</span></a>
<a class="kt-ai-nav-item"><span class="nav-icon"><svg class="kt-icon"><use href="#map"/></span><span class="nav-label">Регионы</span></a>
<a class="kt-ai-nav-item"><span class="nav-icon"><svg class="kt-icon"><use href="#catalog"/></span><span class="nav-label">Услуги</span></a>
<a class="kt-ai-nav-item"><span class="nav-icon"><svg class="kt-icon"><use href="#spreadsheet"/></span><span class="nav-label">Отчёты</span></a>
</nav>
<div class="sb-bottom">
<button class="kt-ai-nav-item"><span class="nav-icon"><svg class="kt-icon"><use href="#help"/></span><span class="nav-label">Помощь</span></button>
<button class="kt-ai-nav-item"><span class="nav-icon"><svg class="kt-icon"><use href="#user"/></span><span class="nav-label">Профиль</span></button>
</div>
`;
document.body.appendChild(sidebar);
// Main content
const main = document.createElement("main");
main.className = "kt-ai-main";
main.innerHTML = `
<header class="kt-ai-topbar">
<button class="shell-topbar-burger" aria-label="Меню"><svg class="kt-icon"><use href="#menu"/></svg></button>
<div class="topbar-title">
<span class="t">Топ услуг B2C по регионам</span>
<span class="sub">Июль 2026</span>
</div>
<div class="kt-ai-topbar-actions">
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="toggleTheme()"><svg class="kt-icon" style="width:18px;height:18px"><use href="#sun"/></svg></button>
<button class="kt-ai-btn" data-variant="ghost" data-size="sm"><svg class="kt-icon" style="width:18px;height:18px"><use href="#bell"/></svg></button>
</div>
</header>
<div id="view">
<div class="page-hero">
<div class="ph-text">
<h1>Топ услуг B2C по регионам</h1>
<p class="sub">Рейтинг услуг по количеству покупок и сумме в разбивке по регионам</p>
</div>
</div>
<!-- KPI tiles -->
<div class="summary-grid" id="kpi-strip"></div>
<!-- Chart -->
<div class="chart-wrap">
<div class="chart-title">По регионам (сумма)</div>
<div class="chart-bars" id="region-chart"></div>
<div class="chart-labels" id="region-labels"></div>
</div>
<!-- Filters -->
<div class="toolbar-row">
<div class="chip-row" id="region-filters"></div>
<div class="spacer"></div>
<button class="kt-ai-btn" data-variant="ghost" onclick="exportCsv()"><svg class="kt-icon" style="margin-right:6px"><use href="#download"/></svg>Выгрузить</button>
</div>
<!-- Table -->
<div class="kt-ai-card">
<table class="kt-ai-table">
<thead>
<tr>
<th>Регион</th>
<th>Услуга</th>
<th style="text-align:right">Кол-во</th>
<th style="text-align:right">Сумма (₸)</th>
<th>Статус</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
</div>
`;
document.body.appendChild(main);
// Render KPIs
const kpis = [
{ label: "Всего покупок", value: fmtNum(RAW_DATA.reduce((s, r) => s + r.qty, 0)), sub: "Июль 2026" },
{ label: "Всего регионов", value: new Set(RAW_DATA.map(r => r.region)).size, sub: "в топ-50" },
{ label: "Услуга-лидер", value: "B2C Абонплата ШПД", sub: "37 372 покупок" },
{ label: "Регион-лидер", value: "г.Алматы", sub: "237 млн ₸" }
];
document.getElementById("kpi-strip").innerHTML = kpis.map(k => `
<div class="summary-tile">
<div class="label">${k.label}</div>
<div class="value">${k.value}</div>
<div class="sub">${k.sub}</div>
</div>
`).join("");
// Region chart data
const regionData = {};
RAW_DATA.forEach(r => {
regionData[r.region] = (regionData[r.region] || 0) + r.amount;
});
const sortedRegions = Object.entries(regionData).sort((a, b) => b[1] - a[1]).slice(0, 8);
const maxVal = Math.max(...sortedRegions.map(([,v]) => v));
document.getElementById("region-chart").innerHTML = sortedRegions.map(([r, v]) => {
const h = (v / maxVal) * 160;
return `<div class="chart-bar" style="height:${h}px" title="${r}: ${fmtMoney(v)}"></div>`;
}).join("");
document.getElementById("region-labels").innerHTML = sortedRegions.map(([r]) => `<span>${r.replace("г.","")}</span>`).join("");
// Region filters
const regions = ["Все", ...new Set(RAW_DATA.map(r => r.region))];
document.getElementById("region-filters").innerHTML = regions.map(r => `
<button class="chip${r === filter ? " active" : ""}" data-region="${r}">${r}</button>
`).join("");
document.querySelectorAll(".chip").forEach(chip => {
chip.addEventListener("click", () => {
document.querySelectorAll(".chip").forEach(c => c.classList.remove("active"));
chip.classList.add("active");
localStorage.setItem("b2c_dashboard_filter", chip.dataset.region);
renderTable(chip.dataset.region);
});
});
// Render table
function renderTable(filterVal) {
const filtered = filterVal === "Все" || filterVal === "all"
? rows
: rows.filter(r => r.region === filterVal);
document.getElementById("table-body").innerHTML = filtered.map((r, i) => `
<tr>
<td>${r.region}</td>
<td><div class="cell-title">${r.service}</div></td>
<td style="text-align:right">${r.qty}</td>
<td style="text-align:right">${r.amount}</td>
<td><span class="kt-ai-chip" data-tone="${r.status === "top" ? "ok" : r.status === "high" ? "info" : r.status === "medium" ? "info" : "warn"}">${r.status === "top" ? "Топ-10" : r.status === "high" ? "Высокий спрос" : r.status === "medium" ? "Средний" : "Растущий"}</span></td>
</tr>
`).join("");
}
renderTable(filter);
// Export CSV
window.exportCsv = function() {
const csv = "\uFEFF" + ["Регион", "Услуга", "Кол-во", "Сумма"].join(";") + "\n" +
rows.map(r => [r.region, r.service, r._raw.qty, r._raw.amount].join(";")).join("\n");
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "top_uslug_b2c.csv";
a.click();
};
// Theme toggle
window.toggleTheme = function() {
const html = document.documentElement;
const cur = html.getAttribute("data-theme");
html.setAttribute("data-theme", cur === "light" ? "dark" : "light");
};
})();
</script>
</body>
</html>