Team Competency Dashboard

Team Competency Dashboard

Visualize team skills, identify strengths, and pinpoint skill gaps.

Overall Skill Distribution

Team Skill Coverage

Team Competency Matrix

${skillWithMostExperts ? skillWithMostExperts.name : 'N/A'}

`; }; 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 = ``; skills.forEach(skill => { tableHtml += ``; }); tableHtml += ``; teamMembers.forEach(member => { tableHtml += ``; skills.forEach(skill => { const comp = getCompetency(member.id, skill.id); const levelInfo = competencyLevels[comp.level]; tableHtml += ``; }); tableHtml += ``; }); tableHtml += `
Member${skill.name}
${member.name}${levelInfo.name}
`; 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 = ``; skills.forEach(skill => { tableHtml += ``; }); tableHtml += ``; teamMembers.forEach(member => { tableHtml += ``; skills.forEach(skill => { const comp = getCompetency(member.id, skill.id); let options = ''; for (const level in competencyLevels) { options += ``; } tableHtml += ``; }); tableHtml += ``; }); tableHtml += `
Member${skill.name}
${member.name}
`; 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(); });
Scroll to Top