237 lines
17 KiB
HTML
237 lines
17 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.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-tokens.css">
|
||
<style>
|
||
body { background: var(--kt-ai-bg-base); font-family: var(--kt-ai-font-family); }
|
||
.kt-ai-pagehead { display: flex; align-items: center; gap: 16px; padding: 24px 32px; background: var(--kt-ai-bg-surface); border-bottom: 1px solid var(--kt-ai-border-subtle); }
|
||
.kt-ai-pagehead h1 { font-size: 24px; font-weight: 600; color: var(--kt-ai-text-primary); margin: 0; }
|
||
.kpi-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; padding: 24px 32px; }
|
||
@media (max-width: 900px) { .kpi-strip { grid-template-columns: repeat(2, 1fr); } }
|
||
.kt-ai-kpi { background: var(--kt-ai-bg-surface); border: 1px solid var(--kt-ai-border-subtle); border-radius: var(--kt-ai-radius-m); padding: 20px; }
|
||
.kt-ai-kpi .value { font-size: 28px; font-weight: 700; color: var(--kt-ai-text-primary); margin-bottom: 4px; }
|
||
.kt-ai-kpi .label { font-size: 14px; color: var(--kt-ai-text-secondary); margin-bottom: 2px; }
|
||
.kt-ai-kpi .hint { font-size: 12px; color: var(--kt-ai-text-muted); }
|
||
.charts-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; padding: 0 32px 24px; }
|
||
@media (max-width: 900px) { .charts-row { grid-template-columns: 1fr; } }
|
||
.kt-ai-card { background: var(--kt-ai-bg-surface); border: 1px solid var(--kt-ai-border-subtle); border-radius: var(--kt-ai-radius-m); padding: 20px; }
|
||
.kt-ai-card h3 { margin: 0 0 16px; font-size: 16px; font-weight: 600; color: var(--kt-ai-text-primary); }
|
||
.chart-wrap { height: 200px; position: relative; display: flex; align-items: flex-end; gap: 4px; padding-top: 10px; overflow-x: auto; }
|
||
.bar { flex: 1; min-width: 20px; max-width: 28px; border-radius: 2px 2px 0 0; position: relative; cursor: pointer; }
|
||
.bar[data-kt-tip]::after { content: attr(data-kt-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--kt-ai-bg-overlay); color: var(--kt-ai-text-inverse); padding: 4px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.15s; }
|
||
.bar:hover::after { opacity: 1; }
|
||
.bar-label { position: absolute; bottom: -18px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--kt-ai-text-muted); white-space: nowrap; }
|
||
.bar-value { position: absolute; top: -16px; left: 50%; transform: translateX(-50%); font-size: 11px; font-family: monospace; color: var(--kt-ai-text-muted); }
|
||
.table-section { padding: 0 32px 32px; }
|
||
.table-controls { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
|
||
.kt-ai-input { padding: 10px 12px; border: 1px solid var(--kt-ai-border-default); border-radius: var(--kt-ai-radius-s); font-size: 14px; background: var(--kt-ai-bg-base); color: var(--kt-ai-text-primary); min-width: 200px; }
|
||
.kt-ai-select { padding: 10px 12px; border: 1px solid var(--kt-ai-border-default); border-radius: var(--kt-ai-radius-s); font-size: 14px; background: var(--kt-ai-bg-base); color: var(--kt-ai-text-primary); }
|
||
.kt-ai-table-wrap { overflow-x: auto; }
|
||
table.kt-ai-table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||
table.kt-ai-table th { text-align: left; padding: 12px 16px; font-weight: 600; color: var(--kt-ai-text-secondary); border-bottom: 1px solid var(--kt-ai-border-subtle); white-space: nowrap; }
|
||
table.kt-ai-table td { padding: 12px 16px; border-bottom: 1px solid var(--kt-ai-border-subtle); color: var(--kt-ai-text-primary); }
|
||
table.kt-ai-table tr:hover td { background: var(--kt-ai-bg-hover); }
|
||
table.kt-ai-table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.chip-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||
.kt-ai-chip { padding: 6px 12px; border-radius: 999px; font-size: 13px; background: var(--kt-ai-bg-tertiary); color: var(--kt-ai-text-primary); border: 1px solid var(--kt-ai-border-subtle); cursor: pointer; }
|
||
.kt-ai-chip.active { background: var(--kt-ai-bg-brand); color: var(--kt-ai-text-inverse); border-color: var(--kt-ai-bg-brand); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="kt-ai-pagehead">
|
||
<img src="./kt-logo.svg" alt="Казахтелеком" height="36">
|
||
<h1>Топ услуг B2C по регионам</h1>
|
||
</div>
|
||
|
||
<div class="kpi-strip">
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-total">0</div>
|
||
<div class="label">Всего покупок</div>
|
||
<div class="hint">за июль 2026</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-regions">0</div>
|
||
<div class="label">Всего регионов</div>
|
||
<div class="hint">в выборке</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-service" style="font-size:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">-</div>
|
||
<div class="label">Услуга-лидер</div>
|
||
<div class="hint">по покупкам</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-region">-</div>
|
||
<div class="label">Регион-лидер</div>
|
||
<div class="hint">по покупкам</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-row">
|
||
<div class="kt-ai-card">
|
||
<h3>Топ регионов по покупкам</h3>
|
||
<div class="chart-wrap" id="chart-regions"></div>
|
||
</div>
|
||
<div class="kt-ai-card">
|
||
<h3>Топ услуг по количеству</h3>
|
||
<div class="chart-wrap" id="chart-services"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-section">
|
||
<div class="table-controls">
|
||
<input type="text" class="kt-ai-input" id="search" placeholder="Поиск по услуге...">
|
||
<select class="kt-ai-select" id="region-filter">
|
||
<option value="">Все регионы</option>
|
||
</select>
|
||
</div>
|
||
<div class="chip-row" id="chip-row"></div>
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr><th>Услуга</th><th>Регион</th><th class="num">Покупок</th><th class="num">Сумма, ₸</th></tr>
|
||
</thead>
|
||
<tbody id="table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const DATA = [
|
||
{"region":"Астана","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":37372,"sum":238463786.09},
|
||
{"region":"г.Алматы","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":39671,"sum":237005698.16},
|
||
{"region":"г.Алматы","service":"Абонплата ШПД по FTTx для B2C","qty":35588,"sum":152893561.28},
|
||
{"region":"Астана","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":35637,"sum":136187938.99},
|
||
{"region":"Астана","service":"B2C TV Plus Абонплата Full ГТС","qty":47661,"sum":132060372.93},
|
||
{"region":"Астана","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":61700,"sum":123403164.43},
|
||
{"region":"г.Алматы","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":57378,"sum":110497209.55},
|
||
{"region":"г.Алматы","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":29997,"sum":106749675.59},
|
||
{"region":"Астана","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":52504,"sum":99235780.41},
|
||
{"region":"Астана","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":36946,"sum":96430127.27},
|
||
{"region":"Астана","service":"Абонплата ШПД по FTTx для B2C","qty":19565,"sum":88834659.73},
|
||
{"region":"Астана","service":"2024 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":22167,"sum":83549325.34},
|
||
{"region":"г.Алматы","service":"B2C TV Plus Абонплата Full ГТС","qty":31221,"sum":83511013.05},
|
||
{"region":"г.Алматы","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":40838,"sum":82748215.46},
|
||
{"region":"г.Семей","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":41486,"sum":76400272.02},
|
||
{"region":"Астана","service":"TV Plus Абонплата за Премиальный пакет ГТС","qty":29210,"sum":71278165.09},
|
||
{"region":"г.Алматы","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":24001,"sum":62521274.30},
|
||
{"region":"Астана","service":"2021 B2C Абонплата за мобильный номер Kcell GSM откр.","qty":27864,"sum":59635624.39},
|
||
{"region":"г.Алматы","service":"2024 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":15678,"sum":58970815.48},
|
||
{"region":"г.Актобе","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":30641,"sum":55819936.98},
|
||
{"region":"г.Алматы","service":"B2C Абонплата за основной телефон ГТС","qty":52732,"sum":49636818.50},
|
||
{"region":"г.Актобе","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":25178,"sum":48000483.43},
|
||
{"region":"г.Алматы","service":"TV Plus Абонплата за Премиальный пакет ГТС","qty":19055,"sum":45298192.89},
|
||
{"region":"г.Павлодар","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":23334,"sum":44857518.84},
|
||
{"region":"г.Алматы","service":"B2C TV Plus Аренда STB оператора новое","qty":96183,"sum":43252519.71},
|
||
{"region":"г.Караганда","service":"Абонплата ШПД по FTTx для B2C","qty":8916,"sum":41624727.66},
|
||
{"region":"Астана","service":"2026 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":13685,"sum":41618770.34},
|
||
{"region":"г.Алматы","service":"2018 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":11568,"sum":40199964.35},
|
||
{"region":"г.Тараз","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":21880,"sum":39564813.34},
|
||
{"region":"г.Алматы","service":"Абонплата ШПД по ADSL для B2C","qty":10710,"sum":36238454.72},
|
||
{"region":"г.Алматы","service":"2020 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":12148,"sum":36010481.68},
|
||
{"region":"г.Усть-Каменогорск","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":18362,"sum":35883527.39},
|
||
{"region":"г. Шымкент","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":19180,"sum":35338634.51},
|
||
{"region":"г. Шымкент","service":"2021 B2C Абонплата за мобильный номер Kcell GSM закр.","qty":18945,"sum":35138664.91},
|
||
{"region":"Астана","service":"B2C TV Plus Аренда STB оператора новое","qty":76655,"sum":34746783.92},
|
||
{"region":"Астана","service":"2018 B2C Абонплата за Домашний интернет FTTx ГТС","qty":8705,"sum":32905725.03},
|
||
{"region":"г.Актобе","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":6742,"sum":32313021.91},
|
||
{"region":"г.Усть-Каменогорск","service":"Абонплата ШПД по FTTx для B2C","qty":7269,"sum":31834355.15},
|
||
{"region":"г.Атырау","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":5495,"sum":31688118.61},
|
||
{"region":"г.Усть-Каменогорск","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":5600,"sum":31520413.53},
|
||
{"region":"Астана","service":"B2C Аренда оборудования ONT оператора - новое","qty":87071,"sum":31220264.09},
|
||
{"region":"г.Павлодар","service":"B2C Абонплата ШПД по FTTx ГТС (New)","qty":6104,"sum":30831455.08},
|
||
{"region":"г.Кокшетау","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16818,"sum":30654924.73},
|
||
{"region":"г.Актобе","service":"2025 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":8613,"sum":30562705.78},
|
||
{"region":"г.Костанай","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16473,"sum":30539180.35},
|
||
{"region":"Енбекшиказахский район","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":17347,"sum":30429552.38},
|
||
{"region":"г.Актобе","service":"2022 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":11709,"sum":30303557.00},
|
||
{"region":"г.Кызылорда","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":16539,"sum":29772241.67},
|
||
{"region":"г.Алматы","service":"2026 B2C Абонплата за Домашний Интернет FTTx ГТС","qty":10206,"sum":29200316.21},
|
||
{"region":"г.Караганда","service":"2022 B2C Абонплата за мобильный номер MT-C GSM закр.","qty":15714,"sum":28837871.98}
|
||
];
|
||
|
||
const fmtNum = n => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||
const fmtSum = n => n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||
|
||
const totalQty = DATA.reduce((s, r) => s + r.qty, 0);
|
||
const regions = [...new Set(DATA.map(r => r.region))];
|
||
const byRegion = {};
|
||
regions.forEach(reg => byRegion[reg] = DATA.filter(r => r.region === reg).reduce((s, r) => s + r.qty, 0));
|
||
const topRegion = Object.entries(byRegion).sort((a, b) => b[1] - a[1])[0];
|
||
const byService = {};
|
||
DATA.forEach(r => byService[r.service] = (byService[r.service] || 0) + r.qty);
|
||
const topService = Object.entries(byService).sort((a, b) => b[1] - a[1])[0];
|
||
|
||
document.getElementById('kpi-total').textContent = fmtNum(totalQty);
|
||
document.getElementById('kpi-regions').textContent = regions.length;
|
||
document.getElementById('kpi-service').textContent = topService[0];
|
||
document.getElementById('kpi-service').title = topService[0];
|
||
document.getElementById('kpi-region').textContent = topRegion[0];
|
||
|
||
function renderChart(containerId, data, colorVar) {
|
||
const container = document.getElementById(containerId);
|
||
const maxVal = Math.max(...data.map(d => d.value));
|
||
container.innerHTML = data.map((d, i) => {
|
||
const h = (d.value / maxVal) * 170;
|
||
return `<div class="bar" data-kt-tip="${d.label}: ${fmtNum(d.value)}" style="height:${h}px;background:var(${colorVar});">
|
||
<span class="bar-value">${fmtNum(d.value)}</span>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
const regionData = Object.entries(byRegion).sort((a, b) => b[1] - a[1]).slice(0, 12).map(([label, value]) => ({ label, value }));
|
||
const serviceData = Object.entries(byService).sort((a, b) => b[1] - a[1]).slice(0, 12).map(([label, value]) => ({ label, value: value }));
|
||
renderChart('chart-regions', regionData, '--kt-ai-chart-blue');
|
||
renderChart('chart-services', serviceData, '--kt-ai-chart-green');
|
||
|
||
const regionFilter = document.getElementById('region-filter');
|
||
const chipRow = document.getElementById('chip-row');
|
||
regions.sort().forEach(reg => {
|
||
const opt = document.createElement('option');
|
||
opt.value = reg; opt.textContent = reg;
|
||
regionFilter.appendChild(opt);
|
||
const chip = document.createElement('button');
|
||
chip.className = 'kt-ai-chip';
|
||
chip.textContent = reg;
|
||
chip.onclick = () => { regionFilter.value = reg; renderTable(); };
|
||
chipRow.appendChild(chip);
|
||
});
|
||
|
||
let activeChip = null;
|
||
regionFilter.addEventListener('change', () => {
|
||
const val = regionFilter.value;
|
||
chipRow.querySelectorAll('.kt-ai-chip').forEach(c => {
|
||
c.classList.toggle('active', c.textContent === val);
|
||
});
|
||
renderTable();
|
||
});
|
||
|
||
document.getElementById('search').addEventListener('input', renderTable);
|
||
|
||
function renderTable() {
|
||
const search = document.getElementById('search').value.toLowerCase();
|
||
const region = regionFilter.value;
|
||
let filtered = DATA.filter(r => {
|
||
const matchSearch = r.service.toLowerCase().includes(search);
|
||
const matchRegion = region ? r.region === region : true;
|
||
return matchSearch && matchRegion;
|
||
});
|
||
const tbody = document.getElementById('table-body');
|
||
tbody.innerHTML = filtered.map(r => `
|
||
<tr>
|
||
<td>${r.service}</td>
|
||
<td>${r.region}</td>
|
||
<td class="num">${fmtNum(r.qty)}</td>
|
||
<td class="num">${fmtSum(r.sum)}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
renderTable();
|
||
</script>
|
||
</body>
|
||
</html>
|