dashboard: updated with new Excel file data and added trend charts

This commit is contained in:
Almas 2026-08-18 11:56:37 +00:00
parent 77de4b9d5c
commit 8ddf5a4d7e
2 changed files with 185 additions and 75 deletions

Binary file not shown.

View File

@ -391,7 +391,7 @@
<div class="container">
<header>
<h1>📊 Дашборд продаж автомобилей</h1>
<p class="subtitle">Анализ продаж по регионам Казахстана</p>
<p class="subtitle">Kaisar KZ-7 · Казахстан · <span id="periodLabel">последние 30 дней</span></p>
</header>
<div class="kpi-grid" id="kpiGrid">
@ -458,22 +458,22 @@
</div>
<div class="charts-grid">
<div class="chart-card">
<div class="chart-title">📈 Динамика продаж по дням</div>
<div class="chart-container">
<canvas id="salesTrendChart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-title">📍 Продажи по регионам (Топ-10)</div>
<div class="chart-container">
<canvas id="regionChart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-title">🎯 Продажи по комплектациям</div>
<div class="chart-container">
<canvas id="trimChart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-title">🎨 Предпочтения по цветам</div>
<div class="chart-container">
<canvas id="colorChart"></canvas>
<div class="chart-card full-width">
<div class="chart-title">🎯 Продажи по комплектациям и цветам</div>
<div class="chart-container" style="height: 350px;">
<canvas id="trimColorChart"></canvas>
</div>
</div>
<div class="chart-card">
@ -482,6 +482,12 @@
<canvas id="revenueChart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-title">🏆 Рейтинг регионов (Топ-10)</div>
<div class="chart-container">
<canvas id="ratingChart"></canvas>
</div>
</div>
</div>
<div class="table-section">
@ -531,22 +537,32 @@
async function loadExcelData() {
try {
const response = await fetch('car_sales_kz_dashboard.xlsx');
const response = await fetch('car_sales_kz_dashboard-1.xlsx');
const arrayBuffer = await response.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const rawData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
const headers = rawData[0];
allData = rawData.slice(1).map(row => ({
regionCode: row[0],
const rawSheet = workbook.Sheets['Сырые данные'];
const rawData = XLSX.utils.sheet_to_json(rawSheet, { header: 1 });
allData = rawData.slice(3).map(row => ({
date: row[0],
region: row[1],
trim: row[2],
color: row[3],
units: row[4],
price: row[5],
total: row[6]
})).filter(row => row.region && row.trim);
})).filter(row => row.region && row.trim && row.units);
const overviewSheet = workbook.Sheets['Обзор'];
const overviewData = XLSX.utils.sheet_to_json(overviewSheet, { header: 1 });
const kpiRow = overviewData[4];
window.kpiData = {
totalUnits: kpiRow?.[1] || 0,
totalRevenue: kpiRow?.[4] || 0,
period: overviewData[3]?.[1] || ''
};
filteredData = [...allData];
initDashboard();
@ -580,17 +596,75 @@
const avgCheck = totalUnits > 0 ? totalRevenue / totalUnits : 0;
const regions = new Set(filteredData.map(d => d.region)).size;
document.getElementById('totalUnits').textContent = formatNumber(totalUnits);
document.getElementById('totalRevenue').textContent = formatCurrency(totalRevenue).replace('KZT', '₸');
document.getElementById('totalUnits').textContent = formatNumber(window.kpiData?.totalUnits || totalUnits);
document.getElementById('totalRevenue').textContent = formatCurrency(window.kpiData?.totalRevenue || totalRevenue).replace('KZT', '₸');
document.getElementById('avgCheck').textContent = formatCurrency(avgCheck).replace('KZT', '₸');
document.getElementById('totalRegions').textContent = regions;
if (window.kpiData?.period) {
document.getElementById('periodLabel').textContent = window.kpiData.period;
}
}
function updateCharts() {
updateSalesTrendChart();
updateRegionChart();
updateTrimChart();
updateColorChart();
updateTrimColorChart();
updateRevenueChart();
updateRatingChart();
}
function updateSalesTrendChart() {
const daysData = {};
filteredData.forEach(d => {
if (d.date) {
daysData[d.date] = (daysData[d.date] || 0) + d.units;
}
});
const sortedDates = Object.keys(daysData).sort((a, b) => a - b);
const labels = sortedDates.map(date => {
const day = new Date((date - 25569) * 86400 * 1000);
return day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' });
});
const ctx = document.getElementById('salesTrendChart').getContext('2d');
if (charts.trend) charts.trend.destroy();
charts.trend = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Продано авто',
data: sortedDates.map(date => daysData[date]),
borderColor: 'rgba(102, 126, 234, 1)',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointRadius: 4,
pointBackgroundColor: 'rgba(102, 126, 234, 1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
x: {
grid: { display: false }
}
}
}
});
}
function updateRegionChart() {
@ -639,74 +713,57 @@
});
}
function updateTrimChart() {
function updateTrimColorChart() {
const trimData = {};
const colorData = {};
const trimColorData = {};
filteredData.forEach(d => {
trimData[d.trim] = (trimData[d.trim] || 0) + d.units;
});
const ctx = document.getElementById('trimChart').getContext('2d');
if (charts.trim) charts.trim.destroy();
charts.trim = new Chart(ctx, {
type: 'doughnut',
data: {
labels: Object.keys(trimData),
datasets: [{
data: Object.values(trimData),
backgroundColor: [
'rgba(102, 126, 234, 0.8)',
'rgba(118, 75, 162, 0.8)',
'rgba(237, 137, 54, 0.8)'
],
borderWidth: 2,
borderColor: 'rgba(255, 255, 255, 1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
function updateColorChart() {
const colorData = {};
filteredData.forEach(d => {
colorData[d.color] = (colorData[d.color] || 0) + d.units;
const key = `${d.trim} - ${d.color}`;
trimColorData[key] = (trimColorData[key] || 0) + d.units;
});
const ctx = document.getElementById('colorChart').getContext('2d');
const ctx = document.getElementById('trimColorChart').getContext('2d');
if (charts.color) charts.color.destroy();
if (charts.trimColor) charts.trimColor.destroy();
charts.color = new Chart(ctx, {
type: 'pie',
const sortedData = Object.entries(trimColorData)
.sort((a, b) => b[1] - a[1])
.slice(0, 9);
charts.trimColor = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(colorData),
labels: sortedData.map(([key]) => key),
datasets: [{
data: Object.values(colorData),
backgroundColor: [
'rgba(255, 255, 255, 0.9)',
'rgba(26, 32, 44, 0.9)',
'rgba(203, 213, 224, 0.9)'
],
borderWidth: 2,
borderColor: 'rgba(102, 126, 234, 1)'
label: 'Продано авто',
data: sortedData.map(([, value]) => value),
backgroundColor: sortedData.map(([key]) => {
const trim = key.split(' - ')[0];
if (trim === 'Standard') return 'rgba(102, 126, 234, 0.8)';
if (trim === 'Comfort') return 'rgba(118, 75, 162, 0.8)';
return 'rgba(237, 137, 54, 0.8)';
}),
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 1)',
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
x: {
grid: { display: false }
}
}
}
@ -716,7 +773,7 @@
function updateRevenueChart() {
const revenueData = {};
filteredData.forEach(d => {
revenueData[d.region] = (revenueData[d.region] || 0) + d.total;
revenueData[d.region] = (revenueData[d.region] || 0) + (d.total || 0);
});
const sorted = Object.entries(revenueData)
@ -762,6 +819,59 @@
});
}
function updateRatingChart() {
const regionData = {};
filteredData.forEach(d => {
regionData[d.region] = (regionData[d.region] || 0) + d.units;
});
const sorted = Object.entries(regionData)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
const total = Object.values(regionData).reduce((a, b) => a + b, 0);
const ctx = document.getElementById('ratingChart').getContext('2d');
if (charts.rating) charts.rating.destroy();
charts.rating = new Chart(ctx, {
type: 'bar',
data: {
labels: sorted.map(([name]) => name),
datasets: [{
label: 'Доля рынка (%)',
data: sorted.map(([, value]) => ((value / total) * 100).toFixed(1)),
backgroundColor: sorted.map(([, , i], idx) => {
if (idx === 0) return 'rgba(237, 137, 54, 0.9)';
if (idx === 1) return 'rgba(118, 75, 162, 0.9)';
if (idx === 2) return 'rgba(102, 126, 234, 0.9)';
return 'rgba(203, 213, 224, 0.9)';
}),
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 1)',
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
x: {
grid: { display: false }
}
}
}
});
}
function updateTable() {
const startIndex = (currentPage - 1) * rowsPerPage;
const endIndex = startIndex + rowsPerPage;