v2 added training breakdown charts and tables

This commit is contained in:
Dauren777 2026-06-19 06:03:06 +00:00
parent c22404737a
commit 4466c3ddb0
3 changed files with 158 additions and 7 deletions

View File

@ -49,27 +49,64 @@
</div>
<div class="charts-row">
<div class="chart-card">
<h3>Прохождение по дивизионам</h3>
<canvas id="divisionChart"></canvas>
</div>
<div class="chart-card">
<h3>Нагрузка тренеров</h3>
<canvas id="trainerChart"></canvas>
<div class="chart-card wide">
<h3>Тренинги — количество прошедших</h3>
<canvas id="trainingChart"></canvas>
</div>
</div>
<div class="charts-row">
<div class="chart-card">
<h3>Тренинги по дивизионам</h3>
<canvas id="trainingByDivisionChart"></canvas>
</div>
<div class="chart-card">
<h3>Прохождение по дивизионам</h3>
<canvas id="divisionChart"></canvas>
</div>
</div>
<div class="charts-row">
<div class="chart-card">
<h3>Нагрузка тренеров</h3>
<canvas id="trainerChart"></canvas>
</div>
<div class="chart-card">
<h3>Распределение оценок</h3>
<canvas id="scoreChart"></canvas>
</div>
</div>
<div class="charts-row">
<div class="chart-card">
<h3>Средняя оценка по тренингам</h3>
<canvas id="trainingScoreChart"></canvas>
</div>
<div class="chart-card">
<h3>Статусы обучения</h3>
<canvas id="statusChart"></canvas>
</div>
</div>
<div class="table-card full-width">
<h3>📋 Кто какие тренинги прошёл</h3>
<div class="table-wrapper">
<table id="trainingDetailTable">
<thead>
<tr>
<th>Тренинг</th>
<th>Прошли</th>
<th>Не прошли</th>
<th>% прохождения</th>
<th>Средняя оценка</th>
<th>Тренер</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="tables-row">
<div class="table-card">
<h3>🏆 Чемпионы по обучению</h3>

110
script.js
View File

@ -65,6 +65,77 @@ function processAndRender(rawData) {
document.getElementById('completionRate').textContent = rate + '%';
document.getElementById('avgScore').textContent = avgScore;
const trainingStats = {};
data.forEach(r => {
const course = r.course || 'Без названия';
if (!trainingStats[course]) trainingStats[course] = { completed: new Set(), all: new Set(), scores: [], trainers: new Set(), divisions: {} };
trainingStats[course].all.add(r.name);
const s = (r.status || '').toLowerCase();
const isCompleted = s.includes('завершен') || s.includes('прошёл') || s.includes('прошел') || s === 'completed' || s.includes('ок');
if (isCompleted) {
trainingStats[course].completed.add(r.name);
}
const score = parseFloat(r.score);
if (!isNaN(score) && score > 0) trainingStats[course].scores.push(score);
if (r.trainer) trainingStats[course].trainers.add(r.trainer);
const div = r.division || 'Без дивизиона';
if (!trainingStats[course].divisions[div]) trainingStats[course].divisions[div] = { total: new Set(), completed: new Set() };
trainingStats[course].divisions[div].total.add(r.name);
if (isCompleted) trainingStats[course].divisions[div].completed.add(r.name);
});
const trainingLabels = Object.keys(trainingStats);
const trainingCompletedCounts = trainingLabels.map(t => trainingStats[t].completed.size);
const trainingTotalCounts = trainingLabels.map(t => trainingStats[t].all.size);
renderChart('trainingChart', 'bar', {
labels: trainingLabels,
datasets: [
{
label: 'Всего записей',
data: trainingTotalCounts,
backgroundColor: 'rgba(33, 150, 243, 0.4)',
borderColor: '#2196F3',
borderWidth: 1
},
{
label: 'Прошли',
data: trainingCompletedCounts,
backgroundColor: 'rgba(76, 175, 80, 0.7)',
borderColor: '#4caf50',
borderWidth: 1
}
]
}, {
indexAxis: 'y',
scales: {
x: { beginAtZero: true, ticks: { color: '#90a4ae' }, grid: { color: '#1e2d3d' } },
y: { ticks: { color: '#e0e0e0', font: { size: 11 } }, grid: { color: '#1e2d3d' } }
}
});
const allDivisions = [...new Set(data.map(r => r.division || 'Без дивизиона'))];
const trainingByDivDatasets = allDivisions.map((div, i) => {
const colors = ['#2196F3', '#4caf50', '#ff9800', '#9c27b0', '#00bcd4', '#ff5722', '#8bc34a', '#e91e63'];
return {
label: div,
data: trainingLabels.map(t => trainingStats[t].divisions[div] ? trainingStats[t].divisions[div].completed.size : 0),
backgroundColor: colors[i % colors.length] + 'aa',
borderColor: colors[i % colors.length],
borderWidth: 1
};
});
renderChart('trainingByDivisionChart', 'bar', {
labels: trainingLabels,
datasets: trainingByDivDatasets
}, {
scales: {
x: { stacked: true, ticks: { color: '#90a4ae', maxRotation: 45 }, grid: { color: '#1e2d3d' } },
y: { stacked: true, beginAtZero: true, ticks: { color: '#90a4ae' }, grid: { color: '#1e2d3d' } }
}
});
const divisionStats = {};
data.forEach(r => {
const div = r.division || 'Без дивизиона';
@ -169,6 +240,45 @@ function processAndRender(rawData) {
}]
});
const trainingAvgScores = trainingLabels.map(t => {
const sc = trainingStats[t].scores;
return sc.length ? (sc.reduce((a, b) => a + b, 0) / sc.length).toFixed(1) : 0;
});
renderChart('trainingScoreChart', 'bar', {
labels: trainingLabels,
datasets: [{
label: 'Средняя оценка',
data: trainingAvgScores,
backgroundColor: trainingAvgScores.map(v => v >= 4 ? 'rgba(76,175,80,0.7)' : v >= 3 ? 'rgba(255,152,0,0.7)' : 'rgba(244,67,54,0.7)'),
borderColor: trainingAvgScores.map(v => v >= 4 ? '#4caf50' : v >= 3 ? '#ff9800' : '#f44336'),
borderWidth: 1
}]
}, {
scales: {
y: { beginAtZero: true, max: 5, ticks: { color: '#90a4ae' }, grid: { color: '#1e2d3d' } },
x: { ticks: { color: '#e0e0e0', maxRotation: 45 }, grid: { color: '#1e2d3d' } }
}
});
const trainingDetailBody = document.querySelector('#trainingDetailTable tbody');
trainingDetailBody.innerHTML = trainingLabels.map(t => {
const st = trainingStats[t];
const total = st.all.size;
const done = st.completed.size;
const pct = total ? Math.round(done / total * 100) : 0;
const avg = st.scores.length ? (st.scores.reduce((a, b) => a + b, 0) / st.scores.length).toFixed(1) : '—';
const trainers = [...st.trainers].join(', ') || '—';
return `<tr>
<td><strong>${t}</strong></td>
<td>${done}</td>
<td>${total - done}</td>
<td>${pct}%</td>
<td>${avg}</td>
<td>${trainers}</td>
</tr>`;
}).join('');
const coursesPerPerson = {};
data.forEach(r => {
if (!coursesPerPerson[r.name]) coursesPerPerson[r.name] = { division: r.division || '', courses: new Set() };

View File

@ -142,6 +142,10 @@ main {
border: 1px solid #263545;
}
.chart-card.wide {
grid-column: 1 / -1;
}
.chart-card h3 {
font-size: 16px;
color: #fff;