Spaces:
Running
Running
v16: fix applyAiDiscount with phụ phí/cọc in quote + add +Giỏ/+Đơn buttons to AI search results
Browse files- index.html +73 -22
index.html
CHANGED
|
@@ -2041,18 +2041,19 @@ let btn=document.getElementById('aiDiscBtn');
|
|
| 2041 |
let statusEl=document.getElementById('aiDiscStatus');
|
| 2042 |
if(!txt){if(statusEl)statusEl.textContent='VD: Malloca ck 35%, Eurogold ck 50%, gov304190 giá 7tr';return}
|
| 2043 |
|
| 2044 |
-
// Parse commands — split by comma, semicolon, or newline
|
| 2045 |
let commands=txt.split(/[,;\n]+/).map(c=>c.trim()).filter(c=>c.length>1);
|
| 2046 |
let results=[];
|
| 2047 |
let applied=0;
|
|
|
|
|
|
|
|
|
|
| 2048 |
|
| 2049 |
-
// Known brands for matching
|
| 2050 |
const BRANDS=['malloca','grob','canzy','eurogold','garis','demax','hafele','boss','teka','bosch'];
|
| 2051 |
|
| 2052 |
commands.forEach(cmd=>{
|
| 2053 |
let cmdLow=cmd.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d');
|
| 2054 |
|
| 2055 |
-
// Pattern 1: [Brand] ck/giảm [X]%
|
| 2056 |
let brandCkMatch=cmdLow.match(/^(\w+)\s+(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%?/);
|
| 2057 |
if(brandCkMatch){
|
| 2058 |
let brandName=brandCkMatch[1];
|
|
@@ -2075,62 +2076,70 @@ commands.forEach(cmd=>{
|
|
| 2075 |
}
|
| 2076 |
}
|
| 2077 |
|
| 2078 |
-
// Pattern 2: [Mã SP] giá [X]
|
| 2079 |
-
let skuPriceMatch=cmdLow.match(/^([a-z0-9_\-]+)\s+(?:gia|
|
| 2080 |
if(skuPriceMatch){
|
| 2081 |
let skuQuery=skuPriceMatch[1].replace(/[.\-_ ]/g,'');
|
| 2082 |
let priceVal=parseFloat(skuPriceMatch[2].replace(/\./g,'').replace(',','.'));
|
| 2083 |
let unit=(skuPriceMatch[3]||'').toLowerCase();
|
| 2084 |
if(unit==='tr'||unit==='trieu'||unit==='m')priceVal*=1000000;
|
| 2085 |
-
else if(unit==='k'||unit==='nghin'
|
| 2086 |
-
else if(priceVal>0&&priceVal<500)priceVal*=1000000;
|
| 2087 |
-
|
| 2088 |
if(priceVal>0){
|
| 2089 |
cart.forEach((c,i)=>{
|
| 2090 |
-
let cSku=((c.sku||'')+(c.mod||'')).toLowerCase().replace(/[.\-_ ]/g,'');
|
| 2091 |
if(cSku.indexOf(skuQuery)!==-1||skuQuery.indexOf(cSku)!==-1){
|
| 2092 |
let el=document.querySelector('.qt-disc[data-idx="'+i+'"]');
|
| 2093 |
-
if(el){el.value=priceVal.toLocaleString('vi-VN');applied++;results.push((c.sku||c.
|
| 2094 |
}
|
| 2095 |
});
|
| 2096 |
}
|
| 2097 |
return;
|
| 2098 |
}
|
| 2099 |
|
| 2100 |
-
// Pattern 3: ck/giảm [X]% (tất cả)
|
| 2101 |
let globalCkMatch=cmdLow.match(/(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%/);
|
| 2102 |
if(globalCkMatch){
|
| 2103 |
let pct=parseFloat(globalCkMatch[1]);
|
| 2104 |
if(pct>0&&pct<=90){
|
|
|
|
| 2105 |
cart.forEach((c,i)=>{
|
| 2106 |
if(c.priceNum>0){
|
| 2107 |
let dp=Math.round(c.priceNum*(1-pct/100));
|
| 2108 |
let el=document.querySelector('.qt-disc[data-idx="'+i+'"]');
|
| 2109 |
-
if(el){el.value=dp.toLocaleString('vi-VN');
|
| 2110 |
}
|
| 2111 |
});
|
| 2112 |
-
results.push('CK '+pct+'% tất cả ('+
|
| 2113 |
}
|
| 2114 |
return;
|
| 2115 |
}
|
| 2116 |
|
| 2117 |
-
// Pattern 4: phụ phí
|
| 2118 |
-
let feeMatch=cmdLow.match(/(?:phu phi|phi|fee)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 2119 |
if(feeMatch){
|
| 2120 |
let fee=parseFloat(feeMatch[1].replace(/\./g,'').replace(',','.'));
|
| 2121 |
let u2=(feeMatch[2]||'').toLowerCase();
|
| 2122 |
if(u2==='k'||u2==='nghin')fee*=1000;
|
| 2123 |
else if(u2==='tr'||u2==='trieu'||u2==='m')fee*=1000000;
|
| 2124 |
-
|
|
|
|
|
|
|
| 2125 |
return;
|
| 2126 |
}
|
|
|
|
|
|
|
| 2127 |
let cocMatch=cmdLow.match(/(?:coc|dat coc|deposit)\s*([\d.,]+)\s*(k|tr|trieu|%|m)?/);
|
| 2128 |
if(cocMatch){
|
| 2129 |
let cocVal=parseFloat(cocMatch[1].replace(/\./g,'').replace(',','.'));
|
| 2130 |
let cu=(cocMatch[2]||'').toLowerCase();
|
| 2131 |
-
if(cu==='%'){
|
| 2132 |
-
|
|
|
|
|
|
|
| 2133 |
if(cu==='k')cocVal*=1000;else if(cu==='tr'||cu==='trieu'||cu==='m')cocVal*=1000000;
|
|
|
|
|
|
|
| 2134 |
results.push('Cọc: '+cocVal.toLocaleString('vi-VN')+'đ');
|
| 2135 |
}
|
| 2136 |
return;
|
|
@@ -2138,9 +2147,47 @@ commands.forEach(cmd=>{
|
|
| 2138 |
});
|
| 2139 |
|
| 2140 |
updateQuoteTotal();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2141 |
if(statusEl){
|
| 2142 |
if(results.length)statusEl.innerHTML='✅ '+results.join(' | ');
|
| 2143 |
-
else statusEl.textContent='⚠️ Không nhận diện. VD: Malloca ck 35%, gov304190 giá 7tr,
|
| 2144 |
}
|
| 2145 |
}
|
| 2146 |
|
|
@@ -2834,10 +2881,9 @@ Miễn phí giao hàng trong TPHCM. | Giá đã bao gồm VAT.
|
|
| 2834 |
document.getElementById('aiResults').innerHTML='⏳ Đang tải dữ liệu...';document.getElementById('aiResults').style.display='block';
|
| 2835 |
}
|
| 2836 |
function _renderAIResults(prods,res){
|
| 2837 |
-
var
|
| 2838 |
var cat=window._vaiDetectCategory?window._vaiDetectCategory(document.getElementById('aiSearch').value):'';
|
| 2839 |
var brand=window._vaiDetectBrand?window._vaiDetectBrand(document.getElementById('aiSearch').value):'';
|
| 2840 |
-
var fmt=function(n){if(!n||isNaN(n))return'LH';return Number(n).toLocaleString('vi-VN')+'đ'};
|
| 2841 |
var parts=[];
|
| 2842 |
if(cat)parts.push('📂 '+cat);if(brand)parts.push('🏷 '+brand);
|
| 2843 |
parts.push('🔍 '+prods.length+' SP');
|
|
@@ -2845,7 +2891,7 @@ function _renderAIResults(prods,res){
|
|
| 2845 |
h+='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px">';
|
| 2846 |
prods.slice(0,24).forEach(function(p){
|
| 2847 |
var idx=D.indexOf(p);
|
| 2848 |
-
var name=p.name||p.n||'';var price=p.price||p.p||'LH';var img=p.image||p.img||p.i||'';var sku=p.sku||p.model||p.mod||'';var brand2=p.brand||'';
|
| 2849 |
var feats=(p.feats||[]).slice(0,2).join(' • ');
|
| 2850 |
var specs=p.specs||{};var sk=Object.entries(specs).slice(0,3);
|
| 2851 |
h+='<div style="background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden;transition:all .2s;cursor:pointer" onclick="showDetail('+idx+')" onmouseover="this.style.transform=\'translateY(-2px)\';this.style.boxShadow=\'0 4px 12px rgba(0,0,0,.1)\'" onmouseout="this.style.transform=\'\';this.style.boxShadow=\'\'">';
|
|
@@ -2856,6 +2902,11 @@ function _renderAIResults(prods,res){
|
|
| 2856 |
if(feats)h+='<div style="font-size:8.5px;color:#64748b;margin-top:2px;height:12px;overflow:hidden">'+feats+'</div>';
|
| 2857 |
if(sk.length){h+='<div style="margin-top:3px;font-size:8px;color:#555;border-top:1px solid #f0f0f0;padding-top:3px">';sk.forEach(function(kv){h+=kv[0]+': <b>'+kv[1]+'</b><br>'});h+='</div>'}
|
| 2858 |
h+='<div style="font-size:13px;font-weight:800;color:#059669;margin-top:4px">'+price+'</div>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2859 |
h+='</div></div>';
|
| 2860 |
});
|
| 2861 |
h+='</div>';res.innerHTML=h;
|
|
|
|
| 2041 |
let statusEl=document.getElementById('aiDiscStatus');
|
| 2042 |
if(!txt){if(statusEl)statusEl.textContent='VD: Malloca ck 35%, Eurogold ck 50%, gov304190 giá 7tr';return}
|
| 2043 |
|
|
|
|
| 2044 |
let commands=txt.split(/[,;\n]+/).map(c=>c.trim()).filter(c=>c.length>1);
|
| 2045 |
let results=[];
|
| 2046 |
let applied=0;
|
| 2047 |
+
let extraFee=0;
|
| 2048 |
+
let deposit=0;
|
| 2049 |
+
let depositPct=0;
|
| 2050 |
|
|
|
|
| 2051 |
const BRANDS=['malloca','grob','canzy','eurogold','garis','demax','hafele','boss','teka','bosch'];
|
| 2052 |
|
| 2053 |
commands.forEach(cmd=>{
|
| 2054 |
let cmdLow=cmd.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d');
|
| 2055 |
|
| 2056 |
+
// Pattern 1: [Brand] ck/giảm [X]%
|
| 2057 |
let brandCkMatch=cmdLow.match(/^(\w+)\s+(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%?/);
|
| 2058 |
if(brandCkMatch){
|
| 2059 |
let brandName=brandCkMatch[1];
|
|
|
|
| 2076 |
}
|
| 2077 |
}
|
| 2078 |
|
| 2079 |
+
// Pattern 2: [Mã SP] giá [X]
|
| 2080 |
+
let skuPriceMatch=cmdLow.match(/^([a-z0-9_\-]+)\s+(?:gia|price)\s*([\d.,]+)\s*(tr|trieu|k|m|nghin)?/);
|
| 2081 |
if(skuPriceMatch){
|
| 2082 |
let skuQuery=skuPriceMatch[1].replace(/[.\-_ ]/g,'');
|
| 2083 |
let priceVal=parseFloat(skuPriceMatch[2].replace(/\./g,'').replace(',','.'));
|
| 2084 |
let unit=(skuPriceMatch[3]||'').toLowerCase();
|
| 2085 |
if(unit==='tr'||unit==='trieu'||unit==='m')priceVal*=1000000;
|
| 2086 |
+
else if(unit==='k'||unit==='nghin')priceVal*=1000;
|
| 2087 |
+
else if(priceVal>0&&priceVal<500)priceVal*=1000000;
|
|
|
|
| 2088 |
if(priceVal>0){
|
| 2089 |
cart.forEach((c,i)=>{
|
| 2090 |
+
let cSku=((c.sku||'')+(c.model||c.mod||'')).toLowerCase().replace(/[.\-_ ]/g,'');
|
| 2091 |
if(cSku.indexOf(skuQuery)!==-1||skuQuery.indexOf(cSku)!==-1){
|
| 2092 |
let el=document.querySelector('.qt-disc[data-idx="'+i+'"]');
|
| 2093 |
+
if(el){el.value=priceVal.toLocaleString('vi-VN');applied++;results.push((c.sku||c.model||skuQuery)+' → '+priceVal.toLocaleString('vi-VN')+'đ');}
|
| 2094 |
}
|
| 2095 |
});
|
| 2096 |
}
|
| 2097 |
return;
|
| 2098 |
}
|
| 2099 |
|
| 2100 |
+
// Pattern 3: ck/giảm [X]% (tất cả)
|
| 2101 |
let globalCkMatch=cmdLow.match(/(?:ck|chiet khau|giam|discount)\s*(\d+)\s*%/);
|
| 2102 |
if(globalCkMatch){
|
| 2103 |
let pct=parseFloat(globalCkMatch[1]);
|
| 2104 |
if(pct>0&&pct<=90){
|
| 2105 |
+
let count=0;
|
| 2106 |
cart.forEach((c,i)=>{
|
| 2107 |
if(c.priceNum>0){
|
| 2108 |
let dp=Math.round(c.priceNum*(1-pct/100));
|
| 2109 |
let el=document.querySelector('.qt-disc[data-idx="'+i+'"]');
|
| 2110 |
+
if(el){el.value=dp.toLocaleString('vi-VN');count++;}
|
| 2111 |
}
|
| 2112 |
});
|
| 2113 |
+
results.push('CK '+pct+'% tất cả ('+count+' SP)');applied+=count;
|
| 2114 |
}
|
| 2115 |
return;
|
| 2116 |
}
|
| 2117 |
|
| 2118 |
+
// Pattern 4: phụ phí
|
| 2119 |
+
let feeMatch=cmdLow.match(/(?:phu phi|phi|fee|van chuyen|ship)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 2120 |
if(feeMatch){
|
| 2121 |
let fee=parseFloat(feeMatch[1].replace(/\./g,'').replace(',','.'));
|
| 2122 |
let u2=(feeMatch[2]||'').toLowerCase();
|
| 2123 |
if(u2==='k'||u2==='nghin')fee*=1000;
|
| 2124 |
else if(u2==='tr'||u2==='trieu'||u2==='m')fee*=1000000;
|
| 2125 |
+
else if(fee>0&&fee<500)fee*=1000;
|
| 2126 |
+
extraFee+=fee;
|
| 2127 |
+
results.push('Phụ phí: +'+fee.toLocaleString('vi-VN')+'đ');
|
| 2128 |
return;
|
| 2129 |
}
|
| 2130 |
+
|
| 2131 |
+
// Pattern 5: cọc
|
| 2132 |
let cocMatch=cmdLow.match(/(?:coc|dat coc|deposit)\s*([\d.,]+)\s*(k|tr|trieu|%|m)?/);
|
| 2133 |
if(cocMatch){
|
| 2134 |
let cocVal=parseFloat(cocMatch[1].replace(/\./g,'').replace(',','.'));
|
| 2135 |
let cu=(cocMatch[2]||'').toLowerCase();
|
| 2136 |
+
if(cu==='%'){
|
| 2137 |
+
depositPct=cocVal;
|
| 2138 |
+
results.push('Cọc: '+cocVal+'%');
|
| 2139 |
+
}else{
|
| 2140 |
if(cu==='k')cocVal*=1000;else if(cu==='tr'||cu==='trieu'||cu==='m')cocVal*=1000000;
|
| 2141 |
+
else if(cocVal>0&&cocVal<500)cocVal*=1000000;
|
| 2142 |
+
deposit=cocVal;
|
| 2143 |
results.push('Cọc: '+cocVal.toLocaleString('vi-VN')+'đ');
|
| 2144 |
}
|
| 2145 |
return;
|
|
|
|
| 2147 |
});
|
| 2148 |
|
| 2149 |
updateQuoteTotal();
|
| 2150 |
+
|
| 2151 |
+
// Add phụ phí/cọc rows to quote footer
|
| 2152 |
+
let tfoot=document.querySelector('.quote-table tfoot');
|
| 2153 |
+
// Remove old extra rows
|
| 2154 |
+
document.querySelectorAll('.qt-extra-row').forEach(r=>r.remove());
|
| 2155 |
+
|
| 2156 |
+
if(tfoot&&(extraFee>0||deposit>0||depositPct>0)){
|
| 2157 |
+
let totalCell=document.getElementById('quoteTotalCell');
|
| 2158 |
+
let totalVal=parseInt((totalCell?totalCell.textContent:'0').replace(/\D/g,''))||0;
|
| 2159 |
+
let insertBefore=tfoot.querySelector('.quote-total-row');
|
| 2160 |
+
|
| 2161 |
+
if(extraFee>0){
|
| 2162 |
+
let feeRow=document.createElement('tr');
|
| 2163 |
+
feeRow.className='qt-extra-row';
|
| 2164 |
+
feeRow.innerHTML='<td colspan="8" style="text-align:right;padding-right:12px;font-size:.82rem;color:#64748b">Phụ phí:</td><td class="qt-price" style="font-size:.82rem;color:#e53935;font-weight:700">+'+extraFee.toLocaleString('vi-VN')+'đ</td><td></td>';
|
| 2165 |
+
if(insertBefore)tfoot.insertBefore(feeRow,insertBefore);else tfoot.appendChild(feeRow);
|
| 2166 |
+
totalVal+=extraFee;
|
| 2167 |
+
}
|
| 2168 |
+
|
| 2169 |
+
// Update total with fee
|
| 2170 |
+
if(extraFee>0&&totalCell){
|
| 2171 |
+
totalCell.textContent=totalVal.toLocaleString('vi-VN')+'đ';
|
| 2172 |
+
}
|
| 2173 |
+
|
| 2174 |
+
if(deposit>0||depositPct>0){
|
| 2175 |
+
let cocAmt=deposit>0?deposit:Math.round(totalVal*depositPct/100);
|
| 2176 |
+
let cocRow=document.createElement('tr');
|
| 2177 |
+
cocRow.className='qt-extra-row';
|
| 2178 |
+
cocRow.innerHTML='<td colspan="8" style="text-align:right;padding-right:12px;font-size:.82rem;color:#2563eb;font-weight:600">Đặt cọc'+(depositPct>0?' ('+depositPct+'%)':'')+':</td><td class="qt-price" style="font-size:.82rem;color:#2563eb;font-weight:700">'+cocAmt.toLocaleString('vi-VN')+'đ</td><td></td>';
|
| 2179 |
+
tfoot.appendChild(cocRow);
|
| 2180 |
+
|
| 2181 |
+
let remainRow=document.createElement('tr');
|
| 2182 |
+
remainRow.className='qt-extra-row';
|
| 2183 |
+
remainRow.innerHTML='<td colspan="8" style="text-align:right;padding-right:12px;font-size:.82rem;color:#059669;font-weight:600">Còn lại:</td><td class="qt-price" style="font-size:.82rem;color:#059669;font-weight:700">'+(totalVal-cocAmt).toLocaleString('vi-VN')+'đ</td><td></td>';
|
| 2184 |
+
tfoot.appendChild(remainRow);
|
| 2185 |
+
}
|
| 2186 |
+
}
|
| 2187 |
+
|
| 2188 |
if(statusEl){
|
| 2189 |
if(results.length)statusEl.innerHTML='✅ '+results.join(' | ');
|
| 2190 |
+
else statusEl.textContent='⚠️ Không nhận diện. VD: Malloca ck 35%, gov304190 giá 7tr, phụ phí 500k, cọc 30%';
|
| 2191 |
}
|
| 2192 |
}
|
| 2193 |
|
|
|
|
| 2881 |
document.getElementById('aiResults').innerHTML='⏳ Đang tải dữ liệu...';document.getElementById('aiResults').style.display='block';
|
| 2882 |
}
|
| 2883 |
function _renderAIResults(prods,res){
|
| 2884 |
+
var fmt=function(n){if(!n||isNaN(n))return'LH';return Number(n).toLocaleString('vi-VN')+'đ'};
|
| 2885 |
var cat=window._vaiDetectCategory?window._vaiDetectCategory(document.getElementById('aiSearch').value):'';
|
| 2886 |
var brand=window._vaiDetectBrand?window._vaiDetectBrand(document.getElementById('aiSearch').value):'';
|
|
|
|
| 2887 |
var parts=[];
|
| 2888 |
if(cat)parts.push('📂 '+cat);if(brand)parts.push('🏷 '+brand);
|
| 2889 |
parts.push('🔍 '+prods.length+' SP');
|
|
|
|
| 2891 |
h+='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px">';
|
| 2892 |
prods.slice(0,24).forEach(function(p){
|
| 2893 |
var idx=D.indexOf(p);
|
| 2894 |
+
var name=p.name||p.n||'';var price=p.price||p.p||'LH';var img=p.image||p.img||p.i||'';var sku=p.sku||p.model||p.mod||'';var brand2=p.brand||'';var slug=p.slug||'';
|
| 2895 |
var feats=(p.feats||[]).slice(0,2).join(' • ');
|
| 2896 |
var specs=p.specs||{};var sk=Object.entries(specs).slice(0,3);
|
| 2897 |
h+='<div style="background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden;transition:all .2s;cursor:pointer" onclick="showDetail('+idx+')" onmouseover="this.style.transform=\'translateY(-2px)\';this.style.boxShadow=\'0 4px 12px rgba(0,0,0,.1)\'" onmouseout="this.style.transform=\'\';this.style.boxShadow=\'\'">';
|
|
|
|
| 2902 |
if(feats)h+='<div style="font-size:8.5px;color:#64748b;margin-top:2px;height:12px;overflow:hidden">'+feats+'</div>';
|
| 2903 |
if(sk.length){h+='<div style="margin-top:3px;font-size:8px;color:#555;border-top:1px solid #f0f0f0;padding-top:3px">';sk.forEach(function(kv){h+=kv[0]+': <b>'+kv[1]+'</b><br>'});h+='</div>'}
|
| 2904 |
h+='<div style="font-size:13px;font-weight:800;color:#059669;margin-top:4px">'+price+'</div>';
|
| 2905 |
+
h+='</div>';
|
| 2906 |
+
// Buttons: +Giỏ hàng + +Đơn
|
| 2907 |
+
h+='<div style="display:flex;gap:4px;padding:0 6px 6px">';
|
| 2908 |
+
h+='<button onclick="event.stopPropagation();if(typeof addToCart===\'function\')addToCart('+idx+')" style="flex:1;padding:4px;background:#db9815;color:#fff;border:none;border-radius:5px;font-size:9px;font-weight:700;cursor:pointer">+ Giỏ</button>';
|
| 2909 |
+
h+='<button onclick="event.stopPropagation();window._vaiAddOrder(\''+slug+'\',this)" style="flex:1;padding:4px;background:#003f62;color:#fff;border:none;border-radius:5px;font-size:9px;font-weight:700;cursor:pointer">+ Đơn</button>';
|
| 2910 |
h+='</div></div>';
|
| 2911 |
});
|
| 2912 |
h+='</div>';res.innerHTML=h;
|