${totalProjects}
`;
document.getElementById('summary-card-coverage').innerHTML = `
Avg. Coverage
${avgCoverage}%
`;
document.getElementById('summary-card-bugs').innerHTML = `
Total Bugs
${totalBugs}
`;
document.getElementById('summary-card-vulnerabilities').innerHTML = `
Vulnerabilities
${totalVulnerabilities}
`;
}
function renderProjectSummaryTable() {
const tableBody = document.getElementById('project-summary-table');
if (!tableBody) return;
tableBody.innerHTML = projects.map(p => `
| ${p.name} |
${p.coverage}% |
${p.bugs} |
${p.vulnerabilities} |
${p.smells} |
${p.duplication}% |
`).join('');
}
function renderCharts() {
// Destroy existing charts before re-rendering to prevent issues
if (charts.coverage) charts.coverage.destroy();
if (charts.issues) charts.issues.destroy();
if (charts.bubble) charts.bubble.destroy();
const projectNames = projects.map(p => p.name);
const coverageData = projects.map(p => p.coverage);
const totalBugs = projects.reduce((sum, p) => sum + p.bugs, 0);
const totalVulnerabilities = projects.reduce((sum, p) => sum + p.vulnerabilities, 0);
const totalSmells = projects.reduce((sum, p) => sum + p.smells, 0);
// Coverage Chart (Bar)
const coverageCtx = document.getElementById('coverageChart')?.getContext('2d');
if (coverageCtx) {
charts.coverage = new Chart(coverageCtx, {
type: 'bar',
data: {
labels: projectNames,
datasets: [{
label: 'Code Coverage (%)',
data: coverageData,
backgroundColor: 'rgba(59, 130, 246, 0.6)',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, max: 100 } }, plugins: { legend: { display: false } } }
});
}
// Issues Chart (Doughnut)
const issuesCtx = document.getElementById('issuesChart')?.getContext('2d');
if (issuesCtx) {
charts.issues = new Chart(issuesCtx, {
type: 'doughnut',
data: {
labels: ['Bugs', 'Vulnerabilities', 'Code Smells'],
datasets: [{
label: 'Issues Breakdown',
data: [totalBugs, totalVulnerabilities, totalSmells],
backgroundColor: ['rgba(239, 68, 68, 0.7)', 'rgba(245, 158, 11, 0.7)', 'rgba(107, 114, 128, 0.7)'],
borderColor: ['rgba(239, 68, 68, 1)', 'rgba(245, 158, 11, 1)', 'rgba(107, 114, 128, 1)'],
borderWidth: 1
}]
},
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } } }
});
}
// Bubble Chart
const bubbleCtx = document.getElementById('bubbleChart')?.getContext('2d');
if (bubbleCtx) {
const bubbleDatasets = projects.map((p, index) => {
const colors = [
'rgba(59, 130, 246, 0.7)', 'rgba(239, 68, 68, 0.7)', 'rgba(16, 185, 129, 0.7)',
'rgba(245, 158, 11, 0.7)', 'rgba(139, 92, 246, 0.7)', 'rgba(236, 72, 153, 0.7)'
];
const color = colors[index % colors.length];
const totalIssues = p.bugs + p.vulnerabilities + p.smells;
// Scale radius for better visualization
const radius = Math.max(5, Math.sqrt(totalIssues) * 2.5);
return {
label: p.name,
data: [{ x: p.coverage, y: p.duplication, r: radius }],
backgroundColor: color,
borderColor: color.replace('0.7', '1'),
borderWidth: 1
};
});
charts.bubble = new Chart(bubbleCtx, {
type: 'bubble',
data: { datasets: bubbleDatasets },
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { title: { display: true, text: 'Code Coverage (%)' }, min: 0, max: 100 },
y: { title: { display: true, text: 'Code Duplication (%)' }, beginAtZero: true }
},
plugins: {
legend: { position: 'top' },
tooltip: {
callbacks: {
label: function(context) {
const datasetLabel = context.dataset.label || '';
const projectIndex = context.datasetIndex;
const project = projects[projectIndex];
const totalIssues = project.bugs + project.vulnerabilities + project.smells;
return `${datasetLabel}: (Coverage: ${context.parsed.x}%, Duplication: ${context.parsed.y}%, Issues: ${totalIssues})`;
}
}
}
}
}
});
}
}
function renderConfigTable() {
const tableBody = document.getElementById('config-table');
if (!tableBody) return;
tableBody.innerHTML = projects.map((p, index) => `
|
|
|
|
|
|
|
`).join('');
}
// --- EVENT HANDLING & ACTIONS ---
window.switchTab = (tabName) => {
activeTab = tabName;
updateTabStyles();
updateNavButtons();
};
function updateTabStyles() {
const dashboardTab = document.getElementById('tab-content-dashboard');
const configTab = document.getElementById('tab-content-config');
const dashboardBtn = document.getElementById('tab-btn-dashboard');
const configBtn = document.getElementById('tab-btn-config');
if (activeTab === 'dashboard') {
dashboardTab.classList.remove('hidden');
configTab.classList.add('hidden');
dashboardBtn.classList.add('tab-active');
dashboardBtn.classList.remove('tab-inactive');
configBtn.classList.add('tab-inactive');
configBtn.classList.remove('tab-active');
} else {
dashboardTab.classList.add('hidden');
configTab.classList.remove('hidden');
dashboardBtn.classList.add('tab-inactive');
dashboardBtn.classList.remove('tab-active');
configBtn.classList.add('tab-active');
configBtn.classList.remove('tab-inactive');
}
}
window.navigateTabs = (direction) => {
if (direction === 'next' && activeTab === 'dashboard') {
switchTab('config');
} else if (direction === 'prev' && activeTab === 'config') {
switchTab('dashboard');
}
};
function updateNavButtons() {
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
if (activeTab === 'dashboard') {
prevBtn.disabled = true;
prevBtn.classList.add('opacity-50', 'cursor-not-allowed');
nextBtn.disabled = false;
nextBtn.classList.remove('opacity-50', 'cursor-not-allowed');
} else {
prevBtn.disabled = false;
prevBtn.classList.remove('opacity-50', 'cursor-not-allowed');
nextBtn.disabled = true;
nextBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
}
window.addProject = () => {
const newId = projects.length > 0 ? Math.max(...projects.map(p => p.id)) + 1 : 1;
projects.push({
id: newId, name: 'New Project', coverage: 0, bugs: 0, vulnerabilities: 0, smells: 0, duplication: 0
});
saveData();
updateUI();
};
window.updateProject = (index, field, value) => {
if (projects[index]) {
const parsedValue = (field === 'name') ? value : parseFloat(value);
if (!isNaN(parsedValue) || field === 'name') {
projects[index][field] = parsedValue;
saveData();
renderDashboard(); // Re-render dashboard to reflect changes
}
}
};
window.deleteProject = (index) => {
projects.splice(index, 1);
saveData();
updateUI();
};
window.downloadPDF = async () => {
const { jsPDF } = window.jspdf;
const dashboardContent = document.getElementById('dashboard-pdf-content');
const pdfButtonContainer = document.getElementById('pdf-button-container');
if (!dashboardContent || !pdfButtonContainer) return;
pdfButtonContainer.style.display = 'none'; // Hide button for capture
try {
const canvas = await html2canvas(dashboardContent, {
scale: 2, useCORS: true, logging: false, backgroundColor: '#f9fafb'
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape', unit: 'px', format: [canvas.width, canvas.height]
});
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
pdf.save('Code_Quality_Dashboard.pdf');
} catch (error) {
console.error("Failed to generate PDF:", error);
} finally {
pdfButtonContainer.style.display = 'block'; // Show button again
}
};
// --- START THE APP ---
init();
});