var charts = {};
document.getElementById('uploadBtn').addEventListener('click', function() {
document.getElementById('excelFile').click();
});
document.getElementById('excelFile').addEventListener('change', function(e) {
var file = e.target.files[0];
if (!file) return;
document.getElementById('statusMsg').textContent = 'Загрузка: ' + file.name + '...';
var reader = new FileReader();
reader.onload = function(ev) {
try {
var arr = new Uint8Array(ev.target.result);
var wb = XLSX.read(arr, {type: 'array'});
var ws = wb.Sheets[wb.SheetNames[0]];
var rows = XLSX.utils.sheet_to_json(ws, {defval: ''});
if (!rows.length) { document.getElementById('statusMsg').textContent = 'Файл пуст'; return; }
build(rows);
} catch(err) {
document.getElementById('statusMsg').textContent = 'Ошибка: ' + err.message;
}
};
reader.readAsArrayBuffer(file);
});
function norm(rows) {
var map = {
'фио':'name','ф.и.о':'name','сотрудник':'name','employee':'name','имя':'name',
'дивизион':'div','подразделение':'div','отдел':'div','division':'div','department':'div',
'курс':'course','обучение':'course','training':'course','программа':'course',
'тренинг':'course','название тренинга':'course','название курса':'course','название обучения':'course',
'тема':'course','тема курса':'course','дисциплина':'course','модуль':'course','module':'course',
'статус':'status','status':'status','состояние':'status',
'оценка':'score','балл':'score','баллы':'score','рейтинг':'score','ball':'score','score':'score',
'тренер':'trainer','преподаватель':'trainer','trainer':'trainer','coach':'trainer',
'дата':'date','date':'date'
};
return rows.map(function(r) {
var out = {};
for (var k in r) {
var key = k.toLowerCase().replace(/\s+/g,' ').trim();
out[map[key] || key] = r[k];
}
return out;
});
}
function done(s) {
s = (s||'').toLowerCase();
return s.indexOf('завершен')>=0 || s.indexOf('прошёл')>=0 || s.indexOf('прошел')>=0 ||
s.indexOf('пройден')>=0 || s==='completed' || s==='ок' || s==='да';
}
function build(raw) {
var d = norm(raw);
document.getElementById('uploadSection').classList.add('hidden');
document.getElementById('dashboard').classList.remove('hidden');
var allN = {}, scores = [], train = {}, trainers = {};
d.forEach(function(r) {
allN[r.name] = true;
var sc = parseFloat(r.score);
if (!isNaN(sc) && sc > 0) scores.push(sc);
var c = r.course || 'Без темы';
if (!train[c]) train[c] = {all:{}, ok:{}, sc:[], tr:{}};
train[c].all[r.name] = true;
if (r.status === 'завершен' || r.status === 'пройден') train[c].ok[r.name] = true;
if (!isNaN(sc) && sc > 0) train[c].sc.push(sc);
if (r.trainer) train[c].tr[r.trainer] = true;
var tr = r.trainer || '—';
if (!trainers[tr]) trainers[tr] = {n:0, sc:[], topics:[]};
trainers[tr].n++;
if (!isNaN(sc) && sc > 0) trainers[tr].sc.push(sc);
if (c && c !== 'Без темы') trainers[tr].topics.push(c);
});
var totalN = Object.keys(allN).length;
document.getElementById('statTotal').textContent = totalN;
document.getElementById('statAvg').textContent = scores.length ? (scores.reduce(function(a,b){return a+b},0)/scores.length).toFixed(1) : '—';
document.querySelector('#tblTrainings tbody').innerHTML = Object.keys(train).map(function(k){
var t=train[k], tot=Object.keys(t.all).length, ok=Object.keys(t.ok).length;
var avg=t.sc.length?(t.sc.reduce(function(a,b){return a+b},0)/t.sc.length).toFixed(1):'—';
var trn=Object.keys(t.tr).join(', ')||'—';
return '
| '+k+' | '+ok+' | '+(tot-ok)+' | '+(tot?Math.round(ok/tot*100):0)+'% | '+avg+' | '+trn+' |
';
}).join('');
document.querySelector('#tblTrainers tbody').innerHTML = Object.keys(trainers).map(function(tr) {
var t = trainers[tr];
var topics = (t.topics && t.topics.length > 0) ? Array.from(new Set(t.topics)).join(', ') : '—';
var avgScore = t.sc && t.sc.length ? (t.sc.reduce(function(a,b){return a+b},0)/t.sc.length).toFixed(1) : '—';
return '| ' + tr + ' | ' + t.n + ' | ' + topics + ' | ' + avgScore + ' |
';
}).join('');
cph = {};
d.forEach(function(r){if(!cph[r.name])cph[r.name]={d:r.division||'',c:{} };if(r.course)cph[r.name].c[r.course]=true});
var champs=Object.keys(cph).map(function(n){return{name:n,div:cph[n].d,cnt:Object.keys(cph[n].c).length}})
.sort(function(a,b){return b.cnt-a.cnt}).slice(0,10);
document.querySelector('#tblChamp tbody').innerHTML = champs.map(function(c){
return '| '+c.name+' | '+c.div+' | '+c.cnt+' |
';
}).join('');
var noT = Object.keys(allN);
document.querySelector('#tblNoTrain tbody').innerHTML = noT.slice(0,20).map(function(n){
return '| '+n+' | '+(cph[n]?cph[n].d:'')+' |
';
}).join('');
document.querySelector('#tblAll tbody').innerHTML = d.map(function(r){
return '| '+(r.name||'')+' | '+(r.division||'')+' | '+(r.course||'')+' | '+(r.status||'')+' | '+(r.score||'')+' | '+(r.trainer||'')+' |
';
}).join('');
var cph = {};
d.forEach(function(r){if(!cph[r.name])cph[r.name]={d:r.division||'',c:{} };if(r.course)cph[r.name].c[r.course]=true});
var champs=Object.keys(cph).map(function(n){return{name:n,div:cph[n].d,cnt:Object.keys(cph[n].c).length}})
.sort(function(a,b){return b.cnt-a.cnt}).slice(0,10);
document.querySelector('#tblChamp tbody').innerHTML = champs.map(function(c){
return '| '+c.name+' | '+c.div+' | '+c.cnt+' |
';
}).join('');
var noT=Object.keys(allN).filter(function(n){return !doneN[n]});
document.querySelector('#tblNoTrain tbody').innerHTML = noT.slice(0,20).map(function(n){
return '| '+n+' | '+(cph[n]?cph[n].d:'')+' |
';
}).join('');
document.querySelector('#tblAll tbody').innerHTML = d.map(function(r){
var cls=done(r.status)?'status-completed':'status-not-started';
return '| '+(r.name||'')+' | '+(r.division||'')+' | '+(r.course||'')+' | '+(r.status||'')+' | '+(r.score||'')+' | '+(r.trainer||'')+' |
';
}).join('');
}
function barH(id, labels, ds) {
if(charts[id])charts[id].destroy();
charts[id]=new Chart(document.getElementById(id),{type:'bar',data:{labels:labels,datasets:ds.map(function(s){return{label:s.label,data:s.data,backgroundColor:s.bg,borderColor:s.bc,borderWidth:1}})},
options:{indexAxis:'y',responsive:true,plugins:{legend:{labels:{color:'#e0e0e0'}}},scales:{x:{beginAtZero:true,ticks:{color:'#90a4ae'},grid:{color:'#1e2d3d'}},y:{ticks:{color:'#e0e0e0',font:{size:11}},grid:{color:'#1e2d3d'}}}}});
}
function bar(id, labels, ds) {
if(charts[id])charts[id].destroy();
charts[id]=new Chart(document.getElementById(id),{type:'bar',data:{labels:labels,datasets:ds.map(function(s){return{label:s.label,data:s.data,backgroundColor:s.bg,borderColor:s.bc,borderWidth:1}})},
options:{responsive:true,plugins:{legend:{labels:{color:'#e0e0e0'}}},scales:{y:{beginAtZero:true,ticks:{color:'#90a4ae'},grid:{color:'#1e2d3d'}},x:{ticks:{color:'#90a4ae'},grid:{color:'#1e2d3d'}}}}});
}
function stacked(id, labels, ds) {
if(charts[id])charts[id].destroy();
charts[id]=new Chart(document.getElementById(id),{type:'bar',data:{labels:labels,datasets:ds.map(function(s){return{label:s.label,data:s.data,backgroundColor:s.bg,borderColor:s.bc,borderWidth:1}})},
options:{responsive:true,plugins:{legend:{labels:{color:'#e0e0e0'}}},scales:{x:{stacked:true,ticks:{color:'#90a4ae',maxRotation:45},grid:{color:'#1e2d3d'}},y:{stacked:true,beginAtZero:true,ticks:{color:'#90a4ae'},grid:{color:'#1e2d3d'}}}}});
}
function doughnut(id, labels, data, colors) {
if(charts[id])charts[id].destroy();
charts[id]=new Chart(document.getElementById(id),{type:'doughnut',data:{labels:labels,datasets:[{data:data,backgroundColor:colors,borderWidth:0}]},
options:{responsive:true,plugins:{legend:{labels:{color:'#e0e0e0'}}}}});
}