Đánh giá sản phẩm
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);
}
}
})();