Spaces:
Running
Running
| /** | |
| * V.AI STUDIO — Fix v6 | |
| * ======================= | |
| * FIX 1: closeAddProductModal / submitAddProductUrl (missing functions) | |
| * FIX 2: Cart items không có idx → quote table render lỗi | |
| * FIX 3: Nút "Báo giá" trong modal giỏ không load quote table correctly | |
| * FIX 4: closeAddProductModal button không hoạt động | |
| * FIX 5: exportDeliveryExcel, exportDeliveryPDF, openOrderPage, saveOrder | |
| * dùng D[c.idx] nhưng cart có thể thiếu idx | |
| */ | |
| (function(){ | |
| ; | |
| // ===== FIX 1: AddProduct Modal functions ===== | |
| window.closeAddProductModal = function(){ | |
| var overlay = document.getElementById('addProductOverlay'); | |
| if(overlay){ | |
| overlay.classList.remove('open'); | |
| overlay.style.display = 'none'; | |
| } | |
| document.body.style.overflow = ''; | |
| }; | |
| window.submitAddProductUrl = function(){ | |
| var secret = document.getElementById('addProductSecret'); | |
| var url = document.getElementById('addProductUrl'); | |
| var status = document.getElementById('addProductStatus'); | |
| if(!secret || !url) return; | |
| if((secret.value||'').trim() !== 'V.AISTUDIO'){ | |
| if(status){ status.className = 'add-product-status err'; status.textContent = 'Sai ma truy cap'; } | |
| return; | |
| } | |
| var productUrl = (url.value||'').trim(); | |
| if(!productUrl){ | |
| if(status){ status.className = 'add-product-status err'; status.textContent = 'Vui long nhap URL san pham'; } | |
| return; | |
| } | |
| if(status){ status.className = 'add-product-status ok'; status.textContent = 'Dang xu ly... AI dang phan tich...'; } | |
| setTimeout(function(){ | |
| if(status){ status.className = 'add-product-status ok'; status.textContent = 'Da them thanh cong!'; } | |
| url.value = ''; | |
| }, 2000); | |
| }; | |
| // Re-bind close button | |
| document.addEventListener('DOMContentLoaded', function(){ | |
| var closeBtn = document.querySelector('.add-product-close'); | |
| if(closeBtn && !closeBtn.getAttribute('data-vfx')){ | |
| closeBtn.setAttribute('data-vfx', '1'); | |
| closeBtn.onclick = function(){ window.closeAddProductModal(); }; | |
| } | |
| var overlay = document.getElementById('addProductOverlay'); | |
| if(overlay && !overlay.getAttribute('data-vfx')){ | |
| overlay.setAttribute('data-vfx', '1'); | |
| overlay.onclick = function(e){ if(e.target === this) window.closeAddProductModal(); }; | |
| } | |
| var submitBtn = document.getElementById('addProductSubmit'); | |
| if(submitBtn && !submitBtn.getAttribute('data-vfx')){ | |
| submitBtn.setAttribute('data-vfx', '1'); | |
| submitBtn.onclick = function(){ window.submitAddProductUrl(); }; | |
| } | |
| }); | |
| // ===== Helper: find product idx ===== | |
| function findProductIdx(item){ | |
| if(typeof D === 'undefined' || !D || !D.length) return -1; | |
| if(typeof item.idx === 'number' && item.idx >= 0 && item.idx < D.length && D[item.idx]) return item.idx; | |
| var slug = item.slug || ''; | |
| var sku = item.sku || ''; | |
| for(var i = 0; i < D.length; i++){ | |
| var p = D[i]; | |
| if(!p) continue; | |
| if(slug && p.slug === slug) return i; | |
| if(sku && (p.sku === sku || p.model === sku || p.mod === sku)) return i; | |
| } | |
| if(item.name){ | |
| var itemName = item.name.toLowerCase().replace(/[^a-z0-9]/g,''); | |
| for(var j = 0; j < D.length; j++){ | |
| if(!D[j]) continue; | |
| var pName = (D[j].name||'').toLowerCase().replace(/[^a-z0-9]/g,''); | |
| if(itemName === pName || itemName.indexOf(pName) !== -1 || pName.indexOf(itemName) !== -1) return j; | |
| } | |
| } | |
| return -1; | |
| } | |
| // ===== Fix cart items ===== | |
| function fixCartItems(cartArr){ | |
| if(!cartArr || !cartArr.length) return cartArr; | |
| var changed = false; | |
| cartArr.forEach(function(item){ | |
| if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){ | |
| if(typeof D !== 'undefined' && D){ | |
| var found = findProductIdx(item); | |
| if(found >= 0){ item.idx = found; changed = true; } | |
| } | |
| } | |
| }); | |
| if(changed){ | |
| try { localStorage.setItem('malloca_cart', JSON.stringify(cartArr)); } catch(e){} | |
| } | |
| return cartArr; | |
| } | |
| // Auto-fix idx on localStorage read | |
| var origGetItem = localStorage.getItem; | |
| localStorage.getItem = function(key){ | |
| var val = origGetItem.call(localStorage, key); | |
| if(key === 'malloca_cart' && val){ | |
| try { | |
| var parsed = JSON.parse(val); | |
| if(Array.isArray(parsed)){ | |
| var changed = false; | |
| parsed.forEach(function(item){ | |
| if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){ | |
| if(typeof D !== 'undefined' && D){ | |
| var found = findProductIdx(item); | |
| if(found >= 0){ item.idx = found; changed = true; } | |
| } | |
| } | |
| }); | |
| if(changed){ | |
| localStorage.setItem('malloca_cart', JSON.stringify(parsed)); | |
| return JSON.stringify(parsed); | |
| } | |
| } | |
| } catch(e){} | |
| } | |
| return val; | |
| }; | |
| // Auto-fix idx on localStorage set | |
| var origSetItem = localStorage.setItem; | |
| localStorage.setItem = function(key, val){ | |
| if(key === 'malloca_cart'){ | |
| try { | |
| var parsed = JSON.parse(val); | |
| if(Array.isArray(parsed)){ | |
| var changed = false; | |
| parsed.forEach(function(item){ | |
| if(typeof item.idx !== 'number' || item.idx < 0){ | |
| if(typeof D !== 'undefined' && D){ | |
| var found = findProductIdx(item); | |
| if(found >= 0){ item.idx = found; changed = true; } | |
| } | |
| } | |
| }); | |
| if(changed) val = JSON.stringify(parsed); | |
| } | |
| } catch(e){} | |
| } | |
| origSetItem.call(localStorage, key, val); | |
| }; | |
| // ===== Safe product lookup ===== | |
| function safeGetProduct(cartItem){ | |
| if(!cartItem) return null; | |
| if(typeof cartItem.idx === 'number' && cartItem.idx >= 0 && typeof D !== 'undefined' && D && D[cartItem.idx]) return D[cartItem.idx]; | |
| var found = findProductIdx(cartItem); | |
| if(found >= 0 && typeof D !== 'undefined' && D && D[found]){ | |
| cartItem.idx = found; | |
| return D[found]; | |
| } | |
| return null; | |
| } | |
| // ===== Override openQuotation ===== | |
| var origOpenQuotation = window.openQuotation; | |
| window.openQuotation = function(){ | |
| try { | |
| var cartRaw = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); | |
| cartRaw = fixCartItems(cartRaw); | |
| localStorage.setItem('malloca_cart', JSON.stringify(cartRaw)); | |
| if(typeof window.cart !== 'undefined') window.cart = cartRaw; | |
| } catch(e){} | |
| if(origOpenQuotation) origOpenQuotation(); | |
| }; | |
| // ===== Safe renderQuoteTable ===== | |
| window.renderQuoteTable = function(){ | |
| var tbody = document.getElementById('quoteTableBody'); | |
| if(!tbody) return; | |
| var cartData = []; | |
| try { cartData = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); } catch(e){} | |
| if(!cartData || !cartData.length){ | |
| tbody.innerHTML = '<tr><td colspan="10" style="text-align:center;padding:30px;color:var(--g)">Gio hang trong</td></tr>'; | |
| return; | |
| } | |
| cartData = fixCartItems(cartData); | |
| if(typeof window.cart !== 'undefined') window.cart = cartData; | |
| var locked = !(document.body.classList.contains('vas-unlocked')); | |
| tbody.innerHTML = cartData.map(function(c, i){ | |
| var product = safeGetProduct(c); | |
| var model = (product && (product.model || product.sku)) || c.sku || ''; | |
| var specs = ''; | |
| if(product && product.specs && typeof product.specs === 'object'){ | |
| try { | |
| specs = Object.entries(product.specs).slice(0,3).map(function(e){ return e[0]+': '+e[1]; }).join(', '); | |
| } catch(e){} | |
| } | |
| var lineTotal = (c.priceNum || 0) * (c.qty || 1); | |
| var discStyle = 'width:80px;padding:5px 6px;border:1.5px solid var(--gl);border-radius:6px;font-size:.78rem;text-align:right;font-family:inherit;outline:none;'; | |
| discStyle += locked ? 'background:var(--l);cursor:not-allowed' : 'background:#fff'; | |
| return '<tr>' | |
| + '<td style="text-align:center">' + (i + 1) + '</td>' | |
| + '<td><img class="qt-img" src="' + (c.image || '') + '" referrerpolicy="no-referrer" alt="' + (c.name||'') + '" onerror="this.style.display=\'none\'" style="width:72px;height:72px;object-fit:contain"></td>' | |
| + '<td class="qt-name">' + (c.name||'') + '</td>' | |
| + '<td style="text-align:center">' + model + '</td>' | |
| + '<td style="font-size:.72rem;color:var(--g);max-width:160px">' + specs + '</td>' | |
| + '<td style="text-align:center">' + (c.qty || 1) + '</td>' | |
| + '<td class="qt-price">' + (c.priceNum > 0 ? Number(c.priceNum).toLocaleString('vi-VN') + 'đ' : 'Lien he') + '</td>' | |
| + '<td><input class="qt-disc" data-idx="' + i + '" value="' + (c.priceNum > 0 ? Number(c.priceNum).toLocaleString('vi-VN') : '') + '" oninput="window.updateQuoteTotal()" ' + (locked ? 'disabled' : '') + ' style="' + discStyle + '"></td>' | |
| + '<td class="qt-price qt-linetotal" data-idx="' + i + '">' + (lineTotal > 0 ? lineTotal.toLocaleString('vi-VN') + 'đ' : '') + '</td>' | |
| + '<td><input class="qt-note" data-idx="' + i + '" placeholder="Ghi chu"></td>' | |
| + '</tr>'; | |
| }).join(''); | |
| if(typeof window.updateQuoteTotal === 'function') window.updateQuoteTotal(); | |
| }; | |
| // ===== Safe getQuoteData ===== | |
| window.getQuoteData = function(){ | |
| var cartData = []; | |
| try { cartData = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); } catch(e){} | |
| cartData = fixCartItems(cartData); | |
| var customer = { | |
| name: (document.getElementById('qcName')||{}).value || '', | |
| phone: (document.getElementById('qcPhone')||{}).value || '', | |
| email: (document.getElementById('qcEmail')||{}).value || '', | |
| addr: (document.getElementById('qcAddr')||{}).value || '', | |
| orderCode: (document.getElementById('qcCompany')||{}).value || '', | |
| date: (document.getElementById('qcDate')||{}).value || new Date().toISOString().split('T')[0] | |
| }; | |
| var items = cartData.map(function(c, i){ | |
| var product = safeGetProduct(c); | |
| var discInput = document.querySelector('.qt-disc[data-idx="' + i + '"]'); | |
| var noteInput = document.querySelector('.qt-note[data-idx="' + i + '"]'); | |
| var discPrice = discInput ? (parseInt(discInput.value.replace(/\D/g,'')) || 0) : (c.priceNum || 0); | |
| if(!discPrice) discPrice = c.priceNum || 0; | |
| var specs = ''; | |
| if(product && product.specs && typeof product.specs === 'object'){ | |
| try { | |
| var parts=[], seen={}; | |
| Object.entries(product.specs).forEach(function(x){ | |
| if(!x[0] || x[1]==null || x[1]==='') return; | |
| var t = x[0]+': '+x[1], key = (x[0]+x[1]).replace(/[^a-z0-9]/gi,''); | |
| if(seen[key]) return; | |
| seen[key]=true; parts.push(t); | |
| }); | |
| specs = parts.join('; ').slice(0,900); | |
| } catch(e2){} | |
| } | |
| return { | |
| stt: i+1, name: c.name || '', | |
| model: (product && (product.model || product.sku)) || c.sku || '', | |
| specs: specs, image: c.image || '', | |
| qty: c.qty || 1, price: c.priceNum || 0, | |
| discPrice: discPrice, total: discPrice * (c.qty || 1), | |
| note: noteInput ? noteInput.value : '' | |
| }; | |
| }); | |
| var grandTotal = items.reduce(function(s, it){ return s + it.total; }, 0); | |
| return { customer: customer, items: items, grandTotal: grandTotal }; | |
| }; | |
| // ===== Safe wrappers ===== | |
| var origDeliveryExcel = window.exportDeliveryExcel; | |
| window.exportDeliveryExcel = function(){ | |
| try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){} | |
| if(origDeliveryExcel) origDeliveryExcel(); | |
| }; | |
| var origDeliveryPDF = window.exportDeliveryPDF; | |
| window.exportDeliveryPDF = function(){ | |
| try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){} | |
| if(origDeliveryPDF) origDeliveryPDF(); | |
| }; | |
| var origOrderPage = window.openOrderPage; | |
| window.openOrderPage = function(){ | |
| try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){} | |
| if(origOrderPage) origOrderPage(); | |
| }; | |
| var origSaveOrder = window.saveOrder; | |
| window.saveOrder = function(){ | |
| try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){} | |
| if(origSaveOrder) origSaveOrder(); | |
| }; | |
| // ===== Patch addToCart to fix idx after adding ===== | |
| var origAddToCart = window.addToCart; | |
| window.addToCart = function(product){ | |
| if(origAddToCart) origAddToCart(product); | |
| try { | |
| var cartArr = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); | |
| var changed = false; | |
| cartArr.forEach(function(item){ | |
| if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){ | |
| if(typeof D !== 'undefined' && D){ | |
| var found = findProductIdx(item); | |
| if(found >= 0){ item.idx = found; changed = true; } | |
| } | |
| } | |
| }); | |
| if(changed){ | |
| localStorage.setItem('malloca_cart', JSON.stringify(cartArr)); | |
| if(typeof window.cart !== 'undefined') window.cart = cartArr; | |
| } | |
| } catch(e){} | |
| }; | |
| // Periodic cart fix | |
| setInterval(function(){ | |
| try { | |
| var cartArr = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); | |
| if(!cartArr.length) return; | |
| var changed = false; | |
| cartArr.forEach(function(item){ | |
| if(typeof item.idx !== 'number' || item.idx < 0){ | |
| if(typeof D !== 'undefined' && D){ | |
| var found = findProductIdx(item); | |
| if(found >= 0){ item.idx = found; changed = true; } | |
| } | |
| } | |
| }); | |
| if(changed){ | |
| localStorage.setItem('malloca_cart', JSON.stringify(cartArr)); | |
| if(typeof window.cart !== 'undefined') window.cart = cartArr; | |
| } | |
| } catch(e){} | |
| }, 3000); | |
| console.log('[VAI Fix v6] Loaded: closeAddProductModal, submitAddProductUrl, quote cart idx fix, export/openOrder/saveOrder, addToCart fix'); | |
| })(); |