vai-market / big_sale.js
bep40
Xóa sp mọi danh sách (admin), phân loại BIGSALE >=40% / FLASHSALE <40%, chatbox z-index khi kéo
ecbd691
Raw
History Blame
40.2 kB
// BIG SALE Floating Promotion Module v1.1
// Integrates BIG SALE promotion button with product detail modal
// Compatible with bep40/vai-avatar2 space architecture
//
// CHANGES v1.1:
// - Removed 6 BIG SALE products that have NO image and NO description in the
// catalog dataset (bep40/grob-products-updated): K136A, MST-6667,
// MOVS-10128, "BỘ LÀM SẠCH NHÀ BẾP", 901X, 642X. These fell back to emoji
// placeholders / empty body in the detail modal. 25 -> 19 products.
// - Fixed the share link. Previously it pointed to the production space
// https://bep40-vai-avatar.hf.space/?product=<slug>, which (a) does not load
// this big_sale module at all and (b) opens the catalog product at its
// LISTED price (no BIG SALE discount). Now the share link is self-hosted
// (?bigsale=<code> on the CURRENT page/origin), and a deep-link handler opens
// the BIG SALE detail modal with the correct BIG SALE (discounted) price.
console.log('[BIG SALE] Module loaded (v1.1)');
// BIG SALE Product Data — 19 products with discount info
// (removed: K136A, MST-6667, MOVS-10128, BỘ LÀM SẠCH NHÀ BẾP, 901X, 642X)
const DEFAULT_BIG_SALE_PRODUCTS = [
{ stt: 1, code: "MC-7086HS", name: "MÁY HÚT", category: "MÁY HÚT", qty: 3, price: "11.389.000", discount: "45%", bigsale: "6.263.950", status: "HÀNG MỚI" },
{ stt: 2, code: "MH-70SMC", name: "MÁY HÚT ECO", category: "MÁY HÚT", qty: 2, price: "4.620.000", discount: "50%", bigsale: "2.310.000", status: "HÀNG MỚI" },
{ stt: 3, code: "MH-90SMC", name: "MÁY HÚT ECO", category: "MÁY HÚT", qty: 4, price: "5.060.000", discount: "50%", bigsale: "2.530.000", status: "HÀNG MỚI" },
{ stt: 4, code: "MH-600GT", name: "MÁY HÚT", category: "MÁY HÚT", qty: 5, price: "8.208.000", discount: "40%", bigsale: "4.924.800", status: "HÀNG MỚI" },
{ stt: 5, code: "AS-9602DG", name: "BẾP 2 GAS", category: "BẾP GAS", qty: 2, price: "7.815.000", discount: "40%", bigsale: "4.689.000", status: "HÀNG MỚI" },
{ stt: 6, code: "SMART K-450", name: "CHẬU ĐÁ", category: "CHẬU ĐÁ", qty: 1, price: "21.109.000", discount: "45%", bigsale: "11.609.950", status: "HÀNG MỚI" },
{ stt: 7, code: "MS-1022D", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 4, price: "5.852.000", discount: "40%", bigsale: "3.511.200", status: "HÀNG MỚI" },
{ stt: 8, code: "MS-2025", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 1, price: "5.700.000", discount: "45%", bigsale: "3.135.000", status: "HÀNG MỚI" },
{ stt: 9, code: "MS-6070", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 11, price: "6.464.000", discount: "40%", bigsale: "3.878.400", status: "HÀNG MỚI" },
{ stt: 10, code: "MS-5066W", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 8, price: "4.385.000", discount: "40%", bigsale: "2.631.000", status: "HÀNG MỚI" },
{ stt: 11, code: "MS-5082W", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 3, price: "6.350.000", discount: "40%", bigsale: "3.810.000", status: "HÀNG MỚI" },
{ stt: 12, code: "MS-862", name: "CHẬU RỰA", category: "CHẬU RỰA", qty: 3, price: "12.960.000", discount: "45%", bigsale: "7.128.000", status: "HÀNG MỚI" },
{ stt: 13, code: "MPC-5KCB", name: "BỘ LỌC NƯỚC", category: "LỌC NƯỚC", qty: 1, price: "10.702.000", discount: "40%", bigsale: "6.421.200", status: "HÀNG MỚI" },
{ stt: 14, code: "K061S", name: "VÒI", category: "VÒI", qty: 10, price: "3.056.000", discount: "40%", bigsale: "1.833.600", status: "HÀNG MỚI" },
{ stt: 15, code: "K569SN", name: "VÒI ECO", category: "VÒI", qty: 2, price: "3.520.000", discount: "45%", bigsale: "1.936.000", status: "HÀNG MỚI" },
{ stt: 16, code: "K525B", name: "VÒI", category: "VÒI", qty: 2, price: "2.916.000", discount: "45%", bigsale: "1.603.800", status: "HÀNG MỚI" },
{ stt: 17, code: "K130S", name: "VÒI", category: "VÒI", qty: 4, price: "4.104.000", discount: "45%", bigsale: "2.257.200", status: "HÀNG MỚI" },
{ stt: 18, code: "K501B", name: "VÒI", category: "VÒI", qty: 2, price: "5.076.000", discount: "45%", bigsale: "2.791.800", status: "HÀNG MỚI" },
{ stt: 19, code: "MMPM-657A", name: "XAY TIÊU", category: "PHỤ KIỆN", qty: 34, price: "295.000", discount: "60%", bigsale: "118.000", status: "HÀNG MỚI" }
].map(function(p, i){ return Object.assign({}, p, { stt: i + 1 }); });
// Runtime list (starts from DEFAULT, replaced by /api/promos when available).
let BIG_SALE_PRODUCTS = DEFAULT_BIG_SALE_PRODUCTS.slice().map(function(p, i){ return Object.assign({}, p, { stt: i + 1 }); });
// Expose BIG SALE data + opener so the self-hosted deep-link handler below and
// any other module can open a BIG SALE product by code.
window.BIG_SALE_PRODUCTS = BIG_SALE_PRODUCTS;
// Format VND currency
function formatVND(price) {
const num = parseInt(price.replace(/\./g, ''));
if (isNaN(num)) return price;
return new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(num);
}
// Plain digit-group format WITHOUT currency symbol (for BIGSALE price fields).
function fmtNum(n) {
const num = Number(n || 0);
if (!num) return '0';
return num.toLocaleString('vi-VN');
}
// Build a product card HTML string
function buildProductCard(p) {
const discountClass = p.discount.includes('7') ? 'discount-high' : 'discount-mid';
// Try to get full product name from catalog
var fullName = p.name;
var imgSrc = p.image || '';
try {
if (window.vaix && typeof window.vaix.findProduct === 'function') {
var matched = window.vaix.findProduct(String(p.code || ''));
if (matched && (matched.title_clean || matched.name)) fullName = matched.title_clean || matched.name;
if (!imgSrc) imgSrc = matched && (matched.image || (matched.images && matched.images[0])) ? (matched.image || matched.images[0]) : '';
}
} catch(e) {}
return `
<div class="big-sale-product" data-code="${p.code}" data-stt="${p.stt}">
<div class="big-sale-product-header">
<span class="big-sale-stt">S${p.stt}</span>
<span class="big-sale-discount ${discountClass}">-${p.discount}</span>
</div>
${imgSrc ? '<div class="big-sale-product-img" style="padding:2px 10px 0"><img src="'+imgSrc+'" alt="'+p.code+'" style="width:100%;height:96px;object-fit:cover;border-radius:10px;display:block;background:#f1f5f9" onerror="this.style.display=\'none\'"></div>' : ''}
<div class="big-sale-product-name">${fullName}</div>
<div class="big-sale-product-code">${p.code}</div>
<div class="big-sale-product-meta">
<span class="big-sale-qty">SL: ${p.qty}</span>
<span class="big-sale-status">HÀNG MỚI</span>
</div>
<div class="big-sale-price-row">
<span class="big-sale-price-original">${formatVND(p.price)}</span>
<span class="big-sale-price-new">${formatVND(p.bigsale)}</span>
</div>
<div class="big-sale-card-actions" id="bsa-${p.code}" style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap"></div>
</div>`;
}
// Create the floating button + panel
function createBigSaleElements() {
// Check if already exists
if (document.getElementById('big-sale-floating')) return;
const floating = document.createElement('button');
floating.id = 'big-sale-floating';
floating.setAttribute('aria-label', 'Chương trình BIG SALE');
floating.setAttribute('title', 'Chương trình BIG SALE');
floating.innerHTML = `
<div class="big-sale-btn-content">
<span class="big-sale-badge">BIG SALE</span>
<span class="big-sale-count">${BIG_SALE_PRODUCTS.length} SP</span>
</div>
<div class="big-sale-chevron">▼</div>
`;
const panel = document.createElement('div');
panel.id = 'big-sale-panel';
panel.setAttribute('role', 'dialog');
panel.setAttribute('aria-label', 'Chương trình khuyến mãi BIG SALE');
panel.innerHTML = `
<div class="big-sale-panel-header">
<div class="big-sale-panel-title">
<span class="big-sale-panel-icon">🔥</span>
<span>CHƯƠNG TRÌNH BIG SALE</span>
</div>
<div style="display:flex;align-items:center;gap:8px">
<span class="big-sale-product-count">${BIG_SALE_PRODUCTS.length} sản phẩm</span>
<button type="button" class="big-sale-list-share-btn" onclick="event.stopPropagation();window.shareBigSaleList()" style="padding:5px 10px;border:none;border-radius:8px;background:#f97316;color:#fff;font-size:0.68rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap"><i class="fas fa-share-alt"></i> Chia sẻ danh sách</button>
</div>
</div>
<div class="big-sale-product-list">
${BIG_SALE_PRODUCTS.map(p => buildProductCard(p)).join('')}
</div>
<div class="big-sale-panel-footer">
<button class="big-sale-view-all-btn">Xem tất cả sản phẩm</button>
</div>
`;
// Inject inline style for BIGSALE positioning
var es = document.getElementById('bigsale-pos-style');
if (!es) {
es = document.createElement('style');
es.id = 'bigsale-pos-style';
es.textContent = '#big-sale-floating{position:fixed!important;right:18px!important;bottom:100px!important;z-index:9999!important;}#big-sale-panel{display:none;}#big-sale-panel.open{display:flex!important;}';
document.head.appendChild(es);
}
document.body.appendChild(floating);
document.body.appendChild(panel);
attachBigSaleEventListeners(floating, panel);
}
// Event listeners
function attachBigSaleEventListeners(floating, panel) {
let isOpen = false;
floating.addEventListener('click', function(e) {
e.stopPropagation();
isOpen = !isOpen;
panel.classList.toggle('open', isOpen);
floating.classList.toggle('open', isOpen);
});
// Close when clicking outside
document.addEventListener('click', function(e) {
if (isOpen && !panel.contains(e.target) && !floating.contains(e.target)) {
panel.classList.remove('open');
floating.classList.remove('open');
isOpen = false;
}
});
// Product click → open detail modal (event delegation, survives panel refresh)
panel.addEventListener('click', function(e) {
var card = e.target.closest('.big-sale-product');
if (!card) return;
e.stopPropagation();
var code = card.getAttribute('data-code');
if (!code) return;
var norm = function (s) { return String(s || '').trim().toUpperCase().replace(/\s+/g, ' '); };
var target = norm(code);
var product = BIG_SALE_PRODUCTS.find(function(p) { return norm(p.code) === target; });
if (!product && window.BIG_SALE_PRODUCTS) {
product = window.BIG_SALE_PRODUCTS.find(function(p) { return norm(p.code) === target; });
}
if (product) {
openBigSaleDetail(product);
}
});
// "Xem tất cả" button
const viewAllBtn = panel.querySelector('.big-sale-view-all-btn');
if (viewAllBtn) {
viewAllBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeBigSalePanel();
if (window.vaistudioToggle && typeof window.vaistudioToggle === 'function') {
window.vaistudioToggle();
} else {
const toggle = document.getElementById('vaistudio-toggle');
if (toggle) toggle.click();
}
});
}
}
// Open vaistudio detail modal with BIG SALE product info
function openBigSaleDetail(product) {
const overlay = document.getElementById('vaistudio-detail-overlay');
const modal = document.getElementById('vaistudio-detail-modal');
if (!overlay || !modal) return;
// ── BIG SALE → Malloca product match ──
// If this BIG SALE code exists in the live catalog, reuse the real product's
// gallery/specs/subtitle but keep the BIG SALE price. Falls back to the
// standalone BIG SALE card when there is no match.
let matched = null;
try { if (window.vaix && typeof window.vaix.findProduct === 'function' && product && product.code) matched = window.vaix.findProduct(String(product.code)); } catch (e) { matched = null; }
// Set product title and code
const titleEl = document.getElementById('detail-title');
const modelEl = document.getElementById('detail-model');
const brandEl = document.getElementById('detail-brand');
const priceEl = document.getElementById('detail-price');
if (titleEl) titleEl.textContent = matched ? (matched.title_clean || matched.name || product.name) : product.name;
if (modelEl) modelEl.textContent = `Mã: ${product.code}`;
if (brandEl) {
brandEl.textContent = matched ? (matched.brand || product.category) : product.category;
brandEl.style.background = '#fff1f2';
brandEl.style.color = '#e11d48';
}
if (priceEl) {
priceEl.innerHTML = `
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<span style="color:#c2410c;font-size:1.3rem;font-weight:800">${formatVND(product.bigsale)}</span>
<span style="font-size:0.8rem;color:#64748b;text-decoration:line-through">${formatVND(product.price)}</span>
<span style="font-size:0.85rem;background:#fef3c7;color:#92400e;padding:4px 10px;border-radius:8px;font-weight:700">-${product.discount}</span>
</div>
`;
}
// Set images: full gallery via renderDetailImages (scrollable + zoom)
const imagesEl = document.getElementById('detail-images');
if (imagesEl) {
const gal = (matched && (matched.images && matched.images.length ? matched.images : (matched.image ? [matched.image] : []))) || (product.image ? [product.image] : []);
const synthetic = { image: gal[0] || '', images: gal, slug: (matched&&matched.slug)||'', title_clean: (matched&&matched.title_clean)||product.name };
if (typeof renderDetailImages === 'function') {
renderDetailImages(synthetic);
if (window.vaix && typeof window.vaix.ensureGallery === 'function') {
setTimeout(function(){ try { window.vaix.ensureGallery().then(function(){ renderDetailImages(synthetic); }); } catch(e){} }, 200);
}
} else if (gal.length) {
imagesEl.innerHTML = gal.map(function(u){ return '<img src="'+u+'" alt="'+product.code+'" style="flex:1;min-width:120px;height:120px;border-radius:10px;object-fit:cover;border:1px solid #e2e8f0;background:#f1f5f9" onerror="this.style.display=\'none\'">'; }).join('');
} else {
imagesEl.innerHTML = '<div style="flex:1;min-width:120px;height:120px;background:linear-gradient(135deg,#f1f5f9,#e2e8f0);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:2.5rem">'+(getCategoryEmoji(product.category))+'</div>';
}
}
// Show BIG SALE pricing info in summary
const summaryEl = document.getElementById('detail-summary');
if (summaryEl) {
summaryEl.style.display = 'block';
summaryEl.innerHTML = `
<div style="margin-bottom:8px"><strong>🔥 Giá BigSale:</strong> ${formatVND(product.bigsale)}</div>
<div style="margin-bottom:8px"><strong>💰 Giá bán lẻ:</strong> ${formatVND(product.price)} → <span style="color:#c2410d;font-weight:700">${formatVND(product.bigsale)}</span></div>
<div style="margin-bottom:8px"><strong>🏷️ Chiết khấu:</strong> <span style="color:#dc2626;font-weight:700">-${product.discount}</span></div>
<div style="margin-bottom:8px"><strong>📦 Tình trạng:</strong> <span style="color:#16a34a;font-weight:600">${product.status}</span></div>
<div><strong>💡 Danh mục:</strong> ${product.category}</div>
`;
}
// Hide features and specs sections
const featuresEl = document.getElementById('detail-features');
const specsEl = document.getElementById('detail-specs');
if (featuresEl) featuresEl.style.display = 'none';
if (specsEl) specsEl.style.display = 'none';
// ── Action buttons: Chia sẻ / Hỏi AI / Thêm giỏ hàng / Báo giá ──
// BIG SALE products open through this modal but, unlike catalog products
// (renderDetail in vaix-rag.js), they were NOT being given the share /
// ask-AI / add-to-cart action row. Wire it here so every BIG SALE product
// gets the same actionable buttons as catalog products.
try { setupBigSaleDetailButtons(product, matched); } catch (e) { console.warn('[BIG SALE] detail buttons:', e); }
// CRITICAL: keep the BIG SALE price as THE detail price. The catalog modal's
// reapplyCk() re-renders #detail-price from window.lastShownProduct when the
// customer CK changes — if lastShownProduct were the matched catalog product
// (no promo flag), reapplyCk would overwrite the BIG SALE price with a CK
// price. Point it at the synthetic BIG SALE product (promo:'bigsale',
// noCk:true) so _ckPrice() returns null and the discount price survives.
window.lastShownProduct = bigSaleAsProduct(product, matched);
window.currentContextProduct = window.lastShownProduct;
overlay.style.display = 'flex';
// Close vaistudio panel if open
const vaistudioPanel = document.getElementById('vaistudio-panel');
if (vaistudioPanel && vaistudioPanel.classList.contains('open')) {
vaistudioPanel.classList.remove('open');
const toggle = document.getElementById('vaistudio-toggle');
if (toggle) toggle.classList.remove('open');
}
}
// Build a synthetic product object for the BIG SALE card so we can reuse the
// catalog modal's own button helpers (share / ask-AI / add-to-cart / quote).
function bigSaleAsProduct(product, matched) {
const priceNum = parseInt(String(product.bigsale || '').replace(/\./g, ''), 10) || 0;
const code = product.code || '';
const slug = (matched && (matched.slug || matched.sku)) || code;
return {
slug: slug,
sku: matched && (matched.sku || matched.model) ? (matched.sku || matched.model) : code,
model: (matched && matched.model) || '',
title_clean: (matched && (matched.title_clean || matched.name)) || product.name || code,
name: (matched && (matched.title_clean || matched.name)) || product.name || code,
brand: (matched && matched.brand) || product.category || '',
priceNum: priceNum,
price: product.bigsale || '',
image: (matched && (matched.image || (matched.images && matched.images[0]))) || '',
summary: (matched && (matched.summary || matched.description)) || ('🔥 Giá BIG SALE: ' + product.bigsale + ' (giảm ' + product.discount + ')'),
features: [],
specs: {},
noCk: true,
promo: 'bigsale'
};
}
// Build the self-hosted BIG SALE share link for a given BIG SALE product.
// Points to the CURRENT page/origin with ?bigsale=<code> so it opens the BIG
// SALE detail modal at the discounted price (NOT the catalog listed price and
// NOT the unrelated production space).
function bigSaleShareUrl(product) {
var base = window.location.origin + window.location.pathname;
return base + '?bigsale=' + encodeURIComponent((product && product.code) || '');
}
// Per-card "Chia sẻ link" (SEO share). Copies the self-hosted SEO link to the
// clipboard and shows a toast. Exposed on window because the list cards are
// built as HTML strings with inline onclick.
window.shareBigSale = function (code) {
try {
var url = bigSaleShareUrl({ code: code });
var copy = function (u) {
if (window.navigator && navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(u).then(function(){
if (typeof window.toast === 'function') window.toast('✓ Đã copy link chia sẻ BIG SALE');
else alert('Link chia sẻ BIG SALE (SEO):\n' + u);
}).catch(function(){ window.prompt('Link chia sẻ BIG SALE:', u); });
} else { window.prompt('Link chia sẻ BIG SALE:', u); }
};
copy(url);
} catch (err) {
alert('Lỗi chia sẻ: ' + (err && err.message || err));
}
};
// Create/re-ensure the share + add-to-cart + quote buttons in the BIG SALE
// detail modal. These mirror the catalog modal's detail-cart-section /
// detail-share-section so behaviour (addToCart / openQuotation / shareProduct /
// openAskAIForProduct) is identical for BIG SALE products.
function setupBigSaleDetailButtons(product, matched) {
const p = bigSaleAsProduct(product, matched);
// Share button
let shareSection = document.getElementById('detail-share-section');
if (!shareSection) {
shareSection = document.createElement('div');
shareSection.id = 'detail-share-section';
shareSection.style.cssText = 'padding:10px 16px 4px;display:block';
shareSection.innerHTML = '<button id="detail-share-btn" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:#f0f9ff;color:#003f62;border:1px solid #b8d4e8;border-radius:8px;cursor:pointer;font-size:0.78rem;font-family:inherit;font-weight:600;transition:all 0.2s"><i class="fas fa-share-alt"></i> Chia sẻ sản phẩm</button>';
const specsEl = document.getElementById('detail-specs');
if (specsEl && specsEl.nextSibling) specsEl.parentNode.insertBefore(shareSection, specsEl.nextSibling);
else document.getElementById('vaistudio-detail-modal').appendChild(shareSection);
}
// Cart + Quote buttons
let cartSection = document.getElementById('detail-cart-section');
if (!cartSection) {
cartSection = document.createElement('div');
cartSection.id = 'detail-cart-section';
cartSection.style.cssText = 'padding:8px 16px 2px;display:flex;gap:8px;flex-wrap:wrap';
cartSection.innerHTML =
'<button id="detail-addcart-btn" style="flex:1;min-width:140px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:12px 16px;background:#25d366;color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:0.85rem;font-family:inherit;font-weight:700;transition:all 0.2s"><i class="fas fa-shopping-cart"></i> Thêm giỏ hàng</button>'
+ '<button id="detail-quote-btn" style="flex:1;min-width:120px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:12px 16px;background:#003f62;color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:0.85rem;font-family:inherit;font-weight:700;transition:all 0.2s"><i class="fas fa-file-invoice"></i> Báo giá</button>';
const specsEl2 = document.getElementById('detail-specs');
if (specsEl2 && specsEl2.nextSibling) specsEl2.parentNode.insertBefore(cartSection, specsEl2.nextSibling);
else document.getElementById('vaistudio-detail-modal').appendChild(cartSection);
}
// Wire handlers
const shareBtn = document.getElementById('detail-share-btn');
if (shareBtn) {
shareBtn.onclick = function (e) {
e.stopPropagation();
try {
// Build the share text + self-hosted BIG SALE link ourselves (not the
// catalog's getShareLink which would point to the listed-price deep link).
var url = bigSaleShareUrl(product);
var shareText = '🛒 ' + p.title_clean + '\n💰 ' + (p.priceNum > 0 ? p.priceNum.toLocaleString('vi-VN') + '₫' : 'Liên hệ');
if (p.brand) shareText += '\n🏷️ ' + p.brand;
shareText += '\n🔥 Giá BIG SALE: ' + (product.bigsale || '') + ' (-' + (product.discount || '') + ')';
shareText += '\n\n' + url;
if (navigator.share) { navigator.share({ title: p.title_clean, text: shareText, url: url }).catch(function () {}); }
else { navigator.clipboard.writeText(shareText).then(function () { (window.showShareToast || function () {})( 'Đã sao chép link chia sẻ: ' + p.title_clean); }).catch(function () { prompt('Sao chép link chia sẻ:', url); }); }
} catch (e) { console.warn('[BIG SALE] share:', e); }
};
}
const addCartBtn = document.getElementById('detail-addcart-btn');
if (addCartBtn) {
addCartBtn.onclick = function (e) {
e.stopPropagation();
const prodObj = { slug: p.slug || '', sku: p.sku || '', name: p.title_clean || p.name || '', brand: p.brand || '', price: p.priceNum, priceNum: p.priceNum, image: p.image || '', noCk: true, promo: 'bigsale' };
if (window.addToCart) window.addToCart(prodObj);
addCartBtn.innerHTML = '<i class="fas fa-check"></i> Đã thêm!';
addCartBtn.style.background = '#166534';
const self = this;
setTimeout(function () { self.innerHTML = '<i class="fas fa-shopping-cart"></i> Thêm giỏ hàng'; self.style.background = '#25d366'; }, 1500);
};
}
const quoteBtn = document.getElementById('detail-quote-btn');
if (quoteBtn) { quoteBtn.onclick = function (e) { e.stopPropagation(); if (window.openQuotation) window.openQuotation(); }; }
// Hỏi AI — ask the avatar about THIS product (mirrors catalog openAskAIForProduct).
let askSection = document.getElementById('detail-asksale-section');
if (!askSection) {
askSection = document.createElement('div');
askSection.id = 'detail-asksale-section';
askSection.style.cssText = 'padding:2px 16px 10px;display:block';
askSection.innerHTML = '<button id="detail-asksale-btn" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:linear-gradient(135deg,#7c3aed,#a78bfa);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:0.78rem;font-family:inherit;font-weight:600;transition:all 0.2s"><i class="fas fa-robot"></i> Hỏi AI về sản phẩm</button>';
const cartSectionEl = document.getElementById('detail-cart-section');
if (cartSectionEl && cartSectionEl.nextSibling) askSection = cartSectionEl.parentNode.insertBefore(askSection, cartSectionEl.nextSibling);
else document.getElementById('vaistudio-detail-modal').appendChild(askSection);
}
const askBtn = document.getElementById('detail-asksale-btn');
if (askBtn) {
askBtn.onclick = function (e) {
e.stopPropagation();
if (window.vaix && window.vaix.openAskAIForProduct) {
window.vaix.openAskAIForProduct(p);
} else {
const q = 'Cho em hỏi chi tiết về sản phẩm ' + p.title_clean + '. Cho em biết giá, đặc điểm nổi bật và ưu đãi BIG SALE nhé.';
if (window.sendAsUserQuestion) window.sendAsUserQuestion(q);
else if (window._triggerSuggestionSend) window._triggerSuggestionSend(q);
}
};
}
}
// Get emoji for category
function getCategoryEmoji(category) {
const map = {
"MÁY HÚT": "🧹",
"BẾP GAS": "🔥",
"CHẬU ĐÁ": "🧊",
"CHẬU RỰA": "🚰",
"LỌC NƯỚC": "💧",
"VÒI": "🚿",
"PHỤ KIỆN": "🧰"
};
return map[category] || "📦";
}
// Close BIG SALE panel
function closeBigSalePanel() {
const panel = document.getElementById('big-sale-panel');
const floating = document.getElementById('big-sale-floating');
if (panel) panel.classList.remove('open');
if (floating) floating.classList.remove('open');
}
// ── Self-hosted BIG SALE deep link (?bigsale=<code>) ──
// When a shared BIG SALE link is opened, open the BIG SALE detail modal at the
// discounted price. Waits for the detail modal DOM + this module to be ready,
// then opens the product. Uses ?bigsale= (not ?product=) so it never collides
// with the catalog deep-link handler in greeting-news.js/vaix-rag.js.
function dismissWelcomeForBigSale() {
try {
var wm = document.getElementById('welcome-modal');
if (wm) { wm.classList.remove('show'); wm.style.display = 'none'; }
var ids = ['topbar','controls','subtitles','vaistudio-toggle','text-chat'];
for (var i = 0; i < ids.length; i++) {
var el = document.getElementById(ids[i]);
if (el) { el.style.display = ''; el.hidden = false; }
}
var ld = document.getElementById('loading');
if (ld) ld.classList.add('done');
var av = document.getElementById('avatar-picker-modal');
if (av) { av.classList.remove('show'); av.style.display = 'none'; }
try {
var panel = document.getElementById('vaistudio-panel');
var toggle = document.getElementById('vaistudio-toggle');
if (panel) { panel.classList.add('open'); panel.style.display = 'flex'; }
if (toggle) toggle.classList.add('active');
} catch (_e) {}
} catch (e) { /* never block */ }
}
function handleBigSaleUrlParam() {
try {
var params = new URLSearchParams(location.search);
var code = params.get('bigsale') || '';
if (!code) return;
// ?bigsale=all → open the FULL list (not a single product).
var codeNorm = String(code).trim().toUpperCase().replace(/\s+/g, ' ');
if (codeNorm === 'ALL' || codeNorm === 'DANHSACH' || codeNorm === 'LIST') {
var triesAll = 0;
var ivAll = setInterval(function () {
triesAll++;
try {
if (document.getElementById('big-sale-floating')) {
if (!document.getElementById('big-sale-panel')) initBigSale();
dismissWelcomeForBigSale();
openBigSalePanel();
clearInterval(ivAll);
return;
}
} catch (e) {}
if (triesAll > 40) clearInterval(ivAll);
}, 500);
return;
}
var tries = 0;
var iv = setInterval(function () {
tries++;
try {
var modal = document.getElementById('vaistudio-detail-modal');
// Normalize the code (uppercase, collapse spaces) for matching.
var norm = function (s) { return String(s || '').trim().toUpperCase().replace(/\s+/g, ' '); };
var target = norm(code);
var product = BIG_SALE_PRODUCTS.find(function (p) { return norm(p.code) === target; });
if (!product && window.BIG_SALE_PRODUCTS) {
product = window.BIG_SALE_PRODUCTS.find(function (p) { return norm(p.code) === target; });
}
if (modal && product && typeof openBigSaleDetail === 'function') {
// Make sure the floating panel/module is initialized (creates panel).
if (!document.getElementById('big-sale-floating')) initBigSale();
dismissWelcomeForBigSale();
openBigSaleDetail(product);
try {
if (window.vaix && typeof window.vaix.openAskAIForProduct === 'function') {
const matchedBS = (window.vaix && typeof window.vaix.findProduct === 'function') ? window.vaix.findProduct(String(code)) : null;
const p = (typeof bigSaleAsProduct === 'function') ? bigSaleAsProduct(product, matchedBS) : null;
if (p) window.vaix.openAskAIForProduct(p);
}
} catch(e) {}
clearInterval(iv);
return;
}
} catch (e) {
if (tries > 40) clearInterval(iv);
}
if (tries > 60) clearInterval(iv); // give up after ~30s (catalog may be slow)
}, 500);
} catch (e) { /* never block */ }
}
// Ensure the BIGSALE floating button shows even on discount-link pages (?kh=...)
function ensureBigSaleButton() {
var existing = document.getElementById('big-sale-floating');
if (existing) {
existing.style.display = '';
var panel = document.getElementById('big-sale-panel');
if (panel && panel.classList.contains('open')) { panel.style.display = ''; }
return;
}
if (typeof initBigSale === 'function') {
try { initBigSale(); } catch(e) {}
return;
}
var _retry = 0;
var _iv = setInterval(function() {
_retry++;
if (typeof initBigSale === 'function') {
clearInterval(_iv);
try { initBigSale(); } catch(e) {}
} else if (_retry > 30) {
clearInterval(_iv);
}
}, 300);
}
window.ensureBigSaleButton = ensureBigSaleButton;
// Initialize when DOM is ready
// ── Dynamic BIG SALE data (from Promo CMS /api/promos) ──
// Replaces the runtime list with admin-managed BIGSALE products. Falls back to
// DEFAULT_BIG_SALE_PRODUCTS when the API is unavailable. Emits an event so the
// panel can re-render without a page reload.
function loadBigSaleProducts(force) {
if (force !== true && _bigsaleLoaded) return Promise.resolve(BIG_SALE_PRODUCTS);
return fetch('/api/promos', { method: 'GET', cache: 'no-store' })
.then(function (r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(function (d) {
var list = (d && Array.isArray(d.bigsale)) ? d.bigsale : null;
if (list && list.length) {
BIG_SALE_PRODUCTS = list.slice().map(function (p, i) { return Object.assign({}, p, { stt: i + 1 }); });
} else if (list && list.length === 0 && force === true) {
// Admin explicitly emptied the BIGSALE list — respect it.
BIG_SALE_PRODUCTS = [];
}
// Auto-merge catalog products whose discount is >= 40% (BIGSALE tier).
// Tagged _auto:true so they are NOT deletable via the promo API.
try {
var cat = (window.vaix && typeof window.vaix.allProducts === 'function') ? window.vaix.allProducts() : [];
var normCode = function (s) { return String(s || '').trim().toUpperCase().replace(/\s+/g, ' '); };
var have = {};
for (var h = 0; h < BIG_SALE_PRODUCTS.length; h++) { var hk = normCode(BIG_SALE_PRODUCTS[h].code) || normCode(BIG_SALE_PRODUCTS[h].name); if (hk) have[hk] = 1; }
var added = 0;
for (var ci = 0; ci < cat.length && added < 30; ci++) {
var p2 = cat[ci];
if (p2._deleted || p2.deleted) continue;
var ln = Number(p2.listPn) || (p2.listPrice ? parseInt(String(p2.listPrice).replace(/[^\d]/g, ''), 10) : 0) || 0;
var sn = Number(p2.salePn) || (p2.salePrice ? parseInt(String(p2.salePrice).replace(/[^\d]/g, ''), 10) : 0) || Number(p2.priceNum) || 0;
if (!ln || !sn || ln <= sn) continue;
var disc = Math.round((ln - sn) / ln * 100);
if (disc < 40) continue;
var ck = normCode(p2.sku || p2.model || p2.code || '');
if (!ck) ck = normCode(p2.title_clean || p2.name || '');
if (!ck || have[ck]) continue;
have[ck] = 1;
BIG_SALE_PRODUCTS.push({
stt: BIG_SALE_PRODUCTS.length + 1, code: p2.sku || p2.model || p2.code || ck,
name: (p2.title_clean || p2.name || '').toUpperCase(), category: p2.category || p2.category_slug || '',
price: fmtNum(ln), bigsale: fmtNum(sn), discount: disc + '%', status: 'AUTO', image: p2.image || (p2.images && p2.images[0]) || '',
_auto: true
});
added++;
}
} catch (e) {}
window.BIG_SALE_PRODUCTS = BIG_SALE_PRODUCTS;
_bigsaleLoaded = true;
refreshBigSalePanel();
return BIG_SALE_PRODUCTS;
})
.catch(function () {
return BIG_SALE_PRODUCTS;
});
}
let _bigsaleLoaded = false;
// Re-render the floating panel in place (preserve open state).
function refreshBigSalePanel() {
try {
var panel = document.getElementById('big-sale-panel');
var floating = document.getElementById('big-sale-floating');
if (!panel || !floating) return;
var wasOpen = panel.classList.contains('open');
var listEl = panel.querySelector('.big-sale-product-list');
var countEl = panel.querySelector('.big-sale-product-count');
var countBadge = floating.querySelector('.big-sale-count');
if (listEl) listEl.innerHTML = BIG_SALE_PRODUCTS.map(function (p) { return buildProductCard(p); }).join('');
if (countEl) countEl.textContent = BIG_SALE_PRODUCTS.length + ' sản phẩm';
if (countBadge) countBadge.textContent = BIG_SALE_PRODUCTS.length + ' SP';
if (wasOpen) panel.classList.add('open'); else panel.classList.remove('open');
injectBigSaleAdminActions();
} catch (e) {}
}
// ── List-level share (toàn bộ danh sách BIG SALE) ──
// Copies a self-hosted deep link (?bigsale=all) that opens the FULL BIG SALE
// panel — NOT a single product. Replaces the old per-product share.
window.shareBigSaleList = function () {
var base = window.location.origin + window.location.pathname;
var url = base + '?bigsale=all';
var done = function () {
if (typeof window.toast === 'function') window.toast('✓ Đã copy link chia sẻ danh sách BIG SALE');
else alert('Link chia sẻ danh sách BIG SALE:\n' + url);
};
if (window.navigator && navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(done).catch(function () { window.prompt('Link chia sẻ danh sách BIG SALE:', url); });
} else { window.prompt('Link chia sẻ danh sách BIG SALE:', url); }
};
// Open the full BIG SALE panel (used by ?bigsale=all deep link).
function openBigSalePanel() {
try {
var panel = document.getElementById('big-sale-panel');
var floating = document.getElementById('big-sale-floating');
if (panel) panel.classList.add('open');
if (floating) floating.classList.add('open');
} catch (e) {}
}
window.openBigSalePanel = openBigSalePanel;
// Admin-only delete for a BIG SALE item (permanent, via /api/delete-promo).
async function deleteBigSaleProduct(code) {
try {
var isAdmin = (typeof window.isVaAdmin === 'function') ? window.isVaAdmin() : false;
if (!isAdmin) { alert('🔒 Cần nhập mã V.AISTUDIO trên trang chủ.'); return; }
if (!window.confirm('Xóa sản phẩm ' + code + ' khỏi danh sách BIG SALE?')) return;
var r = await fetch('/api/delete-promo', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-vai-admin': '1' },
body: JSON.stringify({ type: 'bigsale', key: code })
});
var d = await r.json().catch(function () { return {}; });
if (!r.ok) { alert('❌ ' + (d.error || 'Lỗi xóa')); return; }
if (window.toast) window.toast('✓ Đã xóa khỏi BIG SALE');
loadBigSaleProducts(true);
} catch (e) { alert('❌ Lỗi xóa: ' + (e && e.message || e)); }
}
window.deleteBigSaleProduct = deleteBigSaleProduct;
// Inject admin delete buttons into every BIG SALE card (after each render).
function injectBigSaleAdminActions() {
try {
var isAdmin = (typeof window.isVaAdmin === 'function') ? window.isVaAdmin() : false;
if (!isAdmin) return;
var panel = document.getElementById('big-sale-panel');
if (!panel) return;
var norm = function (s) { return String(s || '').trim().toUpperCase().replace(/\s+/g, ' '); };
var cards = panel.querySelectorAll('.big-sale-product');
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
var code = card.getAttribute('data-code') || '';
if (!code) continue;
// Auto-merged catalog items are NOT admin-managed → no delete button.
var isAuto = false;
for (var k = 0; k < BIG_SALE_PRODUCTS.length; k++) {
if (norm(BIG_SALE_PRODUCTS[k].code) === norm(code)) { isAuto = !!BIG_SALE_PRODUCTS[k]._auto; break; }
}
if (isAuto) continue;
var actions = card.querySelector('.big-sale-card-actions');
if (!actions || actions.querySelector('.bs-del-btn')) continue;
var del = document.createElement('button');
del.type = 'button';
del.className = 'bs-del-btn';
del.innerHTML = '<i class="fas fa-trash"></i> Xóa';
del.style.cssText = "flex:1;padding:6px 8px;border:none;border-radius:8px;background:#dc2626;color:#fff;font-size:0.7rem;font-weight:700;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;justify-content:center;gap:5px";
del.addEventListener('click', function (e) {
e.stopPropagation();
window.deleteBigSaleProduct(code);
});
actions.appendChild(del);
}
} catch (e) {}
}
function initBigSale() {
createBigSaleElements();
console.log('[BIG SALE] Floating promotion initialized with ' + BIG_SALE_PRODUCTS.length + ' products');
// Load admin-managed BIGSALE products from the Promo CMS backend.
loadBigSaleProducts().then(function (list) {
console.log('[BIG SALE] Dynamic list loaded: ' + list.length + ' products');
injectBigSaleAdminActions();
handleBigSaleUrlParam();
}).catch(function () {
injectBigSaleAdminActions();
handleBigSaleUrlParam();
});
// Listen for CMS saves so the panel stays in sync.
try {
window.addEventListener('vai-promo-changed', function () { loadBigSaleProducts(true).then(function () { injectBigSaleAdminActions(); }); });
} catch (e) {}
}
// Handle dynamic DOM changes
function tryInitBigSale(retries = 20) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => initBigSale());
} else {
initBigSale();
}
}
tryInitBigSale();