Video sản phẩm
Đang tải...
Đã bán 0
|
|
Truy xuất nguồn gốc
0
Đang tải...
' +
'' + escapedValue + '' +
'';
});
html += '';
// Cột phải
html += '';
rightColumn.forEach(function (spec) {
const label = spec.label?.vi_VN || spec.label || 'N/A';
const value = spec.value?.vi_VN || spec.value || 'N/A';
const escapedLabel = escapeHtml(String(label || '').trim());
const escapedValue = escapeHtml(String(value || '').trim());
html += '' +
'' + escapedLabel + ':' +
'' + escapedValue + '' +
'';
});
html += '';
html += '';
container.innerHTML = html;
if (container.classList) {
container.classList.remove('hidden');
}
}
// Xử lý lỗi
function showSpecificationsError() {
// Ẩn toàn bộ section khi có lỗi
const sectionEl = document.getElementById('product-specifications-section');
if (sectionEl && sectionEl.classList) {
sectionEl.classList.add('hidden');
}
}
// Khởi tạo
async function initProductSpecifications() {
if (!Utils) {
console.warn('CommerceUtils not available for specifications');
showSpecificationsError();
return;
}
// Ensure API hydration (avoid reading DOM data-product-id placeholder)
try {
if (typeof Utils.getApiProduct === 'function') {
await Utils.getApiProduct();
}
} catch (e) { }
const productInfo = Utils.getProductInfo();
if (!productInfo || !productInfo.productId) {
console.warn('Cannot get productId for specifications');
showSpecificationsError();
return;
}
try {
const specifications = await fetchProductSpecifications(productInfo.productId);
renderSpecifications(specifications);
} catch (error) {
console.error('Error loading product specifications:', error);
showSpecificationsError();
}
}
// Chạy khi DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initProductSpecifications);
} else {
initProductSpecifications();
}
// Hỗ trợ Liferay events
if (typeof Liferay !== 'undefined' && Liferay.on) {
Liferay.on('allPortletsReady', function () {
setTimeout(initProductSpecifications, 500);
});
}
})();
';
};
}
// Tạo QR code
function generateQRCode(data) {
const container = document.getElementById('qrcode-container');
if (!container) return;
// Xóa QR code cũ nếu có
container.innerHTML = '';
// Hiển thị loading
container.innerHTML = '';
// Đợi QRCode library load xong (giảm thời gian đợi xuống 3 giây)
waitForQRCode(function (QRCodeLib) {
if (!QRCodeLib) {
// Fallback: Sử dụng API online
generateQRCodeWithAPI(data);
return;
}
// Xóa loading
container.innerHTML = '';
// Tạo canvas element
const canvas = document.createElement('canvas');
container.appendChild(canvas);
// Tạo QR code mới
try {
QRCodeLib.toCanvas(canvas, data.traceUrl, {
width: 300,
margin: 2,
color: {
dark: '#000000',
light: '#FFFFFF'
}
}, function (error) {
if (error) {
console.error('Error generating QR code:', error);
// Fallback: Sử dụng API online
generateQRCodeWithAPI(data);
}
});
} catch (e) {
console.error('Exception generating QR code:', e);
// Fallback: Sử dụng API online
generateQRCodeWithAPI(data);
}
}, 30); // Giảm từ 50 xuống 30 (3 giây)
}
// Hiển thị modal QR code
function showQRCodeModal() {
const modal = document.getElementById('qr-code-modal');
if (!modal) return;
const traceData = getProductTraceData();
if (!traceData) {
showToastSPNB('Không thể lấy thông tin sản phẩm để tạo mã QR', true);
return;
}
generateQRCode(traceData);
modal.classList.remove('hidden');
}
// Đóng modal QR code
function closeQRCodeModal() {
const modal = document.getElementById('qr-code-modal');
if (modal) {
modal.classList.add('hidden');
}
}
// Hiển thị modal quét QR
function showScanQRModal() {
const modal = document.getElementById('scan-qr-modal');
if (!modal) return;
closeQRCodeModal();
modal.classList.remove('hidden');
// Khởi tạo HTML5 QR Code Scanner
const readerContainer = document.getElementById('qr-reader');
if (readerContainer && !html5QrCode) {
html5QrCode = new Html5Qrcode("qr-reader");
html5QrCode.start(
{ facingMode: "environment" }, // Sử dụng camera sau
{
fps: 10,
qrbox: { width: 250, height: 250 }
},
function (decodedText, decodedResult) {
// Khi quét được QR code
handleScannedQR(decodedText);
},
function (errorMessage) {
// Lỗi khi quét (không hiển thị lỗi thường xuyên)
}
).catch(function (err) {
console.error('Error starting QR scanner:', err);
showToastSPNB('Không thể khởi động camera. Vui lòng kiểm tra quyền truy cập camera.', true);
});
}
}
// Đóng modal quét QR
function closeScanQRModal() {
const modal = document.getElementById('scan-qr-modal');
if (modal) {
modal.classList.add('hidden');
}
// Dừng scanner
if (html5QrCode) {
html5QrCode.stop().then(function () {
html5QrCode.clear();
html5QrCode = null;
}).catch(function (err) {
console.error('Error stopping QR scanner:', err);
});
}
}
// Trích productId từ nội dung QR (URL đủ/không scheme, hoặc chỉ số)
function extractProductIdFromQrPayload(qrData) {
const raw = (qrData || '').toString().trim();
if (!raw) return null;
if (/^\d+$/.test(raw)) return raw;
function idFromSearch(str) {
try {
const u = new URL(str);
const p = u.searchParams.get('productId');
if (p) {
const digits = String(p).replace(/[^\d]/g, '');
return digits || String(p);
}
} catch (e) { }
return null;
}
let id = idFromSearch(raw);
if (id) return id;
// Nhiều app quét QR trả về URL không có scheme → new URL() lỗi
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
id = idFromSearch('https://' + raw.replace(/^\/+/, ''));
if (id) return id;
}
const m1 = raw.match(/[?&#]productId=(\d+)/i);
if (m1) return m1[1];
const m2 = raw.match(/productId[=:]\s*(\d+)/i);
if (m2) return m2[1];
return null;
}
// Xử lý khi quét được QR code
function handleScannedQR(qrData) {
closeScanQRModal();
const productId = extractProductIdFromQrPayload(qrData);
if (!productId) {
showToastSPNB('Mã QR không hợp lệ. Vui lòng thử lại.', true);
return;
}
showTraceInfoModal(productId);
}
// Lấy thông tin sản phẩm: ưu tiên API storefront by-id (cùng nguồn với trang chi tiết),
// vì headless-commerce-admin-catalog thường yêu cầu quyền admin — khách quét QR sẽ bị lỗi.
async function fetchProductTraceInfo(productId) {
const idStr = String(productId || '').trim();
if (!idStr) {
throw new Error('Thiếu mã sản phẩm');
}
const csrf = (typeof Liferay !== 'undefined' && Liferay.authToken) ? Liferay.authToken : '';
const headers = { 'Accept': 'application/json' };
if (csrf) {
headers['x-csrf-token'] = csrf;
}
const fetchOpts = { method: 'GET', headers: headers, credentials: 'include' };
let productData = null;
try {
const byIdUrl = '/o/gocgac-commerce/v1.0/products/by-id?id=' + encodeURIComponent(idStr);
const byIdRes = await fetch(byIdUrl, fetchOpts);
if (byIdRes.ok) {
const responseText = await byIdRes.text().catch(function () { return ''; });
const trimmed = (responseText || '').trim();
if (trimmed) {
try {
productData = JSON.parse(trimmed);
} catch (e) {
console.warn('fetchProductTraceInfo: by-id JSON parse failed', e);
}
}
}
} catch (e) {
console.error('fetchProductTraceInfo: by-id request error', e);
}
if (!productData) {
try {
const adminUrl = APP_CONFIG.API_BASE_ADMIN + '/products/' + encodeURIComponent(idStr);
const response = await fetch(adminUrl, fetchOpts);
if (response.ok) {
productData = (window.safeResponseJson ? await window.safeResponseJson(response) : null);
}
} catch (e) {
console.error('fetchProductTraceInfo: admin product request error', e);
}
}
if (!productData) {
throw new Error('Không thể tải thông tin sản phẩm (kiểm tra mạng hoặc đăng nhập).');
}
const product = Object.assign({}, productData, {
productId: productData.productId != null ? productData.productId : idStr,
id: productData.id != null ? productData.id : (productData.productId || idStr)
});
let catalogData = null;
let accountData = null;
const catalogId = product.catalogId || productData.catalogId;
if (catalogId) {
try {
const catalogUrl = APP_CONFIG.API_BASE_ADMIN + '/catalog/' + catalogId;
const catalogRes = await fetch(catalogUrl, fetchOpts);
if (catalogRes.ok) {
catalogData = (window.safeResponseJson ? await window.safeResponseJson(catalogRes) : null);
if (catalogData && catalogData.accountId) {
try {
const accountUrl = APP_CONFIG.API_BASE_ACCOUNT + '/accounts/' + catalogData.accountId;
const accountRes = await fetch(accountUrl, fetchOpts);
if (accountRes.ok) {
accountData = (window.safeResponseJson ? await window.safeResponseJson(accountRes) : null);
}
} catch (e) {
console.error('Error fetching account info:', e);
}
}
}
} catch (e) {
console.error('Error fetching catalog/account info:', e);
}
}
return {
product: product,
catalog: catalogData,
account: accountData
};
}
// Hiển thị modal thông tin truy xuất nguồn gốc
async function showTraceInfoModal(productId) {
const modal = document.getElementById('trace-info-modal');
const content = document.getElementById('trace-info-content');
if (!modal || !content) return;
modal.classList.remove('hidden');
// Hiển thị loading
content.innerHTML = `
Đang tải thông tin...
`;
try {
const traceInfo = await fetchProductTraceInfo(productId);
renderTraceInfo(traceInfo);
} catch (error) {
const errorMsg = error.message || 'Vui lòng thử lại sau';
content.innerHTML =
'' +
'' +
'Không thể tải thông tin sản phẩm
' +
'' + escapeHtml(errorMsg) + '
' +
'';
}
}
// Render thông tin truy xuất nguồn gốc
function renderTraceInfo(traceInfo) {
const content = document.getElementById('trace-info-content');
if (!content) return;
const { product, catalog, account } = traceInfo;
// Xác định nguồn gốc sản phẩm từ một số field phổ biến (nếu có)
let originText = '';
try {
const productCf = product?.customFields || {};
const accountCf = account?.customFields || {};
originText =
productCf.origin ||
productCf.originName ||
productCf.province ||
productCf.xuatXu || // trường hay dùng cho "xuất xứ"
product?.address ||
accountCf.origin ||
accountCf.originName ||
accountCf.address ||
'';
} catch (e) {
originText = '';
}
let html = '';
// Thông tin sản phẩm
html += '';
html += '';
html += '';
html += 'Thông tin sản phẩm';
html += '
';
html += '';
html += 'Tên sản phẩm:' + escapeHtml(product.name?.vi_VN || product.name || 'N/A') + '';
html += 'Mã sản phẩm:' + escapeHtml(product.productId || product.id || 'N/A') + '';
if (product.sku) {
html += 'SKU:' + escapeHtml(product.sku) + '';
}
html += '';
html += '';
// Thông tin nhà sản xuất/cửa hàng
if (account) {
html += '';
html += '';
html += '';
html += 'Thông tin nhà sản xuất';
html += '
';
html += '';
html += 'Tên tổ chức:' + escapeHtml(account.name || 'N/A') + '';
if (account.customFields?.address) {
html += 'Địa chỉ:' + escapeHtml(account.customFields.address) + '';
}
if (account.customFields?.phone) {
html += 'Điện thoại:' + escapeHtml(account.customFields.phone) + '';
}
html += '';
html += '';
}
// Thông tin catalog
if (catalog) {
html += '';
html += '';
html += '';
html += 'Thông tin danh mục';
html += '
';
html += '';
html += 'Tên danh mục:' + escapeHtml(catalog.name?.vi_VN || catalog.name || 'N/A') + '';
html += '';
html += '';
}
// Thông tin truy xuất
html += '';
html += '';
html += '';
html += 'Thông tin truy xuất';
html += '
';
html += '';
const traceDate = new Date().toLocaleString('vi-VN');
html += 'Thời gian truy xuất:' + escapeHtml(traceDate) + '';
// Hiển thị nguồn gốc (nếu không có dữ liệu thì ghi "Đang cập nhật")
const originDisplay = originText ? escapeHtml(String(originText)) : 'Đang cập nhật';
html += 'Nguồn gốc:' + originDisplay + '';
html += 'Trạng thái:✓ Đã xác thực';
html += '';
html += '';
html += '';
content.innerHTML = html;
}
// Đóng modal thông tin truy xuất
function closeTraceInfoModal() {
const modal = document.getElementById('trace-info-modal');
if (modal) {
modal.classList.add('hidden');
}
}
// Khởi tạo event listeners (chỉ gắn một lần — tránh double fire khi Liferay allPortletsReady)
function initTraceOrigin() {
if (!window.__newDetailTraceOriginUiBound) {
window.__newDetailTraceOriginUiBound = true;
const traceLink = document.getElementById('trace-origin-link');
if (traceLink) {
traceLink.addEventListener('click', function (e) {
e.preventDefault();
showQRCodeModal();
});
}
const closeQRBtns = [
document.getElementById('close-qr-modal'),
document.getElementById('close-qr-modal-btn')
];
closeQRBtns.forEach(function (btn) {
if (btn) {
btn.addEventListener('click', closeQRCodeModal);
}
});
const scanQRBtn = document.getElementById('scan-qr-btn');
if (scanQRBtn) {
scanQRBtn.addEventListener('click', showScanQRModal);
}
const closeScanBtns = [
document.getElementById('close-scan-modal'),
document.getElementById('close-scan-modal-btn')
];
closeScanBtns.forEach(function (btn) {
if (btn) {
btn.addEventListener('click', closeScanQRModal);
}
});
const closeTraceInfoBtns = [
document.getElementById('close-trace-info-modal'),
document.getElementById('close-trace-info-modal-btn')
];
closeTraceInfoBtns.forEach(function (btn) {
if (btn) {
btn.addEventListener('click', closeTraceInfoModal);
}
});
const modals = [
document.getElementById('qr-code-modal'),
document.getElementById('scan-qr-modal'),
document.getElementById('trace-info-modal')
];
modals.forEach(function (modal) {
if (modal) {
modal.addEventListener('click', function (e) {
if (e.target === modal) {
if (modal.id === 'qr-code-modal') {
closeQRCodeModal();
} else if (modal.id === 'scan-qr-modal') {
closeScanQRModal();
} else if (modal.id === 'trace-info-modal') {
closeTraceInfoModal();
}
}
});
}
});
}
try {
const params = new URLSearchParams(window.location.search || '');
if (params.get('traceOrigin') === '1' && !window.__newDetailTraceOriginAutoShown) {
let pid = params.get('productId');
if (pid) {
pid = String(pid).replace(/[^\d]/g, '') || pid;
}
if (!pid && Utils && typeof Utils.getProductInfo === 'function') {
if (Utils.apiProduct && Utils.apiProduct.productId) {
pid = String(Utils.apiProduct.productId);
} else {
const info = Utils.getProductInfo();
if (info && info.productId) {
pid = String(info.productId);
}
}
}
if (pid) {
window.__newDetailTraceOriginAutoShown = true;
showTraceInfoModal(pid);
}
}
} catch (e) { }
}
// Khởi tạo khi DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
setTimeout(initTraceOrigin, 100);
});
} else {
setTimeout(initTraceOrigin, 100);
}
// Hỗ trợ Liferay events
if (typeof Liferay !== 'undefined' && Liferay.on) {
Liferay.on('allPortletsReady', function () {
setTimeout(initTraceOrigin, 500);
});
}
})();
(function () {
'use strict';
async function updateStaticViewProductsAddToCart() {
var STATIC_PRODUCT_ID = null;
try {
var Utils = window.CommerceUtils;
if (Utils && typeof Utils.getApiProduct === 'function') {
var api = await Utils.getApiProduct();
if (api && api.catalogEntryId) {
STATIC_PRODUCT_ID = String(api.catalogEntryId);
}
}
} catch (e) { }
if (!STATIC_PRODUCT_ID) {
var el = document.querySelector('[data-product-id]');
if (el) {
STATIC_PRODUCT_ID = (el.getAttribute('data-product-id') || '').toString().replace(/[^\d]/g, '');
}
}
if (!STATIC_PRODUCT_ID || STATIC_PRODUCT_ID === '0') return;
var API_BASE = window.location.origin + '/o/c/staticviewproducts';
var csrf = (typeof Liferay !== 'undefined' && Liferay.authToken) ? Liferay.authToken : '';
// GET theo filter
var filter = "r_staticProductId_CProductId eq '" + STATIC_PRODUCT_ID + "'";
var getUrl = API_BASE + '/?filter=' + encodeURIComponent(filter);
var getRes = await fetch(getUrl, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-csrf-token': csrf
},
credentials: 'include'
});
if (!getRes.ok) {
throw new Error('GET staticviewproducts failed: ' + getRes.status);
}
var data = await getRes.json();
var item = (data && data.items && data.items.length > 0) ? data.items[0] : null;
// Nếu có record => PUT clickNumber giữ nguyên, chỉ tăng addToCartNumber
if (item) {
var erc = item.externalReferenceCode;
if (!erc) throw new Error('Missing externalReferenceCode on existing item');
var nextAddToCart = (Number(item.addToCartNumber) || 0) + 1;
var clickNumber = String(item.clickNumber ?? 0);
var putUrl = API_BASE + '/by-external-reference-code/' + encodeURIComponent(erc);
var putBody = {
addToCartNumber: String(nextAddToCart),
clickNumber: clickNumber,
r_staticProductId_CProductId: String(STATIC_PRODUCT_ID)
};
var putRes = await fetch(putUrl, {
method: 'PUT',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'x-csrf-token': csrf
},
credentials: 'include',
body: JSON.stringify(putBody)
});
if (!putRes.ok) {
var text = await putRes.text().catch(function () { return ''; });
throw new Error('PUT update failed: ' + putRes.status + ' ' + text);
}
return;
}
// Nếu chưa có record => POST tạo mới (clickNumber = 0, addToCartNumber = 1)
var postBody = {
addToCartNumber: '1',
clickNumber: '0',
r_staticProductId_CProductId: String(STATIC_PRODUCT_ID)
};
var postRes = await fetch(API_BASE + '/', {
method: 'POST',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'x-csrf-token': csrf
},
credentials: 'include',
body: JSON.stringify(postBody)
});
if (!postRes.ok) {
var text2 = await postRes.text().catch(function () { return ''; });
throw new Error('POST create failed: ' + postRes.status + ' ' + text2);
}
}
// expose để gọi từ addToCart()
window.updateStaticViewProductsAddToCart = updateStaticViewProductsAddToCart;
})();
sản phẩm'
: 'Hết hàng';
} else {
stockInfo.removeAttribute('data-max-quantity');
stockInfo.innerHTML = 'Đang cập nhật tồn kho theo phân loại';
}
}
btnIds.forEach(function (id) {
var btn = document.getElementById(id);
if (btn) btn.disabled = !canPurchase;
});
if (window.CommerceUtils && window.CommerceUtils.apiProduct) {
var rawUomList = sku.skuUnitOfMeasures || [];
var primaryUom = rawUomList.find(function (u) { return u.primary === true; }) || rawUomList[0];
window.CommerceUtils.apiProduct.skuId = sku.id || null;
window.CommerceUtils.apiProduct.skuUnitOfMeasureKey = sku.skuUnitOfMeasureKey || (primaryUom ? primaryUom.key : '');
window.CommerceUtils.apiProduct.skuUnitOfMeasure = normalizeUOM(primaryUom);
window.CommerceUtils.apiProduct.stockLevel = stock;
window.CommerceUtils.apiProduct.price = listPrice;
window.CommerceUtils.apiProduct.promoPrice = promoPrice;
window.CommerceUtils.apiProduct.finalPrice = finalPrice;
}
var quantityInput = document.getElementById('quantity-input');
var quantityInputMobile = document.getElementById('quantity-input-mobile');
if (quantityInput) {
// Khi đổi SKU thì reset về 1 để tính giá/tồn theo biến thể mới
quantityInput.value = '1';
if (quantityInputMobile) quantityInputMobile.value = '1';
quantityInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}
function renderSkuOptions(skus) {
var section = document.getElementById('sku-variant-section');
var container = document.getElementById('sku-variant-options');
if (!section || !container) return;
if (!Array.isArray(skus) || skus.length === 0) {
syncNoVariantBadge(false);
section.classList.remove('hidden');
container.innerHTML = 'Chưa có dữ liệu phân loại.';
return;
}
if (skus.length === 1) {
syncNoVariantBadge(false);
section.classList.add('hidden');
return;
}
syncNoVariantBadge(true);
container.innerHTML = '';
section.classList.remove('hidden');
var currentSkuId = window.CommerceUtils && window.CommerceUtils.apiProduct
? parseInt(window.CommerceUtils.apiProduct.skuId, 10)
: null;
var selectedIdx = 0;
if (currentSkuId) {
var found = skus.findIndex(function (s) { return parseInt(s.id, 10) === currentSkuId; });
if (found >= 0) selectedIdx = found;
}
skus.forEach(function (sku, idx) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'px-3 py-1.5 rounded border text-sm transition-colors ' +
(idx === selectedIdx ? 'border-emerald-600 text-emerald-700 bg-emerald-50' : 'border-gray-300 text-gray-700 hover:border-emerald-400');
btn.textContent = getSkuLabel(sku, idx);
btn.addEventListener('click', function () {
Array.prototype.forEach.call(container.children, function (el) {
el.className = 'px-3 py-1.5 rounded border text-sm transition-colors border-gray-300 text-gray-700 hover:border-emerald-400';
});
btn.className = 'px-3 py-1.5 rounded border text-sm transition-colors border-emerald-600 text-emerald-700 bg-emerald-50';
applySkuToUi(sku);
});
container.appendChild(btn);
});
applySkuToUi(skus[selectedIdx]);
}
/** Đồng bộ: map từ apiProduct (by-id) — không gọi API SKU / Admin. */
function buildVariantSkusFromApiProduct(ap) {
if (!ap) {
return { skus: [], resolvedProductId: null };
}
try {
if (ap.skuStockLevels && ap.skuStockLevels.length > 0) {
return {
skus: ap.skuStockLevels.map(function (row) {
// Giá từng SKU độc lập: 0 = chưa niêm yết, không gộp giá cấp sản phẩm (tránh SIZE S hiện giá SIZE M)
var rp = row.price;
var rpp = row.promoPrice;
var rowList = (rp !== undefined && rp !== null && !isNaN(Number(rp))) ? Number(rp) : NaN;
var rowPromo = (rpp !== undefined && rpp !== null && !isNaN(Number(rpp))) ? Number(rpp) : NaN;
return {
id: row.skuId,
sku: row.sku || ('SKU ' + row.skuId),
price: !isNaN(rowList) ? rowList : (Number(ap.listPrice) || Number(ap.price) || 0),
promoPrice: !isNaN(rowPromo) ? rowPromo : (Number(ap.promoPrice) || 0),
stockLevel: row.stockLevel,
skuUnitOfMeasureKey: row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey,
skuUnitOfMeasures: (row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey)
? [{
key: row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey,
name: row.unitOfMeasure || row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey,
primary: true
}]
: (ap.skuUnitOfMeasure ? [ap.skuUnitOfMeasure] : [])
};
}),
resolvedProductId: ap.productId || null
};
}
if (ap.skuId) {
return {
skus: [{
id: ap.skuId,
sku: 'SKU ' + ap.skuId,
price: ap.price,
promoPrice: ap.promoPrice,
stockLevel: ap.stockLevel,
skuUnitOfMeasureKey: ap.skuUnitOfMeasureKey,
skuUnitOfMeasures: ap.skuUnitOfMeasure ? [ap.skuUnitOfMeasure] : []
}],
resolvedProductId: ap.productId || null
};
}
} catch (e) { }
return { skus: [], resolvedProductId: null };
}
async function initVariantSelector() {
try {
if (!window.CommerceUtils || typeof window.CommerceUtils.getApiProduct !== 'function') return;
var apiProduct = await window.CommerceUtils.getApiProduct();
if (!apiProduct || !apiProduct.productId) return;
var result = buildVariantSkusFromApiProduct(apiProduct);
var enrichedSkus = enrichSkuInventoryLevels(result.skus || []);
renderSkuOptions(enrichedSkus);
} catch (e) {
console.warn('initVariantSelector error:', e);
var section = document.getElementById('sku-variant-section');
var container = document.getElementById('sku-variant-options');
if (section && container) {
section.classList.remove('hidden');
container.innerHTML = 'Không tải được phân loại.';
}
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initVariantSelector);
} else {
initVariantSelector();
}
})();
<!-- THÔNG TIN DỊCH VỤ -->
<div class="mt-3 lg:mt-0">
<div class="grid grid-cols-3 gap-2 lg:gap-3 text-center">
<!-- Đảm bảo chất lượng -->
<div class="flex flex-col items-center border border-gray-200 py-3 px-2">
<div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
<path d="m9 12 2 2 4-4"></path>
</svg>
</div>
<p class="font-medium text-gray-900 text-xs lg:text-xs">Đảm bảo chất lượng</p>
</div>
<!-- Giao hàng nhanh -->
<div class="flex flex-col items-center border border-gray-200 py-3 px-2">
<div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5">
<rect x="1" y="3" width="15" height="13"></rect>
<polygon points="16 8 20 8 23 11 23 16 16 16 16 8"></polygon>
<circle cx="5.5" cy="18.5" r="2.5"></circle>
<circle cx="18.5" cy="18.5" r="2.5"></circle>
</svg>
</div>
<p class="font-medium text-gray-900 text-xs lg:text-xs">Giao hàng nhanh</p>
</div>
<!-- Đóng gói cẩn thận -->
<div class="flex flex-col items-center border border-gray-200 py-3 px-2">
<div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5">
<path d="M16.5 9.4 7.55 4.24"></path>
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z">
</path>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</svg>
</div>
<p class="font-medium text-gray-900 text-xs lg:text-xs">Đóng gói cẩn thận</p>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== THÔNG SỐ KỸ THUẬT / CHI TIẾT SẢN PHẨM ==================== -->
<div class="w-full mx-auto mb-12 px-[18px] lg:px-0" id="product-specifications-section">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<!-- Header -->
<div class="bg-gradient-to-r from-emerald-50 to-green-50 px-4 lg:px-6 py-4 border-b border-gray-200">
<h2 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-emerald-600">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Thông số kỹ thuật
</h2>
<p class="text-sm text-gray-600 mt-1">Thông tin chi tiết về sản phẩm</p>
</div>
<!-- Content - Container để render dữ liệu từ API -->
<div class="p-4 lg:p-6" id="product-specifications-container">
<!-- Loading state -->
<div id="specifications-loading" class="text-center py-8">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div>
<p class="text-gray-600 mt-2">Đang tải thông số kỹ thuật...</p>
</div>
<!-- Error state -->
<div id="specifications-error" class="hidden text-center py-8">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-gray-400 mx-auto">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<p class="text-gray-600 mt-2">Không thể tải thông số kỹ thuật</p>
</div>
<!-- Specifications content (sẽ được render bởi JavaScript) -->
<div id="specifications-content" class="hidden">
<!-- Sẽ được render động -->
</div>
</div>
</div>
</div>
<!-- ==================== CARD THÔNG TIN TỔ CHỨC/CỬA HÀNG ==================== -->
<div class="w-full mx-auto mb-0 xl:mb-12 px-[18px] lg:px-0">
<div class="bg-white shadow-sm border border-gray-200">
<div class="p-4 lg:p-5">
<div class="flex gap-4">
<!-- Logo -->
<div class="w-12 h-12 rounded-full bg-emerald-100 flex items-center justify-center flex-shrink-0 overflow-hidden"
id="organization-logo-container">
<span class="text-2xl" id="organization-logo-placeholder">🏢</span>
</div>
<!-- Thông tin chính -->
<div class="flex-1 min-w-0">
<h3 class="font-bold text-base text-gray-900 truncate" id="organization-name">Đang tải...</h3>
<!-- Địa chỉ và đánh giá ở dòng riêng bên dưới -->
<div class="flex items-center gap-3 text-sm text-gray-600 mt-2">
<div class="flex items-center gap-1 flex-shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="fill-yellow-400 text-yellow-400">
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2">
</polygon>
</svg>
<span class="font-medium" id="organization-rating">—</span>
</div>
</div>
</div>
<!-- Nút Chat ngay: mở chat với HTX của sản phẩm (gocgac-chat-widget), kèm thẻ sản phẩm -->
<div class="flex flex-col gap-2 flex-shrink-0 self-start">
<button type="button" id="organization-chat-btn"
onclick="(function(){var pid=new URLSearchParams(window.location.search).get('productId');if(!(window.Liferay&&Liferay.ThemeDisplay&&Liferay.ThemeDisplay.isSignedIn())){window.location.href='/c/portal/login?redirect='+encodeURIComponent(window.location.pathname+window.location.search);return;}window.dispatchEvent(new CustomEvent('gocgac-start-chat',{detail:{productId:pid}}));})()"
class="inline-flex items-center justify-center gap-1 px-3 py-1.5 text-sm font-medium text-white bg-emerald-600 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition">
💬 Chat ngay
</button>
<a href="#" id="organization-url-link"
class="inline-flex items-center justify-center px-3 py-1.5 border border-gray-300 text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition flex-shrink-0 self-start">
Xem cửa hàng
</a>
</div>
</div>
<div class="flex items-center gap-1 mt-3">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path
d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0">
</path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<span id="organization-location" class="truncate">Đang tải...</span>
</div>
</div>
</div>
</div>
<!-- Dialog Điều kiện voucher (cửa hàng) -->
<div id="voucher-conditions-dialog-overlay"
class="fixed inset-0 bg-black/40 z-[9998] hidden items-center justify-center p-4" style="display: none;">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full p-6 relative border border-purple-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-900">Điều kiện áp dụng</h3>
<button type="button" id="voucher-conditions-dialog-close"
class="text-gray-400 hover:text-gray-600 text-2xl leading-none">×</button>
</div>
<div id="voucher-conditions-dialog-body" class="text-sm text-gray-600 space-y-2"></div>
<p id="voucher-conditions-dialog-empty" class="text-sm text-gray-500 hidden">Không có điều kiện.</p>
<div class="mt-4 flex justify-end">
<button type="button" id="voucher-conditions-dialog-ok"
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">Đóng</button>
</div>
</div>
</div>
<!-- ==================== VOUCHER SLIDER (discounts theo channel HTX) ==================== -->
<div class="w-full mx-auto mb-6 xl:mb-12 px-[18px] lg:px-0 mt-6" id="voucher-section">
<div class="bg-white shadow-sm border border-gray-200 rounded-lg overflow-hidden">
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50">
<h3 class="font-bold text-gray-900">Voucher của cửa hàng</h3>
<p class="text-sm text-gray-500 mt-0.5">Chọn voucher và nhấn Lưu để dùng khi thanh toán</p>
</div>
<div class="p-4">
<div id="voucher-loading" class="flex justify-center py-8">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div>
</div>
<div id="voucher-error" class="hidden text-center py-6 text-gray-500 text-sm"></div>
<div id="voucher-slider-wrap" class="hidden relative">
<div id="voucher-slider" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Voucher cards render here -->
</div>
<button type="button" id="voucher-slider-prev"
class="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1 w-10 h-10 rounded-full bg-white border border-gray-300 shadow-md flex items-center justify-center text-gray-600 hover:bg-gray-50 z-10 hidden"
aria-label="Trước">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M15 18l-6-6 6-6" />
</svg>
</button>
<button type="button" id="voucher-slider-next"
class="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1 w-10 h-10 rounded-full bg-white border border-gray-300 shadow-md flex items-center justify-center text-gray-600 hover:bg-gray-50 z-10 hidden"
aria-label="Sau">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M9 18l6-6-6-6" />
</svg>
</button>
</div>
<div id="voucher-empty" class="hidden text-center py-6 text-gray-500 text-sm">Chưa có voucher nào cho
cửa hàng này.</div>
</div>
</div>
</div>
<!-- ==================== MODAL QR CODE TRUY XUẤT NGUỒN GỐC ==================== -->
<div id="qr-code-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-900">Mã QR Truy xuất nguồn gốc</h3>
<button id="close-qr-modal" class="text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="text-center">
<div id="qrcode-container"
class="flex justify-center mb-4 p-4 bg-white rounded-lg border-2 border-gray-200">
<!-- QR Code sẽ được render ở đây -->
</div>
<p class="text-sm text-gray-600 mb-4">Quét mã QR để xem thông tin truy xuất nguồn gốc sản phẩm</p>
<div class="flex gap-3">
<button id="scan-qr-btn"
class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Quét mã QR
</button>
<button id="close-qr-modal-btn"
class="flex-1 border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg hover:bg-gray-50 transition-colors">
Đóng
</button>
</div>
</div>
</div>
</div>
<!-- ==================== MODAL QUÉT QR CODE ==================== -->
<div id="scan-qr-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-900">Quét mã QR</h3>
<button id="close-scan-modal" class="text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="text-center">
<div id="qr-reader" class="mb-4" style="width: 100%; max-width: 400px; margin: 0 auto;">
<!-- QR Scanner sẽ được render ở đây -->
</div>
<p class="text-sm text-gray-600 mb-4">Đưa mã QR vào khung hình để quét</p>
<div class="flex gap-3">
<button id="close-scan-modal-btn"
class="flex-1 border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg hover:bg-gray-50 transition-colors">
Đóng
</button>
</div>
</div>
</div>
</div>
<!-- ==================== MODAL THÔNG TIN TRUY XUẤT NGUỒN GỐC ==================== -->
<div id="trace-info-modal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 p-6 max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-900">Thông tin truy xuất nguồn gốc</h3>
<button id="close-trace-info-modal" class="text-gray-400 hover:text-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div id="trace-info-content" class="space-y-4">
<!-- Thông tin sẽ được render động ở đây -->
<div class="text-center py-8">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div>
<p class="text-gray-600 mt-2">Đang tải thông tin...</p>
</div>
</div>
<div class="mt-6 flex justify-end">
<button id="close-trace-info-modal-btn"
class="px-6 py-2 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors">
Đóng
</button>
</div>
</div>
</div>
<!-- ==================== MODAL CHIA SẺ SẢN PHẨM ==================== -->
<div id="share-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl max-w-sm w-full mx-4">
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-200">
<h3 class="text-base font-semibold text-gray-900">Chia sẻ sản phẩm</h3>
<button id="share-modal-close" class="text-gray-400 hover:text-gray-600 transition-colors">
<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">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="px-4 pt-4 pb-3">
<p class="text-sm text-gray-600 mb-4">
Chọn nền tảng để chia sẻ liên kết sản phẩm:
</p>
<div class="grid grid-cols-2 gap-3">
<!-- Facebook -->
<button type="button"
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-blue-600 hover:bg-blue-50 transition-colors"
data-platform="facebook">
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-600 text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="currentColor">
<path
d="M13.5 9H15V6.75A15.64 15.64 0 0 0 12.93 6C11.17 6 9.9 7.24 9.9 9.52V12H8v3h1.9v7h3.1v-7h2.03L15 12h-2v-2.26C13 9.29 13.13 9 13.5 9Z">
</path>
</svg>
</span>
<span class="text-sm font-medium text-gray-900">Facebook</span>
</button>
<!-- Zalo -->
<button type="button"
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-500 hover:bg-sky-50 transition-colors"
data-platform="zalo">
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-500 text-white">
<span class="text-xs font-bold tracking-tight">ZL</span>
</span>
<span class="text-sm font-medium text-gray-900">Zalo</span>
</button>
<!-- Twitter -->
<button type="button"
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-400 hover:bg-sky-50 transition-colors"
data-platform="twitter">
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-400 text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="currentColor">
<path
d="M22 5.92a8.38 8.38 0 0 1-2.36.65 4.12 4.12 0 0 0 1.8-2.27 8.27 8.27 0 0 1-2.61.98A4.14 4.14 0 0 0 16.11 4a4.18 4.18 0 0 0-4.18 4.18c0 .33 0 .65.07.95A11.78 11.78 0 0 1 3.15 4.6a4.18 4.18 0 0 0 1.29 5.58 4.1 4.1 0 0 1-1.89-.52v.05A4.18 4.18 0 0 0 6.1 13.3a4.23 4.23 0 0 1-1.89.07 4.19 4.19 0 0 0 3.9 2.9A8.33 8.33 0 0 1 2 18.13a11.75 11.75 0 0 0 6.29 1.84c7.55 0 11.68-6.26 11.68-11.68 0-.18 0-.36-.01-.54A8.18 8.18 0 0 0 22 5.92Z">
</path>
</svg>
</span>
<span class="text-sm font-medium text-gray-900">Twitter</span>
</button>
<!-- LinkedIn -->
<button type="button"
class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-700 hover:bg-sky-50 transition-colors"
data-platform="linkedin">
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-700 text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="currentColor">
<path
d="M6.94 6.5A2.44 2.44 0 1 1 4.5 4.06 2.44 2.44 0 0 1 6.94 6.5ZM4.75 8.75h3.5v10.5h-3.5ZM10.38 8.75h3.36v1.44h.05a3.69 3.69 0 0 1 3.32-1.83c3.55 0 4.21 2.34 4.21 5.38v5.51h-3.5v-4.88c0-1.16 0-2.65-1.62-2.65s-1.87 1.27-1.87 2.57v4.96h-3.5Z">
</path>
</svg>
</span>
<span class="text-sm font-medium text-gray-900">LinkedIn</span>
</button>
</div>
</div>
<div class="px-4 pb-4 flex justify-end">
<button id="share-modal-cancel" type="button"
class="px-4 py-2 text-sm font-medium text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
Đóng
</button>
</div>
</div>
</div>
<!-- ==================== MODAL VIDEO SẢN PHẨM (media.attachments) ==================== -->
<div id="product-video-modal"
class="product-video-modal fixed inset-0 z-[100] hidden flex items-center justify-center p-3 sm:p-6 bg-black/65 backdrop-blur-sm"
role="dialog" aria-modal="true" aria-labelledby="product-video-modal-title">
<div
class="product-video-modal__panel relative w-full max-w-[min(100%,56rem)] max-h-[min(90vh,900px)] flex flex-col bg-white rounded-2xl shadow-[0_25px_50px_-12px_rgba(0,0,0,0.45)] ring-1 ring-black/10 overflow-hidden">
<div
class="flex items-center justify-between gap-3 px-4 py-3 sm:px-5 sm:py-3.5 border-b border-gray-200 bg-white shrink-0">
<span id="product-video-modal-title"
class="text-base sm:text-lg font-semibold text-gray-900 truncate pr-2">Video sản phẩm</span>
<button type="button" id="product-video-modal-close"
class="product-video-modal__close shrink-0 w-10 h-10 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 flex items-center justify-center transition-colors shadow-sm border border-gray-200/80"
aria-label="Đóng">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div
class="relative w-full bg-black aspect-video max-h-[calc(min(90vh,900px)-4.25rem)] min-h-[200px]"
id="product-video-modal-stage">
<div id="product-video-modal-loading"
class="absolute inset-0 z-20 hidden flex-col items-center justify-center gap-3 bg-black/80 text-white text-sm">
<svg class="h-10 w-10 animate-spin text-emerald-400" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
<span id="product-video-modal-loading-msg">Đang tải video…</span>
</div>
<video id="product-video-modal-player"
class="absolute inset-0 z-[1] w-full h-full object-contain hidden [&::-webkit-media-controls-panel]:bg-black/40"
controls playsinline preload="none"></video>
<iframe id="product-video-modal-iframe"
class="absolute inset-0 z-[1] w-full h-full hidden border-0 bg-black"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
</div>
</div>
<style>
/* Modal video: đảm bảo hiển thị đẹp kể cả khi theme Liferay override Tailwind */
#product-video-modal.product-video-modal {
box-sizing: border-box;
}
#product-video-modal .product-video-modal__panel {
animation: productVideoModalIn 0.2s ease-out;
}
@keyframes productVideoModalIn {
from {
opacity: 0;
transform: scale(0.96) translateY(8px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
#product-video-modal #product-video-modal-player {
background: #0a0a0a;
}
@media (max-width: 640px) {
#product-video-modal .product-video-modal__panel {
max-height: 88vh;
}
}
</style>
<!-- ==================== STICKY FOOTER CHO MOBILE ==================== -->
<div id="mobile-action-footer"
class="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg z-50 safe-area-inset-bottom">
<div class="flex items-stretch">
<!-- Quantity Selector - Mobile -->
<div class="flex items-center justify-center px-3 bg-white">
<!-- Quantity Controls -->
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden bg-white">
<!-- Nút giảm -->
<button type="button" id="btn-decrease-mobile"
class="w-10 h-10 flex items-center justify-center border-r border-gray-300 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-gray-600">
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<!-- Input số lượng -->
<input id="quantity-input-mobile" name="quantity-mobile" type="text" value="1"
class="w-12 h-10 text-center border-0 border-x border-gray-300 font-medium text-gray-700 focus:ring-0 focus:outline-none"
readonly />
<!-- Nút tăng -->
<button type="button" id="btn-increase-mobile"
class="w-10 h-10 flex items-center justify-center border-l border-gray-300 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-gray-600">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
<!-- Label Số lượng -->
<!-- <label class="text-xs text-gray-500 mt-1">Số lượng</label> -->
</div>
<!-- Button Thêm vào giỏ hàng - Mobile (icon trên, label dưới) -->
<button id="btn-add-to-cart-mobile" type="button"
class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-medium text-sm py-2.5 px-3 flex flex-col items-center justify-center gap-1 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
disabled>
<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">
<circle cx="8" cy="21" r="1"></circle>
<circle cx="19" cy="21" r="1"></circle>
<path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"></path>
</svg>
<span id="add-to-cart-text-mobile" class="text-xs leading-tight text-center">Thêm vào giỏ hàng</span>
</button>
<!-- Button Mua ngay -->
<button id="btn-buy-now-mobile" type="button"
class="flex-1 bg-orange-500 hover:bg-orange-600 text-white font-medium text-base py-3 px-4 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
disabled>
<span id="buy-now-text-mobile">Mua ngay</span>
</button>
</div>
</div>
</div>
<!-- ==================== CUSTOM STYLES ==================== -->
<style>
/* Padding bottom cho content để không bị che bởi sticky footer trên mobile */
@media (max-width: 1023px) {
body {
padding-bottom: 90px;
}
/* Đảm bảo các section có padding bottom đủ */
#product-specifications-section,
.w-full.mx-auto.mb-12 {
margin-bottom: 4rem !important;
padding-bottom: 1rem;
}
/* Đảm bảo container chính có padding bottom */
.grid.grid-cols-1.lg\\:grid-cols-2 {
padding-bottom: 1rem;
}
}
/* Safe area inset cho iPhone với notch */
.safe-area-inset-bottom {
padding-bottom: env(safe-area-inset-bottom, 12px);
}
/* Sticky footer styling */
#mobile-action-footer {
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
/* Quantity selector mobile styling */
#quantity-input-mobile {
appearance: none;
-webkit-appearance: none;
-moz-appearance: textfield;
}
#quantity-input-mobile::-webkit-outer-spin-button,
#quantity-input-mobile::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
margin: 0;
}
/* Tối ưu spacing cho mobile */
@media (max-width: 1023px) {
.space-y-6>*+* {
margin-top: 1rem;
}
.space-y-4>*+* {
margin-top: 0.75rem;
}
}
/* Product Description Styling */
.product-description {
line-height: 1.8;
font-size: 0.875rem;
color: #7a818b;
}
/* Collapsed state - limit to 5 lines */
.product-description.product-description-collapsed {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
line-clamp: 5;
max-height: none;
overflow: hidden;
position: relative;
}
/* Gradient fade effect when collapsed */
.product-description.product-description-collapsed::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 40px;
background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.95));
pointer-events: none;
}
/* Expanded state */
.product-description.product-description-expanded {
max-height: none;
overflow: visible;
}
/* Style for paragraphs within description */
.product-description p {
margin-bottom: 0.75rem;
line-height: 1.8;
}
.product-description p:last-child {
margin-bottom: 0;
}
/* Style for bold text (headings, labels) */
.product-description strong,
.product-description b {
font-weight: 600;
color: #1f2937;
}
/* Style for lists */
.product-description ul,
.product-description ol {
margin: 0.75rem 0;
padding-left: 1.5rem;
}
.product-description li {
margin-bottom: 0.5rem;
line-height: 1.7;
}
/* Enhanced spacing */
.product-description br {
line-height: 1.8;
}
/* Toggle button styling */
.product-description-toggle {
transition: color 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.product-description-toggle:hover {
text-decoration: none;
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
#voucher-slider .voucher-card {
min-width: 0;
}
#voucher-slider .voucher-card {
position: relative;
}
.voucher-conditions-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
font-size: 0.75rem;
color: #6d28d9;
font-weight: 500;
cursor: pointer;
background: none;
border: none;
padding: 0;
}
.voucher-conditions-btn:hover {
color: #5b21b6;
}
#voucher-conditions-dialog-overlay.show {
display: flex !important;
}
</style>
<!-- ==================== LOAD CSS FRAMEWORK ==================== -->
<script src="https://cdn.tailwindcss.com">
Đang tải...
0
Đang tải...
' +
'' + escapedLabel + ':' +
'' + escapedValue + '' +
'
';
});
html += 'Đang tải thông tin...
Không thể tải thông tin sản phẩm
' + '' + escapeHtml(errorMsg) + '
' + '';
html += '
';
// Thông tin nhà sản xuất/cửa hàng
if (account) {
html += ''; html += ''; html += 'Thông tin sản phẩm'; html += '
'; html += '';
html += '
';
html += 'Tên sản phẩm:' + escapeHtml(product.name?.vi_VN || product.name || 'N/A') + '
';
html += 'Mã sản phẩm:' + escapeHtml(product.productId || product.id || 'N/A') + '
';
if (product.sku) {
html += 'SKU:' + escapeHtml(product.sku) + '
';
}
html += '';
html += '
';
}
// Thông tin catalog
if (catalog) {
html += ''; html += ''; html += 'Thông tin nhà sản xuất'; html += '
'; html += '';
html += '
';
html += 'Tên tổ chức:' + escapeHtml(account.name || 'N/A') + '
';
if (account.customFields?.address) {
html += 'Địa chỉ:' + escapeHtml(account.customFields.address) + '
';
}
if (account.customFields?.phone) {
html += 'Điện thoại:' + escapeHtml(account.customFields.phone) + '
';
}
html += '';
html += '
';
}
// Thông tin truy xuất
html += ''; html += ''; html += 'Thông tin danh mục'; html += '
'; html += '';
html += '
';
html += 'Tên danh mục:' + escapeHtml(catalog.name?.vi_VN || catalog.name || 'N/A') + '
';
html += '';
html += '
';
html += ''; html += ''; html += 'Thông tin truy xuất'; html += '
'; html += '';
const traceDate = new Date().toLocaleString('vi-VN');
html += '
';
html += 'Thời gian truy xuất:' + escapeHtml(traceDate) + '
';
// Hiển thị nguồn gốc (nếu không có dữ liệu thì ghi "Đang cập nhật")
const originDisplay = originText ? escapeHtml(String(originText)) : 'Đang cập nhật';
html += 'Nguồn gốc:' + originDisplay + '
';
html += 'Trạng thái:✓ Đã xác thực
';
html += '
Đánh giá sản phẩm
0
Dựa trên 0 đánh giá
Đánh giá sản phẩm
0
Dựa trên 0 đánh giá