Spaces:
Running
Running
bep40 commited on
Commit ·
ecbd691
1
Parent(s): 50d5689
Xóa sp mọi danh sách (admin), phân loại BIGSALE >=40% / FLASHSALE <40%, chatbox z-index khi kéo
Browse files- big_sale.js +41 -0
- index.html +4 -4
- src/app.js +2 -2
- src/vaix-flashsale.js +8 -2
- src/vaix-rag.js +4 -3
big_sale.js
CHANGED
|
@@ -53,6 +53,12 @@ function formatVND(price) {
|
|
| 53 |
if (isNaN(num)) return price;
|
| 54 |
return new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(num);
|
| 55 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
// Build a product card HTML string
|
| 58 |
function buildProductCard(p) {
|
|
@@ -595,6 +601,35 @@ function loadBigSaleProducts(force) {
|
|
| 595 |
// Admin explicitly emptied the BIGSALE list — respect it.
|
| 596 |
BIG_SALE_PRODUCTS = [];
|
| 597 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
window.BIG_SALE_PRODUCTS = BIG_SALE_PRODUCTS;
|
| 599 |
_bigsaleLoaded = true;
|
| 600 |
refreshBigSalePanel();
|
|
@@ -682,6 +717,12 @@ function injectBigSaleAdminActions() {
|
|
| 682 |
var card = cards[i];
|
| 683 |
var code = card.getAttribute('data-code') || '';
|
| 684 |
if (!code) continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
var actions = card.querySelector('.big-sale-card-actions');
|
| 686 |
if (!actions || actions.querySelector('.bs-del-btn')) continue;
|
| 687 |
var del = document.createElement('button');
|
|
|
|
| 53 |
if (isNaN(num)) return price;
|
| 54 |
return new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(num);
|
| 55 |
}
|
| 56 |
+
// Plain digit-group format WITHOUT currency symbol (for BIGSALE price fields).
|
| 57 |
+
function fmtNum(n) {
|
| 58 |
+
const num = Number(n || 0);
|
| 59 |
+
if (!num) return '0';
|
| 60 |
+
return num.toLocaleString('vi-VN');
|
| 61 |
+
}
|
| 62 |
|
| 63 |
// Build a product card HTML string
|
| 64 |
function buildProductCard(p) {
|
|
|
|
| 601 |
// Admin explicitly emptied the BIGSALE list — respect it.
|
| 602 |
BIG_SALE_PRODUCTS = [];
|
| 603 |
}
|
| 604 |
+
// Auto-merge catalog products whose discount is >= 40% (BIGSALE tier).
|
| 605 |
+
// Tagged _auto:true so they are NOT deletable via the promo API.
|
| 606 |
+
try {
|
| 607 |
+
var cat = (window.vaix && typeof window.vaix.allProducts === 'function') ? window.vaix.allProducts() : [];
|
| 608 |
+
var normCode = function (s) { return String(s || '').trim().toUpperCase().replace(/\s+/g, ' '); };
|
| 609 |
+
var have = {};
|
| 610 |
+
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; }
|
| 611 |
+
var added = 0;
|
| 612 |
+
for (var ci = 0; ci < cat.length && added < 30; ci++) {
|
| 613 |
+
var p2 = cat[ci];
|
| 614 |
+
if (p2._deleted || p2.deleted) continue;
|
| 615 |
+
var ln = Number(p2.listPn) || (p2.listPrice ? parseInt(String(p2.listPrice).replace(/[^\d]/g, ''), 10) : 0) || 0;
|
| 616 |
+
var sn = Number(p2.salePn) || (p2.salePrice ? parseInt(String(p2.salePrice).replace(/[^\d]/g, ''), 10) : 0) || Number(p2.priceNum) || 0;
|
| 617 |
+
if (!ln || !sn || ln <= sn) continue;
|
| 618 |
+
var disc = Math.round((ln - sn) / ln * 100);
|
| 619 |
+
if (disc < 40) continue;
|
| 620 |
+
var ck = normCode(p2.sku || p2.model || p2.code || '');
|
| 621 |
+
if (!ck) ck = normCode(p2.title_clean || p2.name || '');
|
| 622 |
+
if (!ck || have[ck]) continue;
|
| 623 |
+
have[ck] = 1;
|
| 624 |
+
BIG_SALE_PRODUCTS.push({
|
| 625 |
+
stt: BIG_SALE_PRODUCTS.length + 1, code: p2.sku || p2.model || p2.code || ck,
|
| 626 |
+
name: (p2.title_clean || p2.name || '').toUpperCase(), category: p2.category || p2.category_slug || '',
|
| 627 |
+
price: fmtNum(ln), bigsale: fmtNum(sn), discount: disc + '%', status: 'AUTO', image: p2.image || (p2.images && p2.images[0]) || '',
|
| 628 |
+
_auto: true
|
| 629 |
+
});
|
| 630 |
+
added++;
|
| 631 |
+
}
|
| 632 |
+
} catch (e) {}
|
| 633 |
window.BIG_SALE_PRODUCTS = BIG_SALE_PRODUCTS;
|
| 634 |
_bigsaleLoaded = true;
|
| 635 |
refreshBigSalePanel();
|
|
|
|
| 717 |
var card = cards[i];
|
| 718 |
var code = card.getAttribute('data-code') || '';
|
| 719 |
if (!code) continue;
|
| 720 |
+
// Auto-merged catalog items are NOT admin-managed → no delete button.
|
| 721 |
+
var isAuto = false;
|
| 722 |
+
for (var k = 0; k < BIG_SALE_PRODUCTS.length; k++) {
|
| 723 |
+
if (norm(BIG_SALE_PRODUCTS[k].code) === norm(code)) { isAuto = !!BIG_SALE_PRODUCTS[k]._auto; break; }
|
| 724 |
+
}
|
| 725 |
+
if (isAuto) continue;
|
| 726 |
var actions = card.querySelector('.big-sale-card-actions');
|
| 727 |
if (!actions || actions.querySelector('.bs-del-btn')) continue;
|
| 728 |
var del = document.createElement('button');
|
index.html
CHANGED
|
@@ -758,18 +758,18 @@
|
|
| 758 |
first paint — the welcome/avatar-picker modal renders immediately. -->
|
| 759 |
<script defer src="./src/cart-quote.js?gc=33"></script> <!-- Cart + Quote module (Giỏ hàng / Báo giá) -->
|
| 760 |
<script defer src="./src/customers.js?gc=17"></script> <!-- Customer list (Danh sách khách hàng) -->
|
| 761 |
-
<script defer src="./src/vaix-rag.js?gc=
|
| 762 |
<script defer src="./src/greeting-news.js?gc=40"></script> <!-- Greeting HOT news cards + source links -->
|
| 763 |
<script defer src="./src/order-sync.js?gc=17"></script> <!-- Order sync with V.AISTUDIO backend -->
|
| 764 |
-
<script type="module" src="./src/app.js?gc=
|
| 765 |
<script defer src="./src/greeting-source-cards.js?gc=15"></script> <!-- FIX: Nguồn tin cards under greeting -->
|
| 766 |
<script defer src="./src/avatar-picker.js?gc=13"></script> <!-- ✨ Circular avatar picker (Mr V / Lisamy) + loading progress bar -->
|
| 767 |
<!-- BIG SALE Floating Promotion Module -->
|
| 768 |
-
<script defer src="./big_sale.js?v=
|
| 769 |
<!-- V.AI STUDIO Promo CMS (admin editor + BIGSALE/COMBO manager) -->
|
| 770 |
<script defer src="./src/vaix-promo-cms.js?v=3"></script>
|
| 771 |
<script defer src="./src/vaix-combo.js?v=5"></script>
|
| 772 |
-
<script defer src="./src/vaix-flashsale.js?v=
|
| 773 |
|
| 774 |
<script>
|
| 775 |
// Wire the "Đơn hàng" buttons to the order modal
|
|
|
|
| 758 |
first paint — the welcome/avatar-picker modal renders immediately. -->
|
| 759 |
<script defer src="./src/cart-quote.js?gc=33"></script> <!-- Cart + Quote module (Giỏ hàng / Báo giá) -->
|
| 760 |
<script defer src="./src/customers.js?gc=17"></script> <!-- Customer list (Danh sách khách hàng) -->
|
| 761 |
+
<script defer src="./src/vaix-rag.js?gc=58"></script> <!-- V.AI STUDIO RAG Module - loads FASTER via light index; lazy galleries; spec-aware search -->
|
| 762 |
<script defer src="./src/greeting-news.js?gc=40"></script> <!-- Greeting HOT news cards + source links -->
|
| 763 |
<script defer src="./src/order-sync.js?gc=17"></script> <!-- Order sync with V.AISTUDIO backend -->
|
| 764 |
+
<script type="module" src="./src/app.js?gc=60"></script>
|
| 765 |
<script defer src="./src/greeting-source-cards.js?gc=15"></script> <!-- FIX: Nguồn tin cards under greeting -->
|
| 766 |
<script defer src="./src/avatar-picker.js?gc=13"></script> <!-- ✨ Circular avatar picker (Mr V / Lisamy) + loading progress bar -->
|
| 767 |
<!-- BIG SALE Floating Promotion Module -->
|
| 768 |
+
<script defer src="./big_sale.js?v=15"></script>
|
| 769 |
<!-- V.AI STUDIO Promo CMS (admin editor + BIGSALE/COMBO manager) -->
|
| 770 |
<script defer src="./src/vaix-promo-cms.js?v=3"></script>
|
| 771 |
<script defer src="./src/vaix-combo.js?v=5"></script>
|
| 772 |
+
<script defer src="./src/vaix-flashsale.js?v=2"></script>
|
| 773 |
|
| 774 |
<script>
|
| 775 |
// Wire the "Đơn hàng" buttons to the order modal
|
src/app.js
CHANGED
|
@@ -147,9 +147,9 @@ function clampRect(){const vw=innerWidth,vh=innerHeight,r=textChat.getBoundingCl
|
|
| 147 |
function makeDraggable(){
|
| 148 |
let d=false,sx,sy,sl,st;
|
| 149 |
function gp(e){const p=e.changedTouches?e.changedTouches[0]:e;return{x:p.clientX,y:p.clientY}}
|
| 150 |
-
function os(e){if(e.target.closest("#chat-header-actions,#chat-avatar-select"))return;const p=gp(e);d=true;const r=textChat.getBoundingClientRect();sx=p.x;sy=p.y;sl=r.left;st=r.top;textChat.classList.add("dragging");e.preventDefault}
|
| 151 |
function om(e){if(!d)return;const p=gp(e);textChat.style.left=(sl+p.x-sx)+"px";textChat.style.top=(st+p.y-sy)+"px";textChat.style.right="auto";textChat.style.bottom="auto";e.preventDefault}
|
| 152 |
-
function oe(){if(!d)return;d=false;textChat.classList.remove("dragging");clampRect()}
|
| 153 |
chatHeader.addEventListener("mousedown",os);document.addEventListener("mousemove",om);document.addEventListener("mouseup",oe);chatHeader.addEventListener("touchstart",os,{passive:false});document.addEventListener("touchmove",om,{passive:false});document.addEventListener("touchend",oe);
|
| 154 |
}
|
| 155 |
function makeResizable(){
|
|
|
|
| 147 |
function makeDraggable(){
|
| 148 |
let d=false,sx,sy,sl,st;
|
| 149 |
function gp(e){const p=e.changedTouches?e.changedTouches[0]:e;return{x:p.clientX,y:p.clientY}}
|
| 150 |
+
function os(e){if(e.target.closest("#chat-header-actions,#chat-avatar-select"))return;const p=gp(e);d=true;const r=textChat.getBoundingClientRect();sx=p.x;sy=p.y;sl=r.left;st=r.top;textChat.classList.add("dragging");textChat.style.zIndex="110020";e.preventDefault}
|
| 151 |
function om(e){if(!d)return;const p=gp(e);textChat.style.left=(sl+p.x-sx)+"px";textChat.style.top=(st+p.y-sy)+"px";textChat.style.right="auto";textChat.style.bottom="auto";e.preventDefault}
|
| 152 |
+
function oe(){if(!d)return;d=false;textChat.classList.remove("dragging");textChat.style.zIndex="";clampRect()}
|
| 153 |
chatHeader.addEventListener("mousedown",os);document.addEventListener("mousemove",om);document.addEventListener("mouseup",oe);chatHeader.addEventListener("touchstart",os,{passive:false});document.addEventListener("touchmove",om,{passive:false});document.addEventListener("touchend",oe);
|
| 154 |
}
|
| 155 |
function makeResizable(){
|
src/vaix-flashsale.js
CHANGED
|
@@ -34,12 +34,15 @@
|
|
| 34 |
var saleN = Number(p.salePn) || (p.salePrice ? parseInt(String(p.salePrice).replace(/[^\d]/g, ""), 10) : 0) || Number(p.priceNum) || 0;
|
| 35 |
if (!listN || !saleN || listN <= saleN) continue;
|
| 36 |
if (p._deleted || p.deleted) continue;
|
|
|
|
|
|
|
|
|
|
| 37 |
out.push({
|
| 38 |
name: p.title_clean || p.name || p.title || "",
|
| 39 |
code: p.sku || p.model || p.code || "",
|
| 40 |
image: p.image || (p.images && p.images[0]) || "",
|
| 41 |
listPn: listN, salePn: saleN,
|
| 42 |
-
|
| 43 |
slug: p.slug || "", catalog: true
|
| 44 |
});
|
| 45 |
}
|
|
@@ -70,9 +73,12 @@
|
|
| 70 |
var listN = parseInt(String(b.price || "0").replace(/\./g, ""), 10) || 0;
|
| 71 |
var saleN = parseInt(String(b.bigsale || "0").replace(/\./g, ""), 10) || 0;
|
| 72 |
if (listN <= 0 || saleN <= 0 || listN <= saleN) continue;
|
|
|
|
|
|
|
|
|
|
| 73 |
push({
|
| 74 |
name: b.name, code: b.code, image: b.image || "", listPn: listN, salePn: saleN,
|
| 75 |
-
|
| 76 |
});
|
| 77 |
}
|
| 78 |
} catch (e) {}
|
|
|
|
| 34 |
var saleN = Number(p.salePn) || (p.salePrice ? parseInt(String(p.salePrice).replace(/[^\d]/g, ""), 10) : 0) || Number(p.priceNum) || 0;
|
| 35 |
if (!listN || !saleN || listN <= saleN) continue;
|
| 36 |
if (p._deleted || p.deleted) continue;
|
| 37 |
+
var disc = Math.round((listN - saleN) / listN * 100);
|
| 38 |
+
// BIGSALE takes discount >= 40%; FLASHSALE keeps only < 40%.
|
| 39 |
+
if (disc >= 40) continue;
|
| 40 |
out.push({
|
| 41 |
name: p.title_clean || p.name || p.title || "",
|
| 42 |
code: p.sku || p.model || p.code || "",
|
| 43 |
image: p.image || (p.images && p.images[0]) || "",
|
| 44 |
listPn: listN, salePn: saleN,
|
| 45 |
+
discountNum: disc, discount: disc + "%",
|
| 46 |
slug: p.slug || "", catalog: true
|
| 47 |
});
|
| 48 |
}
|
|
|
|
| 73 |
var listN = parseInt(String(b.price || "0").replace(/\./g, ""), 10) || 0;
|
| 74 |
var saleN = parseInt(String(b.bigsale || "0").replace(/\./g, ""), 10) || 0;
|
| 75 |
if (listN <= 0 || saleN <= 0 || listN <= saleN) continue;
|
| 76 |
+
var bdisc = Math.round((listN - saleN) / listN * 100);
|
| 77 |
+
// BIGSALE items are discount >= 40% → not FLASHSALE.
|
| 78 |
+
if (bdisc >= 40) continue;
|
| 79 |
push({
|
| 80 |
name: b.name, code: b.code, image: b.image || "", listPn: listN, salePn: saleN,
|
| 81 |
+
discountNum: bdisc, discount: bdisc + "%", _fromBigSale: true
|
| 82 |
});
|
| 83 |
}
|
| 84 |
} catch (e) {}
|
src/vaix-rag.js
CHANGED
|
@@ -1772,9 +1772,10 @@ function renderPanelResults(products) {
|
|
| 1772 |
const adminBtns = document.createElement("div");
|
| 1773 |
adminBtns.className = "product-card-admin";
|
| 1774 |
adminBtns.style.cssText = "display:flex;gap:6px;margin-top:8px;flex-wrap:wrap";
|
| 1775 |
-
// Delete button (admin only) — appears on
|
| 1776 |
-
//
|
| 1777 |
-
|
|
|
|
| 1778 |
const delBtn = document.createElement("button");
|
| 1779 |
delBtn.type = "button";
|
| 1780 |
delBtn.className = "product-card-btn del-btn";
|
|
|
|
| 1772 |
const adminBtns = document.createElement("div");
|
| 1773 |
adminBtns.className = "product-card-admin";
|
| 1774 |
adminBtns.style.cssText = "display:flex;gap:6px;margin-top:8px;flex-wrap:wrap";
|
| 1775 |
+
// Delete button (admin only) — appears on EVERY product card. For
|
| 1776 |
+
// admin-added products it removes them from the shared dataset; for
|
| 1777 |
+
// base catalog products it clears the promo-edit mirror (soft remove).
|
| 1778 |
+
{
|
| 1779 |
const delBtn = document.createElement("button");
|
| 1780 |
delBtn.type = "button";
|
| 1781 |
delBtn.className = "product-card-btn del-btn";
|