nado-proanalizirovat-dannye/index.html
2026-08-18 07:24:33 +00:00

335 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
* { margin: 0; box-sizing: border-box; }
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.on { display: flex; }
.slide h1 { font-size: clamp(34px, 6vw, 64px); margin-bottom: 24px; color: #00b8cc; }
.slide h2 { font-size: clamp(24px, 4vw, 42px); margin-bottom: 20px; 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::before { content: ""; position: absolute; left: 0; top: .55em; width: 12px; height: 12px; border-radius: 3px; background: #00b8cc; }
.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 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 .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-card { background: #1a1d24; border-radius: 12px; padding: 24px; border: 1px solid #2a2f3a; }
.metric-card h3 { font-size: 16px; color: #8a8a92; margin-bottom: 8px; }
.metric-card .value { font-size: clamp(28px, 4vw, 42px); font-weight: bold; color: #00b8cc; }
.metric-card .sub { font-size: 14px; color: #6a6a72; margin-top: 4px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: clamp(14px, 2vw, 18px); }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #2a2f3a; }
th { color: #00b8cc; font-weight: 600; }
tr:hover { background: #1a1d24; }
.chart-placeholder { background: #1a1d24; border-radius: 12px; padding: 40px; text-align: center; margin: 20px 0; border: 1px solid #2a2f3a; }
.bar-row { display: flex; align-items: center; margin: 8px 0; }
.bar-label { width: 200px; font-size: 14px; color: #a1a1aa; }
.bar-container { flex: 1; background: #2a2f3a; border-radius: 4px; height: 24px; overflow: hidden; }
.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: 80px; text-align: right; }
.file-input { margin: 20px 0; }
.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:hover { background: #00d4ec; }
@media print {
.nav, .num, .file-input { display: none; }
.slide { display: flex; page-break-after: always; min-height: 100vh; }
}
</style>
</head>
<body>
<div id="deck"></div>
<div class="nav">
<button onclick="go(-1)">← Назад</button>
<button onclick="go(1)">Вперёд →</button>
<button class="pptx" onclick="downloadPptx()">📥 Скачать .pptx</button>
<button class="refresh" onclick="document.getElementById('fileInput').click()">📂 Загрузить новые данные</button>
</div>
<div class="num" id="num"></div>
<input type="file" id="fileInput" class="file-input" multiple accept=".xlsx,.xls" onchange="handleFiles(this.files)">
<script>
var DATA = {
installations: [],
rating: [],
presence: [],
presenceODT: []
};
var SLIDES = [];
function parseExcelFile(buffer, fileName) {
var wb = XLSX.read(buffer, { type: 'array' });
var sheet = wb.Sheets[wb.SheetNames[0]];
var json = XLSX.utils.sheet_to_json(sheet, { header: 1 });
return json;
}
function processData() {
if (DATA.installations.length === 0) return;
var installations = DATA.installations;
var rating = DATA.rating;
var presence = DATA.presence;
var presenceODT = DATA.presenceODT;
var totalPoints = 0;
var totalRevenue = 0;
var topPackages = [];
var regionalData = [];
var monthlyGrowth = [];
if (presence.length > 1) {
var lastRow = presence[presence.length - 1];
for (var i = 0; i < lastRow.length; i++) {
if (typeof lastRow[i] === 'number' && lastRow[i] > 100) {
totalPoints = lastRow[i];
break;
}
}
var revenueCols = presence[0].filter(function(h) { return h && h.toString().includes('Доход'); });
if (revenueCols.length > 0) {
var lastRevenueRow = presence[presence.length - 1];
var revenueStartIdx = presence[0].indexOf(revenueCols[revenueCols.length - 1]);
if (revenueStartIdx >= 0 && lastRevenueRow[revenueStartIdx]) {
totalRevenue = lastRevenueRow[revenueStartIdx];
}
}
}
if (rating.length > 1) {
for (var i = 1; i < Math.min(rating.length, 11); i++) {
var row = rating[i];
if (row[0] && row[1] && row[2]) {
topPackages.push({
name: row[0],
points: row[2] || 0,
revenue: row[3] || 0
});
}
}
topPackages.sort(function(a, b) { return b.revenue - a.revenue; });
}
if (presenceODT.length > 1) {
var odtRows = presenceODT.filter(function(row) {
return row[0] && row[0].toString().includes('ОДТ') && !row[0].toString().includes('Итоги');
});
odtRows.slice(0, 8).forEach(function(row) {
var odtName = row[1] || row[0];
var totalVal = row[2] || row[row.length - 1] || 0;
regionalData.push({ name: odtName, value: totalVal });
});
regionalData.sort(function(a, b) { return b.value - a.value; });
}
if (installations.length > 1) {
var months = ['202601', '202602', '202603', '202604', '202605', '202606', '202607', '202608'];
var totalsRow = installations.find(function(row) { return row[0] === 'Итоги' || row[1] === 'Итоги'; });
if (totalsRow) {
months.forEach(function(m, idx) {
var colIdx = installations[0].indexOf(parseFloat(m));
if (colIdx < 0) colIdx = installations[0].findIndex(function(c) { return c && c.toString() === m; });
if (colIdx >= 0 && totalsRow[colIdx]) {
monthlyGrowth.push({ month: m.slice(4), value: totalsRow[colIdx] });
}
});
}
}
var revenueFormatted = totalRevenue >= 1000 ? (totalRevenue / 1000).toFixed(1) + ' млн' : totalRevenue.toFixed(0);
SLIDES = [
{
title: "Дашборд: Анализ данных",
bullets: [
"Период: Январь - Август 2026",
"Всего точек: " + totalPoints.toLocaleString('ru-RU'),
"Доход: " + revenueFormatted + " тыс.₸",
"Пакетов в работе: " + (rating.length - 1),
"",
"Навигация: стрелки ← → или кнопки внизу",
"Загрузите новые файлы для обновления"
]
},
{
title: "Ключевые метрики",
bullets: [],
custom: function() {
var cards = [
{ label: "Всего точек", value: totalPoints.toLocaleString('ru-RU'), sub: "Активные подключения" },
{ label: "Доход", value: revenueFormatted, sub: "тыс. ₸ в месяц" },
{ label: "Пакетов", value: (rating.length - 1).toString(), sub: "Тарифных планов" },
{ label: "Регионов", value: regionalData.length.toString(), sub: "ОДТ в отчёте" }
];
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>';
}).join('') + '</div>';
}
},
{
title: "Топ пакетов по доходу",
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('');
}
},
{
title: "Топ пакетов по точкам",
bullets: [],
custom: function() {
if (topPackages.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
var maxPts = Math.max.apply(null, topPackages.map(function(p) { return p.points; }));
return topPackages.slice(0, 8).map(function(p) {
var pct = maxPts > 0 ? (p.points / maxPts * 100) : 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">' + p.points.toLocaleString('ru-RU') + '</div></div>';
}).join('');
}
},
{
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.value; }));
return monthlyGrowth.map(function(m) {
var pct = maxVal > 0 ? (m.value / maxVal * 100) : 0;
var monthName = ['01','02','03','04','05','06','07','08'][parseInt(m.month)-1] || 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.value.toLocaleString('ru-RU') + '</div></div>';
}).join('');
}
},
{
title: "Итоги",
bullets: [
"Дашборд готов к использованию",
"Загрузите новые Excel-файлы через кнопку «Загрузить новые данные»",
"Данные обновятся автоматически",
"Скачайте презентацию в формате .pptx",
"Все метрики пересчитаются для новых данных"
]
}
];
}
var cur = 0;
function render() {
var deck = document.getElementById("deck");
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>';
return '<section class="slide' + (i === cur ? " on" : "") + '">' +
(s.title.includes('Дашборд') ? '<h1>' + s.title + '</h1>' : '<h2>' + s.title + '</h2>') +
content + '</section>';
}).join("");
document.getElementById("num").textContent = (cur + 1) + " / " + SLIDES.length;
}
function go(d) {
cur = Math.min(SLIDES.length - 1, Math.max(0, cur + d));
render();
}
document.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === " ") go(1);
if (e.key === "ArrowLeft") go(-1);
});
function downloadPptx() {
var p = new PptxGenJS();
p.defineLayout({ name: "W", width: 13.33, height: 7.5 });
p.layout = "W";
SLIDES.forEach(function (s) {
var sl = p.addSlide();
sl.background = { color: "0F1218" };
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.customText) {
sl.addText(s.customText, { x: 0.9, y: 2.0, w: 11.5, h: 4.8, fontSize: 18, color: "E4E4E7" });
} else if (s.bullets && s.bullets.length > 0) {
var bulletPoints = s.bullets.filter(function(b) { return b; }).map(function(b) {
return { text: b, options: { bullet: true, breakLine: true, fontSize: 20, color: "E4E4E7" } };
});
sl.addText(bulletPoints, { x: 0.9, y: 2.0, w: 11.5, h: 4.8, lineSpacingMultiple: 1.4 });
}
});
p.writeFile({ fileName: "dashboard_" + new Date().toISOString().slice(0,10) + ".pptx" });
}
function handleFiles(files) {
var loaded = 0;
var expected = 4;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = function(e) {
var buffer = e.target.result;
var fileName = e.target.fileName.toLowerCase();
var data = parseExcelFile(buffer, fileName);
if (fileName.includes('установки') || fileName.includes('переключения')) {
DATA.installations = data;
} else if (fileName.includes('рейтинг')) {
DATA.rating = data;
} else if (fileName.includes('наличие') && fileName.includes('одт')) {
DATA.presenceODT = data;
} else if (fileName.includes('наличие') && !fileName.includes('одт')) {
DATA.presence = data;
}
loaded++;
if (loaded >= expected || loaded >= files.length) {
processData();
cur = 0;
render();
alert('Данные обновлены! Слайдов: ' + SLIDES.length);
}
};
reader.fileName = file.name;
reader.readAsArrayBuffer(file);
}
}
processData();
render();
</script>
</body>
</html>