Employee Peer Review System

Employee Peer Review System

Facilitate constructive, anonymous feedback to foster growth and collaboration.

Configure Your Team

Add the team members who will participate in this peer review cycle.

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.

    Anonymous Comments

      ${commentsHtml}
    `; 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(); });
    Scroll to Top