No reviews have been submitted for ${employee.name} yet.
`;
resultsDisplay.classList.remove('hidden');
downloadPdfBtn.disabled = true;
return;
}
const commentsHtml = analysis.comments.length > 0
? analysis.comments.map(c => `
"${c}"`).join('')
: `
No comments submitted.`;
resultsDisplay.innerHTML = `
Average Scores for ${employee.name}
Based on ${analysis.reviewCount} peer reviews.
`;
resultsDisplay.classList.remove('hidden');
downloadPdfBtn.disabled = false;
renderResultsChart(analysis.avgRatings);
};
const renderResultsChart = (avgRatings) => {
const ctx = document.getElementById('resultsChart').getContext('2d');
if (resultsChart) resultsChart.destroy();
resultsChart = new Chart(ctx, {
type: 'radar',
data: {
labels: appState.reviewCriteria,
datasets: [{
label: 'Average Score',
data: Object.values(avgRatings),
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: 'rgba(59, 130, 246, 1)',
pointBackgroundColor: 'rgba(59, 130, 246, 1)',
pointBorderColor: '#fff',
}]
},
options: {
responsive: true,
scales: { r: { beginAtZero: true, max: 10, ticks: { stepSize: 2 } } }
}
});
};
// --- DATA ANALYSIS ---
const analyzeReviewsFor = (employeeId) => {
const relevantReviews = appState.reviews.filter(r => r.revieweeId === employeeId);
const avgRatings = appState.reviewCriteria.reduce((acc, criterion) => ({...acc, [criterion]: 0}), {});
let ratingCounts = appState.reviewCriteria.reduce((acc, criterion) => ({...acc, [criterion]: 0}), {});
const comments = [];
relevantReviews.forEach(review => {
for (const [criterion, score] of Object.entries(review.ratings)) {
avgRatings[criterion] += score;
ratingCounts[criterion]++;
}
if (review.comments) comments.push(review.comments);
});
appState.reviewCriteria.forEach(criterion => {
if (ratingCounts[criterion] > 0) {
avgRatings[criterion] /= ratingCounts[criterion];
}
});
return {
reviewCount: relevantReviews.length,
avgRatings,
comments
};
};
// --- EVENT HANDLERS ---
const handleTabClick = (tabNum) => {
appState.currentTab = tabNum;
Object.values(tabButtons).forEach((btn, i) => {
btn.classList.toggle('tab-active', i + 1 === tabNum);
btn.classList.toggle('tab-inactive', i + 1 !== tabNum);
tabPanels[i+1].classList.toggle('hidden', i + 1 !== tabNum);
});
// Refresh content when switching to a tab
if (tabNum === 2) renderReviewForm();
if (tabNum === 3) {
const options = appState.team.map(m => `
`).join('');
employeeSelectResults.innerHTML = `
${options}`;
renderResultsView();
}
};
addMemberBtn.addEventListener('click', () => {
const name = newMemberNameInput.value.trim();
if (name) {
appState.team.push({ id: `emp-${Date.now()}`, name });
newMemberNameInput.value = '';
renderTeamList();
}
});
teamListContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-member-btn')) {
const idToRemove = e.target.dataset.id;
appState.team = appState.team.filter(m => m.id !== idToRemove);
// Also remove any reviews associated with this person
appState.reviews = appState.reviews.filter(r => r.reviewerId !== idToRemove && r.revieweeId !== idToRemove);
renderTeamList();
}
});
const updateRevieweeOptions = () => {
const reviewerId = document.getElementById('reviewer-select').value;
const revieweeSelect = document.getElementById('reviewee-select');
const currentRevieweeId = revieweeSelect.value;
revieweeSelect.innerHTML = appState.team
.filter(m => m.id !== reviewerId)
.map(m => `
`)
.join('');
// Try to preserve selection if possible
if (revieweeSelect.querySelector(`option[value="${currentRevieweeId}"]`)) {
revieweeSelect.value = currentRevieweeId;
}
};
reviewFormContainer.addEventListener('change', (e) => {
if (e.target.id === 'reviewer-select') {
updateRevieweeOptions();
}
});
reviewFormContainer.addEventListener('input', (e) => {
if (e.target.classList.contains('review-rating')) {
const key = e.target.id.replace('rating-', '');
document.getElementById(`rating-value-${key}`).textContent = e.target.value;
}
});
reviewFormContainer.addEventListener('click', (e) => {
if (e.target.id === 'submit-review-btn') {
const reviewerId = document.getElementById('reviewer-select').value;
const revieweeId = document.getElementById('reviewee-select').value;
const ratings = {};
appState.reviewCriteria.forEach(criterion => {
const key = criterion.toLowerCase().replace(/\s/g, '-');
ratings[criterion] = parseInt(document.getElementById(`rating-${key}`).value, 10);
});
const comments = document.getElementById('review-comments').value.trim();
// Remove existing review from this reviewer for this reviewee to prevent duplicates
appState.reviews = appState.reviews.filter(r => !(r.reviewerId === reviewerId && r.revieweeId === revieweeId));
appState.reviews.push({ reviewerId, revieweeId, ratings, comments });
// Simple confirmation message without alert()
const btn = e.target;
const originalText = btn.textContent;
btn.textContent = 'Submitted!';
btn.classList.add('bg-green-500', 'hover:bg-green-600');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('bg-green-500', 'hover:bg-green-600');
renderReviewForm(); // Reset form
}, 2000);
}
});
employeeSelectResults.addEventListener('change', renderResultsView);
downloadPdfBtn.addEventListener('click', async () => {
const { jsPDF } = window.jspdf;
const reportContent = document.getElementById('report-content');
const employeeName = employeeSelectResults.options[employeeSelectResults.selectedIndex].text;
downloadPdfBtn.textContent = 'Generating...';
try {
const canvas = await html2canvas(reportContent, { scale: 2, useCORS: true });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const imgProps = pdf.getImageProperties(imgData);
const imgRatio = imgProps.height / imgProps.width;
const imgWidth = pdfWidth - 80;
const imgHeight = imgWidth * imgRatio;
pdf.setFillColor('#3B82F6').rect(0, 0, pdfWidth, 60, 'F');
pdf.setFontSize(20).setFont('helvetica', 'bold').setTextColor('#FFFFFF');
pdf.text(`Peer Review Report for ${employeeName}`, pdfWidth / 2, 38, { align: 'center' });
pdf.addImage(imgData, 'PNG', 40, 80, imgWidth, imgHeight);
pdf.save(`peer_review_report_${employeeName.replace(/\s/g, '_')}.pdf`);
} catch (error) {
console.error("PDF Generation Error:", error);
} finally {
downloadPdfBtn.textContent = 'Export Report as PDF';
}
});
// --- INITIALIZATION ---
const init = () => {
Object.values(tabButtons).forEach((btn, i) => btn.addEventListener('click', () => handleTabClick(i + 1)));
appState.team = [
{ id: 'emp-1', name: 'Alice Johnson' },
{ id: 'emp-2', name: 'Bob Williams' },
{ id: 'emp-3', name: 'Charlie Brown' },
];
renderTeamList();
};
init();
});