МСБ дашборд 2 слайда
This commit is contained in:
parent
4dbf471213
commit
27cf3ee249
323
index.html
323
index.html
@ -3,37 +3,45 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Дашборд: Анализ данных</title>
|
<title>Дашборд: Захват рынка МСБ</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.12.0/dist/pptxgen.bundle.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.12.0/dist/pptxgen.bundle.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; box-sizing: border-box; }
|
* { margin: 0; box-sizing: border-box; }
|
||||||
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #0f1218; color: #f4f4f5; }
|
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #0f1218; color: #f4f4f5; }
|
||||||
.slide { display: none; min-height: 100vh; padding: 8vh 10vw; flex-direction: column; justify-content: center; }
|
.slide { display: none; min-height: 100vh; padding: 6vh 8vw; flex-direction: column; justify-content: flex-start; }
|
||||||
.slide.on { display: flex; }
|
.slide.on { display: flex; }
|
||||||
.slide h1 { font-size: clamp(34px, 6vw, 64px); margin-bottom: 24px; color: #00b8cc; }
|
.slide h1 { font-size: clamp(28px, 5vw, 52px); margin-bottom: 20px; color: #00b8cc; }
|
||||||
.slide h2 { font-size: clamp(24px, 4vw, 42px); margin-bottom: 20px; color: #00b8cc; }
|
.slide h2 { font-size: clamp(22px, 4vw, 38px); margin-bottom: 16px; color: #00b8cc; }
|
||||||
.slide li { font-size: clamp(18px, 2.6vw, 28px); line-height: 1.6; margin-bottom: 10px; list-style: none; padding-left: 28px; position: relative; }
|
.slide li { font-size: clamp(16px, 2.4vw, 24px); line-height: 1.5; margin-bottom: 8px; list-style: none; padding-left: 24px; position: relative; }
|
||||||
.slide li::before { content: ""; position: absolute; left: 0; top: .55em; width: 12px; height: 12px; border-radius: 3px; background: #00b8cc; }
|
.slide li::before { content: ""; position: absolute; left: 0; top: .55em; width: 10px; height: 10px; border-radius: 3px; background: #00b8cc; }
|
||||||
.num { position: fixed; bottom: 18px; right: 22px; color: #8a8a92; font-size: 14px; }
|
.num { position: fixed; bottom: 18px; right: 22px; color: #8a8a92; font-size: 14px; }
|
||||||
.nav { position: fixed; bottom: 14px; left: 22px; display: flex; gap: 8px; flex-wrap: wrap; max-width: 80vw; }
|
.nav { position: fixed; bottom: 14px; left: 22px; display: flex; gap: 8px; flex-wrap: wrap; max-width: 80vw; }
|
||||||
.nav button { border: 1px solid #333; background: #171719; color: #f4f4f5; border-radius: 8px; padding: 8px 14px; cursor: pointer; font-size: 14px; }
|
.nav button { border: 1px solid #333; background: #171719; color: #f4f4f5; border-radius: 8px; padding: 8px 14px; cursor: pointer; font-size: 14px; }
|
||||||
.nav .pptx { border-color: #00b8cc; color: #9beeff; }
|
.nav .pptx { border-color: #00b8cc; color: #9beeff; }
|
||||||
.nav .refresh { border-color: #10b981; color: #6ee7b7; }
|
.nav .refresh { border-color: #10b981; color: #6ee7b7; }
|
||||||
.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-top: 30px; }
|
.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 20px; }
|
||||||
.metric-card { background: #1a1d24; border-radius: 12px; padding: 24px; border: 1px solid #2a2f3a; }
|
.metric-card { background: #1a1d24; border-radius: 10px; padding: 20px; border: 1px solid #2a2f3a; }
|
||||||
.metric-card h3 { font-size: 16px; color: #8a8a92; margin-bottom: 8px; }
|
.metric-card h3 { font-size: 14px; color: #8a8a92; margin-bottom: 6px; }
|
||||||
.metric-card .value { font-size: clamp(28px, 4vw, 42px); font-weight: bold; color: #00b8cc; }
|
.metric-card .value { font-size: clamp(24px, 3.5vw, 36px); font-weight: bold; color: #00b8cc; }
|
||||||
.metric-card .sub { font-size: 14px; color: #6a6a72; margin-top: 4px; }
|
.metric-card .sub { font-size: 13px; color: #6a6a72; margin-top: 4px; }
|
||||||
.chart-placeholder { background: #1a1d24; border-radius: 12px; padding: 40px; text-align: center; margin: 20px 0; border: 1px solid #2a2f3a; }
|
.chart-placeholder { background: #1a1d24; border-radius: 12px; padding: 30px; text-align: center; margin: 16px 0; border: 1px solid #2a2f3a; }
|
||||||
.bar-row { display: flex; align-items: center; margin: 8px 0; }
|
.bar-row { display: flex; align-items: center; margin: 10px 0; }
|
||||||
.bar-label { width: 220px; font-size: 13px; color: #a1a1aa; }
|
.bar-label { width: 180px; font-size: 12px; color: #a1a1aa; }
|
||||||
.bar-container { flex: 1; background: #2a2f3a; border-radius: 4px; height: 24px; overflow: hidden; }
|
.bar-container { flex: 1; background: #2a2f3a; border-radius: 4px; height: 22px; overflow: hidden; }
|
||||||
.bar { height: 100%; background: linear-gradient(90deg, #00b8cc, #0ea5e9); border-radius: 4px; transition: width 0.3s; }
|
.bar { height: 100%; background: linear-gradient(90deg, #00b8cc, #0ea5e9); border-radius: 4px; transition: width 0.3s; }
|
||||||
.bar-value { margin-left: 12px; font-size: 14px; color: #00b8cc; min-width: 90px; text-align: right; }
|
.bar-value { margin-left: 10px; font-size: 13px; color: #00b8cc; min-width: 70px; text-align: right; font-weight: 600; }
|
||||||
.file-input { margin: 20px 0; }
|
.file-input { margin: 16px 0; }
|
||||||
.file-input input { display: none; }
|
.file-input input { display: none; }
|
||||||
.file-input label { display: inline-block; padding: 10px 20px; background: #00b8cc; color: #0f1218; border-radius: 8px; cursor: pointer; font-weight: 600; }
|
.file-input label { display: inline-block; padding: 8px 16px; background: #00b8cc; color: #0f1218; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; }
|
||||||
.file-input label:hover { background: #00d4ec; }
|
.file-input label:hover { background: #00d4ec; }
|
||||||
|
.rdt-section { margin: 20px 0; }
|
||||||
|
.rdt-title { font-size: 18px; color: #00b8cc; margin: 16px 0 10px 0; font-weight: 600; }
|
||||||
|
.tariff-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #2a2f3a; font-size: 13px; }
|
||||||
|
.tariff-row:last-child { border-bottom: none; }
|
||||||
|
.tariff-name { color: #a1a1aa; }
|
||||||
|
.tariff-val { color: #00b8cc; font-weight: 600; }
|
||||||
|
.summary-box { background: #1a1d24; border: 1px solid #00b8cc; border-radius: 10px; padding: 20px; margin: 16px 0; }
|
||||||
|
.summary-box h3 { color: #00b8cc; margin-bottom: 12px; font-size: 18px; }
|
||||||
@media print {
|
@media print {
|
||||||
.nav, .num, .file-input { display: none; }
|
.nav, .num, .file-input { display: none; }
|
||||||
.slide { display: flex; page-break-after: always; min-height: 100vh; }
|
.slide { display: flex; page-break-after: always; min-height: 100vh; }
|
||||||
@ -53,215 +61,90 @@
|
|||||||
<input type="file" id="fileInput" class="file-input" multiple accept=".xlsx,.xls" onchange="handleFiles(this.files)">
|
<input type="file" id="fileInput" class="file-input" multiple accept=".xlsx,.xls" onchange="handleFiles(this.files)">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var RATING_DATA = [
|
var RDT_DATA = [
|
||||||
["Пакет", "Стоимость, тг с НДС", "количество точек", "доходы, тыс.тг"],
|
{ name: "Восточная РДТ", paynet: 0, bastau5k: 69, bastau7_5k: 5, bastau10k: 5, horeca: 1 },
|
||||||
["2026 KASIPKER Bailanysta", "15 000", 1974, 123940.01],
|
{ name: "Западная РДТ", paynet: 1, bastau5k: 128, bastau7_5k: 6, bastau10k: 60, horeca: 6 },
|
||||||
["2026 Dara 50", "9 000", 1780, 73421.37],
|
{ name: "РДТ Алматытелеком", paynet: 51, bastau5k: 68, bastau7_5k: 17, bastau10k: 42, horeca: 10 },
|
||||||
["2026 KASIPKER Bari bar", "25 000", 975, 82870.37],
|
{ name: "Северная РДТ", paynet: 0, bastau5k: 53, bastau7_5k: 1, bastau10k: 10, horeca: 0 },
|
||||||
["2026 KASIPKER Qorganysta", "17 000", 955, 56667.10],
|
{ name: "Центральная РДТ", paynet: 4, bastau5k: 1076, bastau7_5k: 9, bastau10k: 132, horeca: 0 },
|
||||||
["2026 Bastau 50", "5 000", 533, 4569.04],
|
{ name: "Южная РДТ", paynet: 15, bastau5k: 144, bastau7_5k: 8, bastau10k: 21, horeca: 2 }
|
||||||
["2026 Интернет 100 БКВ", "100-10000", 341, 252.27],
|
|
||||||
["2026 Bastau Cam 100", "10 000", 270, 3030.72],
|
|
||||||
["NOMAD 5G МТ-С (2 TB)", "13 000", 205, 5831.82],
|
|
||||||
["2026 ВЦИ SERIKTES Bailanysta", "100 000", 201, 77132.87],
|
|
||||||
["2026 Dara_pr 100/100", "13 000", 161, 9125.79],
|
|
||||||
["2026 ВЦИ SERIKTES Qorganysta", "100 000", 142, 53111.16],
|
|
||||||
["2026 Kasipker Qorganysta 5G MTC", "17 000", 137, 7956.44],
|
|
||||||
["2026 Bastau 8", "5 000", 90, 750.14],
|
|
||||||
["2026 Dara 30/30 SaveDesk", "5 000", 78, 1420.08],
|
|
||||||
["2026 Dara_pr 100/100 контракт 1 год", "10 000", 75, 2987.86],
|
|
||||||
["2026 Dara 30/30 контракт 1 год", "300 - 5 000", 72, 1438.68],
|
|
||||||
["2026 Paynet 5 (Wi-Fi роутер Sapa)", "1500", 71, 97.57],
|
|
||||||
["2026 Интернет для ОСИ", "5 500", 66, 1766.85],
|
|
||||||
["2026 Bastau Cam 50", "7 500", 23, 180.99],
|
|
||||||
["2026 Bastau Call 50", "7 500", 20, 307.47],
|
|
||||||
["2026 HoReCa KT 150 (Wi-Fi роутер Sapa)", "15 000", 19, 367.36],
|
|
||||||
["2026 Dara 100 Halyk", "100-10000", 12, 1.17]
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var PRESENCE_DATA = [
|
var TOTALS = {
|
||||||
["Пакет", "202608", "Доход 202608"],
|
paynet: 71,
|
||||||
["Итоги", 8200, 112467.38],
|
bastau5k: 1538,
|
||||||
["2026 KASIPKER Bailanysta", 1974, 25472.38],
|
bastau7_5k: 46,
|
||||||
["2026 Dara 50", 1780, 14060.23],
|
bastau10k: 270,
|
||||||
["2026 KASIPKER Bari bar", 975, 18793.02],
|
horeca: 19
|
||||||
["2026 KASIPKER Qorganysta", 955, 11700.64],
|
};
|
||||||
["2026 Bastau 50", 533, 2220.52],
|
|
||||||
["2026 Интернет 100 БКВ", 341, 49.32],
|
|
||||||
["2026 Bastau Cam 100", 270, 1625.44],
|
|
||||||
["NOMAD 5G МТ-С (2 TB)", 205, 2190.41],
|
|
||||||
["2026 ВЦИ SERIKTES Bailanysta", 201, 17939.68],
|
|
||||||
["2026 Dara_pr 100/100", 161, 1809.37],
|
|
||||||
["2026 ВЦИ SERIKTES Qorganysta", 142, 12078.70],
|
|
||||||
["2026 Kasipker Qorganysta 5G MTC", 137, 1976.25],
|
|
||||||
["2026 Bastau 8", 90, 376.25],
|
|
||||||
["2026 Dara 30/30 SaveDesk", 78, 335.37],
|
|
||||||
["2026 Dara_pr 100/100 контракт 1 год", 75, 646.55],
|
|
||||||
["2026 Dara 30/30 контракт 1 год", 72, 310.34],
|
|
||||||
["2026 Paynet 5 (Wi-Fi роутер Sapa)", 71, 91.31],
|
|
||||||
["2026 Интернет для ОСИ", 66, 334.87],
|
|
||||||
["2026 Bastau Cam 50", 23, 93.82],
|
|
||||||
["2026 Bastau Call 50", 20, 123.04],
|
|
||||||
["2026 HoReCa KT 150 (Wi-Fi роутер Sapa)", 19, 239.02],
|
|
||||||
["2026 Dara 100 Halyk", 12, 0.84]
|
|
||||||
];
|
|
||||||
|
|
||||||
var ODT_DATA = [
|
|
||||||
["Филиал", "Итоги"],
|
|
||||||
["ГЦТ Астанателеком", 10907],
|
|
||||||
["ГЦТ Алматытелеком", 6166],
|
|
||||||
["Западная региональная дирекция телекоммуникаций", 5614],
|
|
||||||
["Южная региональная дирекция телекоммуникаций", 4514],
|
|
||||||
["Восточная региональная дирекция телекоммуникаций", 2675],
|
|
||||||
["Карагандинская ОДТ", 2203],
|
|
||||||
["Актюбинская ОДТ", 2077],
|
|
||||||
["Алматинская ОДТ", 2060],
|
|
||||||
["Мангистауская ОДТ", 1270],
|
|
||||||
["Атырауская ОДТ", 1253],
|
|
||||||
["Западно-Казахстанская ОДТ", 1014],
|
|
||||||
["Павлодарская ОДТ", 1132],
|
|
||||||
["Жамбылская ОДТ", 1031],
|
|
||||||
["Семипалатинск", 871],
|
|
||||||
["Кызылординская ОДТ", 738],
|
|
||||||
["Восточно-Казахстанская ОДТ", 672],
|
|
||||||
["Северо-Казахстанская ОДТ", 542],
|
|
||||||
["Костанайская ОДТ", 536],
|
|
||||||
["Акмолинская ОДТ", 1232]
|
|
||||||
];
|
|
||||||
|
|
||||||
var INSTALL_DATA = [
|
|
||||||
["Месяц", "Переключения", "Новые установки"],
|
|
||||||
["202601", 234, 464],
|
|
||||||
["202602", 559, 1113],
|
|
||||||
["202603", 1008, 1758],
|
|
||||||
["202604", 1401, 2494],
|
|
||||||
["202605", 1726, 3297],
|
|
||||||
["202606", 2009, 4395],
|
|
||||||
["202607", 2154, 5637],
|
|
||||||
["202608", 2205, 5995]
|
|
||||||
];
|
|
||||||
|
|
||||||
var SLIDES = [];
|
var SLIDES = [];
|
||||||
|
|
||||||
function processData() {
|
function buildSlides() {
|
||||||
var totalPoints = 8200;
|
var totalMsb = TOTALS.paynet + TOTALS.bastau5k + TOTALS.bastau7_5k + TOTALS.bastau10k + TOTALS.horeca;
|
||||||
var totalRevenue = 112467.38;
|
|
||||||
var topPackages = [];
|
|
||||||
var regionalData = [];
|
|
||||||
var monthlyGrowth = [];
|
|
||||||
|
|
||||||
for (var i = 1; i < RATING_DATA.length; i++) {
|
|
||||||
var row = RATING_DATA[i];
|
|
||||||
topPackages.push({
|
|
||||||
name: row[0],
|
|
||||||
points: row[2] || 0,
|
|
||||||
revenue: row[3] || 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
topPackages.sort(function(a, b) { return b.revenue - a.revenue; });
|
|
||||||
|
|
||||||
for (var i = 1; i < ODT_DATA.length; i++) {
|
|
||||||
regionalData.push({ name: ODT_DATA[i][0], value: ODT_DATA[i][1] });
|
|
||||||
}
|
|
||||||
regionalData.sort(function(a, b) { return b.value - a.value; });
|
|
||||||
|
|
||||||
for (var i = 1; i < INSTALL_DATA.length; i++) {
|
|
||||||
monthlyGrowth.push({
|
|
||||||
month: INSTALL_DATA[i][0].slice(4),
|
|
||||||
switches: INSTALL_DATA[i][1],
|
|
||||||
new: INSTALL_DATA[i][2]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var revenueFormatted = totalRevenue >= 1000 ? (totalRevenue / 1000).toFixed(1) + ' млн' : totalRevenue.toFixed(0);
|
|
||||||
|
|
||||||
SLIDES = [
|
SLIDES = [
|
||||||
{
|
|
||||||
title: "Дашборд: Анализ данных",
|
|
||||||
bullets: [
|
|
||||||
"Период: Январь - Август 2026",
|
|
||||||
"Всего точек: " + totalPoints.toLocaleString('ru-RU'),
|
|
||||||
"Доход: " + revenueFormatted + " тыс.₸",
|
|
||||||
"Пакетов в работе: " + (RATING_DATA.length - 1),
|
|
||||||
"",
|
|
||||||
"Навигация: стрелки ← → или кнопки внизу",
|
|
||||||
"Загрузите новые Excel-файлы для обновления"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Ключевые метрики",
|
title: "1. ОСНОВНЫЕ ФОКУСЫ ДЛЯ АГРЕССИВНОГО ЗАХВАТА РЫНКА МСБ",
|
||||||
bullets: [],
|
|
||||||
custom: function() {
|
custom: function() {
|
||||||
var cards = [
|
var maxVal = Math.max(TOTALS.paynet, TOTALS.bastau5k, TOTALS.bastau7_5k, TOTALS.bastau10k, TOTALS.horeca);
|
||||||
{ label: "Всего точек", value: totalPoints.toLocaleString('ru-RU'), sub: "Активные подключения" },
|
var tariffs = [
|
||||||
{ label: "Доход", value: revenueFormatted, sub: "тыс. ₸ в месяц" },
|
{ name: "Paynet 5 (1 500 ₸)", val: TOTALS.paynet, color: "#00b8cc" },
|
||||||
{ label: "Пакетов", value: (RATING_DATA.length - 1).toString(), sub: "Тарифных планов" },
|
{ name: "BASTAU 8/50 (5 000 ₸)", val: TOTALS.bastau5k, color: "#0ea5e9" },
|
||||||
{ label: "Регионов", value: ODT_DATA.length - 1 + "", sub: "ОДТ в отчёте" }
|
{ name: "BASTAU CAM/CALL 50 (7 500 ₸)", val: TOTALS.bastau7_5k, color: "#10b981" },
|
||||||
|
{ name: "BASTAU CAM 100 (10 000 ₸)", val: TOTALS.bastau10k, color: "#f59e0b" },
|
||||||
|
{ name: "HoReCa KT (15 000 ₸)", val: TOTALS.horeca, color: "#ef4444" }
|
||||||
];
|
];
|
||||||
return '<div class="metric-grid">' + cards.map(function(c) {
|
|
||||||
return '<div class="metric-card"><h3>' + c.label + '</h3><div class="value">' + c.value + '</div><div class="sub">' + c.sub + '</div></div>';
|
var bars = tariffs.map(function(t) {
|
||||||
}).join('') + '</div>';
|
var pct = maxVal > 0 ? (t.val / maxVal * 100) : 0;
|
||||||
}
|
return '<div class="bar-row">' +
|
||||||
},
|
'<div class="bar-label">' + t.name + '</div>' +
|
||||||
{
|
'<div class="bar-container"><div class="bar" style="width:' + pct + '%; background:' + t.color + '"></div></div>' +
|
||||||
title: "Топ пакетов по доходу",
|
'<div class="bar-value">' + t.val + ' точек</div></div>';
|
||||||
bullets: [],
|
|
||||||
custom: function() {
|
|
||||||
if (topPackages.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
|
|
||||||
var maxRev = Math.max.apply(null, topPackages.map(function(p) { return p.revenue; }));
|
|
||||||
return topPackages.slice(0, 8).map(function(p) {
|
|
||||||
var pct = maxRev > 0 ? (p.revenue / maxRev * 100) : 0;
|
|
||||||
var revStr = p.revenue >= 1000 ? (p.revenue/1000).toFixed(1) + ' млн' : p.revenue.toFixed(0);
|
|
||||||
return '<div class="bar-row"><div class="bar-label">' + p.name + '</div><div class="bar-container"><div class="bar" style="width:' + pct + '%"></div></div><div class="bar-value">' + revStr + ' тыс.₸</div></div>';
|
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
return '<div class="summary-box"><h3>📊 Итого по МСБ тарифам: ' + totalMsb.toLocaleString('ru-RU') + ' точек</h3>' + bars + '</div>' +
|
||||||
|
'<div class="metric-grid">' +
|
||||||
|
'<div class="metric-card"><h3>Paynet 5</h3><div class="value">' + TOTALS.paynet + '</div><div class="sub">1 500 ₸</div></div>' +
|
||||||
|
'<div class="metric-card"><h3>BASTAU 8/50</h3><div class="value">' + TOTALS.bastau5k + '</div><div class="sub">5 000 ₸</div></div>' +
|
||||||
|
'<div class="metric-card"><h3>BASTAU CAM/CALL 50</h3><div class="value">' + TOTALS.bastau7_5k + '</div><div class="sub">7 500 ₸</div></div>' +
|
||||||
|
'<div class="metric-card"><h3>BASTAU CAM 100</h3><div class="value">' + TOTALS.bastau10k + '</div><div class="sub">10 000 ₸</div></div>' +
|
||||||
|
'<div class="metric-card"><h3>HoReCa KT</h3><div class="value">' + TOTALS.horeca + '</div><div class="sub">15 000 ₸</div></div>' +
|
||||||
|
'</div>';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Топ пакетов по точкам",
|
title: "2. Тарифы МСБ в разрезе РДТ (Август 2026)",
|
||||||
bullets: [],
|
|
||||||
custom: function() {
|
custom: function() {
|
||||||
if (topPackages.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
|
var sections = RDT_DATA.map(function(rdt) {
|
||||||
var maxPts = Math.max.apply(null, topPackages.map(function(p) { return p.points; }));
|
var total = rdt.paynet + rdt.bastau5k + rdt.bastau7_5k + rdt.bastau10k + rdt.horeca;
|
||||||
return topPackages.slice(0, 8).map(function(p) {
|
return '<div class="rdt-section">' +
|
||||||
var pct = maxPts > 0 ? (p.points / maxPts * 100) : 0;
|
'<div class="rdt-title">' + rdt.name + ' — ' + total + ' точек</div>' +
|
||||||
return '<div class="bar-row"><div class="bar-label">' + p.name + '</div><div class="bar-container"><div class="bar" style="width:' + pct + '%"></div></div><div class="bar-value">' + p.points.toLocaleString('ru-RU') + '</div></div>';
|
'<div class="tariff-row"><span class="tariff-name">Paynet 5 (1 500 ₸)</span><span class="tariff-val">' + rdt.paynet + '</span></div>' +
|
||||||
|
'<div class="tariff-row"><span class="tariff-name">BASTAU 8/50 (5 000 ₸)</span><span class="tariff-val">' + rdt.bastau5k + '</span></div>' +
|
||||||
|
'<div class="tariff-row"><span class="tariff-name">BASTAU CAM/CALL 50 (7 500 ₸)</span><span class="tariff-val">' + rdt.bastau7_5k + '</span></div>' +
|
||||||
|
'<div class="tariff-row"><span class="tariff-name">BASTAU CAM 100 (10 000 ₸)</span><span class="tariff-val">' + rdt.bastau10k + '</span></div>' +
|
||||||
|
'<div class="tariff-row"><span class="tariff-name">HoReCa KT (15 000 ₸)</span><span class="tariff-val">' + rdt.horeca + '</span></div>' +
|
||||||
|
'</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
var maxRdt = Math.max.apply(null, RDT_DATA.map(function(r) { return r.paynet + r.bastau5k + r.bastau7_5k + r.bastau10k + r.horeca; }));
|
||||||
|
var rdtBars = RDT_DATA.sort(function(a,b) { return (b.paynet+b.bastau5k+b.bastau7_5k+b.bastau10k+b.horeca) - (a.paynet+a.bastau5k+a.bastau7_5k+a.bastau10k+a.horeca); })
|
||||||
|
.map(function(rdt) {
|
||||||
|
var total = rdt.paynet + rdt.bastau5k + rdt.bastau7_5k + rdt.bastau10k + rdt.horeca;
|
||||||
|
var pct = maxRdt > 0 ? (total / maxRdt * 100) : 0;
|
||||||
|
return '<div class="bar-row">' +
|
||||||
|
'<div class="bar-label">' + rdt.name + '</div>' +
|
||||||
|
'<div class="bar-container"><div class="bar" style="width:' + pct + '%"></div></div>' +
|
||||||
|
'<div class="bar-value">' + total + ' точек</div></div>';
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return '<div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px;">' +
|
||||||
|
'<div><h2 style="margin-bottom:12px;">По РДТ</h2>' + rdtBars + '</div>' +
|
||||||
|
'<div>' + sections + '</div>' +
|
||||||
|
'</div>';
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Региональное распределение",
|
|
||||||
bullets: [],
|
|
||||||
custom: function() {
|
|
||||||
if (regionalData.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
|
|
||||||
var maxVal = Math.max.apply(null, regionalData.map(function(r) { return r.value; }));
|
|
||||||
return regionalData.slice(0, 7).map(function(r) {
|
|
||||||
var pct = maxVal > 0 ? (r.value / maxVal * 100) : 0;
|
|
||||||
return '<div class="bar-row"><div class="bar-label">' + r.name + '</div><div class="bar-container"><div class="bar" style="width:' + pct + '%"></div></div><div class="bar-value">' + r.value.toLocaleString('ru-RU') + '</div></div>';
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Динамика подключений",
|
|
||||||
bullets: [],
|
|
||||||
custom: function() {
|
|
||||||
if (monthlyGrowth.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
|
|
||||||
var maxVal = Math.max.apply(null, monthlyGrowth.map(function(m) { return m.new; }));
|
|
||||||
return monthlyGrowth.map(function(m) {
|
|
||||||
var pct = maxVal > 0 ? (m.new / maxVal * 100) : 0;
|
|
||||||
var monthName = m.month;
|
|
||||||
return '<div class="bar-row"><div class="bar-label">Месяц ' + monthName + '</div><div class="bar-container"><div class="bar" style="width:' + pct + '%"></div></div><div class="bar-value">' + m.new.toLocaleString('ru-RU') + ' новых</div></div>';
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Итоги",
|
|
||||||
bullets: [
|
|
||||||
"Дашборд готов к использованию",
|
|
||||||
"Загрузите новые Excel-файлы через кнопку «Загрузить новые данные»",
|
|
||||||
"Данные обновятся автоматически",
|
|
||||||
"Скачайте презентацию в формате .pptx",
|
|
||||||
"Все метрики пересчитаются для новых данных"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -273,8 +156,7 @@ function render() {
|
|||||||
deck.innerHTML = SLIDES.map(function (s, i) {
|
deck.innerHTML = SLIDES.map(function (s, i) {
|
||||||
var content = s.custom ? s.custom() : '<ul>' + s.bullets.map(function (b) { return "<li>" + b + "</li>"; }).join("") + '</ul>';
|
var content = s.custom ? s.custom() : '<ul>' + s.bullets.map(function (b) { return "<li>" + b + "</li>"; }).join("") + '</ul>';
|
||||||
return '<section class="slide' + (i === cur ? " on" : "") + '">' +
|
return '<section class="slide' + (i === cur ? " on" : "") + '">' +
|
||||||
(s.title.includes('Дашборд') ? '<h1>' + s.title + '</h1>' : '<h2>' + s.title + '</h2>') +
|
'<h1>' + s.title + '</h1>' + content + '</section>';
|
||||||
content + '</section>';
|
|
||||||
}).join("");
|
}).join("");
|
||||||
document.getElementById("num").textContent = (cur + 1) + " / " + SLIDES.length;
|
document.getElementById("num").textContent = (cur + 1) + " / " + SLIDES.length;
|
||||||
}
|
}
|
||||||
@ -297,29 +179,24 @@ function downloadPptx() {
|
|||||||
SLIDES.forEach(function (s) {
|
SLIDES.forEach(function (s) {
|
||||||
var sl = p.addSlide();
|
var sl = p.addSlide();
|
||||||
sl.background = { color: "0F1218" };
|
sl.background = { color: "0F1218" };
|
||||||
|
sl.addText(s.title, { x: 0.5, y: 0.4, w: 12.3, h: 1, fontSize: 28, bold: true, color: "00B8CC" });
|
||||||
var titleStyle = { x: 0.7, y: 0.5, w: 12, h: 1.2, fontSize: 32, bold: true, color: "00B8CC" };
|
|
||||||
if (s.title.includes('Дашборд')) {
|
|
||||||
titleStyle.fontSize = 36;
|
|
||||||
}
|
|
||||||
sl.addText(s.title, titleStyle);
|
|
||||||
|
|
||||||
if (s.bullets && s.bullets.length > 0) {
|
if (s.bullets && s.bullets.length > 0) {
|
||||||
var bulletPoints = s.bullets.filter(function(b) { return b; }).map(function(b) {
|
var bulletPoints = s.bullets.filter(function(b) { return b; }).map(function(b) {
|
||||||
return { text: b, options: { bullet: true, breakLine: true, fontSize: 20, color: "E4E4E7" } };
|
return { text: b, options: { bullet: true, breakLine: true, fontSize: 18, color: "E4E4E7" } };
|
||||||
});
|
});
|
||||||
sl.addText(bulletPoints, { x: 0.9, y: 2.0, w: 11.5, h: 4.8, lineSpacingMultiple: 1.4 });
|
sl.addText(bulletPoints, { x: 0.7, y: 1.6, w: 12, h: 5, lineSpacingMultiple: 1.4 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
p.writeFile({ fileName: "dashboard_" + new Date().toISOString().slice(0,10) + ".pptx" });
|
p.writeFile({ fileName: "MSB_dashboard_" + new Date().toISOString().slice(0,10) + ".pptx" });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFiles(files) {
|
function handleFiles(files) {
|
||||||
alert('Файлов выбрано: ' + files.length + '\\n\\nДля чтения Excel в браузере нужен сервер.\\nПока данные встроены в код.\\n\\nЧтобы обновить — отправьте новые файлы, я обновлю дашборд.');
|
alert('Файлов выбрано: ' + files.length + '\\n\\nДанные встроены в код.\\nДля обновления — отправьте новые Excel-файлы, я обновлю дашборд.');
|
||||||
}
|
}
|
||||||
|
|
||||||
processData();
|
buildSlides();
|
||||||
render();
|
render();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user