🏆 Чемпионы по обучению
diff --git a/script.js b/script.js
index afb53e4..1f4af33 100644
--- a/script.js
+++ b/script.js
@@ -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 `
+ | ${t} |
+ ${done} |
+ ${total - done} |
+ ${pct}% |
+ ${avg} |
+ ${trainers} |
+
`;
+ }).join('');
+
const coursesPerPerson = {};
data.forEach(r => {
if (!coursesPerPerson[r.name]) coursesPerPerson[r.name] = { division: r.division || '', courses: new Set() };
diff --git a/style.css b/style.css
index 1a736a4..79d4b06 100644
--- a/style.css
+++ b/style.css
@@ -142,6 +142,10 @@ main {
border: 1px solid #263545;
}
+.chart-card.wide {
+ grid-column: 1 / -1;
+}
+
.chart-card h3 {
font-size: 16px;
color: #fff;