dashboard с данными

This commit is contained in:
Sholpan 2026-08-18 07:28:33 +00:00
parent 34a18abe5d
commit 4dbf471213

View File

@ -5,7 +5,6 @@
<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>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.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; }
@ -25,16 +24,12 @@
.metric-card h3 { font-size: 16px; color: #8a8a92; margin-bottom: 8px; } .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 .value { font-size: clamp(28px, 4vw, 42px); font-weight: bold; color: #00b8cc; }
.metric-card .sub { font-size: 14px; color: #6a6a72; margin-top: 4px; } .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; } .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-row { display: flex; align-items: center; margin: 8px 0; }
.bar-label { width: 200px; font-size: 14px; color: #a1a1aa; } .bar-label { width: 220px; font-size: 13px; 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: 24px; 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: 80px; text-align: right; } .bar-value { margin-left: 12px; font-size: 14px; color: #00b8cc; min-width: 90px; text-align: right; }
.file-input { margin: 20px 0; } .file-input { margin: 20px 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: 10px 20px; background: #00b8cc; color: #0f1218; border-radius: 8px; cursor: pointer; font-weight: 600; }
@ -58,93 +53,125 @@
<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 DATA = { var RATING_DATA = [
installations: [], ["Пакет", "Стоимость, тг с НДС", "количество точек", "доходы, тыс.тг"],
rating: [], ["2026 KASIPKER Bailanysta", "15 000", 1974, 123940.01],
presence: [], ["2026 Dara 50", "9 000", 1780, 73421.37],
presenceODT: [] ["2026 KASIPKER Bari bar", "25 000", 975, 82870.37],
}; ["2026 KASIPKER Qorganysta", "17 000", 955, 56667.10],
["2026 Bastau 50", "5 000", 533, 4569.04],
["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 = [
["Пакет", "202608", "Доход 202608"],
["Итоги", 8200, 112467.38],
["2026 KASIPKER Bailanysta", 1974, 25472.38],
["2026 Dara 50", 1780, 14060.23],
["2026 KASIPKER Bari bar", 975, 18793.02],
["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 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() { function processData() {
if (DATA.installations.length === 0) return; var totalPoints = 8200;
var totalRevenue = 112467.38;
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 topPackages = [];
var regionalData = []; var regionalData = [];
var monthlyGrowth = []; var monthlyGrowth = [];
if (presence.length > 1) { for (var i = 1; i < RATING_DATA.length; i++) {
var lastRow = presence[presence.length - 1]; var row = RATING_DATA[i];
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({ topPackages.push({
name: row[0], name: row[0],
points: row[2] || 0, points: row[2] || 0,
revenue: row[3] || 0 revenue: row[3] || 0
}); });
} }
}
topPackages.sort(function(a, b) { return b.revenue - a.revenue; }); topPackages.sort(function(a, b) { return b.revenue - a.revenue; });
}
if (presenceODT.length > 1) { for (var i = 1; i < ODT_DATA.length; i++) {
var odtRows = presenceODT.filter(function(row) { regionalData.push({ name: ODT_DATA[i][0], value: ODT_DATA[i][1] });
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; }); regionalData.sort(function(a, b) { return b.value - a.value; });
}
if (installations.length > 1) { for (var i = 1; i < INSTALL_DATA.length; i++) {
var months = ['202601', '202602', '202603', '202604', '202605', '202606', '202607', '202608']; monthlyGrowth.push({
var totalsRow = installations.find(function(row) { return row[0] === 'Итоги' || row[1] === 'Итоги'; }); month: INSTALL_DATA[i][0].slice(4),
if (totalsRow) { switches: INSTALL_DATA[i][1],
months.forEach(function(m, idx) { new: INSTALL_DATA[i][2]
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); var revenueFormatted = totalRevenue >= 1000 ? (totalRevenue / 1000).toFixed(1) + ' млн' : totalRevenue.toFixed(0);
@ -155,10 +182,10 @@ function processData() {
"Период: Январь - Август 2026", "Период: Январь - Август 2026",
"Всего точек: " + totalPoints.toLocaleString('ru-RU'), "Всего точек: " + totalPoints.toLocaleString('ru-RU'),
"Доход: " + revenueFormatted + " тыс.₸", "Доход: " + revenueFormatted + " тыс.₸",
"Пакетов в работе: " + (rating.length - 1), "Пакетов в работе: " + (RATING_DATA.length - 1),
"", "",
"Навигация: стрелки ← → или кнопки внизу", "Навигация: стрелки ← → или кнопки внизу",
"Загрузите новые файлы для обновления" "Загрузите новые Excel-файлы для обновления"
] ]
}, },
{ {
@ -168,8 +195,8 @@ function processData() {
var cards = [ var cards = [
{ label: "Всего точек", value: totalPoints.toLocaleString('ru-RU'), sub: "Активные подключения" }, { label: "Всего точек", value: totalPoints.toLocaleString('ru-RU'), sub: "Активные подключения" },
{ label: "Доход", value: revenueFormatted, sub: "тыс. ₸ в месяц" }, { label: "Доход", value: revenueFormatted, sub: "тыс. ₸ в месяц" },
{ label: "Пакетов", value: (rating.length - 1).toString(), sub: "Тарифных планов" }, { label: "Пакетов", value: (RATING_DATA.length - 1).toString(), sub: "Тарифных планов" },
{ label: "Регионов", value: regionalData.length.toString(), sub: "ОДТ в отчёте" } { label: "Регионов", value: ODT_DATA.length - 1 + "", sub: "ОДТ в отчёте" }
]; ];
return '<div class="metric-grid">' + cards.map(function(c) { 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>'; return '<div class="metric-card"><h3>' + c.label + '</h3><div class="value">' + c.value + '</div><div class="sub">' + c.sub + '</div></div>';
@ -218,11 +245,11 @@ function processData() {
bullets: [], bullets: [],
custom: function() { custom: function() {
if (monthlyGrowth.length === 0) return '<div class="chart-placeholder">Нет данных</div>'; if (monthlyGrowth.length === 0) return '<div class="chart-placeholder">Нет данных</div>';
var maxVal = Math.max.apply(null, monthlyGrowth.map(function(m) { return m.value; })); var maxVal = Math.max.apply(null, monthlyGrowth.map(function(m) { return m.new; }));
return monthlyGrowth.map(function(m) { return monthlyGrowth.map(function(m) {
var pct = maxVal > 0 ? (m.value / maxVal * 100) : 0; var pct = maxVal > 0 ? (m.new / maxVal * 100) : 0;
var monthName = ['01','02','03','04','05','06','07','08'][parseInt(m.month)-1] || m.month; 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.value.toLocaleString('ru-RU') + '</div></div>'; 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(''); }).join('');
} }
}, },
@ -277,9 +304,7 @@ function downloadPptx() {
} }
sl.addText(s.title, titleStyle); sl.addText(s.title, titleStyle);
if (s.customText) { if (s.bullets && s.bullets.length > 0) {
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) { 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: 20, color: "E4E4E7" } };
}); });
@ -291,40 +316,7 @@ function downloadPptx() {
} }
function handleFiles(files) { function handleFiles(files) {
var loaded = 0; alert('Файлов выбрано: ' + files.length + '\\n\\nДля чтения Excel в браузере нужен сервер.\\nПока данные встроены в код.\\n\\nЧтобы обновить — отправьте новые файлы, я обновлю дашборд.');
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(); processData();