334 lines
20 KiB
HTML
334 lines
20 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Топ услуг B2C по регионам — Июль 2026</title>
|
||
<style>
|
||
:root {
|
||
--bg: #f5f7fa;
|
||
--card-bg: #ffffff;
|
||
--text: #1a1a2e;
|
||
--text-secondary: #6b7280;
|
||
--primary: #2563eb;
|
||
--primary-light: #3b82f6;
|
||
--success: #10b981;
|
||
--accent: #8b5cf6;
|
||
--border: #e5e7eb;
|
||
--shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
|
||
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
|
||
}
|
||
* { margin: 0; box-sizing: border-box; }
|
||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; padding: 24px; }
|
||
.container { max-width: 1400px; margin: 0 auto; }
|
||
header { margin-bottom: 32px; background: linear-gradient(135deg, #00b8cc 0%, #0095a8 100%); border-radius: 16px; padding: 32px; color: white; }
|
||
h1 { font-size: 28px; font-weight: 700; color: white; margin-bottom: 8px; }
|
||
.subtitle { color: rgba(255,255,255,0.9); font-size: 14px; }
|
||
|
||
/* KPI карточки */
|
||
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; margin-bottom: 32px; }
|
||
.kpi-card { background: var(--card-bg); border-radius: 16px; padding: 24px; box-shadow: var(--shadow); border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; }
|
||
.kpi-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
|
||
.kpi-label { font-size: 13px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
|
||
.kpi-value { font-size: 32px; font-weight: 700; color: var(--primary); }
|
||
.kpi-sub { font-size: 13px; color: var(--text-secondary); margin-top: 8px; }
|
||
|
||
/* Графики */
|
||
.charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); gap: 24px; margin-bottom: 32px; }
|
||
.chart-card { background: var(--card-bg); border-radius: 16px; padding: 24px; box-shadow: var(--shadow); border: 1px solid var(--border); }
|
||
.chart-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; color: var(--text); }
|
||
.chart-container { position: relative; height: 300px; overflow-x: auto; }
|
||
|
||
/* Столбчатая диаграмма */
|
||
.bar-chart { display: flex; align-items: flex-end; gap: 8px; height: 260px; padding-top: 20px; min-width: 600px; }
|
||
.bar-wrapper { flex: 1; display: flex; flex-direction: column; align-items: center; min-width: 60px; }
|
||
.bar { width: 100%; max-width: 50px; border-radius: 6px 6px 0 0; transition: opacity 0.2s; cursor: pointer; position: relative; }
|
||
.bar:hover { opacity: 0.8; }
|
||
.bar-label { font-size: 11px; color: var(--text-secondary); margin-top: 8px; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px; }
|
||
.bar-value { font-size: 10px; color: var(--text-secondary); margin-top: 4px; text-align: center; }
|
||
|
||
/* Таблица */
|
||
.table-card { background: var(--card-bg); border-radius: 16px; padding: 24px; box-shadow: var(--shadow); border: 1px solid var(--border); }
|
||
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 16px; }
|
||
.table-title { font-size: 18px; font-weight: 600; }
|
||
.controls { display: flex; gap: 12px; flex-wrap: wrap; }
|
||
.search-input { padding: 10px 16px; border: 1px solid var(--border); border-radius: 10px; font-size: 14px; min-width: 250px; outline: none; transition: border-color 0.2s; }
|
||
.search-input:focus { border-color: var(--primary); }
|
||
.filter-select { padding: 10px 16px; border: 1px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--card-bg); outline: none; cursor: pointer; }
|
||
.table-wrapper { overflow-x: auto; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--border); }
|
||
th { font-size: 12px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; background: #fafafa; position: sticky; top: 0; }
|
||
td { font-size: 14px; }
|
||
tr:hover { background: #f9fafb; }
|
||
.rank { font-weight: 600; color: var(--primary); }
|
||
.region { font-weight: 500; }
|
||
.service { color: var(--text); }
|
||
.purchases { font-weight: 600; color: var(--success); }
|
||
.amount { font-weight: 600; color: var(--accent); }
|
||
|
||
/* Легенда */
|
||
.legend { display: flex; gap: 20px; margin-top: 16px; flex-wrap: wrap; }
|
||
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-secondary); }
|
||
.legend-color { width: 16px; height: 16px; border-radius: 4px; }
|
||
|
||
/* Адаптив */
|
||
@media (max-width: 768px) {
|
||
body { padding: 16px; }
|
||
h1 { font-size: 22px; }
|
||
.kpi-value { font-size: 24px; }
|
||
.charts-grid { grid-template-columns: 1fr; }
|
||
.bar-chart { min-width: 100%; }
|
||
.search-input { min-width: 100%; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1>Топ услуг B2C по регионам</h1>
|
||
<p class="subtitle">Данные за июль 2026 года • Сегмент B2C • Источник: ClickHouse АО «Казахтелеком»</p>
|
||
</header>
|
||
|
||
<div class="kpi-grid" id="kpiGrid"></div>
|
||
|
||
<div class="charts-grid">
|
||
<div class="chart-card">
|
||
<div class="chart-title">Топ-10 регионов по количеству покупок</div>
|
||
<div class="chart-container">
|
||
<div class="bar-chart" id="regionChart"></div>
|
||
</div>
|
||
<div class="legend" id="regionLegend"></div>
|
||
</div>
|
||
<div class="chart-card">
|
||
<div class="chart-title">Топ-10 услуг по количеству покупок</div>
|
||
<div class="chart-container">
|
||
<div class="bar-chart" id="serviceChart"></div>
|
||
</div>
|
||
<div class="legend" id="serviceLegend"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-card">
|
||
<div class="table-header">
|
||
<div class="table-title">Детальные данные</div>
|
||
<div class="controls">
|
||
<input type="text" class="search-input" id="searchInput" placeholder="Поиск по услуге или региону...">
|
||
<select class="filter-select" id="regionFilter">
|
||
<option value="">Все регионы</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>№</th>
|
||
<th>Регион</th>
|
||
<th>Услуга</th>
|
||
<th>Покупок</th>
|
||
<th>Сумма (₸)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ===== ДАННЫЕ ИЗ БРИФА =====
|
||
const rawData = [
|
||
{ rank: 1, region: "Астана", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 37372, amount: 238463786.09 },
|
||
{ rank: 2, region: "г.Алматы", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 39671, amount: 237005698.16 },
|
||
{ rank: 3, region: "г.Алматы", service: "Абонплата ШПД по FTTx для B2C", purchases: 35588, amount: 152893561.28 },
|
||
{ rank: 4, region: "Астана", service: "2025 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 35637, amount: 136187938.99 },
|
||
{ rank: 5, region: "Астана", service: "B2C TV Plus Абонплата Full ГТС", purchases: 47661, amount: 132060372.93 },
|
||
{ rank: 6, region: "Астана", service: "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", purchases: 61700, amount: 123403164.43 },
|
||
{ rank: 7, region: "г.Алматы", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 57378, amount: 110497209.55 },
|
||
{ rank: 8, region: "г.Алматы", service: "2025 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 29997, amount: 106749675.59 },
|
||
{ rank: 9, region: "Астана", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 52504, amount: 99235780.41 },
|
||
{ rank: 10, region: "Астана", service: "2022 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 36946, amount: 96430127.27 },
|
||
{ rank: 11, region: "Астана", service: "Абонплата ШПД по FTTx для B2C", purchases: 19565, amount: 88834659.73 },
|
||
{ rank: 12, region: "Астана", service: "2024 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 22167, amount: 83549325.34 },
|
||
{ rank: 13, region: "г.Алматы", service: "B2C TV Plus Абонплата Full ГТС", purchases: 31221, amount: 83511013.05 },
|
||
{ rank: 14, region: "г.Алматы", service: "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", purchases: 40838, amount: 82748215.46 },
|
||
{ rank: 15, region: "г.Семей", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 41486, amount: 76400272.02 },
|
||
{ rank: 16, region: "Астана", service: "TV Plus Абонплата за Премиальный пакет ГТС", purchases: 29210, amount: 71278165.09 },
|
||
{ rank: 17, region: "г.Алматы", service: "2022 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 24001, amount: 62521274.30 },
|
||
{ rank: 18, region: "Астана", service: "2021 B2C Абонплата за мобильный номер Kcell GSM откр.", purchases: 27864, amount: 59635624.39 },
|
||
{ rank: 19, region: "г.Алматы", service: "2024 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 15678, amount: 58970815.48 },
|
||
{ rank: 20, region: "г.Актобе", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 30641, amount: 55819936.98 },
|
||
{ rank: 21, region: "г.Алматы", service: "B2C Абонплата за основной телефон ГТС", purchases: 52732, amount: 49636818.50 },
|
||
{ rank: 22, region: "г.Актобе", service: "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", purchases: 25178, amount: 48000483.43 },
|
||
{ rank: 23, region: "г.Алматы", service: "TV Plus Абонплата за Премиальный пакет ГТС", purchases: 19055, amount: 45298192.89 },
|
||
{ rank: 24, region: "г.Павлодар", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 23334, amount: 44857518.84 },
|
||
{ rank: 25, region: "г.Алматы", service: "B2C TV Plus Аренда STB оператора новое", purchases: 96183, amount: 43252519.71 },
|
||
{ rank: 26, region: "г.Караганда", service: "Абонплата ШПД по FTTx для B2C", purchases: 8916, amount: 41624727.66 },
|
||
{ rank: 27, region: "Астана", service: "2026 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 13685, amount: 41618770.34 },
|
||
{ rank: 28, region: "г.Алматы", service: "2018 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 11568, amount: 40199964.35 },
|
||
{ rank: 29, region: "г.Тараз", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 21880, amount: 39564813.34 },
|
||
{ rank: 30, region: "г.Алматы", service: "Абонплата ШПД по ADSL для B2C", purchases: 10710, amount: 36238454.72 },
|
||
{ rank: 31, region: "г.Алматы", service: "2020 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 12148, amount: 36010481.68 },
|
||
{ rank: 32, region: "г.Усть-Каменогорск", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 18362, amount: 35883527.39 },
|
||
{ rank: 33, region: "г. Шымкент", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 19180, amount: 35338634.51 },
|
||
{ rank: 34, region: "г. Шымкент", service: "2021 B2C Абонплата за мобильный номер Kcell GSM закр.", purchases: 18945, amount: 35138664.91 },
|
||
{ rank: 35, region: "Астана", service: "B2C TV Plus Аренда STB оператора новое", purchases: 76655, amount: 34746783.92 },
|
||
{ rank: 36, region: "Астана", service: "2018 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 8705, amount: 32905725.03 },
|
||
{ rank: 37, region: "г.Актобе", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 6742, amount: 32313021.91 },
|
||
{ rank: 38, region: "г.Усть-Каменогорск", service: "Абонплата ШПД по FTTx для B2C", purchases: 7269, amount: 31834355.15 },
|
||
{ rank: 39, region: "г.Атырау", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 5495, amount: 31688118.61 },
|
||
{ rank: 40, region: "г.Усть-Каменогорск", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 5600, amount: 31520413.53 },
|
||
{ rank: 41, region: "Астана", service: "B2C Аренда оборудования ONT оператора - новое", purchases: 87071, amount: 31220264.09 },
|
||
{ rank: 42, region: "г.Павлодар", service: "B2C Абонплата ШПД по FTTx ГТС (New)", purchases: 6104, amount: 30831455.08 },
|
||
{ rank: 43, region: "г.Кокшетау", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 16818, amount: 30654924.73 },
|
||
{ rank: 44, region: "г.Актобе", service: "2025 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 8613, amount: 30562705.78 },
|
||
{ rank: 45, region: "г.Костанай", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 16473, amount: 30539180.35 },
|
||
{ rank: 46, region: "Енбекшиказахский район", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 17347, amount: 30429552.38 },
|
||
{ rank: 47, region: "г.Актобе", service: "2022 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 11709, amount: 30303557.00 },
|
||
{ rank: 48, region: "г.Кызылорда", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 16539, amount: 29772241.67 },
|
||
{ rank: 49, region: "г.Алматы", service: "2026 B2C Абонплата за Домашний интернет FTTx ГТС", purchases: 10206, amount: 29200316.21 },
|
||
{ rank: 50, region: "г.Караганда", service: "2022 B2C Абонплата за мобильный номер MT-C GSM закр.", purchases: 15714, amount: 28837871.98 }
|
||
];
|
||
|
||
// ===== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ =====
|
||
function formatNumber(num) {
|
||
return num.toString().replace(/\s/g, ' ').replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||
}
|
||
|
||
function formatAmount(amount) {
|
||
return amount.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' ₸';
|
||
}
|
||
|
||
function shortenService(service) {
|
||
return service.replace(/^B2C\s*/, '').replace(/ГТС/g, '').replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
// ===== АГРЕГАЦИЯ ДАННЫХ =====
|
||
function aggregateByRegion(data) {
|
||
const map = {};
|
||
data.forEach(r => {
|
||
if (!map[r.region]) map[r.region] = { region: r.region, purchases: 0, amount: 0 };
|
||
map[r.region].purchases += r.purchases;
|
||
map[r.region].amount += r.amount;
|
||
});
|
||
return Object.values(map).sort((a, b) => b.purchases - a.purchases);
|
||
}
|
||
|
||
function aggregateByService(data) {
|
||
const map = {};
|
||
data.forEach(r => {
|
||
if (!map[r.service]) map[r.service] = { service: r.service, purchases: 0, amount: 0 };
|
||
map[r.service].purchases += r.purchases;
|
||
map[r.service].amount += r.amount;
|
||
});
|
||
return Object.values(map).sort((a, b) => b.purchases - a.purchases);
|
||
}
|
||
|
||
// ===== KPI ПЛИТКИ =====
|
||
function renderKPI(data) {
|
||
const totalPurchases = data.reduce((sum, r) => sum + r.purchases, 0);
|
||
const totalAmount = data.reduce((sum, r) => sum + r.amount, 0);
|
||
const regions = new Set(data.map(r => r.region)).size;
|
||
const topService = data.reduce((max, r) => r.purchases > max.purchases ? r : max, data[0]);
|
||
const topRegion = aggregateByRegion(data)[0];
|
||
|
||
const kpis = [
|
||
{ label: 'Всего покупок', value: formatNumber(totalPurchases), sub: 'за июль 2026' },
|
||
{ label: 'Регионов', value: regions, sub: 'в топ-50' },
|
||
{ label: 'Лидер по покупкам', value: formatNumber(topService.purchases), sub: shortenService(topService.service) },
|
||
{ label: 'Регион-лидер', value: topRegion.region, sub: formatNumber(topRegion.purchases) + ' покупок' }
|
||
];
|
||
|
||
const colors = ['var(--primary)', 'var(--success)', 'var(--accent)', 'var(--primary-light)'];
|
||
document.getElementById('kpiGrid').innerHTML = kpis.map((k, i) => `
|
||
<div class="kpi-card">
|
||
<div class="kpi-label">${k.label}</div>
|
||
<div class="kpi-value" style="color: ${colors[i]}">${k.value}</div>
|
||
<div class="kpi-sub">${k.sub}</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// ===== ГРАФИКИ =====
|
||
function renderBarChart(containerId, data, valueKey, labelKey, color, topN = 10) {
|
||
const top = data.slice(0, topN);
|
||
const max = Math.max(...top.map(d => d[valueKey]));
|
||
const container = document.getElementById(containerId);
|
||
|
||
container.innerHTML = top.map(d => {
|
||
const height = (d[valueKey] / max) * 220;
|
||
return `
|
||
<div class="bar-wrapper" title="${d[labelKey]}: ${formatNumber(d[valueKey])}">
|
||
<div class="bar" style="height: ${height}px; background: ${color}"></div>
|
||
<div class="bar-label">${d[labelKey].length > 12 ? d[labelKey].slice(0, 12) + '...' : d[labelKey]}</div>
|
||
<div class="bar-value">${formatNumber(d[valueKey])}</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// ===== ТАБЛИЦА С ФИЛЬТРАМИ =====
|
||
let filteredData = [...rawData];
|
||
|
||
function populateRegionFilter() {
|
||
const regions = [...new Set(rawData.map(r => r.region))].sort();
|
||
const select = document.getElementById('regionFilter');
|
||
select.innerHTML = '<option value="">Все регионы</option>' +
|
||
regions.map(r => `<option value="${r}">${r}</option>`).join('');
|
||
}
|
||
|
||
function renderTable() {
|
||
const tbody = document.getElementById('tableBody');
|
||
if (filteredData.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;">Нет данных для отображения</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = filteredData.map(r => `
|
||
<tr>
|
||
<td class="rank">${r.rank}</td>
|
||
<td class="region">${r.region}</td>
|
||
<td class="service" title="${r.service}">${shortenService(r.service)}</td>
|
||
<td class="purchases">${formatNumber(r.purchases)}</td>
|
||
<td class="amount">${formatAmount(r.amount)}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// ===== ФИЛЬТРАЦИЯ =====
|
||
function filterData() {
|
||
const search = document.getElementById('searchInput').value.toLowerCase();
|
||
const region = document.getElementById('regionFilter').value;
|
||
|
||
filteredData = rawData.filter(r => {
|
||
const matchesSearch = !search || r.service.toLowerCase().includes(search) || r.region.toLowerCase().includes(search);
|
||
const matchesRegion = !region || r.region === region;
|
||
return matchesSearch && matchesRegion;
|
||
});
|
||
|
||
renderTable();
|
||
updateCharts();
|
||
}
|
||
|
||
function updateCharts() {
|
||
const byRegion = aggregateByRegion(filteredData);
|
||
const byService = aggregateByService(filteredData);
|
||
renderBarChart('regionChart', byRegion, 'purchases', 'region', 'var(--primary)', 10);
|
||
renderBarChart('serviceChart', byService, 'purchases', 'service', 'var(--accent)', 10);
|
||
}
|
||
|
||
// ===== ИНИЦИАЛИЗАЦИЯ =====
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderKPI(rawData);
|
||
populateRegionFilter();
|
||
renderTable();
|
||
updateCharts();
|
||
|
||
document.getElementById('searchInput').addEventListener('input', filterData);
|
||
document.getElementById('regionFilter').addEventListener('change', filterData);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|