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
Files changed (5) hide show
  1. big_sale.js +41 -0
  2. index.html +4 -4
  3. src/app.js +2 -2
  4. src/vaix-flashsale.js +8 -2
  5. 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=57"></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=59"></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=14"></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=1"></script>
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
- discount: saleN ? Math.round((listN - saleN) / listN * 100) + "%" : "",
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
- discount: b.discount || "", _fromBigSale: true
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 admin-added products and
1776
- // permanently removes them from the shared dataset.
1777
- if (isAddedProduct) {
 
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";