`;
};
const renderSkillDistributionChart = () => {
const ctx = document.getElementById('skillDistributionChart').getContext('2d');
const levelCounts = Object.keys(competencyLevels).map(level =>
level > 0 ? competencies.filter(c => c.level == level).length : 0
).slice(1); // Exclude N/A
if (skillDistributionChart) skillDistributionChart.destroy();
skillDistributionChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: Object.values(competencyLevels).map(l => l.name).slice(1),
datasets: [{
data: levelCounts,
backgroundColor: Object.values(competencyLevels).map(l => l.color.replace('bg-', '#').replace('-200', '')).slice(1), // Simplified color conversion
}]
},
options: { responsive: true, maintainAspectRatio: false }
});
};
const renderSkillCoverageChart = () => {
const ctx = document.getElementById('skillCoverageChart').getContext('2d');
const skillScores = skills.map(skill => {
const skillComps = competencies.filter(c => c.skillId === skill.id);
return skillComps.reduce((sum, c) => sum + c.level, 0);
});
if (skillCoverageChart) skillCoverageChart.destroy();
skillCoverageChart = new Chart(ctx, {
type: 'bar',
data: {
labels: skills.map(s => s.name),
datasets: [{
label: 'Total Competency Score',
data: skillScores,
backgroundColor: 'rgba(79, 70, 229, 0.6)',
borderColor: 'rgba(79, 70, 229, 1)',
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', scales: { x: { beginAtZero: true } } }
});
};
const renderCompetencyMatrix = () => {
const container = document.getElementById('competency-matrix-container');
let tableHtml = `
| Member | `;
skills.forEach(skill => {
tableHtml += `${skill.name} | `;
});
tableHtml += `
`;
teamMembers.forEach(member => {
tableHtml += `| ${member.name} | `;
skills.forEach(skill => {
const comp = getCompetency(member.id, skill.id);
const levelInfo = competencyLevels[comp.level];
tableHtml += `${levelInfo.name} | `;
});
tableHtml += `
`;
});
tableHtml += `
`;
container.innerHTML = tableHtml;
};
const renderMemberList = () => {
const container = document.getElementById('member-list');
container.innerHTML = teamMembers.map(m => `
${m.name}
`).join('');
};
const renderSkillList = () => {
const container = document.getElementById('skill-list');
container.innerHTML = skills.map(s => `
${s.name}
`).join('');
};
const renderCompetencyEditor = () => {
const container = document.getElementById('competency-editor-container');
let tableHtml = `
| Member | `;
skills.forEach(skill => {
tableHtml += `${skill.name} | `;
});
tableHtml += `
`;
teamMembers.forEach(member => {
tableHtml += `| ${member.name} | `;
skills.forEach(skill => {
const comp = getCompetency(member.id, skill.id);
let options = '';
for (const level in competencyLevels) {
options += ``;
}
tableHtml += ` | `;
});
tableHtml += `
`;
});
tableHtml += `
`;
container.innerHTML = tableHtml;
};
// --- EVENT HANDLERS ---
document.getElementById('member-form').addEventListener('submit', e => {
e.preventDefault();
const input = document.getElementById('member-name');
if (input.value.trim()) {
const newId = teamMembers.length > 0 ? Math.max(...teamMembers.map(m => m.id)) + 1 : 1;
teamMembers.push({ id: newId, name: input.value.trim() });
input.value = '';
renderAll();
}
});
document.getElementById('skill-form').addEventListener('submit', e => {
e.preventDefault();
const input = document.getElementById('skill-name');
if (input.value.trim()) {
const newId = skills.length > 0 ? Math.max(...skills.map(s => s.id)) + 1 : 1;
skills.push({ id: newId, name: input.value.trim() });
input.value = '';
renderAll();
}
});
document.getElementById('member-list').addEventListener('click', e => {
if (e.target.classList.contains('delete-member-btn')) {
const id = parseInt(e.target.dataset.id);
teamMembers = teamMembers.filter(m => m.id !== id);
competencies = competencies.filter(c => c.memberId !== id);
renderAll();
}
});
document.getElementById('skill-list').addEventListener('click', e => {
if (e.target.classList.contains('delete-skill-btn')) {
const id = parseInt(e.target.dataset.id);
skills = skills.filter(s => s.id !== id);
competencies = competencies.filter(c => c.skillId !== id);
renderAll();
}
});
document.getElementById('competency-editor-container').addEventListener('change', e => {
if (e.target.classList.contains('competency-select')) {
const memberId = parseInt(e.target.dataset.memberId);
const skillId = parseInt(e.target.dataset.skillId);
const level = parseInt(e.target.value);
let existingComp = competencies.find(c => c.memberId === memberId && c.skillId === skillId);
if (existingComp) {
existingComp.level = level;
} else {
competencies.push({ memberId, skillId, level });
}
renderAll();
}
});
document.getElementById('download-pdf-btn').addEventListener('click', () => {
const exportArea = document.getElementById('dashboard-export-area');
const mainTitleEl = document.querySelector('#competency-tool-container h1');
if (!exportArea || !mainTitleEl) return;
const btn = document.getElementById('download-pdf-btn');
const originalBtnText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `
Processing...`;
html2canvas(exportArea, { scale: 2, useCORS: true, windowWidth: exportArea.scrollWidth, windowHeight: exportArea.scrollHeight })
.then(canvas => {
const { jsPDF } = window.jspdf;
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
const pageMargin = 15;
const pdfPageWidth = pdf.internal.pageSize.getWidth();
const pdfPageHeight = pdf.internal.pageSize.getHeight();
const pdfContentWidth = pdfPageWidth - (2 * pageMargin);
const canvasAspectRatio = canvas.width / canvas.height;
const pdfImgHeight = pdfContentWidth / canvasAspectRatio;
let yPos = pageMargin;
pdf.setFontSize(18).setFont('helvetica', 'bold');
pdf.text(mainTitleEl.innerText, pdfPageWidth / 2, yPos, { align: 'center' });
yPos += 12;
let heightLeft = pdfImgHeight;
let positionOnCanvas = 0;
pdf.addImage(imgData, 'PNG', pageMargin, yPos, pdfContentWidth, pdfImgHeight);
heightLeft -= (pdfPageHeight - yPos - pageMargin);
while (heightLeft > 0) {
positionOnCanvas -= (pdfPageHeight - (2 * pageMargin));
pdf.addPage();
pdf.addImage(imgData, 'PNG', pageMargin, positionOnCanvas + pageMargin, pdfContentWidth, pdfImgHeight);
heightLeft -= (pdfPageHeight - (2 * pageMargin));
}
pdf.save(`team-competency-dashboard-${new Date().toISOString().slice(0,10)}.pdf`);
})
.catch(err => { console.error("PDF generation failed:", err); alert("Error generating PDF."); })
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalBtnText;
});
});
// --- INITIALIZATION ---
const switchTab = (tabName) => {
const tabDashboardBtn = document.getElementById('tab-dashboard-btn');
const tabConfigBtn = document.getElementById('tab-config-btn');
const tabDashboardContent = document.getElementById('tab-dashboard-content');
const tabConfigContent = document.getElementById('tab-config-content');
const prevTabBtn = document.getElementById('prev-tab-btn');
const nextTabBtn = document.getElementById('next-tab-btn');
if (tabName === 'dashboard') {
tabDashboardBtn.classList.add('active');
tabConfigBtn.classList.remove('active');
tabDashboardContent.classList.remove('hidden');
tabConfigContent.classList.add('hidden');
prevTabBtn.disabled = true;
nextTabBtn.disabled = false;
} else {
tabDashboardBtn.classList.remove('active');
tabConfigBtn.classList.add('active');
tabDashboardContent.classList.add('hidden');
tabConfigContent.classList.remove('hidden');
prevTabBtn.disabled = false;
nextTabBtn.disabled = true;
}
};
document.getElementById('tab-dashboard-btn').addEventListener('click', () => switchTab('dashboard'));
document.getElementById('tab-config-btn').addEventListener('click', () => switchTab('config'));
document.getElementById('prev-tab-btn').addEventListener('click', () => switchTab('dashboard'));
document.getElementById('next-tab-btn').addEventListener('click', () => switchTab('config'));
switchTab('dashboard');
renderAll();
});