Sản phẩm
Đang tải...

Đang tải...

Mã SKU: Đang tải...

Đã bán 0 | Chia sẻ | Truy xuất nguồn gốc

Đảm bảo chất lượng

Giao hàng nhanh

Đóng gói cẩn thận

Thông số kỹ thuật

Thông tin chi tiết về sản phẩm

Đang tải thông số kỹ thuật...

🏢

Đang tải...

Đang tải...

Voucher của cửa hàng

Chọn voucher và nhấn Lưu để dùng khi thanh toán

Đánh giá sản phẩm

0

Dựa trên 0 đánh giá

Dựa trên 0 đánh giá

(function() { 'use strict'; const EVALUATES_API_URL = '/o/c/evaluates'; let productId = null; let allEvaluates = []; let displayedEvaluates = []; let currentPage = 1; let pageSize = 20; let totalEvaluates = 0; let isViewingAll = false; let isLoading = false; const getProductId = () => { try { const qs = new URLSearchParams(window.location.search || ''); const fromUrl = (qs.get('productId') || '').toString().replace(/[^\d]/g, ''); if (fromUrl) { return fromUrl; } } catch (e) {} const tagsContainer = document.querySelector('[data-product-id]'); if (tagsContainer) { const raw = (tagsContainer.getAttribute('data-product-id') || '').toString().replace(/[^\d]/g, ''); if (raw) { return raw; } } return null; }; const fetchProductEvaluates = async (productId, page = 1, size = 20) => { if (!productId) return { items: [], totalCount: 0 }; try { const params = new URLSearchParams({ filter: 'isHidden eq false and r_productEvaluateId_CProductId eq \'' + productId + '\'', nestedFields: 'productEvaluateId', page: page.toString(), pageSize: size.toString(), sort: 'dateCreated:desc' }); const response = await fetch(EVALUATES_API_URL + '?' + params, { method: 'GET', headers: { 'accept': 'application/json', 'x-csrf-token': Liferay.authToken }, credentials: 'include' }); if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const data = await response.json(); return { items: data.items || [], totalCount: data.totalCount || 0 }; } catch (error) { console.error('Error fetching product evaluates:', error); return { items: [], totalCount: 0 }; } }; const fetchAllEvaluatesForStats = async (productId) => { if (!productId) return []; try { const params = new URLSearchParams({ filter: 'r_productEvaluateId_CProductId eq \'' + productId + '\'', nestedFields: 'productEvaluateId', page: '1', pageSize: '100', sort: 'dateCreated:desc' }); const response = await fetch(EVALUATES_API_URL + '?' + params, { method: 'GET', headers: { 'accept': 'application/json', 'x-csrf-token': Liferay.authToken }, credentials: 'include' }); if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const data = await response.json(); return data.items || []; } catch (error) { console.error('Error fetching all evaluates:', error); return []; } }; const calculateStats = (evaluates) => { if (!evaluates.length) { return { average: 0, total: 0, distribution: { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 } }; } const total = evaluates.length; const sum = evaluates.reduce(function(acc, e) { return acc + (e.point || 0); }, 0); const average = (sum / total).toFixed(1); const distribution = { 5: 0, 4: 0, 3: 0, 2: 0, 1: 0 }; evaluates.forEach(function(e) { const point = e.point || 0; if (point >= 1 && point <= 5) { distribution[point]++; } }); return { average: average, total: total, distribution: distribution }; }; const renderStars = (point) => { const filledClass = 'fill-yellow-400 text-yellow-400'; const emptyClass = 'fill-gray-300 text-gray-300'; const starSvg = function(isFilled) { const starClass = isFilled ? filledClass : emptyClass; return '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star ' + starClass + '"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path></svg>'; }; let result = ''; for (let i = 0; i < 5; i++) { result += starSvg(i < point); } return result; }; const renderRatingSummary = (stats) => { const summaryEl = document.getElementById('ratingSummary'); if (!summaryEl) return; const avgPoint = parseFloat(stats.average) || 0; summaryEl.innerHTML = renderStars(Math.round(avgPoint)) + '<span class="ml-2 text-base font-medium text-gray-700">' + avgPoint.toFixed(1) + '</span>'; if (typeof lucide !== 'undefined') { lucide.createIcons(); } }; const renderOverview = (stats) => { const avgEl = document.getElementById('averageRating'); if (avgEl) avgEl.textContent = stats.average; const avgStarsEl = document.getElementById('averageStars'); if (avgStarsEl) { const avgPoint = Math.round(parseFloat(stats.average)); avgStarsEl.innerHTML = renderStars(avgPoint); } const countEl = document.getElementById('reviewCount'); if (countEl) countEl.textContent = stats.total; const totalCountEl = document.getElementById('totalReviewsCount'); if (totalCountEl) totalCountEl.textContent = stats.total; }; const renderProgress = (stats) => { const progressEl = document.getElementById('starProgress'); if (progressEl) { const stars = [5, 4, 3, 2, 1]; let progressHtml = ''; stars.forEach(function(star) { const count = stats.distribution[star] || 0; const percentage = stats.total > 0 ? (count / stats.total * 100) : 0; progressHtml += '<div class="flex items-center gap-3"><span class="text-xs w-8">' + star + '★</span><div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden"><div class="h-full bg-yellow-400" style="width: ' + percentage + '%"></div></div><span class="text-xs text-gray-600 w-10 text-right">' + count + '</span></div>'; }); progressEl.innerHTML = progressHtml; } }; const getInitials = (name) => { if (!name) return '?'; const parts = name.trim().split(' '); if (parts.length >= 2) { return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } return name[0].toUpperCase(); }; const renderSingleReview = (evaluate) => { const userName = evaluate.creator?.name || 'Người dùng'; const point = evaluate.point || 0; const comment = evaluate.comment || ''; const dateCreated = evaluate.dateCreated.replace("T", " ").slice(0, 16); const initials = getInitials(userName); return '<div class="border-b pb-4 last:border-0"><div class="flex items-start gap-3"><div class="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center flex-shrink-0 text-sm font-medium text-emerald-700">' + initials + '</div><div class="flex-1"><div class="flex items-center gap-2 mb-1"><span class="font-medium text-gray-900">' + userName + '</span><span class="text-xs text-gray-500">• ' + dateCreated + '</span></div><div class="flex items-center gap-1 mb-2">' + renderStars(point) + '</div><p class="text-gray-700 text-sm leading-relaxed">' + comment + '</p></div></div></div>'; }; const renderReviews = (evaluates, append = false) => { const reviewsListEl = document.getElementById('reviewsList'); if (!reviewsListEl) return; if (!evaluates.length && !append) { reviewsListEl.innerHTML = '<div class="text-center py-[0.5rem] text-gray-500">Chưa có đánh giá nào.</div>'; return; } let reviewsHtml = ''; evaluates.forEach(function(evaluate) { reviewsHtml += renderSingleReview(evaluate); }); if (append) { reviewsListEl.innerHTML += reviewsHtml; } else { reviewsListEl.innerHTML = reviewsHtml; } if (typeof lucide !== 'undefined') { lucide.createIcons(); } }; const updateButtonStates = () => { const actionContainer = document.getElementById('actionButtonsContainer'); const loadMoreBtn = document.getElementById('loadMoreBtn'); const reduceBtn = document.getElementById('reduceBtn'); if (!actionContainer || !loadMoreBtn || !reduceBtn) return; if (!isViewingAll) { actionContainer.style.display = 'none'; return; } actionContainer.style.display = 'flex'; if (displayedEvaluates.length < allEvaluates.length || displayedEvaluates.length < totalEvaluates) { loadMoreBtn.style.display = 'block'; } else { loadMoreBtn.style.display = 'none'; } if (displayedEvaluates.length > 20) { reduceBtn.style.display = 'block'; } else { reduceBtn.style.display = 'none'; } }; const reduceReviews = function() { if (displayedEvaluates.length <= 20) { displayedEvaluates = allEvaluates.slice(0, 3); isViewingAll = false; renderReviews(displayedEvaluates, false); updateButtonStates(); return; } const newCount = Math.max(20, displayedEvaluates.length - 20); displayedEvaluates = allEvaluates.slice(0, newCount); renderReviews(displayedEvaluates, false); updateButtonStates(); }; const loadMoreReviews = async function() { if (isLoading) return; isLoading = true; const loadMoreBtn = document.getElementById('loadMoreBtn'); if (loadMoreBtn) { loadMoreBtn.disabled = true; loadMoreBtn.textContent = 'Đang tải...'; } if (displayedEvaluates.length >= allEvaluates.length) { currentPage++; const result = await fetchProductEvaluates(productId, currentPage, pageSize); if (result.items.length > 0) { allEvaluates = allEvaluates.concat(result.items); } else { isLoading = false; if (loadMoreBtn) { loadMoreBtn.disabled = false; loadMoreBtn.textContent = 'Hiển thị thêm'; } updateButtonStates(); return; } } const nextCount = Math.min(displayedEvaluates.length + 20, allEvaluates.length); displayedEvaluates = allEvaluates.slice(0, nextCount); renderReviews(displayedEvaluates, false); updateButtonStates(); isLoading = false; if (loadMoreBtn) { loadMoreBtn.disabled = false; loadMoreBtn.textContent = 'Hiển thị thêm'; } }; const viewAllReviews = async function() { if (isViewingAll) return; isViewingAll = true; currentPage = 1; allEvaluates = []; displayedEvaluates = []; const result = await fetchProductEvaluates(productId, 1, pageSize); allEvaluates = result.items; totalEvaluates = result.totalCount; displayedEvaluates = allEvaluates.slice(0, pageSize); renderReviews(displayedEvaluates, false); updateButtonStates(); }; const loadProductReviews = async function() { productId = getProductId(); if (!productId) { console.warn('Product ID not found'); return; } const allEvaluatesForStats = await fetchAllEvaluatesForStats(productId); const stats = calculateStats(allEvaluatesForStats); renderRatingSummary(stats); renderOverview(stats); renderProgress(stats); const result = await fetchProductEvaluates(productId, 1, 3); allEvaluates = result.items; totalEvaluates = result.totalCount; displayedEvaluates = allEvaluates; renderReviews(displayedEvaluates, false); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { loadProductReviews(); const viewAllBtn = document.getElementById('viewAllReviews'); if (viewAllBtn) { viewAllBtn.addEventListener('click', viewAllReviews); } const loadMoreBtn = document.getElementById('loadMoreBtn'); if (loadMoreBtn) { loadMoreBtn.addEventListener('click', loadMoreReviews); } const reduceBtn = document.getElementById('reduceBtn'); if (reduceBtn) { reduceBtn.addEventListener('click', reduceReviews); } }); } else { loadProductReviews(); const viewAllBtn = document.getElementById('viewAllReviews'); if (viewAllBtn) { viewAllBtn.addEventListener('click', viewAllReviews); } const loadMoreBtn = document.getElementById('loadMoreBtn'); if (loadMoreBtn) { loadMoreBtn.addEventListener('click', loadMoreReviews); } const reduceBtn = document.getElementById('reduceBtn'); if (reduceBtn) { reduceBtn.addEventListener('click', reduceReviews); } } })();

Gợi ý sản phẩm

Sản phẩm tương tự