Product Rating Dashboard
Rating Overview
Total Reviews
0
Overall Avg. Rating
0.0
Positive Reviews
0%
Negative Reviews
0%
Average Rating by Product
Ratings Over Time
Full Review Log
| Product | Date | Rating | Review Comment |
|---|
Sentiment & Keyword Analysis
Review Sentiment Breakdown
Common Keywords in Negative Reviews
Add New Review
No negative reviews to analyze.
`; return; } Object.entries(keywords).sort((a,b) => b[1] - a[1]).slice(0, 15).forEach(([word, count]) => { const el = document.createElement('span'); el.className = 'bg-red-100 text-red-800 px-3 py-1 rounded-full text-lg'; el.textContent = word; cloud.appendChild(el); }); } function renderConfigForms() { const productSelect = document.getElementById('review-product-select'); productSelect.innerHTML = ''; state.products.forEach(p => { const option = document.createElement('option'); option.value = p.id; option.textContent = p.name; productSelect.appendChild(option); }); document.getElementById('review-date').value = today.toISOString().split('T')[0]; } // --- EVENT HANDLERS & LOGIC --- // function attachEventListeners() { prevBtn.addEventListener('click', () => changeTab(state.activeTab - 1)); nextBtn.addEventListener('click', () => changeTab(state.activeTab + 1)); downloadPdfBtn.addEventListener('click', handleDownloadPdf); document.getElementById('add-review-form').addEventListener('submit', handleAddReview); } window.changeTab = (tabIndex) => { if (tabIndex < 0 || tabIndex >= tabButtons.length) return; state.activeTab = tabIndex; updateTabUI(); }; function updateTabUI() { tabButtons.forEach((b, i) => b.classList.toggle('active', i === state.activeTab)); tabButtons.forEach((b, i) => b.classList.toggle('inactive', i !== state.activeTab)); tabPanes.forEach((p, i) => p.classList.toggle('hidden', i !== state.activeTab)); prevBtn.disabled = state.activeTab === 0; nextBtn.disabled = state.activeTab === tabButtons.length - 1; prevBtn.classList.toggle('opacity-50', prevBtn.disabled); nextBtn.classList.toggle('opacity-50', nextBtn.disabled); } function handleAddReview(e) { e.preventDefault(); const form = e.target; state.reviews.push({ id: Date.now(), productId: parseInt(form.elements['review-product-select'].value), rating: parseInt(form.elements['review-rating'].value), date: form.elements['review-date'].value, comment: form.elements['review-comment'].value, }); form.reset(); render(); changeTab(1); } async function handleDownloadPdf() { const downloadBtn = document.getElementById('download-pdf-btn'); if (!downloadBtn || downloadBtn.disabled) return; const originalBtnContent = downloadBtn.innerHTML; downloadBtn.disabled = true; downloadBtn.innerHTML = `Generating PDF...`; try { const { jsPDF } = window.jspdf; const contentToClone = document.getElementById('product-rating-container'); const contentToExport = contentToClone.cloneNode(true); contentToExport.querySelectorAll('.pdf-hide').forEach(el => el.remove()); contentToExport.querySelectorAll('.tab-pane:not(#tab-0-content)').forEach(el => el.remove()); contentToExport.querySelector('#tab-0-content').classList.remove('hidden'); document.body.appendChild(contentToExport); contentToExport.style.cssText = 'position:absolute; left:0; top:0; width:1024px; z-index:-1; background-color:white; padding:2rem;'; const canvas = await html2canvas(contentToExport, { scale: 2, useCORS: true, logging: false }); document.body.removeChild(contentToExport); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const scaledHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, scaledHeight); pdf.save('Product_Rating_Summary.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); } finally { downloadBtn.disabled = false; downloadBtn.innerHTML = originalBtnContent; } } // --- UTILITY FUNCTIONS --- // function calculateMetrics() { state.overallAverageRating = state.reviews.length > 0 ? state.reviews.reduce((sum, r) => sum + r.rating, 0) / state.reviews.length : 0; const positiveReviews = state.reviews.filter(r => r.rating >= 4).length; const negativeReviews = state.reviews.filter(r => r.rating <= 2).length; state.positiveReviewPercentage = state.reviews.length > 0 ? (positiveReviews / state.reviews.length) * 100 : 0; state.negativeReviewPercentage = state.reviews.length > 0 ? (negativeReviews / state.reviews.length) * 100 : 0; state.products.forEach(p => { const productReviews = state.reviews.filter(r => r.productId === p.id); p.averageRating = productReviews.length > 0 ? productReviews.reduce((sum, r) => sum + r.rating, 0) / productReviews.length : 0; }); } function extractKeywords(text) { const stopWords = new Set(['the', 'a', 'it', 'is', 'i', 'to', 'for', 'and', 'but', 'was', 'not', 'very', 'also']); const words = text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/); const freq = {}; words.forEach(word => { if (word && !stopWords.has(word)) { freq[word] = (freq[word] || 0) + 1; } }); return freq; } // --- START THE APP --- // initialize(); });