File manager - Edit - /home/webapp69.cm.in.th/u69319090006/shop/cart-checkout.js
Back
// ========================================================================== // 8. SHOPPING CART ENGINE // ========================================================================== function addToCart(productId, color, size, qty = 1) { const user = (typeof db !== "undefined" && db.getCurrentUser) ? db.getCurrentUser() : (state ? state.currentUser : null); if (!user) { showToast("กรุณาเข้าสู่ระบบก่อนดำเนินการค่ะ", "danger"); document.querySelectorAll(".modal-overlay").forEach(m => m.style.display = "none"); if (state) state.postLoginRedirect = "cart"; if (typeof navigateTo === "function") navigateTo("auth"); return false; } const products = db.getProducts(); const product = products.find(p => p.id === productId); if (!product) return false; const sellerObj = db.getUsers().find(u => u.username === product.sellerId); if (sellerObj && sellerObj.isVacationMode) { showToast("ร้านค้านี้กำลังอยู่ในช่วงพักร้อนชั่วคราว ไม่สามารถสั่งซื้อสินค้าได้ในขณะนี้", "danger"); return false; } const variant = product.variants ? product.variants.find(v => v.color === color && v.size === size) : null; const availableStock = variant ? (variant.stock ?? 0) : (product.stock ?? 0); const price = (variant && variant.price !== undefined) ? variant.price : product.price; const existing = state.cart.find(item => item.productId === productId && item.color === color && item.size === size); const currentQtyInCart = existing ? existing.quantity : 0; const requestedQty = currentQtyInCart + qty; if (requestedQty > availableStock) { showToast(`ขออภัย สินค้าชิ้นนี้มีจำนวนในสต็อกคงเหลือเพียง ${availableStock} ชิ้น`, "warning"); return false; } if (existing) { existing.quantity = requestedQty; } else { state.cart.push({ productId: productId, name: product.name, price: price, image: product.image, color: color, size: size, quantity: qty, sellerId: product.sellerId || "admin", sellerName: product.sellerName || "SkyMall" }); } updateCartBadge(); showToast("เพิ่มสินค้าลงตะกร้าแล้ว!", "success"); return true; } window.addToCart = addToCart; function updateCartBadge() { const count = state.cart.reduce((sum, item) => sum + item.quantity, 0); const badge = document.getElementById("cartBadgeCount"); if (badge) badge.innerText = count; } function renderCart() { const container = document.getElementById("cartItemsPanel"); if (!container) return; container.innerHTML = ""; if (!state.cart || state.cart.length === 0) { container.innerHTML = ` <div class="admin-card text-center py-5"> <i class="fa-solid fa-cart-shopping" style="font-size:3.5rem; color:var(--color-sky-light)"></i> <h3 class="mt-3">ไม่มีสินค้าในตะกร้าของคุณ</h3> <p style="color:var(--color-sky-slate)">ลองกลับไปเลือกช้อปสินค้าดีลพิเศษกับเรา</p> <button class="btn btn-sky mt-3" onclick="navigateTo('home')">กลับไปช้อปปิ้ง</button> </div> `; updateCartTotals(0); return; } // Group items by sellerId const groups = {}; state.cart.forEach((item) => { const sId = item.sellerId || "admin"; if (!groups[sId]) { groups[sId] = { sellerId: sId, sellerName: item.sellerName || "SkyMall", items: [] }; } groups[sId].items.push(item); }); Object.values(groups).forEach(group => { const groupCard = document.createElement("div"); groupCard.className = "cart-seller-group mb-4"; groupCard.style.cssText = "border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-bg-card); overflow: hidden; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);"; const groupHeader = document.createElement("div"); groupHeader.className = "cart-seller-header mb-3"; groupHeader.style.cssText = "font-weight: 700; color: var(--color-sky-brand); font-size: 1rem; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--color-border); padding-bottom: 10px;"; groupHeader.innerHTML = `<i class="fa-solid fa-store" style="color: var(--color-sky-brand);"></i> <span>ร้านค้า: ${group.sellerName}</span>`; groupCard.appendChild(groupHeader); const itemsContainer = document.createElement("div"); itemsContainer.className = "cart-seller-items"; itemsContainer.style.cssText = "display: flex; flex-direction: column; gap: 12px;"; group.items.forEach(item => { const itemCard = document.createElement("div"); itemCard.className = "cart-item"; const placeholderImg = (typeof DEFAULT_IMAGE_PLACEHOLDERS !== 'undefined' && DEFAULT_IMAGE_PLACEHOLDERS[item.productId]) ? DEFAULT_IMAGE_PLACEHOLDERS[item.productId] : (typeof DEFAULT_IMAGE_PLACEHOLDERS !== 'undefined' ? DEFAULT_IMAGE_PLACEHOLDERS["default"] : ""); const imgSrc = item.image || placeholderImg; const hasOptionLabels = item.color !== "Default" || item.size !== "Default"; const metaText = hasOptionLabels ? `ตัวเลือก: ${item.color} / ${item.size}` : "รูปแบบปกติ"; itemCard.innerHTML = ` <img src="${imgSrc}" class="cart-item-img" style="width:60px; height:60px; object-fit:cover; border-radius:8px;" id="cart-img" alt="${item.name}"> <div class="cart-item-details"> <div class="cart-item-name" style="font-weight:600;">${item.name}</div> <div class="cart-item-meta" style="font-size:0.85rem; color:var(--color-sky-slate);">${metaText}</div> </div> <div class="cart-item-quantity"> <button class="quantity-btn dec-btn">-</button> <span class="quantity-val">${item.quantity}</span> <button class="quantity-btn inc-btn">+</button> </div> <div class="cart-item-price" style="font-weight:700; color:var(--color-sky-brand);">฿${(item.price * item.quantity).toLocaleString()}</div> <button class="cart-item-remove" title="ลบสินค้า"><i class="fa-solid fa-trash-can"></i></button> `; itemCard.querySelector(".dec-btn").addEventListener("click", () => { if (item.quantity > 1) { item.quantity -= 1; renderCart(); } }); itemCard.querySelector(".inc-btn").addEventListener("click", () => { const products = db.getProducts(); const product = products.find(p => p.id === item.productId); if (!product) return; const variant = product.variants ? product.variants.find(v => v.color === item.color && v.size === item.size) : null; const availableStock = variant ? (variant.stock ?? 0) : (product.stock ?? 0); if (item.quantity + 1 > availableStock) { showToast(`ขออภัย สินค้าชิ้นนี้มีจำนวนในสต็อกคงเหลือเพียง ${availableStock} ชิ้น`, "warning"); return; } item.quantity += 1; renderCart(); }); itemCard.querySelector(".cart-item-remove").addEventListener("click", () => { const idx = state.cart.indexOf(item); if (idx !== -1) { state.cart.splice(idx, 1); updateCartBadge(); renderCart(); } }); itemsContainer.appendChild(itemCard); }); groupCard.appendChild(itemsContainer); container.appendChild(groupCard); }); const actionsBar = document.createElement("div"); actionsBar.className = "cart-actions-bar mt-3"; actionsBar.innerHTML = ` <button class="btn btn-sky-outline" id="continueShoppingBtn"><i class="fa-solid fa-arrow-left"></i> ← กลับไปเลือกสินค้าเพิ่ม</button> <button class="btn btn-sky btn-lg" id="checkoutBtn"><i class="fa-solid fa-credit-card"></i> ไปที่หน้าชำระเงิน →</button> `; actionsBar.querySelector("#continueShoppingBtn").addEventListener("click", () => { navigateTo("home"); }); actionsBar.querySelector("#checkoutBtn").addEventListener("click", () => { navigateTo("checkout"); }); container.appendChild(actionsBar); const subtotal = state.cart.reduce((sum, item) => sum + (Number(item.price) * Number(item.quantity)), 0); updateCartTotals(subtotal); } window.renderCart = renderCart; function updateCartTotals(subtotal) { const subtotalLabel = document.getElementById("cartSubtotal"); const discountLabel = document.getElementById("cartDiscount"); const discountRow = document.getElementById("summaryDiscountRow"); const totalLabel = document.getElementById("cartTotal"); if (!subtotalLabel || !totalLabel) return; // Re-verify coupon minimum spend if (state.appliedCoupon && state.appliedCoupon.minSpend && subtotal < state.appliedCoupon.minSpend) { showToast(`ยอดรวมสินค้าต่ำกว่าขั้นต่ำ ฿${(state.appliedCoupon.minSpend || 0).toLocaleString()} ของคูปอง ${state.appliedCoupon.code} ระบบได้ทำการยกเลิกคูปองนี้อัตโนมัติแล้ว`, "warning"); state.appliedCoupon = null; } subtotalLabel.innerText = `฿${subtotal.toLocaleString()}`; let discount = 0; if (state.appliedCoupon && subtotal > 0) { if (discountRow) discountRow.style.display = "flex"; const codeSpan = document.getElementById("activeCouponCode"); if (codeSpan) codeSpan.innerText = state.appliedCoupon.code; if (state.appliedCoupon.type === "percent") { discount = Math.round(subtotal * (state.appliedCoupon.value / 100)); } else { discount = state.appliedCoupon.value; } if (discount > subtotal) discount = subtotal; if (discountLabel) discountLabel.innerText = `-฿${discount.toLocaleString()}`; } else { if (discountRow) discountRow.style.display = "none"; } const total = subtotal - discount; totalLabel.innerText = `฿${total.toLocaleString()}`; } window.updateCartTotals = updateCartTotals; // HELPERS FOR SAVING COLLECTED COUPONS function getCollectedCoupons() { const user = (typeof db !== "undefined" && db.getCurrentUser) ? db.getCurrentUser() : (state ? state.currentUser : null); const username = user ? (user.username || user.id || "guest") : "guest"; const key = `sk_collected_coupons_${username}`; try { const stored = localStorage.getItem(key); const parsed = stored ? JSON.parse(stored) : []; return Array.isArray(parsed) ? parsed : []; } catch (e) { return []; } } window.getCollectedCoupons = getCollectedCoupons; function saveCollectedCoupon(code) { if (!code) return; const user = (typeof db !== "undefined" && db.getCurrentUser) ? db.getCurrentUser() : (state ? state.currentUser : null); if (!user) { showToast("กรุณาเข้าสู่ระบบก่อนดำเนินการค่ะ", "danger"); document.querySelectorAll(".modal-overlay").forEach(m => m.style.display = "none"); if (state) state.postLoginRedirect = "home"; if (typeof navigateTo === "function") navigateTo("auth"); return; } const username = user.username || user.id || "guest"; const key = `sk_collected_coupons_${username}`; let collected = getCollectedCoupons(); if (!collected.includes(code)) { collected.push(code); localStorage.setItem(key, JSON.stringify(collected)); } } window.saveCollectedCoupon = saveCollectedCoupon; // RENDER COUPON SHOWCASE CARDS (HOME PAGE) function renderCouponShowcase() { const container = document.getElementById("couponCardsContainer"); if (!container) return; container.innerHTML = ""; let coupons = db.getCoupons(); if (!coupons || coupons.length === 0) { coupons = typeof DEFAULT_COUPONS !== 'undefined' ? DEFAULT_COUPONS : []; db.saveCoupons(coupons); } const collected = getCollectedCoupons(); // 1. จัดการปุ่ม "เก็บคูปองทั้งหมด" (#collectAllCouponsBtn) แยกต่างหาก const collectAllBtn = document.getElementById("collectAllCouponsBtn"); if (collectAllBtn) { const newCollectAllBtn = collectAllBtn.cloneNode(true); collectAllBtn.parentNode.replaceChild(newCollectAllBtn, collectAllBtn); newCollectAllBtn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const user = (typeof db !== "undefined" && db.getCurrentUser) ? db.getCurrentUser() : (state ? state.currentUser : null); if (!user) { showToast("กรุณาเข้าสู่ระบบก่อนทำการเก็บคูปองส่วนลดค่ะ", "danger"); document.querySelectorAll(".modal-overlay").forEach(m => m.style.display = "none"); if (state) state.postLoginRedirect = "home"; if (typeof navigateTo === "function") navigateTo("auth"); return; } const currentCollected = getCollectedCoupons(); const uncollected = coupons.filter(c => !currentCollected.includes(c.code)); if (uncollected.length === 0) { showToast("คุณเก็บคูปองส่วนลดทั้งหมดเรียบร้อยแล้วค่ะ", "info"); return; } uncollected.forEach(c => saveCollectedCoupon(c.code)); showToast(`เก็บคูปองส่วนลดเพิ่มอีก ${uncollected.length} ใบเรียบร้อยแล้ว!`, "success"); renderCouponShowcase(); if (typeof updateHeroWidgets === "function") updateHeroWidgets(); }); } // 2. วาดการ์ดคูปองทีละใบ และผูก Event แยกเฉพาะใบนั้นๆ const offText = 'ลด'; const minSpendText = 'ขั้นต่ำ'; coupons.forEach(coupon => { const discountText = coupon.type === "percent" ? `${coupon.value}%` : `฿${coupon.value}`; const isCollected = collected.includes(coupon.code); const card = document.createElement("div"); card.className = "coupon-card"; const buttonText = isCollected ? 'เก็บแล้ว' : 'เก็บคูปอง'; const buttonClass = isCollected ? "btn btn-sky-outline btn-xs" : "btn btn-sky btn-xs"; const btnStyle = isCollected ? "opacity: 0.6; cursor: default;" : ""; const icon = isCollected ? "fa-check" : "fa-ticket"; card.innerHTML = ` <div class="coupon-card-left"> <i class="fa-solid fa-ticket"></i> </div> <div class="coupon-card-right"> <div class="coupon-discount">${offText} ${discountText}</div> <div class="coupon-min-spend">${minSpendText} ฿${(coupon.minSpend || 0).toLocaleString()}</div> <div class="coupon-code-wrapper"> <span class="coupon-code">${coupon.code}</span> <button type="button" class="btn-copy-coupon ${buttonClass}" data-code="${coupon.code}" ${isCollected ? 'disabled' : ''} style="${btnStyle}"> <i class="fa-solid ${icon}"></i> ${buttonText} </button> </div> </div> `; const copyBtn = card.querySelector(".btn-copy-coupon"); if (copyBtn && !isCollected) { copyBtn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const user = (typeof db !== "undefined" && db.getCurrentUser) ? db.getCurrentUser() : (state ? state.currentUser : null); if (!user) { showToast("กรุณาเข้าสู่ระบบก่อนทำการเก็บคูปองส่วนลดค่ะ", "danger"); document.querySelectorAll(".modal-overlay").forEach(m => m.style.display = "none"); if (state) state.postLoginRedirect = "home"; if (typeof navigateTo === "function") navigateTo("auth"); return; } // บันทึกเฉพาะรหัสคูปองใบนี้เท่านั้น saveCollectedCoupon(coupon.code); showToast(`เก็บคูปองส่วนลด ${coupon.code} สำเร็จ!`, "success"); // สั่ง Re-render เพื่ออัปเดตปุ่มเป็น "เก็บแล้ว" เฉพาะใบที่กด และซิงก์สไลด์แบนเนอร์ renderCouponShowcase(); if (typeof updateHeroWidgets === "function") updateHeroWidgets(); }); } container.appendChild(card); }); } window.renderCouponShowcase = renderCouponShowcase; // OPEN SHIPPING TRACKING MODAL function openOrderTrackingModal(order) { const modal = document.getElementById("orderTrackingModal"); if (!modal) return; // Ensure close button works reliably by binding directly const closeBtn = document.getElementById("closeOrderTrackingModalBtn"); if (closeBtn) { closeBtn.onclick = () => { modal.style.display = "none"; }; } modal.onclick = (e) => { if (e.target === modal) { modal.style.display = "none"; } }; const steps = [ document.getElementById("trackStep1"), document.getElementById("trackStep2"), document.getElementById("trackStep3"), document.getElementById("trackStep4") ]; const lines = [ document.getElementById("trackLine1"), document.getElementById("trackLine2"), document.getElementById("trackLine3") ]; // Reset styles steps.forEach(step => { if (step) step.classList.remove("active"); }); lines.forEach(line => { if (line) line.classList.remove("active"); }); const infoBox = document.getElementById("trackingInfoBox"); if (!infoBox) return; infoBox.innerHTML = ""; const status = (order.status || "").toLowerCase(); let activeStepsCount = 1; let mockTrackingHtml = ""; if (status === "paid" || status === "preparing") { activeStepsCount = 2; mockTrackingHtml = ` <h4><i class="fa-solid fa-box"></i> สถานะ: กำลังจัดเตรียมพัสดุ</h4> <p>ร้านค้าได้รับยอดชำระและกำลังอยู่ระหว่างจัดเตรียมสินค้าลงกล่องพัสดุ</p> <p style="font-size:12px; color:var(--color-sky-slate);">เตรียมจัดส่งภายใน 24-48 ชั่วโมง</p> `; } else if (status === "shipped") { activeStepsCount = 3; let trNo = ""; if (order.trackingNumbers) { const keys = Object.keys(order.trackingNumbers); if (keys.length > 0) { trNo = order.trackingNumbers[keys[0]]; } } if (!trNo) { trNo = `TH2026${Math.floor(100000 + Math.random() * 900000)}`; } mockTrackingHtml = ` <h4><i class="fa-solid fa-truck"></i> สถานะ: อยู่ระหว่างการจัดส่ง</h4> <p><strong>ผู้ให้บริการจัดส่ง:</strong> Flash Express</p> <p><strong>เลขพัสดุสินค้า:</strong> <span style="font-weight:600; color:var(--color-sky-brand);">${trNo}</span></p> <p style="font-size:12px; color:var(--color-sky-slate);">พัสดุออกจากคลังสินค้าหลักและอยู่ระหว่างขนส่งไปยังผู้รับ</p> `; } else if (status === "success" || status === "complete") { activeStepsCount = 4; let trNo = ""; if (order.trackingNumbers) { const keys = Object.keys(order.trackingNumbers); if (keys.length > 0) { trNo = order.trackingNumbers[keys[0]]; } } if (!trNo) { trNo = `TH2026889412`; } mockTrackingHtml = ` <h4><i class="fa-solid fa-circle-check" style="color:#22c55e;"></i> สถานะ: จัดส่งสำเร็จ</h4> <p><strong>ผู้ให้บริการจัดส่ง:</strong> Flash Express</p> <p><strong>เลขพัสดุสินค้า:</strong> <span style="font-weight:600; color:var(--color-sky-brand);">${trNo}</span></p> <p>พัสดุส่งถึงผู้รับเรียบร้อยแล้ว หากพบปัญหาใดๆ กรุณาติดต่อฝ่ายบริการลูกค้า</p> `; } else if (status === "failed") { activeStepsCount = 0; mockTrackingHtml = ` <h4 style="color:var(--color-danger);"><i class="fa-solid fa-circle-xmark"></i> สถานะ: คำสั่งซื้อล้มเหลว / ถูกยกเลิก</h4> <p>ออเดอร์นี้ถูกระงับหรือยกเลิกเรียบร้อยแล้ว</p> `; } else { activeStepsCount = 1; mockTrackingHtml = ` <h4><i class="fa-solid fa-clock"></i> สถานะ: คำสั่งซื้อเสร็จสมบูรณ์ (รอชำระเงิน)</h4> <p>ระบบสร้างใบสั่งซื้อแล้ว กรุณาแจ้งโอนเงินหรือรอระบบแอดมินยืนยันความถูกต้องสลิป</p> `; } // Set active highlights for (let i = 0; i < activeStepsCount; i++) { if (steps[i]) steps[i].classList.add("active"); if (i > 0 && lines[i - 1]) lines[i - 1].classList.add("active"); } infoBox.innerHTML = mockTrackingHtml; // Fill Shipping Info const shippingInfoDiv = document.getElementById("orderModalShippingInfo"); if (shippingInfoDiv) { shippingInfoDiv.innerHTML = ` <strong>ชื่อผู้รับ:</strong> ${order.shippingInfo.name}<br> <strong>เบอร์โทรศัพท์:</strong> ${order.shippingInfo.phone}<br> <strong>ที่อยู่จัดส่ง:</strong> ${order.shippingInfo.address} `; } // Fill Payment Status Badge let statusLabel = ""; let statusClass = "badge-dark"; if (order.status === "Unpaid") { statusLabel = "รอชำระเงิน"; statusClass = "badge-dark"; } else if (order.status === "Pending") { statusLabel = "รอตรวจสอบยอดโอน"; statusClass = "badge-warning"; } else if (order.status === "Paid" || order.status === "Preparing") { statusLabel = "ชำระเงินแล้ว"; statusClass = "badge-success"; } else if (order.status === "Shipped") { statusLabel = "จัดส่งพัสดุแล้ว"; statusClass = "badge-success"; } else if (order.status === "Success") { statusLabel = "จัดส่งสำเร็จ"; statusClass = "badge-success"; } else if (order.status === "Failed") { statusLabel = "ยกเลิกคำสั่งซื้อ"; statusClass = "badge-danger"; } const paymentInfoDiv = document.getElementById("orderModalPaymentInfo"); if (paymentInfoDiv) { paymentInfoDiv.innerHTML = ` <strong>วิธีการชำระเงิน:</strong> ${order.paymentMethod || 'PromptPay'}<br> <strong>สถานะคำสั่งซื้อ:</strong> <span class="badge ${statusClass}" style="font-size: 11px;">${statusLabel}</span> `; } // Fill Ordered Items List const itemsDiv = document.getElementById("orderModalItems"); if (itemsDiv) { itemsDiv.innerHTML = order.items.map(item => { const hasOpts = item.color !== "Default" || item.size !== "Default"; const optText = hasOpts ? `${item.color} / ${item.size}` : "ไม่มีตัวเลือก"; const prodPlaceholder = DEFAULT_IMAGE_PLACEHOLDERS[item.productId] || DEFAULT_IMAGE_PLACEHOLDERS["default"]; const prodImg = item.image || prodPlaceholder; return ` <div class="order-modal-item-row"> <div class="order-modal-item-left"> <img src="${prodImg}" class="order-modal-item-img" alt="${item.name}"> <div class="order-modal-item-info"> <span class="order-modal-item-name" style="font-weight:600;">${item.name}</span> <span class="order-modal-item-variant">${optText}</span> </div> </div> <div class="order-modal-item-right"> <span class="order-modal-item-price" style="font-weight:600; color:var(--color-sky-brand);">฿${item.price.toLocaleString()}</span> <span class="order-modal-item-qty"><br>x${item.quantity}</span> </div> </div> `; }).join(""); } // Fill Totals & Coupons Summary const subtotal = order.subtotal || order.items.reduce((s, i) => s + (i.price * i.quantity), 0); const discount = order.discount || 0; const total = order.total || subtotal; const totalsDiv = document.getElementById("orderModalTotals"); if (totalsDiv) { totalsDiv.innerHTML = ` <div class="order-modal-total-line"> <span>ยอดรวมสินค้า:</span> <span>฿${subtotal.toLocaleString()}</span> </div> ${discount > 0 ? ` <div class="order-modal-total-line" style="color: var(--color-danger);"> <span>ส่วนลดคูปอง (${order.couponCode || 'คูปอง'}):</span> <span>-฿${discount.toLocaleString()}</span> </div>` : ''} <div class="order-modal-total-line grand-total"> <span>ยอดชำระสุทธิ:</span> <span>฿${total.toLocaleString()}</span> </div> `; } modal.style.display = "flex"; } function initCouponHandler() { const applyBtn = document.getElementById("applyCouponBtn"); const couponInput = document.getElementById("couponInput"); const statusMsg = document.getElementById("couponStatusMessage"); if (applyBtn) { applyBtn.addEventListener("click", () => { const code = couponInput.value.trim().toUpperCase(); if (!code) return; state.coupons = db.getCoupons(); const found = state.coupons.find(c => c.code === code); if (found) { const subtotal = state.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const minSpend = found.minSpend || 0; if (subtotal < minSpend) { state.appliedCoupon = null; if (statusMsg) { statusMsg.className = "coupon-status text-danger"; statusMsg.innerText = `ยอดสั่งซื้อขั้นต่ำต้องถึง ฿${minSpend.toLocaleString()} สำหรับคูปองนี้`; } } else { state.appliedCoupon = found; if (statusMsg) { statusMsg.className = "coupon-status text-success"; statusMsg.innerText = `ใช้งานคูปองส่วนลด ${code} สำเร็จ!`; } } updateCartTotals(subtotal); if (typeof renderCheckout === "function") renderCheckout(); } else { state.appliedCoupon = null; if (statusMsg) { statusMsg.className = "coupon-status text-danger"; statusMsg.innerText = "ไม่พบคูปองรหัสนี้ในระบบ"; } const subtotal = state.cart.reduce((sum, item) => sum + (Number(item.price) * Number(item.quantity)), 0); updateCartTotals(subtotal); if (typeof renderCheckout === "function") renderCheckout(); } }); } const checkoutBtn = document.getElementById("checkoutBtn"); if (checkoutBtn) { checkoutBtn.addEventListener("click", () => { if (state.cart.length === 0) return; navigateTo("checkout"); }); } const continueShoppingBtn = document.getElementById("continueShoppingBtn"); if (continueShoppingBtn) { continueShoppingBtn.addEventListener("click", () => { navigateTo("home"); }); } } // ========================================================================== // 9. CHECKOUT PAGE (Payment choice Credit Card vs PromptPay) // ========================================================================== function renderCheckout() { const checkoutMiniList = document.getElementById("checkoutMiniList"); if (!checkoutMiniList) return; checkoutMiniList.innerHTML = ""; let subtotal = state.cart.reduce((sum, item) => sum + (Number(item.price) * Number(item.quantity)), 0); // Re-verify coupon minimum spend if (state.appliedCoupon && state.appliedCoupon.minSpend && subtotal < state.appliedCoupon.minSpend) { showToast(`ยอดรวมสินค้าต่ำกว่าขั้นต่ำ ฿${(state.appliedCoupon.minSpend || 0).toLocaleString()} ของคูปอง ${state.appliedCoupon.code} ระบบได้ทำการยกเลิกคูปองนี้อัตโนมัติแล้ว`, "warning"); state.appliedCoupon = null; } let discount = 0; if (state.appliedCoupon) { if (state.appliedCoupon.type === "percent") { discount = Math.round(subtotal * (state.appliedCoupon.value / 100)); } else { discount = state.appliedCoupon.value; } if (discount > subtotal) discount = subtotal; } const total = subtotal - discount; const checkoutSub = document.getElementById("checkoutSubtotal"); if (checkoutSub) checkoutSub.innerText = `฿${subtotal.toLocaleString()}`; const discRow = document.getElementById("checkoutDiscountRow"); if (discount > 0) { if (discRow) discRow.style.display = "flex"; const checkDisc = document.getElementById("checkoutDiscount"); if (checkDisc) checkDisc.innerText = `-฿${discount.toLocaleString()}`; const activeCodeSpan = document.getElementById("activeCheckoutCouponCode"); if (activeCodeSpan && state.appliedCoupon) activeCodeSpan.innerText = state.appliedCoupon.code; } else { if (discRow) discRow.style.display = "none"; } const checkoutTot = document.getElementById("checkoutTotal"); if (checkoutTot) checkoutTot.innerText = `฿${total.toLocaleString()}`; const qrAmt = document.getElementById("qrTargetAmount"); if (qrAmt) qrAmt.innerText = `ยอดชำระ: ฿${total.toLocaleString()}`; state.cart.forEach(item => { const el = document.createElement("div"); el.className = "checkout-mini-item"; const optionDetails = (item.color !== "Default" || item.size !== "Default") ? ` (${item.color}/${item.size})` : ""; el.innerHTML = ` <span>${item.name}${optionDetails} x ${item.quantity}<br><small style="color: #0d9488;"><i class="fa-solid fa-store"></i> ร้านค้า: ${item.sellerName || 'SkyMall'}</small></span> <span>฿${(item.price * item.quantity).toLocaleString()}</span> `; checkoutMiniList.appendChild(el); }); // Render saved address selection cards const addressContainer = document.getElementById("checkoutAddressContainer"); if (addressContainer) { addressContainer.innerHTML = ""; const user = state.currentUser; if (!user || !user.addresses || user.addresses.length === 0) { addressContainer.innerHTML = ` <div class="text-center py-4" style="background:var(--color-bg-gray); border-radius:8px; border:1px dashed var(--color-border);"> <p style="color:var(--color-sky-slate); margin-bottom:10px;">คุณยังไม่มีข้อมูลที่อยู่จัดส่งที่บันทึกไว้</p> <button type="button" class="btn btn-sky btn-sm" onclick="openAddressModal(null)"><i class="fa-solid fa-plus"></i> เพิ่มที่อยู่จัดส่งสินค้าใหม่</button> </div> `; } else { user.addresses.forEach((addr, idx) => { const isSelected = addr.isDefault || idx === 0; const label = document.createElement("label"); label.className = `checkout-address-label ${isSelected ? 'selected' : ''}`; label.innerHTML = ` <input type="radio" name="selectedCheckoutAddress" value="${addr.id}" ${isSelected ? 'checked' : ''} style="margin-top:4px;"> <div class="checkout-address-info"> <div style="display:flex; justify-content:space-between; align-items:center;"> <strong style="font-size:0.95rem; color:var(--color-sky-dark);">${addr.name}</strong> ${addr.isDefault ? '<span class="badge badge-sky" style="font-size:10px;"><i class="fa-solid fa-star"></i> ที่อยู่หลัก</span>' : ''} </div> <div style="font-size:0.85rem; color:var(--color-sky-slate); margin-top:2px;"><i class="fa-solid fa-phone"></i> ${addr.phone}</div> <div style="font-size:0.88rem; color:var(--color-sky-dark); margin-top:4px;">${typeof formatFullAddress === 'function' ? formatFullAddress(addr) : (addr.address + ' จ.' + addr.province + ' ' + addr.zipcode)}</div> </div> `; label.querySelector("input").addEventListener("change", () => { document.querySelectorAll(".checkout-address-label").forEach(l => l.classList.remove("selected")); label.classList.add("selected"); }); addressContainer.appendChild(label); }); } } } function handlePlaceOrder(e) { e.preventDefault(); if (state.cart.length === 0) return; const payOption = document.querySelector("input[name='paymentOption']:checked").value; if (payOption === "promptpay" && !state.selectedSlipBase64) { showToast("กรุณาแนบภาพหลักฐานการโอนเงิน (สลิป)", "danger"); return; } if (payOption === "credit_card") { const card = document.getElementById("cardNum").value.trim(); const exp = document.getElementById("cardExpiry").value.trim(); const cvv = document.getElementById("cardCvv").value.trim(); if (!card || !exp || !cvv) { showToast("กรุณากรอกข้อมูลบัตรเครดิตให้ครบถ้วน", "danger"); return; } } const selectedRadio = document.querySelector("input[name='selectedCheckoutAddress']:checked"); const user = state.currentUser; if (user) { const freshUser = db.getUsers().find(u => u.username === user.username); if ((freshUser && freshUser.isBanned) || user.isBanned) { showToast("บัญชีของคุณถูกระงับการใช้งานชั่วคราว กรุณาติดต่อฝ่ายบริการลูกค้า", "danger"); return; } } if (!selectedRadio || !user || !user.addresses) { showToast("กรุณาเลือกหรือเพิ่มที่อยู่สำหรับจัดส่งสินค้าก่อนค่ะ", "danger"); return; } const selectedAddrObj = user.addresses.find(a => a.id === selectedRadio.value); if (!selectedAddrObj) { showToast("ไม่พบข้อมูลที่อยู่ที่เลือก กรุณาเลือกที่อยู่อีกครั้ง", "danger"); return; } const name = selectedAddrObj.name; const phone = selectedAddrObj.phone; const fullAddress = typeof formatFullAddress === "function" ? formatFullAddress(selectedAddrObj) : `${selectedAddrObj.address} จ.${selectedAddrObj.province} ${selectedAddrObj.zipcode}`; const subtotal = state.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); // Recheck coupon minimum spend right before order creation if (state.appliedCoupon && state.appliedCoupon.minSpend && subtotal < state.appliedCoupon.minSpend) { showToast(`ยอดรวมสินค้าของท่านต่ำกว่าขั้นต่ำที่คูปองกำหนด (${state.appliedCoupon.minSpend} บาท) ระบบจึงทำการยกเลิกคูปองนี้อัตโนมัติ`, "danger"); state.appliedCoupon = null; } let discount = 0; if (state.appliedCoupon) { if (state.appliedCoupon.type === "percent") { discount = Math.round(subtotal * (state.appliedCoupon.value / 100)); } else { discount = state.appliedCoupon.value; } if (discount > subtotal) discount = subtotal; } const total = subtotal - discount; let orderStatus = "Pending"; if (payOption === "pay_later") { orderStatus = "Unpaid"; } else if (payOption === "credit_card" || payOption === "cod") { orderStatus = "Preparing"; // Instant approval for Credit Card and COD } // Real-time stock validation check const products = db.getProducts(); let stockOk = true; let outOfStockMsg = ""; for (const item of state.cart) { const product = products.find(p => p.id === item.productId); if (!product) { stockOk = false; outOfStockMsg = `ไม่พบสินค้า ${item.name} ในระบบ`; break; } const variant = product.variants.find(v => v.color === item.color && v.size === item.size); if (!variant) { stockOk = false; outOfStockMsg = `ไม่พบตัวเลือกของสินค้า ${item.name} ในระบบ`; break; } if (variant.stock < item.quantity) { stockOk = false; outOfStockMsg = `ขออภัย สินค้า ${item.name} (${item.color}/${item.size}) คงเหลือในสต็อกไม่เพียงพอ (เหลือ ${variant.stock} ชิ้น คุณสั่งซื้อ ${item.quantity} ชิ้น)`; break; } } if (!stockOk) { showToast(outOfStockMsg, "danger"); return; } const newOrderId = "ORD-" + Date.now(); const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; let methodLabel = "PromptPay"; if (payOption === "credit_card") methodLabel = "Credit Card"; if (payOption === "pay_later") methodLabel = "โอนเงินภายหลัง (รอแนบสลิป)"; if (payOption === "cod") methodLabel = "เก็บเงินปลายทาง (COD)"; const newOrder = { id: newOrderId, date: dateStr, customerName: state.currentUser ? state.currentUser.name : (name || "ลูกค้าทั่วไป"), customerUsername: state.currentUser ? state.currentUser.username : "customer1", shippingInfo: { name: name, phone: phone, address: fullAddress }, items: JSON.parse(JSON.stringify(state.cart)), subtotal: subtotal, discount: discount, total: total, couponCode: state.appliedCoupon ? state.appliedCoupon.code : "", slipImage: payOption === "promptpay" ? state.selectedSlipBase64 : "", status: orderStatus, paymentMethod: methodLabel, trackingNumbers: {} }; // Deduct stock from variant and update total stock state.cart.forEach(item => { const product = products.find(p => p.id === item.productId); if (product) { const variant = product.variants.find(v => v.color === item.color && v.size === item.size); if (variant) { variant.stock = Math.max(0, variant.stock - item.quantity); } } }); // Save State db.saveProducts(products); const orders = db.getOrders(); orders.unshift(newOrder); db.saveOrders(orders); state.orders = orders; // Update global state // Record last order total details for the Success page display state.lastOrderResult = newOrder; // Reset Cart state.cart = []; state.appliedCoupon = null; state.selectedSlipBase64 = ""; const couponMsgBox = document.getElementById("couponStatusMessage"); if (couponMsgBox) { couponMsgBox.innerHTML = ""; couponMsgBox.className = "coupon-status"; } updateCartBadge(); // Reset forms const checkoutForm = document.getElementById("checkoutForm"); checkoutForm.reset(); const remBtn = document.getElementById("removeSlipBtn"); if (remBtn) remBtn.click(); // Restore payment option default document.querySelector("input[name='paymentOption'][value='promptpay']").checked = true; document.getElementById("checkoutPromptPayArea").style.display = "block"; document.getElementById("checkoutCreditCardArea").style.display = "none"; document.getElementById("slipFileInput").required = true; navigateTo("success"); } function initCheckoutFlow() { const slipFileInput = document.getElementById("slipFileInput"); const previewContainer = document.getElementById("slipPreviewContainer"); const previewImg = document.getElementById("slipPreviewImg"); const removeSlipBtn = document.getElementById("removeSlipBtn"); const checkoutPromptPayArea = document.getElementById("checkoutPromptPayArea"); const checkoutCreditCardArea = document.getElementById("checkoutCreditCardArea"); // Toggle Payment Forms document.querySelectorAll("input[name='paymentOption']").forEach(radio => { radio.addEventListener("change", (e) => { const val = e.target.value; if (val === "promptpay") { if (checkoutPromptPayArea) checkoutPromptPayArea.style.display = "block"; if (checkoutCreditCardArea) checkoutCreditCardArea.style.display = "none"; if (slipFileInput) slipFileInput.required = true; } else if (val === "credit_card") { if (checkoutPromptPayArea) checkoutPromptPayArea.style.display = "none"; if (checkoutCreditCardArea) checkoutCreditCardArea.style.display = "block"; if (slipFileInput) slipFileInput.required = false; } else { // Pay Later if (checkoutPromptPayArea) checkoutPromptPayArea.style.display = "none"; if (checkoutCreditCardArea) checkoutCreditCardArea.style.display = "none"; if (slipFileInput) slipFileInput.required = false; } }); }); if (slipFileInput) { slipFileInput.addEventListener("change", (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { const canvas = document.createElement("canvas"); const MAX_WIDTH = 600; const MAX_HEIGHT = 600; let width = img.width; let height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); const compressedBase64 = canvas.toDataURL("image/jpeg", 0.6); state.selectedSlipBase64 = compressedBase64; previewImg.src = compressedBase64; previewContainer.style.display = "block"; document.getElementById("slipUploadLabel").style.display = "none"; }; img.src = event.target.result; }; reader.readAsDataURL(file); } }); } if (removeSlipBtn) { removeSlipBtn.addEventListener("click", () => { state.selectedSlipBase64 = ""; if (previewImg) previewImg.src = ""; if (previewContainer) previewContainer.style.display = "none"; const slipLabel = document.getElementById("slipUploadLabel"); if (slipLabel) slipLabel.style.display = "flex"; if (slipFileInput) slipFileInput.value = ""; }); } const checkoutForm = document.getElementById("checkoutForm"); if (checkoutForm) { checkoutForm.addEventListener("submit", handlePlaceOrder); } } // ========================================================================== // 10. ORDER SUCCESS & CLIPBOARD TRACKING COPY HOOKS // ========================================================================== function renderOrderSuccess() { if (!state.lastOrderResult) { navigateTo("home"); return; } const o = state.lastOrderResult; const successId = document.getElementById("successOrderId"); if (successId) successId.innerText = o.id; const successTotal = document.getElementById("successOrderTotal"); if (successTotal) successTotal.innerText = `฿${o.total.toLocaleString()}`; const successMethod = document.getElementById("successPaymentMethod"); if (successMethod) successMethod.innerText = o.paymentMethod; } // ========================================================================== // ORDER CANCELLATION SYSTEM CORE ENGINE & CLIENT ORDER HISTORY // ========================================================================== function restockOrderItems(order) { if (!order || !order.items || !Array.isArray(order.items)) return; const products = db.getProducts(); let updated = false; order.items.forEach(item => { const product = products.find(p => p.id === item.productId); if (product && product.variants && Array.isArray(product.variants)) { let variant = product.variants.find(v => v.color === item.color && v.size === item.size); if (!variant && product.variants.length > 0) { variant = product.variants[0]; } if (variant) { variant.stock = (Number(variant.stock) || 0) + (Number(item.quantity) || 1); updated = true; } } }); if (updated) { db.saveProducts(products); } } function restoreOrderCoupon(order) { if (!order || !order.couponCode) return; const users = db.getUsers(); const currentUser = state.currentUser; if (currentUser) { const userObj = users.find(u => u.username === currentUser.username); if (userObj && Array.isArray(userObj.usedCoupons)) { userObj.usedCoupons = userObj.usedCoupons.filter(c => c !== order.couponCode); db.saveUsers(users); if (state.currentUser && state.currentUser.username === userObj.username) { state.currentUser.usedCoupons = userObj.usedCoupons; db.setCurrentUser(state.currentUser); } } } } function handleCancelOrderProcess(orderId, cancelledBy, cancelReason, refundInfo = null) { const ordersDb = db.getOrders(); const targetOrder = ordersDb.find(o => o.id === orderId); if (!targetOrder) { showToast("ไม่พบคำสั่งซื้อที่ต้องการยกเลิก", "danger"); return false; } if (targetOrder.status === "Shipped" || targetOrder.status === "Success") { showToast("ไม่อนุญาตให้ยกเลิกคำสั่งซื้อที่อยู่ระหว่างจัดส่งหรือจัดส่งสำเร็จแล้ว", "danger"); return false; } const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; targetOrder.status = "Cancelled"; targetOrder.cancelledBy = cancelledBy; targetOrder.cancelReason = cancelReason; targetOrder.cancelDate = dateStr; if (refundInfo) { targetOrder.refundInfo = refundInfo; } restockOrderItems(targetOrder); restoreOrderCoupon(targetOrder); db.saveOrders(ordersDb); let cancelledByLabel = "ลูกค้า"; if (cancelledBy === "Seller") cancelledByLabel = "ผู้ขาย"; if (cancelledBy === "Admin") cancelledByLabel = "ผู้ดูแลระบบ"; showToast(`ยกเลิกคำสั่งซื้อ ${orderId} เรียบร้อยแล้ว (โดย${cancelledByLabel})`, "success"); if (typeof renderOrderHistory === "function") renderOrderHistory(); if (typeof renderSellerOrders === "function") renderSellerOrders(); if (typeof renderAdminOrders === "function") renderAdminOrders(); if (typeof renderSellerDashboard === "function") renderSellerDashboard(); if (typeof renderAdminDashboard === "function") renderAdminDashboard(); return true; } function openCustomerCancelModal(order) { const modal = document.getElementById("customerCancelModal"); if (!modal) return; document.getElementById("cancelOrderId").value = order.id; document.getElementById("cancelOrderIdDisplay").innerText = order.id; const reasonSelect = document.getElementById("customerCancelReason"); const detailInput = document.getElementById("customerCancelDetail"); const detailGroup = document.getElementById("customerCancelDetailGroup"); if (reasonSelect) reasonSelect.value = ""; if (detailInput) detailInput.value = ""; if (detailGroup) detailGroup.style.display = "none"; const refundSec = document.getElementById("refundInfoSection"); const isPaidOrPrep = order.status === "Paid" || order.status === "Preparing"; if (refundSec) { if (isPaidOrPrep) { refundSec.style.display = "block"; document.getElementById("refundBankName").required = true; document.getElementById("refundAccountNo").required = true; document.getElementById("refundAccountName").required = true; document.getElementById("refundBankName").value = ""; document.getElementById("refundAccountNo").value = ""; document.getElementById("refundAccountName").value = ""; } else { refundSec.style.display = "none"; document.getElementById("refundBankName").required = false; document.getElementById("refundAccountNo").required = false; document.getElementById("refundAccountName").required = false; } } modal.style.display = "flex"; } function openSellerAdminCancelModal(order) { const modal = document.getElementById("sellerAdminCancelModal"); if (!modal) return; document.getElementById("sellerAdminCancelOrderId").value = order.id; document.getElementById("sellerAdminCancelOrderIdDisplay").innerText = order.id; const reasonSelect = document.getElementById("sellerAdminCancelReason"); const detailInput = document.getElementById("sellerAdminCancelDetail"); const detailGroup = document.getElementById("sellerAdminCancelDetailGroup"); if (reasonSelect) reasonSelect.value = ""; if (detailInput) detailInput.value = ""; if (detailGroup) detailGroup.style.display = "none"; modal.style.display = "flex"; } function initCancelModals() { const custReasonSelect = document.getElementById("customerCancelReason"); const custDetailGroup = document.getElementById("customerCancelDetailGroup"); if (custReasonSelect && custDetailGroup) { custReasonSelect.addEventListener("change", () => { if (custReasonSelect.value === "เหตุผลอื่นๆ") { custDetailGroup.style.display = "block"; } else { custDetailGroup.style.display = "none"; } }); } const custForm = document.getElementById("customerCancelForm"); if (custForm) { custForm.addEventListener("submit", (e) => { e.preventDefault(); const orderId = document.getElementById("cancelOrderId").value; const mainReason = document.getElementById("customerCancelReason").value; const extraDetail = document.getElementById("customerCancelDetail").value.trim(); let fullReason = mainReason; if (extraDetail) fullReason += ` (${extraDetail})`; const refundSec = document.getElementById("refundInfoSection"); let refundInfo = null; if (refundSec && refundSec.style.display !== "none") { refundInfo = { bankName: document.getElementById("refundBankName").value.trim(), accountNo: document.getElementById("refundAccountNo").value.trim(), accountName: document.getElementById("refundAccountName").value.trim() }; } const success = handleCancelOrderProcess(orderId, "Customer", fullReason, refundInfo); if (success) { document.getElementById("customerCancelModal").style.display = "none"; } }); } const closeCustBtn = document.getElementById("closeCustomerCancelModalBtn"); const cancelCustBtn = document.getElementById("cancelCustomerCancelBtn"); const custModal = document.getElementById("customerCancelModal"); if (closeCustBtn && custModal) { closeCustBtn.addEventListener("click", () => custModal.style.display = "none"); } if (cancelCustBtn && custModal) { cancelCustBtn.addEventListener("click", () => custModal.style.display = "none"); } const saReasonSelect = document.getElementById("sellerAdminCancelReason"); const saDetailGroup = document.getElementById("sellerAdminCancelDetailGroup"); if (saReasonSelect && saDetailGroup) { saReasonSelect.addEventListener("change", () => { if (saReasonSelect.value === "เหตุผลอื่นๆ") { saDetailGroup.style.display = "block"; } else { saDetailGroup.style.display = "none"; } }); } const saForm = document.getElementById("sellerAdminCancelForm"); if (saForm) { saForm.addEventListener("submit", (e) => { e.preventDefault(); const orderId = document.getElementById("sellerAdminCancelOrderId").value; const mainReason = document.getElementById("sellerAdminCancelReason").value; const extraDetail = document.getElementById("sellerAdminCancelDetail").value.trim(); let fullReason = mainReason; if (extraDetail) fullReason += ` (${extraDetail})`; const cancelledBy = state.currentRole === "seller" ? "Seller" : "Admin"; const success = handleCancelOrderProcess(orderId, cancelledBy, fullReason); if (success) { document.getElementById("sellerAdminCancelModal").style.display = "none"; } }); } const closeSaBtn = document.getElementById("closeSellerAdminCancelModalBtn"); const cancelSaBtn = document.getElementById("cancelSellerAdminCancelBtn"); const saModal = document.getElementById("sellerAdminCancelModal"); if (closeSaBtn && saModal) { closeSaBtn.addEventListener("click", () => saModal.style.display = "none"); } if (cancelSaBtn && saModal) { cancelSaBtn.addEventListener("click", () => saModal.style.display = "none"); } } // Global active status filter for history page let currentHistoryStatusFilter = "all"; function updateOrderHistoryBadges() { const activeUsername = state.currentUser ? state.currentUser.username : "customer1"; const allOrders = db.getOrders().filter(o => o.customerUsername === activeUsername); const countAll = allOrders.length; const countUnpaid = allOrders.filter(o => o.status === "Unpaid").length; const countPending = allOrders.filter(o => o.status === "Pending").length; const countPaidPreparing = allOrders.filter(o => o.status === "Paid" || o.status === "Preparing").length; const countShipped = allOrders.filter(o => o.status === "Shipped").length; const countSuccess = allOrders.filter(o => o.status === "Success").length; const countCancelled = allOrders.filter(o => o.status === "Cancelled" || o.status === "Failed").length; const bAll = document.getElementById("badge-all"); const bUnpaid = document.getElementById("badge-Unpaid"); const bPending = document.getElementById("badge-Pending"); const bPaidPrep = document.getElementById("badge-Paid_Preparing"); const bShipped = document.getElementById("badge-Shipped"); const bSuccess = document.getElementById("badge-Success"); const bCancelled = document.getElementById("badge-Cancelled") || document.getElementById("badge-Failed"); if (bAll) bAll.innerText = countAll; if (bUnpaid) bUnpaid.innerText = countUnpaid; if (bPending) bPending.innerText = countPending; if (bPaidPrep) bPaidPrep.innerText = countPaidPreparing; if (bShipped) bShipped.innerText = countShipped; if (bSuccess) bSuccess.innerText = countSuccess; if (bCancelled) bCancelled.innerText = countCancelled; } function initOrderHistoryTabs() { const tabs = document.querySelectorAll(".order-status-tab"); tabs.forEach(tab => { tab.addEventListener("click", () => { const status = tab.getAttribute("data-status"); renderOrderHistory(status); }); }); } function renderOrderHistory(statusFilter = currentHistoryStatusFilter) { currentHistoryStatusFilter = statusFilter; const container = document.getElementById("historyListContainer"); if (!container) return; container.innerHTML = ""; const activeUsername = state.currentUser ? state.currentUser.username : "customer1"; const tabs = document.querySelectorAll(".order-status-tab"); tabs.forEach(tab => { if (tab.getAttribute("data-status") === statusFilter) { tab.classList.add("active"); } else { tab.classList.remove("active"); } }); updateOrderHistoryBadges(); const allOrders = db.getOrders().filter(o => o.customerUsername === activeUsername); let filteredOrders = allOrders; if (statusFilter === "Unpaid") { filteredOrders = allOrders.filter(o => o.status === "Unpaid"); } else if (statusFilter === "Pending") { filteredOrders = allOrders.filter(o => o.status === "Pending"); } else if (statusFilter === "Paid_Preparing") { filteredOrders = allOrders.filter(o => o.status === "Paid" || o.status === "Preparing"); } else if (statusFilter === "Shipped") { filteredOrders = allOrders.filter(o => o.status === "Shipped"); } else if (statusFilter === "Success") { filteredOrders = allOrders.filter(o => o.status === "Success"); } else if (statusFilter === "Cancelled" || statusFilter === "Failed") { filteredOrders = allOrders.filter(o => o.status === "Cancelled" || o.status === "Failed"); } if (filteredOrders.length === 0) { const noOrdersTitle = 'ไม่มีประวัติคำสั่งซื้อ'; const noOrdersDesc = 'ไม่มีคำสั่งซื้อที่ตรงกับประเภทสถานะนี้'; const backToShopBtn = 'กลับไปเลือกซื้อสินค้า'; container.innerHTML = ` <div class="admin-card text-center py-5"> <i class="fa-solid fa-receipt" style="font-size:3.5rem; color:var(--color-sky-light)"></i> <h3 class="mt-3">${noOrdersTitle}</h3> <p style="color:var(--color-sky-slate)">${noOrdersDesc}</p> <button class="btn btn-sky mt-3" onclick="navigateTo('home')">${backToShopBtn}</button> </div> `; return; } filteredOrders.forEach(order => { const sellerGroups = {}; order.items.forEach(item => { const sId = (item && item.sellerId) ? item.sellerId : "admin"; if (!sellerGroups[sId]) sellerGroups[sId] = []; sellerGroups[sId].push(item); }); const usersList = db.getUsers(); Object.keys(sellerGroups).forEach(sId => { const sellerObj = usersList.find(u => u.username === sId); const sellerName = sellerObj ? sellerObj.name : 'ร้านค้าทั่วไป'; const items = sellerGroups[sId]; const trNo = order.trackingNumbers && order.trackingNumbers[sId] ? order.trackingNumbers[sId] : ""; let statusBadge = ""; if (order.status === "Unpaid") statusBadge = `<span class="badge badge-dark" style="font-size:11px;"><i class="fa-solid fa-clock"></i> รอชำระเงิน</span>`; else if (order.status === "Pending") statusBadge = `<span class="badge badge-warning" style="font-size:11px;"><i class="fa-solid fa-spinner fa-spin"></i> รอตรวจสอบ</span>`; else if (order.status === "Paid") statusBadge = `<span class="badge badge-success" style="font-size:11px;"><i class="fa-solid fa-check-circle"></i> ชำระเงินแล้ว</span>`; else if (order.status === "Preparing") statusBadge = `<span class="badge badge-sky" style="font-size:11px;"><i class="fa-solid fa-box"></i> เตรียมพัสดุ</span>`; else if (order.status === "Shipped") statusBadge = `<span class="badge badge-success" style="font-size:11px;"><i class="fa-solid fa-truck"></i> จัดส่งแล้ว</span>`; else if (order.status === "Success") statusBadge = `<span class="badge badge-success" style="font-size:11px; background:#10b981; color:#ffffff !important;"><i class="fa-solid fa-circle-check"></i> จัดส่งสำเร็จ</span>`; else if (order.status === "Cancelled" || order.status === "Failed") statusBadge = `<span class="badge badge-cancelled" style="font-size:11px;"><i class="fa-solid fa-ban"></i> ยกเลิกแล้ว</span>`; const itemsHtml = items.map(item => { const hasOpts = item.color !== "Default" || item.size !== "Default"; const optText = hasOpts ? `${item.color} / ${item.size}` : 'ไม่มีตัวเลือก'; const prodPlaceholder = DEFAULT_IMAGE_PLACEHOLDERS[item.productId] || DEFAULT_IMAGE_PLACEHOLDERS["default"]; const prodImg = item.image || prodPlaceholder; return ` <div class="oh-item-row"> <div class="oh-item-img"> <img src="${prodImg}" alt=""> <span class="oh-return-tag">คืนสินค้า 7 วัน</span> </div> <div class="oh-item-info"> <div class="oh-item-name">${item.name}</div> <div class="oh-item-variant">${optText}</div> </div> <div class="oh-item-price">฿${item.price.toLocaleString()}</div> <div class="oh-item-qty">x${item.quantity}</div> </div> `; }).join(""); let trackingSnippet = ""; if (trNo) { trackingSnippet = `<span style="font-size:11px; color:#0d9488;"><i class="fa-solid fa-barcode"></i> ${trNo}</span> <button class="btn-copy-tracking btn btn-sky-outline btn-xs" data-code="${trNo}" style="font-size:9px; padding:1px 5px;">คัดลอก</button>`; } let actionBtns = ""; const isCancellable = order.status === "Unpaid" || order.status === "Pending" || order.status === "Paid" || order.status === "Preparing"; if (isCancellable) { actionBtns += `<button class="btn btn-danger-outline btn-xs btn-request-cancel-order" data-order-id="${order.id}"><i class="fa-solid fa-ban"></i> ขอยกเลิกคำสั่งซื้อ</button>`; } if (order.status === "Unpaid") { actionBtns += ` <button class="btn btn-sky btn-xs btn-show-upload-slip" data-order-id="${order.id}"><i class="fa-solid fa-qrcode"></i> แจ้งชำระเงิน</button>`; } else if (order.status === "Shipped") { actionBtns = `<button class="btn btn-sky btn-xs btn-confirm-receipt" data-order-id="${order.id}">ยืนยันรับสินค้า</button>`; } else if (order.status === "Success") { actionBtns = ` <button class="btn btn-sky-outline btn-xs btn-review-order" data-order-id="${order.id}">ให้คะแนน</button> <button class="btn btn-sky btn-xs btn-buy-again" data-order-id="${order.id}"><i class="fa-solid fa-rotate-right"></i> ซื้ออีกครั้ง</button> `; } let latePayHtml = ""; if (order.status === "Unpaid") { latePayHtml = ` <div class="late-pay-wrapper" style="display:none; padding:12px 16px; background:#fdf2f8; border-top:1px solid #fbcfe8;"> <p style="font-size:0.8rem; color:var(--color-sky-slate); margin-bottom:6px;"> PromptPay ยอดโอน: <strong style="color:var(--color-sky-brand);">฿${order.total.toLocaleString()}</strong> </p> <div style="display:flex; gap:8px; flex-wrap:wrap;"> <input type="file" class="form-control late-slip-input width-auto" accept="image/*" style="font-size:12px;"> <button class="btn btn-sky btn-xs submit-late-slip-btn">อัปโหลดสลิป</button> </div> </div> `; } let cancelDetailsHtml = ""; if (order.status === "Cancelled" || order.status === "Failed") { let byLabel = 'ลูกค้า'; if (order.cancelledBy === "Seller") byLabel = 'ร้านค้าผู้ขาย'; if (order.cancelledBy === "Admin") byLabel = 'ผู้ดูแลระบบ'; let refundText = ""; if (order.refundInfo && order.refundInfo.bankName) { refundText = `<br><strong>ข้อมูลบัญชีคืนเงิน:</strong> ${order.refundInfo.bankName} (${order.refundInfo.accountNo}) - ${order.refundInfo.accountName}`; } cancelDetailsHtml = ` <div class="cancel-details-box m-3"> <div style="font-weight: 700; margin-bottom: 3px; display: flex; align-items: center; gap: 6px;"> <i class="fa-solid fa-circle-exclamation"></i> รายละเอียดการยกเลิกคำสั่งซื้อ </div> <div><strong>ยกเลิกโดย:</strong> ${byLabel} ${order.cancelDate ? `(${order.cancelDate})` : ''}</div> <div><strong>เหตุผลในการยกเลิก:</strong> ${order.cancelReason || 'ไม่ระบุเหตุผล'}</div> ${refundText} </div> `; } const sellerSubtotal = items.reduce((s, i) => s + (i.price * i.quantity), 0); const card = document.createElement("div"); card.className = "history-card"; card.innerHTML = ` <div class="history-card-header"> <div class="history-card-header-left"> <span style="font-weight:600; font-size:0.88rem; color:#0f766e;"><i class="fa-solid fa-store"></i> ${sellerName}</span>${getOfficialBadgeHtml(sId)} <button class="btn btn-sky-outline btn-xs btn-history-go-shop" data-seller-id="${sId}" style="font-size:10px; padding:1px 5px;">ไปที่ร้านค้า</button> <span class="history-order-date">${order.id} · ${order.date}</span> </div> <div>${statusBadge}</div> </div> <div class="history-card-body"> ${itemsHtml} </div> <div class="oh-card-footer"> <div class="oh-footer-left"> ${trackingSnippet} <button class="btn btn-sky-outline btn-xs btn-toggle-detail" style="font-size:10px; padding:1px 5px;"><i class="fa-solid fa-eye"></i> รายละเอียด</button> <button class="btn btn-sky-outline btn-xs print-order-invoice-btn" data-id="${order.id}" style="font-size:10px; padding:1px 5px;"><i class="fa-solid fa-print"></i> พิมพ์</button> </div> <div class="oh-footer-right"> <span style="font-weight:600; font-size:0.9rem; color:var(--color-sky-brand);">รวม: ฿${sellerSubtotal.toLocaleString()}</span> ${actionBtns} </div> </div> <div class="oh-detail-panel"> <div style="display:flex; gap:2rem; flex-wrap:wrap;"> <div> <strong>ผู้รับ:</strong> ${order.shippingInfo.name}<br> <strong>เบอร์:</strong> ${order.shippingInfo.phone}<br> <strong>ที่อยู่:</strong> ${order.shippingInfo.address} </div> <div> <strong>ชำระเงิน:</strong> ${order.paymentMethod || 'PromptPay'}<br> <strong>ยอดรวมทั้งออเดอร์:</strong> ฿${order.total.toLocaleString()} ${order.discount > 0 ? `<br><strong>ส่วนลด:</strong> -฿${order.discount.toLocaleString()} (${order.couponCode})` : ''} </div> </div> </div> ${cancelDetailsHtml} ${latePayHtml} `; const btnDetail = card.querySelector(".btn-toggle-detail"); if (btnDetail) { btnDetail.addEventListener("click", () => { openOrderTrackingModal(order); }); } card.querySelectorAll(".btn-copy-tracking").forEach(btn => { btn.addEventListener("click", () => { const code = btn.getAttribute("data-code"); navigator.clipboard.writeText(code).then(() => { showToast(`คัดลอกเลขพัสดุ ${code} แล้ว!`, "info"); }); }); }); card.querySelectorAll(".btn-history-go-shop").forEach(btn => { btn.addEventListener("click", () => { const sellerId = btn.getAttribute("data-seller-id"); state.activeFilters.sellerId = sellerId; const filterSellerDropdown = document.getElementById("filterSeller"); if (filterSellerDropdown) filterSellerDropdown.value = sellerId; navigateTo("home"); renderCatalog(); }); }); card.querySelectorAll(".print-order-invoice-btn").forEach(btn => { btn.addEventListener("click", () => { printOrderPackingSlip(btn.getAttribute("data-id")); }); }); const btnShowUpload = card.querySelector(".btn-show-upload-slip"); if (btnShowUpload) { btnShowUpload.addEventListener("click", () => { const latePayWrapper = card.querySelector(".late-pay-wrapper"); if (latePayWrapper) { latePayWrapper.style.display = latePayWrapper.style.display === "none" ? "block" : "none"; } }); } const btnReqCancel = card.querySelector(".btn-request-cancel-order"); if (btnReqCancel) { btnReqCancel.addEventListener("click", () => { openCustomerCancelModal(order); }); } // Confirm receipt const btnConfirm = card.querySelector(".btn-confirm-receipt"); if (btnConfirm) { btnConfirm.addEventListener("click", () => { if (confirm("คุณได้รับสินค้าเรียบร้อยแล้วใช่หรือไม่?")) { const ordersDb = db.getOrders(); const targetOrder = ordersDb.find(o => o.id === order.id); if (targetOrder) { targetOrder.status = "Success"; db.saveOrders(ordersDb); showToast("ยืนยันรับสินค้าสำเร็จ!", "success"); renderOrderHistory(statusFilter); } } }); } // Buy again const btnBuyAgain = card.querySelector(".btn-buy-again"); if (btnBuyAgain) { btnBuyAgain.addEventListener("click", () => { order.items.forEach(item => { addToCart(item.productId, item.color, item.size, item.quantity); }); showToast("เพิ่มรายการสินค้าเดิมลงตะกร้าแล้ว!", "success"); }); } // Review const btnReview = card.querySelector(".btn-review-order"); if (btnReview) { btnReview.addEventListener("click", () => { if (order.items.length > 0) { const productId = order.items[0].productId; document.getElementById("reviewProductId").value = productId; document.getElementById("reviewComment").value = ""; const checkedRadio = document.querySelector("input[name='reviewRating']:checked"); if (checkedRadio) checkedRadio.checked = false; document.getElementById("productReviewModal").style.display = "flex"; } }); } // Late payment upload if (order.status === "Unpaid") { const fileInput = card.querySelector(".late-slip-input"); const uploadBtn = card.querySelector(".submit-late-slip-btn"); let localBase64 = ""; if (fileInput) { fileInput.addEventListener("change", (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { const canvas = document.createElement("canvas"); const MAX_WIDTH = 600; const MAX_HEIGHT = 600; let width = img.width; let height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); localBase64 = canvas.toDataURL("image/jpeg", 0.7); }; img.src = event.target.result; }; reader.readAsDataURL(file); } }); } if (uploadBtn) { uploadBtn.addEventListener("click", () => { if (!localBase64) { showToast("กรุณาเลือกไฟล์สลิปก่อนกดอัปโหลด", "warning"); return; } const ordersDb = db.getOrders(); const targetOrder = ordersDb.find(o => o.id === order.id); if (targetOrder) { try { targetOrder.slipImage = localBase64; targetOrder.status = "Pending"; db.saveOrders(ordersDb); showToast("อัปโหลดสลิปเรียบร้อย! รอตรวจสอบการชำระเงิน", "success"); renderOrderHistory(statusFilter); } catch (error) { showToast("เกิดข้อผิดพลาด: ไฟล์รูปภาพสลิปอาจมีขนาดใหญ่เกินไป กรุณาลดขนาดไฟล์แล้วลองใหม่อีกครั้ง", "danger"); console.error("Error saving slip:", error); } } }); } } container.appendChild(card); }); }); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings