fix: относительные пути к CSS (./design-system/...)
This commit is contained in:
parent
8d95047ed3
commit
ec92834514
530
index.html
530
index.html
@ -4,150 +4,16 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Топ услуг B2C по регионам</title>
|
<title>Топ услуг B2C по регионам</title>
|
||||||
<script data-kt-ai-base-resolver>
|
<link rel="stylesheet" href="./design-system/kt-ai-fonts.css">
|
||||||
(function () {
|
<link rel="stylesheet" href="./design-system/kt-ai-tokens.css">
|
||||||
var корень = document.documentElement;
|
<link rel="stylesheet" href="./design-system/kt-ai-components.css">
|
||||||
function норм(значение) {
|
<link rel="stylesheet" href="./design-system/kt-ai-page.css">
|
||||||
значение = (значение || "").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>
|
<style>
|
||||||
@layer product {
|
.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); }
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
.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); }
|
||||||
body { margin: 0; }
|
.summary-tile .label { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2); }
|
||||||
.shell-topbar-burger { display: none; }
|
.summary-tile .value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg); }
|
||||||
@media (max-width: 831.98px) { .shell-topbar-burger { display: inline-flex; } }
|
.summary-tile .sub { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
|
||||||
.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-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-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-bars { display: flex; align-items: flex-end; gap: var(--kt-ai-space-2); height: 200px; padding: var(--kt-ai-space-4) 0; }
|
||||||
@ -155,109 +21,120 @@
|
|||||||
.chart-bar:hover { opacity: 0.8; }
|
.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 { 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; }
|
.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-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 { 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:hover { background: var(--kt-ai-bg-hover); }
|
||||||
.chip.active { background: var(--kt-ai-primary); color: white; }
|
.chip.active { background: var(--kt-ai-primary); color: white; }
|
||||||
/* Summary tiles */
|
.toolbar-row { display: flex; align-items: center; gap: var(--kt-ai-space-4); flex-wrap: wrap; padding: 0 0 var(--kt-ai-stack-block); }
|
||||||
.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); }
|
.toolbar-row .spacer { flex: 1; }
|
||||||
.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); }
|
.page-hero { display: flex; align-items: flex-start; gap: var(--kt-ai-space-6); margin-bottom: var(--kt-ai-stack-group); }
|
||||||
.summary-tile .label { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2); }
|
.page-hero .ph-text { flex: 1; min-width: 0; }
|
||||||
.summary-tile .value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg); }
|
.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; }
|
||||||
.summary-tile .sub { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
|
.page-hero .sub { margin-top: var(--kt-ai-space-3); font-size: var(--kt-ai-text-md); color: var(--kt-ai-fg-muted); }
|
||||||
|
.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; }
|
||||||
|
.kt-ai-sidebar { width: 240px; background: var(--kt-ai-bg-soft); border-right: 1px solid var(--kt-ai-border); padding: var(--kt-ai-space-4); position: fixed; height: 100vh; overflow-y: auto; }
|
||||||
|
.kt-ai-main { margin-left: 240px; min-height: 100vh; background: var(--kt-ai-bg); }
|
||||||
|
.kt-ai-topbar { position: sticky; top: 0; z-index: 100; background: var(--kt-ai-bg-elevated); border-bottom: 1px solid var(--kt-ai-border); padding: var(--kt-ai-space-3) var(--kt-ai-space-5); display: flex; align-items: center; gap: var(--kt-ai-space-4); }
|
||||||
|
.kt-ai-nav-item { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: var(--kt-ai-space-3) var(--kt-ai-space-3); border-radius: var(--kt-ai-radius-lg); color: var(--kt-ai-fg); text-decoration: none; margin-bottom: 2px; }
|
||||||
|
.kt-ai-nav-item:hover { background: var(--kt-ai-bg-hover); }
|
||||||
|
.kt-ai-nav-item--active { background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); }
|
||||||
|
.nav-icon { width: 20px; height: 20px; }
|
||||||
|
.nav-label { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); }
|
||||||
|
.sb-top { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-4); }
|
||||||
|
.sb-top img { width: 28px; height: 28px; border-radius: var(--kt-ai-radius-xl); }
|
||||||
|
.sb-top .nm { font-weight: var(--kt-ai-weight-semibold); font-size: var(--kt-ai-text-sm); }
|
||||||
|
.sb-bottom { margin-top: auto; padding-top: var(--kt-ai-space-4); border-top: 1px solid var(--kt-ai-divider); }
|
||||||
|
.kt-icon { width: 18px; height: 18px; }
|
||||||
|
.shell-topbar-burger { display: none; background: none; border: none; cursor: pointer; padding: var(--kt-ai-space-2); }
|
||||||
|
@media (max-width: 831.98px) {
|
||||||
|
.shell-topbar-burger { display: block; }
|
||||||
|
.kt-ai-sidebar { transform: translateX(-100%); transition: transform 0.3s; z-index: 200; }
|
||||||
|
.kt-ai-sidebar.open { transform: translateX(0); }
|
||||||
|
.kt-ai-main { margin-left: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="kt-ai-app">
|
<body>
|
||||||
|
|
||||||
<script id="kt-app-config" type="application/json">
|
<aside class="kt-ai-sidebar" id="sidebar">
|
||||||
{
|
<div class="sb-top">
|
||||||
"product": {
|
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E📊%3C/text%3E%3C/svg%3E" alt="Logo">
|
||||||
"name": "Топ услуг B2C по регионам",
|
<span class="nm">B2C Дашборд</span>
|
||||||
"purpose": "Дашборд руководителя · Сегмент B2C",
|
</div>
|
||||||
"entity": "услуга",
|
<nav>
|
||||||
"view": "management"
|
<a class="kt-ai-nav-item kt-ai-nav-item--active"><svg class="nav-icon"><use href="#dashboard"/></svg><span class="nav-label">Дашборд</span></a>
|
||||||
},
|
<a class="kt-ai-nav-item"><svg class="nav-icon"><use href="#map"/></svg><span class="nav-label">Регионы</span></a>
|
||||||
"user": {
|
<a class="kt-ai-nav-item"><svg class="nav-icon"><use href="#catalog"/></svg><span class="nav-label">Услуги</span></a>
|
||||||
"name": "Руководитель направления B2C",
|
<a class="kt-ai-nav-item"><svg class="nav-icon"><use href="#spreadsheet"/></svg><span class="nav-label">Отчёты</span></a>
|
||||||
"role": "Директор по продуктам"
|
</nav>
|
||||||
},
|
<div class="sb-bottom">
|
||||||
"kpis": [
|
<a class="kt-ai-nav-item"><svg class="nav-icon"><use href="#help"/></svg><span class="nav-label">Помощь</span></a>
|
||||||
{ "value": "1 178 651", "label": "всего покупок", "hint": "Июль 2026" },
|
</div>
|
||||||
{ "value": "14", "label": "регионов", "hint": "в топ-50" },
|
</aside>
|
||||||
{ "value": "B2C Абонплата ШПД FTTx", "label": "услуга-лидер", "hint": "37 372 покупок в Астане" },
|
|
||||||
{ "value": "г.Алматы", "label": "регион-лидер", "hint": "237 млн ₸ сумма" }
|
<main class="kt-ai-main">
|
||||||
],
|
<header class="kt-ai-topbar">
|
||||||
"nav": [
|
<button class="shell-topbar-burger" onclick="toggleSidebar()"><svg class="kt-icon"><use href="#menu"/></svg></button>
|
||||||
{ "label": "Дашборд", "icon": "dashboard" },
|
<div class="topbar-title">
|
||||||
{ "label": "Регионы", "icon": "map" },
|
<span class="t" style="font-size:var(--kt-ai-text-lg);font-weight:var(--kt-ai-weight-semibold)">Топ услуг B2C по регионам</span>
|
||||||
{ "label": "Услуги", "icon": "catalog" },
|
<span class="sub" style="font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted)">Июль 2026</span>
|
||||||
{ "label": "Отчёты", "icon": "spreadsheet" }
|
</div>
|
||||||
],
|
<div style="margin-left:auto;display:flex;gap:var(--kt-ai-space-2)">
|
||||||
"table": {
|
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="toggleTheme()"><svg class="kt-icon"><use href="#sun"/></svg></button>
|
||||||
"columns": [
|
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="exportCsv()"><svg class="kt-icon"><use href="#download"/></svg></button>
|
||||||
{ "key": "region", "label": "Регион", "type": "text", "source": "ClickHouse" },
|
</div>
|
||||||
{ "key": "service", "label": "Услуга", "type": "text", "source": "ClickHouse" },
|
</header>
|
||||||
{ "key": "qty", "label": "Кол-во покупок", "type": "num", "source": "ClickHouse", "align": "right" },
|
|
||||||
{ "key": "amount", "label": "Сумма (₸)", "type": "num", "source": "ClickHouse", "align": "right" },
|
<div id="view" style="padding:var(--kt-ai-stack-region)">
|
||||||
{ "key": "status", "label": "Статус", "type": "status", "source": "агент" }
|
<div class="page-hero">
|
||||||
],
|
<div class="ph-text">
|
||||||
"rows": []
|
<h1>Топ услуг B2C по регионам</h1>
|
||||||
},
|
<p class="sub">Рейтинг услуг по количеству покупок и сумме в разбивке по регионам</p>
|
||||||
"statuses": {
|
</div>
|
||||||
"top": { "label": "Топ-10", "tone": "ok" },
|
</div>
|
||||||
"high": { "label": "Высокий спрос", "tone": "info" },
|
|
||||||
"medium": { "label": "Средний", "tone": "info" },
|
<div class="summary-grid" id="kpi-strip"></div>
|
||||||
"growing": { "label": "Растущий", "tone": "warn" }
|
|
||||||
},
|
<div class="chart-wrap">
|
||||||
"filterKeys": ["region"],
|
<div class="chart-title">По регионам (сумма)</div>
|
||||||
"dashboard": {
|
<div class="chart-bars" id="region-chart"></div>
|
||||||
"series": [
|
<div class="chart-labels" id="region-labels"></div>
|
||||||
{ "key": "amount", "label": "Сумма", "color": "var(--kt-ai-primary)" }
|
</div>
|
||||||
],
|
|
||||||
"range": "Июль 2026",
|
<div class="toolbar-row">
|
||||||
"breakdowns": [
|
<div class="chip-row" id="region-filters"></div>
|
||||||
{ "title": "По регионам", "type": "bar", "groupKey": "region" },
|
</div>
|
||||||
{ "title": "Топ-10 услуг", "type": "rank", "limit": 10 }
|
|
||||||
]
|
<div class="kt-ai-card">
|
||||||
}
|
<table class="kt-ai-table">
|
||||||
}
|
<thead>
|
||||||
</script>
|
<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>
|
||||||
|
</main>
|
||||||
|
|
||||||
<!-- 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">
|
<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="dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="map" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="catalog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="spreadsheet" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="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="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="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="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="help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><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="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>
|
</svg>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Данные из brief.md — все 50 строк
|
|
||||||
const RAW_DATA = [
|
const RAW_DATA = [
|
||||||
{"region":"Астана","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":37372,"amount":238463786.09},
|
{"region":"Астана","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":37372,"amount":238463786.09},
|
||||||
{"region":"г.Алматы","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":39671,"amount":237005698.16},
|
{"region":"г.Алматы","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":39671,"amount":237005698.16},
|
||||||
@ -311,19 +188,16 @@ const RAW_DATA = [
|
|||||||
{"region":"г.Караганда","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":15714,"amount":28837871.98}
|
{"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 fmtNum(n) { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " "); }
|
||||||
function fmtMoney(n) { return fmtNum(n.toFixed(2).replace(".", ",")); }
|
function fmtMoney(n) { return fmtNum(n.toFixed(2).replace(".", ",")); }
|
||||||
|
|
||||||
// Статусы для строк
|
|
||||||
function getStatus(i) {
|
function getStatus(i) {
|
||||||
if (i < 10) return "top";
|
if (i < 10) return { label: "Топ-10", tone: "ok" };
|
||||||
if (i < 20) return "high";
|
if (i < 20) return { label: "Высокий спрос", tone: "info" };
|
||||||
if (i < 35) return "medium";
|
if (i < 35) return { label: "Средний", tone: "info" };
|
||||||
return "growing";
|
return { label: "Растущий", tone: "warn" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подготовка строк таблицы
|
|
||||||
const rows = RAW_DATA.map((r, i) => ({
|
const rows = RAW_DATA.map((r, i) => ({
|
||||||
region: r.region,
|
region: r.region,
|
||||||
service: r.service,
|
service: r.service,
|
||||||
@ -333,196 +207,74 @@ const rows = RAW_DATA.map((r, i) => ({
|
|||||||
_raw: r
|
_raw: r
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Сохранение в localStorage
|
|
||||||
localStorage.setItem("b2c_dashboard_data", JSON.stringify(rows));
|
localStorage.setItem("b2c_dashboard_data", JSON.stringify(rows));
|
||||||
localStorage.setItem("b2c_dashboard_filter", "all");
|
|
||||||
|
|
||||||
// Обновление конфига
|
// KPIs
|
||||||
const cfg = JSON.parse(document.getElementById("kt-app-config").textContent);
|
const totalQty = RAW_DATA.reduce((s, r) => s + r.qty, 0);
|
||||||
cfg.table.rows = rows;
|
const regions = new Set(RAW_DATA.map(r => r.region));
|
||||||
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 = {};
|
const regionData = {};
|
||||||
RAW_DATA.forEach(r => {
|
RAW_DATA.forEach(r => { regionData[r.region] = (regionData[r.region] || 0) + r.amount; });
|
||||||
regionData[r.region] = (regionData[r.region] || 0) + r.amount;
|
const topRegion = Object.entries(regionData).sort((a, b) => b[1] - a[1])[0];
|
||||||
});
|
|
||||||
|
document.getElementById("kpi-strip").innerHTML = [
|
||||||
|
{ label: "Всего покупок", value: fmtNum(totalQty), sub: "Июль 2026" },
|
||||||
|
{ label: "Всего регионов", value: regions.size, sub: "в топ-50" },
|
||||||
|
{ label: "Услуга-лидер", value: "B2C Абонплата ШПД", sub: "37 372 покупок" },
|
||||||
|
{ label: "Регион-лидер", value: topRegion[0], sub: fmtMoney(topRegion[1] / 1e6) + " млн ₸" }
|
||||||
|
].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("");
|
||||||
|
|
||||||
|
// Chart
|
||||||
const sortedRegions = Object.entries(regionData).sort((a, b) => b[1] - a[1]).slice(0, 8);
|
const sortedRegions = Object.entries(regionData).sort((a, b) => b[1] - a[1]).slice(0, 8);
|
||||||
const maxVal = Math.max(...sortedRegions.map(([,v]) => v));
|
const maxVal = Math.max(...sortedRegions.map(([,v]) => v));
|
||||||
|
|
||||||
document.getElementById("region-chart").innerHTML = sortedRegions.map(([r, v]) => {
|
document.getElementById("region-chart").innerHTML = sortedRegions.map(([r, v]) => {
|
||||||
const h = (v / maxVal) * 160;
|
const h = (v / maxVal) * 160;
|
||||||
return `<div class="chart-bar" style="height:${h}px" title="${r}: ${fmtMoney(v)}"></div>`;
|
return `<div class="chart-bar" style="height:${h}px" title="${r}: ${fmtMoney(v)}"></div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
document.getElementById("region-labels").innerHTML = sortedRegions.map(([r]) => `<span>${r.replace("г.","").replace(" ","")}</span>`).join("");
|
||||||
|
|
||||||
document.getElementById("region-labels").innerHTML = sortedRegions.map(([r]) => `<span>${r.replace("г.","")}</span>`).join("");
|
// Filters
|
||||||
|
const allRegions = ["Все", ...regions];
|
||||||
// Region filters
|
document.getElementById("region-filters").innerHTML = allRegions.map(r => `<button class="chip${r==="Все"?" active":""}" data-region="${r}">${r}</button>`).join("");
|
||||||
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 => {
|
document.querySelectorAll(".chip").forEach(chip => {
|
||||||
chip.addEventListener("click", () => {
|
chip.addEventListener("click", () => {
|
||||||
document.querySelectorAll(".chip").forEach(c => c.classList.remove("active"));
|
document.querySelectorAll(".chip").forEach(c => c.classList.remove("active"));
|
||||||
chip.classList.add("active");
|
chip.classList.add("active");
|
||||||
localStorage.setItem("b2c_dashboard_filter", chip.dataset.region);
|
|
||||||
renderTable(chip.dataset.region);
|
renderTable(chip.dataset.region);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render table
|
function renderTable(filter) {
|
||||||
function renderTable(filterVal) {
|
const filtered = filter === "Все" ? rows : rows.filter(r => r.region === filter);
|
||||||
const filtered = filterVal === "Все" || filterVal === "all"
|
|
||||||
? rows
|
|
||||||
: rows.filter(r => r.region === filterVal);
|
|
||||||
|
|
||||||
document.getElementById("table-body").innerHTML = filtered.map((r, i) => `
|
document.getElementById("table-body").innerHTML = filtered.map((r, i) => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${r.region}</td>
|
<td>${r.region}</td>
|
||||||
<td><div class="cell-title">${r.service}</div></td>
|
<td><div class="cell-title">${r.service}</div></td>
|
||||||
<td style="text-align:right">${r.qty}</td>
|
<td style="text-align:right">${r.qty}</td>
|
||||||
<td style="text-align:right">${r.amount}</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>
|
<td><span class="kt-ai-chip" data-tone="${r.status.tone}">${r.status.label}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join("");
|
`).join("");
|
||||||
}
|
}
|
||||||
|
renderTable("Все");
|
||||||
|
|
||||||
renderTable(filter);
|
|
||||||
|
|
||||||
// Export CSV
|
|
||||||
window.exportCsv = function() {
|
window.exportCsv = function() {
|
||||||
const csv = "\uFEFF" + ["Регион", "Услуга", "Кол-во", "Сумма"].join(";") + "\n" +
|
const csv = "\uFEFF" + ["Регион", "Услуга", "Кол-во", "Сумма"].join(";") + "\n" +
|
||||||
rows.map(r => [r.region, r.service, r._raw.qty, r._raw.amount].join(";")).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 blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = "top_uslug_b2c.csv";
|
a.download = "top_uslug_b2c.csv";
|
||||||
a.click();
|
a.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Theme toggle
|
|
||||||
window.toggleTheme = function() {
|
window.toggleTheme = function() {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const cur = html.getAttribute("data-theme");
|
html.setAttribute("data-theme", html.getAttribute("data-theme") === "light" ? "dark" : "light");
|
||||||
html.setAttribute("data-theme", cur === "light" ? "dark" : "light");
|
};
|
||||||
|
|
||||||
|
window.toggleSidebar = function() {
|
||||||
|
document.getElementById("sidebar").classList.toggle("open");
|
||||||
};
|
};
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user