461 lines
19 KiB
HTML
461 lines
19 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">
|
||
<title>Топ услуг B2C по регионам</title>
|
||
<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">
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||
<style>
|
||
.dashboard-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-bottom: 32px;
|
||
padding: 20px 24px;
|
||
background: var(--kt-ai-card-bg);
|
||
border-radius: var(--kt-ai-radius-xl);
|
||
border: 1px solid var(--kt-ai-border);
|
||
}
|
||
.dashboard-header img { height: 48px; }
|
||
.dashboard-header h1 {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
.header-meta {
|
||
margin-left: auto;
|
||
font-size: 14px;
|
||
color: var(--kt-ai-fg-muted);
|
||
text-align: right;
|
||
}
|
||
|
||
.summary-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 32px;
|
||
}
|
||
.kpi-card {
|
||
background: var(--kt-ai-card-bg);
|
||
border-radius: var(--kt-ai-radius-xl);
|
||
padding: 24px;
|
||
border: 1px solid var(--kt-ai-border);
|
||
}
|
||
.kpi-label {
|
||
font-size: 14px;
|
||
color: var(--kt-ai-fg-muted);
|
||
margin-bottom: 8px;
|
||
}
|
||
.kpi-value {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: var(--kt-ai-primary);
|
||
}
|
||
.kpi-sub {
|
||
font-size: 13px;
|
||
color: var(--kt-ai-fg-faint);
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.charts-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 24px;
|
||
margin-bottom: 32px;
|
||
}
|
||
@media (max-width: 1024px) {
|
||
.charts-grid { grid-template-columns: 1fr; }
|
||
}
|
||
.chart-card {
|
||
background: var(--kt-ai-card-bg);
|
||
border-radius: var(--kt-ai-radius-xl);
|
||
padding: 24px;
|
||
border: 1px solid var(--kt-ai-border);
|
||
}
|
||
.chart-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
|
||
.table-card {
|
||
background: var(--kt-ai-card-bg);
|
||
border-radius: var(--kt-ai-radius-xl);
|
||
padding: 24px;
|
||
border: 1px solid var(--kt-ai-border);
|
||
}
|
||
.table-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.table-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
.filter-group {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
.filter-label {
|
||
font-size: 14px;
|
||
color: var(--kt-ai-fg-muted);
|
||
}
|
||
.filter-select {
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--kt-ai-border);
|
||
border-radius: var(--kt-ai-radius-md);
|
||
font-size: 14px;
|
||
background: var(--kt-ai-card-bg);
|
||
color: var(--kt-ai-fg);
|
||
cursor: pointer;
|
||
}
|
||
.filter-select:focus {
|
||
outline: 2px solid var(--kt-ai-primary);
|
||
border-color: transparent;
|
||
}
|
||
|
||
.table-wrapper {
|
||
overflow-x: auto;
|
||
}
|
||
.data-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
.data-table th {
|
||
text-align: left;
|
||
padding: 12px 16px;
|
||
background: var(--kt-ai-bg-soft);
|
||
color: var(--kt-ai-fg-muted);
|
||
font-weight: 600;
|
||
border-bottom: 2px solid var(--kt-ai-border);
|
||
}
|
||
.data-table td {
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid var(--kt-ai-border);
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
.data-table tr:hover {
|
||
background: var(--kt-ai-bg-soft);
|
||
}
|
||
.rank-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background: var(--kt-ai-primary);
|
||
color: white;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
.amount-cell {
|
||
font-weight: 600;
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
.region-chip {
|
||
display: inline-block;
|
||
padding: 4px 10px;
|
||
background: var(--kt-ai-bg-soft);
|
||
border-radius: var(--kt-ai-radius-md);
|
||
font-size: 13px;
|
||
color: var(--kt-ai-fg);
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 48px 24px;
|
||
color: var(--kt-ai-fg-muted);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="kt-ai-wrap">
|
||
<header class="dashboard-header">
|
||
<img src="./kt-logo.svg" alt="Казахтелеком" height="48">
|
||
<h1>Топ услуг B2C по регионам</h1>
|
||
<div class="header-meta">
|
||
<div>Данные за июль 2026</div>
|
||
<div>Сегмент B2C</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="summary-grid" id="summary"></section>
|
||
|
||
<section class="charts-grid">
|
||
<div class="chart-card">
|
||
<h2 class="chart-title">Топ-10 регионов по количеству покупок</h2>
|
||
<canvas id="regionChart"></canvas>
|
||
</div>
|
||
<div class="chart-card">
|
||
<h2 class="chart-title">Топ-10 услуг по количеству покупок</h2>
|
||
<canvas id="serviceChart"></canvas>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="table-card">
|
||
<div class="table-toolbar">
|
||
<h2 class="table-title">Детализация по услугам</h2>
|
||
<div class="filter-group">
|
||
<label class="filter-label" for="regionFilter">Регион:</label>
|
||
<select class="filter-select" id="regionFilter">
|
||
<option value="">Все регионы</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Регион</th>
|
||
<th>Услуга</th>
|
||
<th>Кол-во покупок</th>
|
||
<th>Сумма (₸)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<script>
|
||
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(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||
}
|
||
function fmtAmount(n) {
|
||
return n.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$& ') + ' ₸';
|
||
}
|
||
|
||
function aggregate() {
|
||
const byRegion = {};
|
||
const byService = {};
|
||
let totalQty = 0;
|
||
|
||
RAW_DATA.forEach(r => {
|
||
byRegion[r.region] = (byRegion[r.region] || 0) + r.qty;
|
||
byService[r.service] = (byService[r.service] || 0) + r.qty;
|
||
totalQty += r.qty;
|
||
});
|
||
|
||
const regions = Object.entries(byRegion).sort((a, b) => b[1] - a[1]);
|
||
const services = Object.entries(byService).sort((a, b) => b[1] - a[1]);
|
||
|
||
return {
|
||
totalQty,
|
||
totalRegions: Object.keys(byRegion).length,
|
||
topService: services[0][0],
|
||
topRegion: regions[0][0],
|
||
regions,
|
||
services
|
||
};
|
||
}
|
||
|
||
function renderSummary() {
|
||
const agg = aggregate();
|
||
const container = document.getElementById('summary');
|
||
container.innerHTML = `
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Всего покупок</div>
|
||
<div class="kpi-value">${fmtNum(agg.totalQty)}</div>
|
||
<div class="kpi-sub">за июль 2026</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Всего регионов</div>
|
||
<div class="kpi-value">${agg.totalRegions}</div>
|
||
<div class="kpi-sub">в топ-50</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Услуга-лидер</div>
|
||
<div class="kpi-value" style="font-size:18px;line-height:1.4">${agg.topService}</div>
|
||
<div class="kpi-sub">по количеству покупок</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">Регион-лидер</div>
|
||
<div class="kpi-value">${agg.topRegion}</div>
|
||
<div class="kpi-sub">больше всего покупок</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
let regionChartInstance = null;
|
||
let serviceChartInstance = null;
|
||
|
||
function renderCharts() {
|
||
const agg = aggregate();
|
||
const top10Regions = agg.regions.slice(0, 10);
|
||
const top10Services = agg.services.slice(0, 10);
|
||
|
||
const colors = [
|
||
'var(--kt-ai-primary)', '#0077A8', '#005F87', '#004A6B', '#003852',
|
||
'#00C7E3', '#00A3C7', '#0087B0', '#006B94', '#005278'
|
||
];
|
||
|
||
const ctx1 = document.getElementById('regionChart').getContext('2d');
|
||
if (regionChartInstance) regionChartInstance.destroy();
|
||
regionChartInstance = new Chart(ctx1, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: top10Regions.map(r => r[0]),
|
||
datasets: [{
|
||
label: 'Покупок',
|
||
data: top10Regions.map(r => r[1]),
|
||
backgroundColor: colors,
|
||
borderRadius: 6
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
plugins: { legend: { display: false } },
|
||
scales: {
|
||
y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
|
||
x: { grid: { display: false } }
|
||
}
|
||
}
|
||
});
|
||
|
||
const ctx2 = document.getElementById('serviceChart').getContext('2d');
|
||
if (serviceChartInstance) serviceChartInstance.destroy();
|
||
serviceChartInstance = new Chart(ctx2, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: top10Services.map(s => s[0].length > 40 ? s[0].substring(0, 40) + '...' : s[0]),
|
||
datasets: [{
|
||
label: 'Покупок',
|
||
data: top10Services.map(s => s[1]),
|
||
backgroundColor: colors,
|
||
borderRadius: 6
|
||
}]
|
||
},
|
||
options: {
|
||
indexAxis: 'y',
|
||
responsive: true,
|
||
plugins: { legend: { display: false } },
|
||
scales: {
|
||
x: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } },
|
||
y: { grid: { display: false } }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function renderTable(filterRegion = '') {
|
||
const tbody = document.getElementById('tableBody');
|
||
let filtered = RAW_DATA;
|
||
|
||
if (filterRegion) {
|
||
filtered = RAW_DATA.filter(r => r.region === filterRegion);
|
||
}
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">Нет данных для выбранного региона</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = filtered.map((r, i) => `
|
||
<tr>
|
||
<td><span class="rank-badge">${i + 1}</span></td>
|
||
<td><span class="region-chip">${r.region}</span></td>
|
||
<td>${r.service}</td>
|
||
<td>${fmtNum(r.qty)}</td>
|
||
<td class="amount-cell">${fmtAmount(r.amount)}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function initFilter() {
|
||
const select = document.getElementById('regionFilter');
|
||
const regions = [...new Set(RAW_DATA.map(r => r.region))].sort();
|
||
|
||
regions.forEach(region => {
|
||
const opt = document.createElement('option');
|
||
opt.value = region;
|
||
opt.textContent = region;
|
||
select.appendChild(opt);
|
||
});
|
||
|
||
select.addEventListener('change', (e) => {
|
||
renderTable(e.target.value);
|
||
localStorage.setItem('b2c_region_filter', e.target.value);
|
||
});
|
||
|
||
const saved = localStorage.getItem('b2c_region_filter') || '';
|
||
select.value = saved;
|
||
renderTable(saved);
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderSummary();
|
||
renderCharts();
|
||
initFilter();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|