Spaces:
Running
Running
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 | // 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(); | |