File manager - Edit - /home/webapp69.cm.in.th/u69319090033/Shop/app.js
Back
// State Management const STATE = { currentUser: null, // { id, name, email, role: 'admin' | 'user', avatar } cart: [], wishlist: [], currentCategory: 'all', currentSort: 'default', products: [], categories: [], users: [ { id: 1, name: "Admin Good boy Shop", email: "admin@goodboyshop.com", password: "123", role: "admin", avatar: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80" }, { id: 2, name: "สมชาย ใจดี", email: "user@gmail.com", password: "123", role: "user", avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80" } ], allOrders: [ { id: "ORD-9901", customer: "สมชาย ใจดี", total: 1850, status: "shipping", date: "2026-08-04" }, { id: "ORD-9902", customer: "วรรณา ใจตรง", total: 3290, status: "delivered", date: "2026-08-03" }, { id: "ORD-9903", customer: "ปิติ รักดี", total: 490, status: "pending", date: "2026-08-05" } ] }; // Initialize App document.addEventListener('DOMContentLoaded', async () => { // Fetch initial data from backend API try { const response = await fetch('api/get_initial_data.php'); if (response.ok) { const data = await response.json(); STATE.categories = data.categories || []; // Adding 'all' category manually for the frontend UI logic if (!STATE.categories.find(c => c.id === 'all')) { STATE.categories.unshift({ id: 'all', name: 'ทั้งหมด', icon: 'grid' }); } STATE.products = data.products || []; if (data.user) { STATE.currentUser = data.user; } } else { console.error("Failed to fetch data from backend"); } } catch (error) { console.error("Error fetching initial data:", error); } // Check LocalStorage for auth state (Fallback) const savedUser = localStorage.getItem('goodboyshop_user'); if (savedUser && !STATE.currentUser) { STATE.currentUser = JSON.parse(savedUser); } // We only load products from local storage if API failed if (STATE.products.length === 0) { const savedProducts = localStorage.getItem('goodboyshop_products'); if (savedProducts) { STATE.products = JSON.parse(savedProducts); } } const savedOrders = localStorage.getItem('goodboyshop_orders'); if (savedOrders) { STATE.allOrders = JSON.parse(savedOrders); } const savedCart = localStorage.getItem('goodboyshop_cart'); if (savedCart) { STATE.cart = JSON.parse(savedCart); } const savedWishlist = localStorage.getItem('goodboyshop_wishlist'); if (savedWishlist) { STATE.wishlist = JSON.parse(savedWishlist); } // Render Components renderAuthHeader(); renderCategories(); renderProducts(); updateBadges(); initSlider(); initSearch(); initFlashTimer(); // Initialize Lucide Icons if (window.lucide) { lucide.createIcons(); } }); // Toast Notification function showToast(message, type = 'info') { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); toast.className = `toast toast-${type}`; let icon = 'info'; if (type === 'success') icon = 'check-circle'; if (type === 'error') icon = 'alert-circle'; toast.innerHTML = ` <i data-lucide="${icon}" class="toast-icon"></i> <span>${message}</span> `; container.appendChild(toast); if (window.lucide) lucide.createIcons(); setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, 3000); } // Dynamic Header Auth State function renderAuthHeader() { const wrapper = document.getElementById('header-auth-wrapper'); if (!wrapper) return; if (STATE.currentUser) { const role = STATE.currentUser.role; const sellerStatus = STATE.currentUser.seller_status || 'none'; const isApprovedSeller = (role === 'seller' && sellerStatus === 'approved'); const isPendingSeller = (role === 'seller' && sellerStatus === 'pending'); const isAdmin = (role === 'admin'); let badgeHtml = '👤 สมาชิก (User)'; let badgeClass = 'status-delivered'; if (isAdmin) { badgeHtml = '🔑 ผู้ดูแลระบบ (Admin)'; badgeClass = 'status-shipping'; } else if (isApprovedSeller) { badgeHtml = '🏪 ผู้ขาย (Seller)'; badgeClass = 'status-delivered'; } else if (isPendingSeller) { badgeHtml = '⏳ รออนุมัติผู้ขาย'; badgeClass = 'status-pending'; } wrapper.innerHTML = ` <div style="position:relative;"> <button class="user-avatar-btn" onclick="toggleUserDropdown()"> <img src="${STATE.currentUser.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80'}" class="avatar-img" alt="avatar"> <span class="avatar-name">${STATE.currentUser.name}</span> <i data-lucide="chevron-down" style="width:14px;height:14px;color:var(--gray-500);"></i> </button> <div id="user-dropdown" class="search-suggestions" style="right:0;left:auto;min-width:220px;top:calc(100% + 8px);"> <div style="padding:12px 16px;border-bottom:1px solid var(--gray-100);"> <div style="font-weight:700;font-size:14px;">${STATE.currentUser.name}</div> <div style="font-size:12px;color:var(--gray-500);">${STATE.currentUser.email}</div> <span class="status-badge ${badgeClass}" style="margin-top:6px;display:inline-block;"> ${badgeHtml} </span> </div> ${isAdmin ? ` <div class="suggestion-item" onclick="navigateTo('admin');closeDropdowns();"> <i data-lucide="layout-dashboard"></i> <span class="suggestion-title" style="color:var(--primary);font-weight:700;">แผงควบคุม (Admin Backoffice)</span> </div> ` : ''} ${isApprovedSeller ? ` <div class="suggestion-item" onclick="navigateTo('seller');closeDropdowns();"> <i data-lucide="store" style="color:var(--success);"></i> <span class="suggestion-title" style="color:var(--success);font-weight:700;">จัดการร้านค้า (Seller Dashboard)</span> </div> ` : ''} <div class="suggestion-item" onclick="navigateTo('profile');closeDropdowns();"> <i data-lucide="user"></i> <span class="suggestion-title">โปรไฟล์ & ข้อมูลส่วนตัว</span> </div> <div class="suggestion-item" onclick="handleLogout()"> <i data-lucide="log-out" style="color:var(--danger);"></i> <span class="suggestion-title" style="color:var(--danger);">ออกจากระบบ</span> </div> </div> </div> `; } else { wrapper.innerHTML = ` <button class="login-btn" onclick="openAuthModal('login')"> <i data-lucide="log-in" style="width:16px;height:16px;display:inline-block;vertical-align:middle;margin-right:4px;"></i> เข้าสู่ระบบ / สมัครสมาชิก </button> `; } if (window.lucide) lucide.createIcons(); } function toggleUserDropdown() { const dropdown = document.getElementById('user-dropdown'); if (dropdown) dropdown.classList.toggle('active'); } function closeDropdowns() { const dropdown = document.getElementById('user-dropdown'); if (dropdown) dropdown.classList.remove('active'); } // Modal Control function openModal(modalId) { const modal = document.getElementById(modalId); if (modal) modal.classList.add('active'); } function closeModal(modalId) { const modal = document.getElementById(modalId); if (modal) modal.classList.remove('active'); } // Auth Modal Form (Login & Register System) function openAuthModal(tab = 'login') { const content = document.getElementById('auth-modal-content'); content.innerHTML = ` <div class="modal-logo"> <div class="logo-icon"><i data-lucide="shopping-bag" style="width:20px;height:20px;"></i></div> Good boy<span> Shop</span> </div> <div class="modal-tabs"> <button class="modal-tab ${tab === 'login' ? 'active' : ''}" onclick="openAuthModal('login')">เข้าสู่ระบบ</button> <button class="modal-tab ${tab === 'register' ? 'active' : ''}" onclick="openAuthModal('register')">ลงทะเบียนสมาชิก</button> </div> ${tab === 'login' ? ` <form onsubmit="handleLoginSubmit(event)"> <div class="form-group"> <label>อีเมล (Email)</label> <input type="email" id="login-email" placeholder="example@gmail.com" required value="user@gmail.com"> </div> <div class="form-group"> <label>รหัสผ่าน (Password)</label> <input type="password" id="login-password" placeholder="••••••••" required value="123"> </div> <div style="font-size:12px;color:var(--gray-500);margin-bottom:16px;background:var(--gray-100);padding:10px;border-radius:var(--radius-sm);"> 💡 <b>ทดสอบระบบ:</b><br> • ผู้ใช้ทั่วไป: user@gmail.com / 123<br> • ผู้ดูแลระบบ (Admin): admin@goodboyshop.com / 123 </div> <button type="submit" class="primary-btn" style="width:100%;">เข้าสู่ระบบ</button> </form> ` : ` <form onsubmit="handleRegisterSubmit(event)"> <div class="form-group"> <label>ชื่อ-นามสกุล</label> <input type="text" id="reg-name" placeholder="ชื่อ นามสกุล" required> </div> <div class="form-group"> <label>อีเมล (Email)</label> <input type="email" id="reg-email" placeholder="example@gmail.com" required> </div> <div class="form-group"> <label>รหัสผ่าน (Password)</label> <input type="password" id="reg-password" placeholder="••••••••" required minlength="3"> </div> <div class="form-group"> <label>ระดับสิทธิ์</label> <select id="reg-role" onchange="handleRoleSelectionChange(this)"> <option value="user" selected>สมาชิกทั่วไป (User)</option> <option value="seller">ผู้ขาย (Seller)</option> </select> <small style="color:var(--gray-500);font-size:11px;display:block;margin-top:4px;">* สิทธิ์ Admin ต้องขอจากผู้ดูแลระบบเท่านั้น</small> <div id="seller-role-notice" style="display:none;margin-top:8px;padding:10px 12px;background:rgba(245,158,11,0.15);border:1px solid rgba(245,158,11,0.4);border-radius:var(--radius-sm);color:#fef3c7;font-size:12px;line-height:1.4;"> <i data-lucide="alert-triangle" style="width:14px;height:14px;display:inline-block;vertical-align:middle;color:var(--warning);margin-right:4px;"></i> <b>เงื่อนไขการสมัครผู้ขาย:</b> การสมัครเป็นผู้ขายต้องได้รับการอนุมัติจากแอดมินก่อน จึงจะสามารถเข้าสู่ระบบและเพิ่มสินค้าได้ </div> </div> <button type="submit" class="primary-btn" style="width:100%;margin-top:8px;">สมัครสมาชิก</button> </form> `} `; openModal('auth-modal'); if (window.lucide) lucide.createIcons(); } function handleRoleSelectionChange(selectEl) { const notice = document.getElementById('seller-role-notice'); if (notice) { notice.style.display = selectEl.value === 'seller' ? 'block' : 'none'; if (window.lucide) lucide.createIcons(); } } async function handleLoginSubmit(e) { e.preventDefault(); const email = document.getElementById('login-email').value.trim(); const password = document.getElementById('login-password').value.trim(); try { const response = await fetch('api/login.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const result = await response.json(); if (result.success && result.user) { const user = { id: result.user.id, name: result.user.name, email: result.user.email, role: result.user.role, seller_status: result.user.seller_status || 'none', avatar: result.user.role === 'admin' ? "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80" : "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80" }; STATE.currentUser = user; localStorage.setItem('goodboyshop_user', JSON.stringify(user)); renderAuthHeader(); closeModal('auth-modal'); showToast(`ยินดีต้อนรับเข้าสู่ระบบ คุณ ${user.name}`, 'success'); if (user.role === 'admin') { navigateTo('admin'); } return; } else { showToast(result.message || 'อีเมลหรือรหัสผ่านไม่ถูกต้อง!', 'error'); return; } } catch (err) { // Fallback to local STATE.users const user = STATE.users.find(u => u.email === email && u.password === password); if (user) { STATE.currentUser = user; localStorage.setItem('goodboyshop_user', JSON.stringify(user)); renderAuthHeader(); closeModal('auth-modal'); showToast(`ยินดีต้อนรับเข้าสู่ระบบ คุณ ${user.name}`, 'success'); if (user.role === 'admin') { navigateTo('admin'); } } else { showToast('อีเมลหรือรหัสผ่านไม่ถูกต้อง!', 'error'); } } } async function handleRegisterSubmit(e) { e.preventDefault(); const name = document.getElementById('reg-name').value.trim(); const email = document.getElementById('reg-email').value.trim(); const password = document.getElementById('reg-password').value.trim(); const roleSelect = document.getElementById('reg-role'); const selectedRole = roleSelect ? roleSelect.value : 'user'; try { const response = await fetch('api/register.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password, role: selectedRole }) }); const result = await response.json(); if (result.success) { closeModal('auth-modal'); if (selectedRole === 'seller' || result.pending_approval) { // แจ้งเตือนผู้ใช้กรณีเลือก Seller โดยไม่ auto-login ในบทบาท seller showToast('สมัครสมาชิกสำเร็จ! กรุณารอการอนุมัติจากแอดมินก่อนเริ่มใช้งานสิทธิ์ผู้ขาย', 'info'); } else { const user = { id: result.user ? result.user.id : Date.now(), name: name, email: email, role: 'user', seller_status: 'none', avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80" }; STATE.users.push(user); STATE.currentUser = user; localStorage.setItem('goodboyshop_user', JSON.stringify(user)); renderAuthHeader(); showToast(`สมัครสมาชิกสำเร็จ! ยินดีต้อนรับคุณ ${name}`, 'success'); renderProfileView('info'); } return; } else { showToast(result.message || 'เกิดข้อผิดพลาดในการลงทะเบียน', 'error'); return; } } catch (err) { // Fallback offline / local if (STATE.users.some(u => u.email === email)) { showToast('อีเมลนี้ถูกใช้แล้ว กรุณาใช้อีเมลอื่น', 'error'); return; } if (selectedRole === 'seller') { const newUser = { id: Date.now(), name, email, password, role: 'seller', seller_status: 'pending', avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80" }; STATE.users.push(newUser); closeModal('auth-modal'); showToast('สมัครสมาชิกสำเร็จ! กรุณารอการอนุมัติจากแอดมิน', 'info'); } else { const newUser = { id: Date.now(), name, email, password, role: 'user', seller_status: 'none', avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80" }; STATE.users.push(newUser); STATE.currentUser = newUser; localStorage.setItem('goodboyshop_user', JSON.stringify(newUser)); renderAuthHeader(); closeModal('auth-modal'); showToast(`สมัครสมาชิกสำเร็จ! ยินดีต้อนรับคุณ ${name}`, 'success'); renderProfileView('info'); } } } async function handleLogout() { // ทำลาย PHP Session บนเซิร์ฟเวอร์ก่อน แล้วค่อยล้าง state ใน browser try { await fetch('api/logout.php', { method: 'POST' }); } catch (e) { // ถ้า network error ก็ยังออกจากระบบฝั่ง frontend ได้ปกติ } STATE.currentUser = null; localStorage.removeItem('goodboyshop_user'); renderAuthHeader(); navigateTo('home'); showToast('ออกจากระบบเรียบร้อยแล้ว', 'info'); } // Navigation Views Switcher (SPA) function navigateTo(viewName, param = null) { document.querySelectorAll('.view-section').forEach(sec => sec.classList.remove('active')); const targetView = document.getElementById(`${viewName}-view`); if (targetView) { targetView.classList.add('active'); window.scrollTo({ top: 0, behavior: 'smooth' }); if (viewName === 'cart') renderCartView(); if (viewName === 'wishlist') renderWishlistView(); if (viewName === 'profile') renderProfileView(); if (viewName === 'admin') renderAdminView(); if (viewName === 'seller') renderSellerView(); if (viewName === 'product' && param) renderProductDetailView(param); if (viewName === 'checkout') renderCheckoutView(); } } // Filter Categories function filterByCategory(catId) { STATE.currentCategory = catId; document.querySelectorAll('.nav-links a').forEach(link => { if (link.dataset.category === catId) link.classList.add('active'); else link.classList.remove('active'); }); document.querySelectorAll('.cat-card').forEach(card => { if (card.dataset.id === catId) card.classList.add('active'); else card.classList.remove('active'); }); const titleEl = document.getElementById('products-title'); const category = STATE.categories.find(c => c.id === catId); if (titleEl && category) { titleEl.textContent = catId === 'all' ? 'สินค้าทั้งหมด' : `หมวดหมู่: ${category.name}`; } renderProducts(); navigateTo('home'); } // Products Rendering function renderProducts() { const grid = document.getElementById('product-grid'); const noRes = document.getElementById('no-results'); if (!grid) return; let filtered = STATE.products; if (STATE.currentCategory !== 'all') { filtered = filtered.filter(p => p.category === STATE.currentCategory); } const searchVal = document.getElementById('search-input')?.value.toLowerCase().trim(); if (searchVal) { filtered = filtered.filter(p => p.name.toLowerCase().includes(searchVal) || p.description.toLowerCase().includes(searchVal)); } // Sort if (STATE.currentSort === 'price-asc') filtered.sort((a, b) => a.price - b.price); if (STATE.currentSort === 'price-desc') filtered.sort((a, b) => b.price - a.price); if (STATE.currentSort === 'rating') filtered.sort((a, b) => b.rating - a.rating); if (filtered.length === 0) { grid.innerHTML = ''; noRes.classList.remove('hidden'); return; } noRes.classList.add('hidden'); grid.innerHTML = filtered.map(p => { const isWish = STATE.wishlist.some(w => w.id === p.id); return ` <div class="product-card" onclick="navigateTo('product', ${p.id})"> <div class="product-img-wrap"> <img src="${p.image}" alt="${p.name}"> ${p.badge ? `<span class="product-badge badge-${p.badge}">${p.badgeText}</span>` : ''} <div class="product-actions"> <button class="product-action-btn ${isWish ? 'wishlisted' : ''}" onclick="event.stopPropagation();toggleWishlist(${p.id})"> <i data-lucide="heart" style="width:16px;height:16px;"></i> </button> <button class="product-action-btn" onclick="event.stopPropagation();openQuickView(${p.id})"> <i data-lucide="eye" style="width:16px;height:16px;"></i> </button> </div> </div> <div class="product-info"> <h3 class="product-name">${p.name}</h3> <div class="product-rating"> <div class="stars">⭐ ${p.rating}</div> <span class="rating-count">(${p.reviewsCount})</span> </div> <div class="product-price"> <span class="price-current">฿${p.price.toLocaleString()}</span> ${p.originalPrice ? `<span class="price-original">฿${p.originalPrice.toLocaleString()}</span>` : ''} </div> <div class="product-footer"> <span class="sold-count">ขายแล้ว ${p.sold} ชิ้น</span> <button class="add-cart-btn" onclick="event.stopPropagation();addToCart(${p.id})">+ เพิ่มลงตะกร้า</button> </div> </div> </div> `; }).join(''); if (window.lucide) lucide.createIcons(); } function sortProducts() { STATE.currentSort = document.getElementById('sort-select').value; renderProducts(); } // Cute Category Icon & Emoji Mapping const CUTE_CATEGORY_CONFIG = { 'all': { emoji: '🎀', icon: 'sparkles', bg: 'linear-gradient(135deg, rgba(233,30,140,0.25), rgba(255,105,180,0.15))', color: '#ff69b4', name: 'ทั้งหมด' }, 'cosmetics': { emoji: '💄', icon: 'sparkles', bg: 'linear-gradient(135deg, rgba(255,105,180,0.25), rgba(255,182,193,0.15))', color: '#ff69b4', name: 'เครื่องสำอาง' }, 'electronics': { emoji: '🎧', icon: 'headphones', bg: 'linear-gradient(135deg, rgba(56,189,248,0.25), rgba(147,197,253,0.15))', color: '#38bdf8', name: 'อิเล็กทรอนิกส์' }, 'fashion': { emoji: '👗', icon: 'shirt', bg: 'linear-gradient(135deg, rgba(244,114,182,0.25), rgba(251,113,133,0.15))', color: '#f472b6', name: 'แฟชั่น' }, 'food': { emoji: '🧋', icon: 'coffee', bg: 'linear-gradient(135deg, rgba(251,146,60,0.25), rgba(253,224,71,0.15))', color: '#fb923c', name: 'อาหาร-เครื่องดื่ม' }, 'home': { emoji: '🧸', icon: 'home', bg: 'linear-gradient(135deg, rgba(168,85,247,0.25), rgba(192,132,252,0.15))', color: '#c084fc', name: 'ของใช้ในบ้าน' }, 'sports': { emoji: '🏸', icon: 'activity', bg: 'linear-gradient(135deg, rgba(52,211,153,0.25), rgba(110,231,183,0.15))', color: '#34d399', name: 'กีฬา-สุขภาพ' } }; // Categories Grid Rendering function renderCategories() { const grid = document.getElementById('category-grid'); if (!grid) return; grid.innerHTML = STATE.categories.map(c => { const cute = CUTE_CATEGORY_CONFIG[c.id] || { emoji: '✨', icon: c.icon || 'sparkles', bg: 'var(--primary-light)', color: 'var(--primary)', name: c.name }; const isActive = c.id === STATE.currentCategory; return ` <div class="cat-card ${isActive ? 'active' : ''}" data-id="${c.id}" onclick="filterByCategory('${c.id}')" style="${isActive ? `border-color:${cute.color};box-shadow:0 0 16px ${cute.color}40;` : ''}"> <div class="cat-icon cute-cat-icon" style="background:${cute.bg};border:1.5px solid ${cute.color}50;"> <span class="cat-emoji" style="font-size:24px;line-height:1;display:inline-block;filter:drop-shadow(0 2px 4px rgba(0,0,0,0.3));transform:scale(1);transition:transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);">${cute.emoji}</span> </div> <span class="cat-name" style="${isActive ? `color:${cute.color};font-weight:800;` : ''}">${c.name}</span> </div> `; }).join(''); if (window.lucide) lucide.createIcons(); } // Cart System function addToCart(productId, qty = 1) { const product = STATE.products.find(p => p.id === productId); if (!product) return; const existing = STATE.cart.find(item => item.id === productId); if (existing) { existing.qty += qty; } else { STATE.cart.push({ ...product, qty }); } localStorage.setItem('goodboyshop_cart', JSON.stringify(STATE.cart)); updateBadges(); showToast(`เพิ่ม "${product.name.substring(0, 20)}..." ลงตะกร้าแล้ว`, 'success'); } function updateCartQty(productId, delta) { const item = STATE.cart.find(i => i.id === productId); if (!item) return; item.qty += delta; if (item.qty <= 0) { STATE.cart = STATE.cart.filter(i => i.id !== productId); } localStorage.setItem('goodboyshop_cart', JSON.stringify(STATE.cart)); updateBadges(); renderCartView(); } function removeFromCart(productId) { STATE.cart = STATE.cart.filter(i => i.id !== productId); localStorage.setItem('goodboyshop_cart', JSON.stringify(STATE.cart)); updateBadges(); renderCartView(); showToast('ลบสินค้าออกจากตะกร้าแล้ว', 'info'); } // Wishlist System function toggleWishlist(productId) { const idx = STATE.wishlist.findIndex(w => w.id === productId); const product = STATE.products.find(p => p.id === productId); if (idx > -1) { STATE.wishlist.splice(idx, 1); showToast('ลบออกจากรายการโปรดแล้ว', 'info'); } else { STATE.wishlist.push(product); showToast('เพิ่มในรายการโปรดแล้ว ❤️', 'success'); } localStorage.setItem('goodboyshop_wishlist', JSON.stringify(STATE.wishlist)); updateBadges(); renderProducts(); } function updateBadges() { const cartBadge = document.getElementById('cart-count'); const wishBadge = document.getElementById('wishlist-count'); const totalCartItems = STATE.cart.reduce((sum, item) => sum + item.qty, 0); if (cartBadge) { cartBadge.textContent = totalCartItems; cartBadge.style.display = totalCartItems > 0 ? 'flex' : 'none'; } if (wishBadge) { wishBadge.textContent = STATE.wishlist.length; wishBadge.style.display = STATE.wishlist.length > 0 ? 'flex' : 'none'; } } // Cart View Render function renderCartView() { const container = document.getElementById('cart-content'); if (!container) return; if (STATE.cart.length === 0) { container.innerHTML = ` <div class="empty-cart"> <i data-lucide="shopping-cart" style="width:72px;height:72px;color:var(--gray-300);"></i> <h2>ตะกร้าสินค้าว่างเปล่า</h2> <p>เลือกสินค้าที่ชอบใส่ตะกร้าได้เลยตอนนี้</p> <button class="primary-btn" onclick="navigateTo('home')">เริ่มเลือกซื้อสินค้า</button> </div> `; if (window.lucide) lucide.createIcons(); return; } const subtotal = STATE.cart.reduce((sum, i) => sum + (i.price * i.qty), 0); const shipping = subtotal >= 1000 ? 0 : 50; container.innerHTML = ` <h2 class="page-title">ตะกร้าสินค้าของฉัน (${STATE.cart.length} รายการ)</h2> <div class="cart-layout"> <div class="cart-items"> ${STATE.cart.map(item => ` <div class="cart-item"> <img src="${item.image}" class="cart-item-img" alt="${item.name}"> <div class="cart-item-info"> <div class="cart-item-name">${item.name}</div> <div class="cart-item-price">฿${item.price.toLocaleString()}</div> <div class="cart-item-controls"> <button class="cart-qty-btn" onclick="updateCartQty(${item.id}, -1)">-</button> <span class="cart-qty-num">${item.qty}</span> <button class="cart-qty-btn" onclick="updateCartQty(${item.id}, 1)">+</button> </div> </div> <button class="cart-item-remove" onclick="removeFromCart(${item.id})"> <i data-lucide="trash-2"></i> </button> </div> `).join('')} </div> <div class="cart-summary"> <h3>สรุปยอดคำสั่งซื้อ</h3> <div class="summary-row"> <span>ยอดรวมสินค้า</span> <span>฿${subtotal.toLocaleString()}</span> </div> <div class="summary-row"> <span>ค่าจัดส่ง</span> <span>${shipping === 0 ? '<strong style="color:var(--success)">ฟรี</strong>' : `฿${shipping}`}</span> </div> <div class="coupon-row"> <input type="text" id="coupon-code" placeholder="โค้ดส่วนลด (GOODBOY10)"> <button class="coupon-btn" onclick="applyCoupon()">ใช้โค้ด</button> </div> <div class="summary-row total"> <span>ยอดรวมทั้งสิ้น</span> <span style="color:var(--primary);font-size:20px;">฿${(subtotal + shipping).toLocaleString()}</span> </div> <button class="primary-btn" style="width:100%;margin-top:20px;" onclick="navigateTo('checkout')">ดำเนินการชำระเงิน</button> </div> </div> `; if (window.lucide) lucide.createIcons(); } function applyCoupon() { const code = document.getElementById('coupon-code').value.trim(); if (code === 'GOODBOY10') { showToast('ใช้โค้ดส่วนลด GOODBOY10 ลด 10% สำเร็จ!', 'success'); } else { showToast('โค้ดส่วนลดไม่ถูกต้องหรือหมดอายุ', 'error'); } } // Wishlist View Render function renderWishlistView() { const container = document.getElementById('wishlist-content'); if (!container) return; if (STATE.wishlist.length === 0) { container.innerHTML = ` <div class="empty-cart"> <i data-lucide="heart" style="width:72px;height:72px;color:var(--gray-300);"></i> <h2>ยังไม่มีรายการโปรด</h2> <p>กดหัวใจที่สินค้าที่ชอบ เพื่อเก็บไว้มาดูทีหลัง</p> <button class="primary-btn" onclick="navigateTo('home')">ไปเลือกซื้อสินค้า</button> </div> `; if (window.lucide) lucide.createIcons(); return; } container.innerHTML = ` <h2 class="page-title">รายการโปรดของฉัน (${STATE.wishlist.length} รายการ)</h2> <div class="product-grid"> ${STATE.wishlist.map(p => ` <div class="product-card" onclick="navigateTo('product', ${p.id})"> <div class="product-img-wrap"> <img src="${p.image}" alt="${p.name}"> <button class="product-action-btn wishlisted" style="position:absolute;top:10px;right:10px;" onclick="event.stopPropagation();toggleWishlist(${p.id})"> <i data-lucide="heart" style="width:16px;height:16px;"></i> </button> </div> <div class="product-info"> <h3 class="product-name">${p.name}</h3> <div class="product-price"> <span class="price-current">฿${p.price.toLocaleString()}</span> </div> <button class="add-cart-btn" style="width:100%;margin-top:10px;" onclick="event.stopPropagation();addToCart(${p.id})">+ เพิ่มลงตะกร้า</button> </div> </div> `).join('')} </div> `; if (window.lucide) lucide.createIcons(); } // Product Detail View function renderProductDetailView(productId) { const product = STATE.products.find(p => p.id === productId); const container = document.getElementById('product-detail-content'); if (!product || !container) return; container.innerHTML = ` <div class="breadcrumb"> <a onclick="navigateTo('home')">หน้าหลัก</a> <span class="breadcrumb-sep">/</span> <a onclick="filterByCategory('${product.category}')">${STATE.categories.find(c => c.id === product.category)?.name}</a> <span class="breadcrumb-sep">/</span> <span>${product.name}</span> </div> <div class="product-detail-grid"> <div class="product-detail-images"> <img src="${product.image}" id="main-detail-img" class="detail-main-img" alt="${product.name}"> <div class="detail-thumbs"> <img src="${product.image}" class="detail-thumb active" alt="${product.name}" onclick="changeDetailImage('${product.image}', this)"> <img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&auto=format&fit=crop&q=80" class="detail-thumb" alt="Alternate view" onclick="changeDetailImage('https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&auto=format&fit=crop&q=80', this)"> </div> </div> <div class="detail-info"> <span class="detail-category">${product.category}</span> <h1 class="detail-name">${product.name}</h1> <div class="detail-rating"> <div class="stars">⭐ ${product.rating}</div> <span style="color:var(--gray-500);font-size:14px;">| ${product.reviewsCount} รีวิว | ขายแล้ว ${product.sold} ชิ้น</span> </div> <div class="detail-price"> <span class="detail-price-current">฿${product.price.toLocaleString()}</span> ${product.originalPrice ? `<span class="detail-price-original">฿${product.originalPrice.toLocaleString()}</span>` : ''} </div> <div class="detail-desc">${product.description}</div> <div class="detail-qty"> <span style="font-weight:600;">จำนวน:</span> <button class="qty-btn" onclick="adjustDetailQty(-1)">-</button> <input type="number" id="detail-qty-input" value="1" min="1" class="qty-input"> <button class="qty-btn" onclick="adjustDetailQty(1)">+</button> </div> <div class="detail-actions"> <button class="btn-add-cart" onclick="addToCartFromDetail(${product.id})"> <i data-lucide="shopping-cart" style="display:inline-block;vertical-align:middle;margin-right:6px;"></i> เพิ่มลงตะกร้า </button> <button class="btn-buy-now" onclick="addToCartFromDetail(${product.id});navigateTo('checkout');">ซื้อสินค้าเลย</button> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); } function changeDetailImage(src, el) { document.getElementById('main-detail-img').src = src; document.querySelectorAll('.detail-thumb').forEach(t => t.classList.remove('active')); el.classList.add('active'); } function adjustDetailQty(delta) { const input = document.getElementById('detail-qty-input'); let val = parseInt(input.value) || 1; val += delta; if (val < 1) val = 1; input.value = val; } function addToCartFromDetail(productId) { const qty = parseInt(document.getElementById('detail-qty-input').value) || 1; addToCart(productId, qty); } // Quick View Modal function openQuickView(productId) { const product = STATE.products.find(p => p.id === productId); const content = document.getElementById('quickview-content'); if (!product || !content) return; content.innerHTML = ` <div class="product-detail-grid"> <img src="${product.image}" alt="${product.name}" style="width:100%;border-radius:var(--radius-md);aspect-ratio:1/1;object-fit:cover;"> <div class="detail-info"> <span class="detail-category">${product.category}</span> <h2 style="font-size:22px;font-weight:700;">${product.name}</h2> <div class="detail-price"> <span class="detail-price-current">฿${product.price.toLocaleString()}</span> </div> <p style="font-size:14px;color:var(--dark-muted);line-height:1.6;">${product.description}</p> <div class="detail-actions" style="margin-top:20px;"> <button class="btn-add-cart" onclick="addToCart(${product.id});closeModal('quickview-modal');">เพิ่มลงตะกร้า</button> <button class="btn-buy-now" onclick="addToCart(${product.id});closeModal('quickview-modal');navigateTo('checkout');">สั่งซื้อเลย</button> </div> </div> </div> `; openModal('quickview-modal'); if (window.lucide) lucide.createIcons(); } // Admin Backend Dashboard & Management System async function renderAdminView() { const container = document.getElementById('admin-content'); if (!container) return; if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { container.innerHTML = ` <div class="empty-cart"> <i data-lucide="shield-alert" style="width:72px;height:72px;color:var(--danger);"></i> <h2>ไม่มีสิทธิ์เข้าถึงระบบผู้ดูแล (Admin Only)</h2> <p>กรุณาเข้าสู่ระบบด้วยบัญชีผู้ดูแลระบบ (admin@goodboyshop.com / 123) เพื่อเข้าถึงหน้านี้</p> <button class="primary-btn" onclick="openAuthModal('login')">เข้าสู่ระบบด้วยบัญชีผู้ดูแล</button> </div> `; if (window.lucide) lucide.createIcons(); return; } // Fetch real orders from database for accurate dashboard let orders = []; try { const res = await fetch('api/orders.php'); const data = await res.json(); if (data.success && Array.isArray(data.orders)) { orders = data.orders; } } catch (e) { } const totalSales = orders.reduce((sum, o) => sum + parseFloat(o.total_amount || 0), 0); const pendingOrders = orders.filter(o => o.status === 'pending').length; container.innerHTML = ` <div class="admin-layout"> <div class="admin-sidebar"> <div class="admin-sidebar-title">Good boy Shop Backoffice</div> <div class="admin-sidebar-sub">ระบบจัดการหลังบ้าน</div> <ul class="admin-nav"> <li><a class="active" onclick="switchAdminTab('dashboard')"><i data-lucide="layout-dashboard"></i> สรุปภาพรวม</a></li> <li><a onclick="switchAdminTab('seller-requests')"><i data-lucide="user-check"></i> คำขอเป็นผู้ขาย <span id="admin-seller-req-badge" class="badge" style="position:static;display:inline-block;margin-left:6px;background:var(--warning);color:#fff;font-size:11px;">0</span></a></li> <li> <a onclick="switchAdminTab('orders')"> <i data-lucide="shopping-bag"></i> จัดการคำสั่งซื้อ (${orders.length}) ${pendingOrders > 0 ? `<span class="badge" style="position:static;display:inline-block;margin-left:6px;background:var(--primary);color:#fff;font-size:11px;">${pendingOrders} รอตรวจ</span>` : ''} </a> </li> <li><a onclick="switchAdminTab('products')"><i data-lucide="package"></i> จัดการสินค้า (${STATE.products.length})</a></li> <li><a onclick="switchAdminTab('users')"><i data-lucide="users"></i> ผู้ใช้ระบบ (${STATE.users.length})</a></li> <li><a onclick="navigateTo('home')" style="color:var(--warning);margin-top:20px;"><i data-lucide="store"></i> กลับไปหน้าร้าน</a></li> </ul> </div> <div class="admin-main"> <div id="admin-tab-content"> <h3>ภาพรวมร้านค้า Good boy Shop</h3> <div class="stats-grid"> <div class="stat-card"> <div class="stat-icon" style="background:#e0f2fe;color:#0284c7;"><i data-lucide="dollar-sign"></i></div> <div class="stat-value">฿${totalSales.toLocaleString()}</div> <div class="stat-label">ยอดขายทั้งหมด</div> <div class="stat-change stat-up">↑ +32% จากเดือนที่แล้ว</div> </div> <div class="stat-card"> <div class="stat-icon" style="background:#fef3c7;color:#d97706;"><i data-lucide="shopping-cart"></i></div> <div class="stat-value">${orders.length}</div> <div class="stat-label">คำสั่งซื้อทั้งหมด</div> <div class="stat-change stat-up">${pendingOrders > 0 ? `⏳ รออนุมัติ ${pendingOrders} รายการ` : '✓ ตรวจสอบครบแล้ว'}</div> </div> <div class="stat-card"> <div class="stat-icon" style="background:#dcfce7;color:#16a34a;"><i data-lucide="box"></i></div> <div class="stat-value">${STATE.products.length}</div> <div class="stat-label">สินค้าทั้งหมด</div> <div class="stat-change">คงที่ (สต๊อกสินค้า)</div> </div> <div class="stat-card"> <div class="stat-icon" style="background:#f3e8ff;color:#9333ea;"><i data-lucide="users"></i></div> <div class="stat-value">${STATE.users.length}</div> <div class="stat-label">สมาชิกทั้งหมด</div> <div class="stat-change stat-up">↑ +5 ใหม่</div> </div> </div> <div class="chart-placeholder"> <i data-lucide="bar-chart-3" style="width:48px;height:48px;"></i> <h4>กราฟยอดขายสินค้าประจำเดือนนี้</h4> <p style="font-size:13px;color:var(--dark-muted);">ยอดขายเติบโตขึ้นถึง +32% จากเดือนที่แล้ว เนื่องมาจากแคมเปญโค้ดส่วนลด GOODBOY10</p> </div> <div style="display:flex;justify-content:space-between;align-items:center;margin:24px 0 12px;"> <h4 style="font-size:16px;margin:0;">คำสั่งซื้อล่าสุด</h4> <button class="view-all-btn" onclick="switchAdminTab('orders')">ดูและจัดการทั้งหมด →</button> </div> <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>รหัสคำสั่งซื้อ</th> <th>ลูกค้า</th> <th>ยอดเงิน</th> <th>สลิป</th> <th>สถานะ</th> <th>วันที่</th> </tr> </thead> <tbody> ${orders.length === 0 ? ` <tr><td colspan="6" style="text-align:center;color:var(--gray-500);padding:30px;">ยังไม่มีรายการสั่งซื้อ</td></tr> ` : orders.slice(0, 5).map(o => { const statusLabel = { pending: 'รอดำเนินการ', shipping: 'กำลังจัดส่ง', delivered: 'จัดส่งแล้ว', cancelled: 'ยกเลิก' }; const statusClass = { pending: 'status-pending', shipping: 'status-shipping', delivered: 'status-delivered', cancelled: 'status-cancelled' }; const orderDate = o.created_at ? new Date(o.created_at).toLocaleDateString('th-TH') : '-'; return ` <tr> <td><b>#${o.order_id}</b></td> <td>${o.customer_name || '-'}</td> <td style="color:var(--primary);font-weight:700;">฿${parseFloat(o.total_amount || 0).toLocaleString()}</td> <td> ${o.payment_slip ? `<button class="coupon-btn" onclick="viewSlipImage('${o.payment_slip}', ${o.order_id})" style="padding:4px 8px;font-size:11px;"><i data-lucide="image" style="width:12px;height:12px;display:inline-block;vertical-align:middle;"></i> ดูสลิป</button>` : '<span style="color:var(--gray-500);font-size:12px;">ไม่มี</span>'} </td> <td><span class="status-badge ${statusClass[o.status] || 'status-pending'}">${statusLabel[o.status] || o.status}</span></td> <td style="font-size:12px;color:var(--gray-500);">${orderDate}</td> </tr> `; }).join('')} </tbody> </table> </div> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); fetchAdminSellerPendingCount(); } async function fetchAdminSellerPendingCount() { try { const res = await fetch('api/admin_seller_applications.php?status=pending'); const data = await res.json(); const badge = document.getElementById('admin-seller-req-badge'); if (badge && data.applications) { badge.textContent = data.applications.length; badge.style.display = data.applications.length > 0 ? 'inline-block' : 'none'; } } catch (e) { } } function switchAdminTab(tab) { const content = document.getElementById('admin-tab-content'); document.querySelectorAll('.admin-nav a').forEach(a => a.classList.remove('active')); if (event && event.currentTarget) event.currentTarget.classList.add('active'); if (tab === 'dashboard') renderAdminView(); if (tab === 'seller-requests') { content.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;"> <div> <h3>คำขอเปิดร้านค้า / สิทธิ์ผู้ขาย (Seller Applications)</h3> <p style="color:var(--gray-500);font-size:14px;margin-top:4px;">ตรวจสอบและอนุมัติผู้ใช้งานที่ยื่นคำขอเป็นผู้ขายบนระบบ Good boy Shop</p> </div> </div> <div id="admin-seller-list-container"> <p style="color:var(--gray-500);">กำลังโหลดรายการคำขอ...</p> </div> `; loadAdminSellerApplications(); } if (tab === 'orders') { content.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;flex-wrap:wrap;gap:10px;"> <div> <h3 style="margin-bottom:4px;">📦 จัดการคำสั่งซื้อและอนุมัติสลิป</h3> <p style="color:var(--gray-500);font-size:13px;">ตรวจสอบสลิปการโอนเงิน และอนุมัติ / ปฏิเสธคำสั่งซื้อแต่ละรายการ</p> </div> <button class="coupon-btn" onclick="loadAdminOrders()" style="gap:6px;"> <i data-lucide="refresh-cw" style="width:14px;height:14px;"></i> รีเฟรช </button> </div> <div id="admin-orders-container"> <div style="text-align:center;padding:40px;color:var(--gray-500);"> <i data-lucide="loader" style="width:32px;height:32px;animation:spin 1s linear infinite;"></i> <p style="margin-top:12px;">กำลังโหลดรายการคำสั่งซื้อ...</p> </div> </div> `; if (window.lucide) lucide.createIcons(); loadAdminOrders(); } if (tab === 'products') { renderAdminProductsTab(content); } if (tab === 'users') { content.innerHTML = ` <h3>รายชื่อผู้ใช้งานที่ลงทะเบียน (User Directory)</h3> <p style="color:var(--gray-500);margin-bottom:20px;font-size:14px;">ข้อมูลลูกค้าที่ลงทะเบียนเข้ามาทั้งหมดของระบบ Good boy Shop</p> <table class="admin-table"> <thead> <tr> <th>ID</th> <th>ชื่อ-นามสกุล</th> <th>อีเมล</th> <th>สิทธิ์ผู้ใช้งาน</th> <th>สถานะ Seller</th> </tr> </thead> <tbody> ${STATE.users.map(u => ` <tr> <td>#${u.id}</td> <td><b>${u.name}</b></td> <td>${u.email}</td> <td><span class="status-badge ${u.role === 'admin' ? 'status-shipping' : (u.role === 'seller' ? 'status-delivered' : 'status-delivered')}">${u.role}</span></td> <td><span class="status-badge ${u.seller_status === 'approved' ? 'status-delivered' : (u.seller_status === 'pending' ? 'status-pending' : (u.seller_status === 'rejected' ? 'status-cancelled' : ''))}">${u.seller_status || 'none'}</span></td> </tr> `).join('')} </tbody> </table> `; } if (window.lucide) lucide.createIcons(); } async function loadAdminSellerApplications() { const container = document.getElementById('admin-seller-list-container'); if (!container) return; try { const res = await fetch('api/admin_seller_applications.php?status=all'); const data = await res.json(); const apps = data.applications || []; if (apps.length === 0) { container.innerHTML = ` <div style="text-align:center;padding:40px;background:var(--gray-100);border-radius:var(--radius-md);"> <i data-lucide="check-circle" style="width:48px;height:48px;color:var(--success);margin-bottom:12px;"></i> <h4>ไม่มีคำขอที่รอการพิจารณา</h4> <p style="color:var(--gray-500);margin-top:4px;">ปัจจุบันไม่มีคำขอสมัครผู้ขายใหม่</p> </div> `; if (window.lucide) lucide.createIcons(); return; } container.innerHTML = ` <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>ID</th> <th>ผู้สมัคร</th> <th>อีเมล / เบอร์โทร</th> <th>ชื่อร้านค้า</th> <th>วันที่ยื่นคำขอ</th> <th>สถานะ</th> <th>การดำเนินการ</th> </tr> </thead> <tbody> ${apps.map(app => { let statusBadge = `<span class="status-badge status-pending">⏳ รออนุมัติ (Pending)</span>`; if (app.status === 'approved') statusBadge = `<span class="status-badge status-delivered">✅ อนุมัติแล้ว</span>`; if (app.status === 'rejected') statusBadge = `<span class="status-badge status-cancelled">❌ ปฏิเสธแล้ว</span>`; return ` <tr> <td>#${app.application_id}</td> <td><b>${app.full_name}</b></td> <td> <div>${app.email}</div> <div style="font-size:12px;color:var(--gray-500);">${app.phone || '-'}</div> </td> <td><span style="color:var(--primary);font-weight:700;">${app.shop_name}</span></td> <td>${new Date(app.applied_at).toLocaleDateString('th-TH')}</td> <td>${statusBadge}</td> <td> ${app.status === 'pending' ? ` <div style="display:flex;gap:8px;"> <button class="primary-btn" style="padding:6px 12px;font-size:12px;background:var(--success);" onclick="processSellerApplication(${app.application_id}, ${app.user_id}, 'approve')"> <i data-lucide="check" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:2px;"></i> อนุมัติ </button> <button class="cart-item-remove" style="padding:6px 12px;border-radius:var(--radius-sm);font-size:12px;" onclick="processSellerApplication(${app.application_id}, ${app.user_id}, 'reject')"> <i data-lucide="x" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:2px;"></i> ปฏิเสธ </button> </div> ` : ` <span style="font-size:12px;color:var(--gray-500);">${app.reviewed_at ? 'ดำเนินการแล้วเมื่อ ' + new Date(app.reviewed_at).toLocaleDateString('th-TH') : '-'}</span> `} </td> </tr> `; }).join('')} </tbody> </table> </div> `; if (window.lucide) lucide.createIcons(); } catch (err) { container.innerHTML = `<p style="color:var(--danger);">เกิดข้อผิดพลาดในการโหลดข้อมูลคำขอ</p>`; } } async function processSellerApplication(appId, userId, action) { const actionText = action === 'approve' ? 'อนุมัติคำขอเป็นผู้ขาย' : 'ปฏิเสธคำขอ'; if (!confirm(`คุณต้องการ ${actionText} ใช่หรือไม่?`)) return; try { const res = await fetch('api/admin_seller_applications.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ application_id: appId, user_id: userId, action }) }); const data = await res.json(); if (data.success) { showToast(data.message, 'success'); loadAdminSellerApplications(); fetchAdminSellerPendingCount(); } else { showToast(data.message || 'เกิดข้อผิดพลาด', 'error'); } } catch (e) { showToast('เกิดข้อผิดพลาดในการส่งข้อมูล', 'error'); } } // ─────── ADMIN ORDERS MANAGEMENT ───────────────────────────────────────────── async function loadAdminOrders() { const container = document.getElementById('admin-orders-container'); if (!container) return; container.innerHTML = ` <div style="text-align:center;padding:40px;color:var(--gray-500);"> <div style="width:36px;height:36px;border:3px solid var(--primary);border-top-color:transparent;border-radius:50%;animation:spin 0.8s linear infinite;margin:0 auto 12px;"></div> <p>กำลังโหลดรายการคำสั่งซื้อ...</p> </div>`; try { const res = await fetch('api/orders.php'); const data = await res.json(); if (data.success && Array.isArray(data.orders)) { renderAdminOrdersTable(data.orders); } else { container.innerHTML = `<p style="color:var(--danger);text-align:center;padding:20px;">ไม่สามารถโหลดรายการคำสั่งซื้อได้: ${data.message || 'เกิดข้อผิดพลาด'}</p>`; } } catch (e) { container.innerHTML = `<p style="color:var(--danger);text-align:center;padding:20px;">เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์</p>`; } } function renderAdminOrdersTable(orders) { const container = document.getElementById('admin-orders-container'); if (!container) return; if (!orders || orders.length === 0) { container.innerHTML = ` <div style="text-align:center;padding:60px;color:var(--gray-500);"> <div style="font-size:48px;margin-bottom:12px;">📭</div> <h4>ยังไม่มีคำสั่งซื้อในระบบ</h4> <p style="font-size:13px;margin-top:6px;">เมื่อลูกค้าสั่งซื้อสินค้าจะแสดงที่นี่</p> </div>`; return; } const statusLabel = { pending: 'รอดำเนินการ (รอตรวจสลิป)', shipping: 'อนุมัติแล้ว / กำลังจัดส่ง', delivered: 'จัดส่งสำเร็จแล้ว', cancelled: 'ยกเลิก / ปฏิเสธ' }; const statusClass = { pending: 'status-pending', shipping: 'status-shipping', delivered: 'status-delivered', cancelled: 'status-cancelled' }; const paymentLabel = { promptpay: '📱 PromptPay / QR', credit: '💳 บัตรเครดิต', cod: '🚚 เก็บเงินปลายทาง' }; container.innerHTML = ` <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>รหัสออเดอร์</th> <th>ลูกค้า</th> <th>การชำระเงิน</th> <th>ยอดรวม</th> <th>สลิปโอนเงิน</th> <th>สถานะ</th> <th>การจัดการ</th> </tr> </thead> <tbody> ${orders.map(o => { const status = o.status || 'pending'; const hasSlip = !!o.payment_slip; const payMethod = paymentLabel[o.payment_method] || o.payment_method; const orderDate = o.created_at ? new Date(o.created_at).toLocaleDateString('th-TH', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' }) : '-'; return ` <tr id="order-row-${o.order_id}"> <td style="min-width:120px;"> <b>#${o.order_id}</b><br> <small style="color:var(--gray-500);font-size:11px;">${orderDate}</small> </td> <td> <div style="font-weight:600;">${o.customer_name || '-'}</div> <small style="color:var(--gray-500);font-size:11px;">${o.customer_email || ''}</small> </td> <td style="font-size:13px;">${payMethod}</td> <td style="color:var(--primary);font-weight:700;">฿${parseFloat(o.total_amount || 0).toLocaleString()}</td> <td style="text-align:center;"> ${hasSlip ? `<button class="coupon-btn" onclick="viewSlipImage('${o.payment_slip}', ${o.order_id})" title="ดูสลิปการโอนเงิน" style="padding:6px 10px;background:rgba(233,30,140,0.15);border-color:var(--primary);"> <i data-lucide="image" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></i>ดูสลิป </button>` : `<span style="color:var(--gray-500);font-size:12px;">${o.payment_method === 'cod' ? '(COD)' : '❌ ไม่มีสลิป'}</span>` } </td> <td> <span class="status-badge ${statusClass[status] || 'status-pending'}" id="order-status-badge-${o.order_id}"> ${statusLabel[status] || status} </span> </td> <td> <div style="display:flex;flex-direction:column;gap:6px;min-width:140px;" id="order-actions-${o.order_id}"> ${status === 'pending' ? ` <button class="primary-btn" style="padding:6px 10px;font-size:12px;justify-content:center;" onclick="updateOrderStatus(${o.order_id}, 'shipping')"> ✅ อนุมัติคำสั่งซื้อ </button> <button class="admin-img-btn-delete" style="padding:6px 10px;font-size:12px;justify-content:center;" onclick="updateOrderStatus(${o.order_id}, 'cancelled')"> ❌ ปฏิเสธ </button> ` : status === 'shipping' ? ` <button class="coupon-btn" style="padding:6px 10px;font-size:12px;" onclick="updateOrderStatus(${o.order_id}, 'delivered')"> 📦 ยืนยันจัดส่งแล้ว </button> ` : ` <span style="font-size:12px;color:var(--gray-500);">-</span> `} </div> </td> </tr>`; }).join('')} </tbody> </table> </div> `; if (window.lucide) lucide.createIcons(); } function viewSlipImage(slipPath, orderId) { const modalContent = document.getElementById('confirm-modal-content'); if (!modalContent) { window.open(slipPath, '_blank'); return; } modalContent.innerHTML = ` <div style="text-align:center;"> <h3 style="font-family:var(--font-heading);font-size:18px;font-weight:800;margin-bottom:16px;color:var(--dark);"> 📎 สลิปการโอนเงิน — คำสั่งซื้อ #${orderId} </h3> <div style="background:#111;padding:8px;border-radius:var(--radius-md);margin-bottom:12px;"> <img src="${slipPath}" alt="สลิปการโอนเงิน" style="max-width:100%;max-height:420px;object-fit:contain;border-radius:var(--radius-md);cursor:pointer;" onclick="window.open('${slipPath}', '_blank')" onerror="this.style.display='none'; document.getElementById('slip-load-err').style.display='block';" > <div id="slip-load-err" style="display:none;color:var(--danger);padding:20px;">❌ ไม่สามารถโหลดรูปสลิปได้</div> </div> <p style="font-size:12px;color:var(--gray-500);margin-top:4px;">คลิกรูปเพื่อเปิดดูขนาดเต็ม</p> <div style="display:flex;gap:10px;margin-top:16px;"> <button class="btn-add-cart" style="flex:1;padding:10px;border-radius:var(--radius-xl);font-weight:700;border:none;cursor:pointer;" onclick="closeModal('confirm-modal')">ปิด</button> <a href="${slipPath}" target="_blank" class="primary-btn" style="flex:1;text-align:center;text-decoration:none;display:flex;align-items:center;justify-content:center;gap:6px;"> <i data-lucide="external-link" style="width:14px;height:14px;"></i> เปิดรูปเต็ม </a> </div> </div> `; openModal('confirm-modal'); if (window.lucide) lucide.createIcons(); } async function updateOrderStatus(orderId, newStatus) { const actionsEl = document.getElementById(`order-actions-${orderId}`); const badgeEl = document.getElementById(`order-status-badge-${orderId}`); if (actionsEl) { actionsEl.innerHTML = '<div style="font-size:12px;color:var(--gray-500);">⏳ กำลังอัปเดต...</div>'; } try { const res = await fetch('api/orders.php', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order_id: orderId, status: newStatus }) }); const data = await res.json(); if (data.success) { showToast(data.message, 'success'); const statusLabel = { pending: 'รอดำเนินการ (รอตรวจสลิป)', shipping: 'อนุมัติแล้ว / กำลังจัดส่ง', delivered: 'จัดส่งสำเร็จแล้ว', cancelled: 'ยกเลิก / ปฏิเสธ' }; const statusClass = { pending: 'status-pending', shipping: 'status-shipping', delivered: 'status-delivered', cancelled: 'status-cancelled' }; if (badgeEl) { badgeEl.className = `status-badge ${statusClass[newStatus] || ''}`; badgeEl.textContent = statusLabel[newStatus] || newStatus; } if (actionsEl) { if (newStatus === 'shipping') { actionsEl.innerHTML = `<button class="coupon-btn" style="padding:6px 10px;font-size:12px;" onclick="updateOrderStatus(${orderId}, 'delivered')">📦 ยืนยันจัดส่งแล้ว</button>`; } else { actionsEl.innerHTML = '<span style="font-size:12px;color:var(--gray-500);">-</span>'; } } if (window.lucide) lucide.createIcons(); } else { showToast(data.message || 'เกิดข้อผิดพลาด', 'error'); loadAdminOrders(); } } catch (err) { showToast('เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์', 'error'); loadAdminOrders(); } } // ─────── ADMIN PRODUCT MODAL (Add / Edit & Image Management) ─────────────────────────────── let currentProductImageFile = null; let currentProductImageUrl = ''; let currentImageMode = 'upload'; // 'upload' | 'url' function renderProductModal(product = null) { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เฉพาะแอดมินเท่านั้นที่สามารถจัดการสินค้าได้', 'error'); return; } const isEdit = !!product; currentProductImageFile = null; currentProductImageUrl = isEdit ? (product.image || '') : ''; currentImageMode = currentProductImageUrl && (currentProductImageUrl.startsWith('http://') || currentProductImageUrl.startsWith('https://')) ? 'url' : 'upload'; const content = document.getElementById('auth-modal-content'); content.innerHTML = ` <div style="text-align:center;margin-bottom:20px;"> <div style="display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;background:linear-gradient(135deg,var(--primary),#ff8c42);border-radius:var(--radius-md);margin-bottom:12px;"> <i data-lucide="${isEdit ? 'pencil' : 'plus-circle'}" style="width:24px;height:24px;color:#fff;"></i> </div> <h2 style="font-family:var(--font-heading);font-size:20px;font-weight:700;">${isEdit ? '✏️ แก้ไขข้อมูลสินค้า' : '📦 เพิ่มสินค้าใหม่'}</h2> <p style="font-size:13px;color:var(--gray-500);margin-top:4px;">สิทธิ์เฉพาะ Admin เท่านั้น</p> </div> <form id="admin-product-form" onsubmit="saveProductForm(event, ${isEdit ? product.id : 'null'})"> <div class="form-group"> <label>ชื่อสินค้า *</label> <input type="text" id="pm-name" required placeholder="ระบุชื่อสินค้า" value="${isEdit ? (product.name || '').replace(/"/g, '"') : ''}"> <div class="form-error-msg" id="err-pm-name">กรุณาระบุชื่อสินค้า</div> </div> <div class="form-grid-2"> <div class="form-group"> <label>หมวดหมู่ *</label> <select id="pm-category" required> <option value="electronics" ${isEdit && product.category === 'electronics' ? 'selected' : ''}>🎧 อิเล็กทรอนิกส์</option> <option value="fashion" ${isEdit && product.category === 'fashion' ? 'selected' : ''}>👗 แฟชั่น</option> <option value="cosmetics" ${isEdit && product.category === 'cosmetics' ? 'selected' : ''}>💄 เครื่องสำอาง</option> <option value="home" ${isEdit && product.category === 'home' ? 'selected' : ''}>🧸 ของใช้ในบ้าน</option> <option value="food" ${isEdit && product.category === 'food' ? 'selected' : ''}>🧋 อาหาร-เครื่องดื่ม</option> <option value="sports" ${isEdit && product.category === 'sports' ? 'selected' : ''}>🏸 กีฬา-สุขภาพ</option> </select> <div class="form-error-msg" id="err-pm-category">กรุณาเลือกหมวดหมู่</div> </div> <div class="form-group"> <label>ป้าย Badge</label> <select id="pm-badge"> <option value="" ${isEdit && !product.badge ? 'selected' : ''}>ไม่มีป้าย</option> <option value="sale" ${isEdit && product.badge === 'sale' ? 'selected' : ''}>ลดราคา (sale)</option> <option value="new" ${isEdit && product.badge === 'new' ? 'selected' : ''}>สินค้าใหม่ (new)</option> <option value="hot" ${isEdit && product.badge === 'hot' ? 'selected' : ''}>ขายดี (hot)</option> </select> </div> </div> <div class="form-grid-2"> <div class="form-group"> <label>ราคาขาย (฿) *</label> <input type="number" id="pm-price" required min="1" step="any" placeholder="เช่น 990" value="${isEdit ? product.price : ''}"> <div class="form-error-msg" id="err-pm-price">กรุณาระบุราคาขายที่ถูกต้อง (มากกว่า 0)</div> </div> <div class="form-group"> <label>ราคาเดิม / ก่อนลด (฿)</label> <input type="number" id="pm-original-price" min="0" step="any" placeholder="เช่น 1990" value="${isEdit ? (product.originalPrice || '') : ''}"> <div class="form-error-msg" id="err-pm-original-price">ราคาเดิมต้องเป็นตัวเลขที่มากกว่าหรือเท่ากับ 0</div> </div> </div> <!-- ระบบจัดการรูปภาพสินค้า --> <div class="form-group"> <label>รูปภาพสินค้า</label> <div class="admin-img-tab-selector"> <button type="button" class="admin-img-tab-btn ${currentImageMode === 'upload' ? 'active' : ''}" id="tab-btn-upload" onclick="switchImageInputMode('upload')"> <i data-lucide="upload" style="width:14px;height:14px;"></i> อัปโหลดรูปจากเครื่อง </button> <button type="button" class="admin-img-tab-btn ${currentImageMode === 'url' ? 'active' : ''}" id="tab-btn-url" onclick="switchImageInputMode('url')"> <i data-lucide="link" style="width:14px;height:14px;"></i> ใช้ Image URL </button> </div> <!-- 1. ส่วนอัปโหลดรูปภาพ --> <div id="image-upload-section" style="${currentImageMode === 'upload' ? '' : 'display:none;'}"> <input type="file" id="pm-image-file" accept="image/jpeg,image/png,image/webp,image/jpg" style="display:none;" onchange="handleAdminProductImageSelect(this)"> <!-- Preview กล่องรูปภาพเมื่อมีรูปเดิมหรือเลือกรูปใหม่แล้ว --> <div id="admin-image-preview-container" class="admin-img-preview-container" style="${currentProductImageUrl ? '' : 'display:none;'}"> <div class="admin-img-preview-wrap"> <img id="admin-image-preview-img" src="${currentProductImageUrl}" alt="Product Preview"> </div> <div style="font-size:12px;color:var(--gray-500);text-align:center;" id="admin-image-info"> ${isEdit && currentProductImageUrl ? 'รูปภาพปัจจุบันของสินค้านี้' : ''} </div> <div class="admin-img-actions"> <button type="button" class="admin-img-btn-change" onclick="triggerAdminImageSelect()"> <i data-lucide="camera" style="width:14px;height:14px;"></i> เปลี่ยนรูป </button> <button type="button" class="admin-img-btn-delete" onclick="removeAdminProductImage()"> <i data-lucide="trash-2" style="width:14px;height:14px;"></i> ลบรูป </button> </div> </div> <!-- กล่อง Dropzone เมื่อยังไม่มีรูปภาพ --> <div id="admin-image-dropzone" class="admin-img-upload-box" style="${currentProductImageUrl ? 'display:none;' : ''}" onclick="triggerAdminImageSelect()"> <div style="font-size:32px;margin-bottom:8px;">🖼️</div> <div style="font-weight:700;font-size:14px;color:var(--primary);margin-bottom:4px;">คลิกเพื่อเลือกรูปภาพสินค้า</div> <div style="font-size:12px;color:var(--gray-500);">รองรับไฟล์ JPG, PNG, WEBP ขนาดไม่เกิน 5MB</div> </div> </div> <!-- 2. ส่วนกรอก URL --> <div id="image-url-section" style="${currentImageMode === 'url' ? '' : 'display:none;'}"> <input type="url" id="pm-image-url" placeholder="https://images.unsplash.com/..." value="${currentProductImageUrl}" oninput="handleAdminImageUrlInput(this.value)"> <small style="color:var(--gray-500);font-size:11px;display:block;margin-top:4px;">กรอกที่อยู่ URL ของรูปภาพ เช่น https://...</small> <div id="admin-url-preview-container" class="admin-img-preview-container" style="margin-top:10px;${currentProductImageUrl ? '' : 'display:none;'}"> <div class="admin-img-preview-wrap"> <img id="admin-url-preview-img" src="${currentProductImageUrl}" alt="URL Preview" onerror="this.src='https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800'"> </div> </div> </div> <div class="form-error-msg" id="err-pm-image"></div> </div> <div class="form-group"> <label>รายละเอียดสินค้า</label> <textarea id="pm-description" placeholder="รายละเอียด จุดเด่น และข้อมูลสินค้า...">${isEdit ? (product.description || '') : ''}</textarea> </div> <div style="display:flex;gap:10px;margin-top:16px;"> <button type="button" class="btn-add-cart" style="flex:1;padding:12px;border-radius:var(--radius-xl);font-weight:700;font-size:14px;cursor:pointer;border:none;" onclick="closeModal('auth-modal')">ยกเลิก</button> <button type="submit" id="pm-submit-btn" class="primary-btn" style="flex:2;"> ${isEdit ? '💾 บันทึกการเปลี่ยนแปลง' : '+ เพิ่มสินค้าใหม่'} </button> </div> </form> `; openModal('auth-modal'); if (window.lucide) lucide.createIcons(); } function switchImageInputMode(mode) { currentImageMode = mode; const tabUpload = document.getElementById('tab-btn-upload'); const tabUrl = document.getElementById('tab-btn-url'); const sectionUpload = document.getElementById('image-upload-section'); const sectionUrl = document.getElementById('image-url-section'); if (tabUpload && tabUrl && sectionUpload && sectionUrl) { if (mode === 'upload') { tabUpload.classList.add('active'); tabUrl.classList.remove('active'); sectionUpload.style.display = ''; sectionUrl.style.display = 'none'; } else { tabUrl.classList.add('active'); tabUpload.classList.remove('active'); sectionUrl.style.display = ''; sectionUpload.style.display = 'none'; // Sync preview if url input has value const urlInput = document.getElementById('pm-image-url'); if (urlInput) handleAdminImageUrlInput(urlInput.value); } } } function triggerAdminImageSelect() { const fileInput = document.getElementById('pm-image-file'); if (fileInput) fileInput.click(); } function handleAdminProductImageSelect(input) { const previewContainer = document.getElementById('admin-image-preview-container'); const dropzone = document.getElementById('admin-image-dropzone'); const previewImg = document.getElementById('admin-image-preview-img'); const infoEl = document.getElementById('admin-image-info'); const errorEl = document.getElementById('err-pm-image'); if (errorEl) { errorEl.textContent = ''; errorEl.classList.remove('visible'); } if (input.files && input.files[0]) { const file = input.files[0]; // ตรวจสอบประเภทไฟล์ const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!validTypes.includes(file.type.toLowerCase())) { showToast('ประเภทไฟล์ไม่ถูกต้อง (รองรับเฉพาะ JPG, PNG, WEBP)', 'error'); if (errorEl) { errorEl.textContent = 'ประเภทไฟล์ไม่ถูกต้อง (รองรับเฉพาะ JPG, PNG, WEBP)'; errorEl.classList.add('visible'); } input.value = ''; return; } // ตรวจสอบขนาดไฟล์ (ไม่เกิน 5MB) if (file.size > 5 * 1024 * 1024) { showToast('ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB', 'error'); if (errorEl) { errorEl.textContent = 'ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB'; errorEl.classList.add('visible'); } input.value = ''; return; } currentProductImageFile = file; // Preview รูปภาพทันทีด้วย FileReader const reader = new FileReader(); reader.onload = function (e) { if (previewImg) previewImg.src = e.target.result; if (infoEl) infoEl.textContent = `ไฟล์ที่เลือก: ${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} MB)`; if (previewContainer) previewContainer.style.display = 'flex'; if (dropzone) dropzone.style.display = 'none'; if (window.lucide) lucide.createIcons(); }; reader.readAsDataURL(file); } } function removeAdminProductImage() { currentProductImageFile = null; currentProductImageUrl = ''; const fileInput = document.getElementById('pm-image-file'); if (fileInput) fileInput.value = ''; const urlInput = document.getElementById('pm-image-url'); if (urlInput) urlInput.value = ''; const previewContainer = document.getElementById('admin-image-preview-container'); const dropzone = document.getElementById('admin-image-dropzone'); const urlPreview = document.getElementById('admin-url-preview-container'); if (previewContainer) previewContainer.style.display = 'none'; if (dropzone) dropzone.style.display = ''; if (urlPreview) urlPreview.style.display = 'none'; } function handleAdminImageUrlInput(url) { const trimmed = (url || '').trim(); currentProductImageUrl = trimmed; const urlPreview = document.getElementById('admin-url-preview-container'); const urlImg = document.getElementById('admin-url-preview-img'); if (trimmed && urlPreview && urlImg) { urlImg.src = trimmed; urlPreview.style.display = 'flex'; } else if (urlPreview) { urlPreview.style.display = 'none'; } } function openAddProductModal() { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เฉพาะแอดมินเท่านั้นที่สามารถเพิ่มสินค้าได้', 'error'); return; } renderProductModal(null); } function editProduct(productId) { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เฉพาะแอดมินเท่านั้นที่สามารถแก้ไขสินค้าได้', 'error'); return; } const product = STATE.products.find(p => p.id === productId); if (!product) { showToast('ไม่พบข้อมูลสินค้านี้', 'error'); return; } renderProductModal(product); } async function saveProductForm(e, productId) { e.preventDefault(); if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('ไม่มีสิทธิ์ดำเนินการนี้ (เฉพาะแอดมินเท่านั้น)', 'error'); return; } const nameInput = document.getElementById('pm-name'); const categorySelect = document.getElementById('pm-category'); const badgeSelect = document.getElementById('pm-badge'); const priceInput = document.getElementById('pm-price'); const originalPriceInput = document.getElementById('pm-original-price'); const descTextarea = document.getElementById('pm-description'); const submitBtn = document.getElementById('pm-submit-btn'); const name = nameInput ? nameInput.value.trim() : ''; const category = categorySelect ? categorySelect.value : ''; const badge = badgeSelect ? badgeSelect.value : ''; const price = priceInput ? parseFloat(priceInput.value) : 0; const originalPrice = (originalPriceInput && originalPriceInput.value) ? parseFloat(originalPriceInput.value) : null; const description = descTextarea ? descTextarea.value.trim() : ''; // Clear previous inline errors document.querySelectorAll('.form-error-msg').forEach(el => { el.textContent = ''; el.classList.remove('visible'); }); let hasError = false; if (!name) { const err = document.getElementById('err-pm-name'); if (err) { err.textContent = 'กรุณาระบุชื่อสินค้า'; err.classList.add('visible'); } hasError = true; } if (!category) { const err = document.getElementById('err-pm-category'); if (err) { err.textContent = 'กรุณาเลือกหมวดหมู่'; err.classList.add('visible'); } hasError = true; } if (isNaN(price) || price <= 0) { const err = document.getElementById('err-pm-price'); if (err) { err.textContent = 'ราคาขายต้องเป็นตัวเลขที่มากกว่า 0'; err.classList.add('visible'); } hasError = true; } if (originalPrice !== null && (isNaN(originalPrice) || originalPrice < 0)) { const err = document.getElementById('err-pm-original-price'); if (err) { err.textContent = 'ราคาเดิมต้องเป็นตัวเลขที่มากกว่าหรือเท่ากับ 0'; err.classList.add('visible'); } hasError = true; } if (hasError) { showToast('กรุณากรอกข้อมูลให้ถูกต้องครบถ้วน', 'error'); return; } // Set Loading State const originalBtnText = submitBtn ? submitBtn.innerHTML : '💾 บันทึก'; if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '⏳ กำลังประมวลผล...'; } try { let finalImageUrl = currentProductImageUrl; // Step 1: Upload new image file if selected if (currentProductImageFile) { if (submitBtn) { submitBtn.innerHTML = '⏳ กำลังอัปโหลดรูปภาพ...'; } const formData = new FormData(); formData.append('image', currentProductImageFile); const uploadRes = await fetch('api/upload_product_image.php', { method: 'POST', body: formData }); const uploadData = await uploadRes.json(); if (!uploadData.success || !uploadData.path) { console.error('Image Upload Error:', uploadData); showToast(uploadData.message || 'ไม่สามารถอัปโหลดรูปภาพได้ กรุณาลองใหม่อีกครั้ง', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; } return; } finalImageUrl = uploadData.path; } else if (currentImageMode === 'url') { const urlInput = document.getElementById('pm-image-url'); if (urlInput) { finalImageUrl = urlInput.value.trim(); } } // Default placeholder if empty if (!finalImageUrl && !productId) { finalImageUrl = 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800'; } if (submitBtn) { submitBtn.innerHTML = '⏳ กำลังบันทึก...'; } let badgeText = null; if (badge === 'sale') badgeText = 'ลด 50%'; else if (badge === 'new') badgeText = 'มาใหม่'; else if (badge === 'hot') badgeText = 'ขายดี'; // Step 2: Send data to Backend Database via PUT or POST const isEdit = !!productId; const method = isEdit ? 'PUT' : 'POST'; const payload = { product_id: productId ? parseInt(productId) : undefined, product_name: name, category_id: category, price: price, original_price: originalPrice, image_url: finalImageUrl, badge: badge || null, badge_text: badgeText, description: description, stock: 10 }; const res = await fetch('api/seller_dashboard.php', { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); if (data.success) { showToast(isEdit ? 'แก้ไขสินค้าสำเร็จ' : 'เพิ่มสินค้าสำเร็จ', 'success'); closeModal('auth-modal'); // Refresh from database to ensure real sync await refreshProductsData(); // Refresh Admin Stock Control Table const adminContent = document.getElementById('admin-tab-content'); if (adminContent) { renderAdminProductsTab(adminContent); } } else { console.error('Backend Update Error:', data); showToast(data.message || 'ไม่สามารถบันทึกสินค้าได้ กรุณาลองใหม่อีกครั้ง', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; } } } catch (err) { console.error('Save Product Fetch Error:', err); showToast('ไม่สามารถบันทึกสินค้าได้ กรุณาลองใหม่อีกครั้ง', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; } } } // ─────── DELETE PRODUCT MODAL & BACKEND DELETE ─────────────────────────────── function deleteProduct(id) { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เฉพาะแอดมินเท่านั้นที่สามารถลบสินค้าได้', 'error'); return; } const product = STATE.products.find(p => p.id === id); const productName = product ? product.name : `รหัส #${id}`; const modalContent = document.getElementById('confirm-modal-content'); if (!modalContent) { // Fallback to native confirm if modal element not present if (confirm(`คุณต้องการลบสินค้า "${productName}" ออกจากคลังสินค้าใช่หรือไม่?`)) { executeDeleteProduct(id); } return; } modalContent.innerHTML = ` <div style="text-align:center;padding:10px 0;"> <div style="width:60px;height:60px;background:rgba(239,68,68,0.15);border:2px solid var(--danger);border-radius:50%;display:inline-flex;align-items:center;justify-content:center;margin-bottom:16px;"> <i data-lucide="trash-2" style="width:28px;height:28px;color:var(--danger);"></i> </div> <h3 style="font-family:var(--font-heading);font-size:20px;font-weight:800;margin-bottom:8px;color:var(--dark);">ยืนยันการลบสินค้า</h3> <p style="font-size:14px;color:var(--gray-500);margin-bottom:16px;">คุณแน่ใจหรือไม่ว่าต้องการลบสินค้านี้ออกจากคลังและฐานข้อมูล?</p> <div style="background:var(--gray-100);padding:12px 16px;border-radius:var(--radius-md);border:1px solid var(--gray-200);margin-bottom:24px;text-align:left;"> <div style="font-size:12px;color:var(--gray-500);">ชื่อสินค้าที่จะลบ:</div> <div style="font-weight:700;font-size:15px;color:var(--dark);margin-top:2px;">${productName}</div> </div> <div style="display:flex;gap:12px;"> <button type="button" class="btn-add-cart" style="flex:1;padding:12px;border-radius:var(--radius-xl);font-weight:700;font-size:14px;border:none;cursor:pointer;" onclick="closeModal('confirm-modal')">ยกเลิก</button> <button type="button" id="confirm-delete-btn" class="primary-btn" style="flex:1;background:var(--danger);border-color:var(--danger);" onclick="executeDeleteProduct(${id})">ยืนยันการลบ</button> </div> </div> `; openModal('confirm-modal'); if (window.lucide) lucide.createIcons(); } async function executeDeleteProduct(id) { const confirmBtn = document.getElementById('confirm-delete-btn'); if (confirmBtn) { confirmBtn.disabled = true; confirmBtn.innerHTML = '⏳ กำลังลบ...'; } try { const res = await fetch('api/seller_dashboard.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_id: parseInt(id) }) }); const data = await res.json(); if (data.success) { closeModal('confirm-modal'); showToast(data.message || 'ลบสินค้าสำเร็จ', 'info'); // Refresh from database await refreshProductsData(); // Refresh Stock Control Table immediately const adminContent = document.getElementById('admin-tab-content'); if (adminContent) { renderAdminProductsTab(adminContent); } } else { console.error('Delete Product Error:', data); showToast(data.message || 'ไม่สามารถลบสินค้าได้ กรุณาลองใหม่อีกครั้ง', 'error'); if (confirmBtn) { confirmBtn.disabled = false; confirmBtn.innerHTML = 'ยืนยันการลบ'; } } } catch (err) { console.error('Delete Fetch Error:', err); showToast('เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์', 'error'); if (confirmBtn) { confirmBtn.disabled = false; confirmBtn.innerHTML = 'ยืนยันการลบ'; } } } // Helper: Reload products data from server & update STATE async function refreshProductsData() { try { const res = await fetch('api/get_initial_data.php'); if (res.ok) { const data = await res.json(); if (data.products) { STATE.products = data.products; localStorage.setItem('goodboyshop_products', JSON.stringify(STATE.products)); renderProducts(); } } } catch (e) { console.error('Error refreshing products:', e); } } // Helper: Render Stock Control table in Admin function renderAdminProductsTab(content) { if (!content) return; content.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;"> <h3>จัดการสินค้าทั้งหมด (Stock Control)</h3> <button class="primary-btn" onclick="openAddProductModal()">+ เพิ่มสินค้าใหม่</button> </div> <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>รูปสินค้า</th> <th>ชื่อสินค้า</th> <th>หมวดหมู่</th> <th>ราคาขาย</th> <th>ราคาเดิม</th> <th>การจัดการ</th> </tr> </thead> <tbody> ${STATE.products.map(p => { const cute = CUTE_CATEGORY_CONFIG[p.category] || { emoji: '✨', name: p.category, color: 'var(--primary)' }; return ` <tr> <td><img src="${p.image}" alt="${p.name}" style="width:44px;height:44px;border-radius:8px;object-fit:cover;" onerror="this.src='https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800'"></td> <td style="max-width:200px;"><b>${p.name}</b></td> <td> <span class="status-badge" style="background:rgba(255,255,255,0.06);border:1px solid ${cute.color || 'var(--primary)'}50;color:${cute.color || 'var(--primary)'};"> ${cute.emoji} ${cute.name || p.category} </span> </td> <td style="color:var(--primary);font-weight:700;">฿${p.price.toLocaleString()}</td> <td style="color:var(--gray-500);text-decoration:line-through;">฿${(p.originalPrice || p.price).toLocaleString()}</td> <td style="display:flex;gap:8px;align-items:center;"> <button class="coupon-btn" onclick="editProduct(${p.id})" title="แก้ไขสินค้า"> <i data-lucide="pencil" style="width:14px;height:14px;display:inline-block;vertical-align:middle;margin-right:4px;"></i>แก้ไข </button> <button class="cart-item-remove" onclick="deleteProduct(${p.id})" title="ลบสินค้า"><i data-lucide="trash-2"></i></button> </td> </tr> `; }).join('')} </tbody> </table> </div> `; if (window.lucide) lucide.createIcons(); } function renderSellerApplyTab() { // Will async-check existing application status const html = ` <h3>สมัครเป็นผู้ขาย Good boy Shop</h3> <p style="color:var(--gray-500);margin-bottom:20px;font-size:14px;">เปิดร้านค้าของคุณเองบน Good boy Shop และเริ่มขายสินค้าให้ลูกค้าหลายล้านคน</p> <div id="seller-apply-status">กำลังโหลดข้อมูล...</div> `; // Fetch status after render setTimeout(loadSellerApplicationStatus, 50); return html; } async function loadSellerApplicationStatus() { const statusDiv = document.getElementById('seller-apply-status'); if (!statusDiv) return; try { const res = await fetch('api/seller_application.php'); const data = await res.json(); const app = data.application; if (app && app.status === 'pending') { statusDiv.innerHTML = ` <div style="text-align:center;padding:40px;background:var(--gray-100);border-radius:var(--radius-md);"> <i data-lucide="clock" style="width:48px;height:48px;color:var(--warning);margin-bottom:12px;"></i> <h4 style="color:var(--warning)">อยู่ระหว่างการพิจารณา</h4> <p style="color:var(--gray-500);margin-top:8px;">คำขอเปิดร้าน "${app.shop_name}" อยู่ระหว่างการตรวจสอบอนุมัติจากผู้ดูแลระบบ</p> <p style="color:var(--gray-500);font-size:13px;margin-top:4px;">วันที่ยื่นคำขอ: ${new Date(app.applied_at).toLocaleDateString('th-TH')}</p> </div>`; } else if (app && app.status === 'approved') { statusDiv.innerHTML = ` <div style="text-align:center;padding:40px;background:var(--primary-light);border-radius:var(--radius-md);"> <i data-lucide="check-circle" style="width:48px;height:48px;color:var(--success);margin-bottom:12px;"></i> <h4 style="color:var(--success)">ได้รับอนุมัติแล้ว!</h4> <p style="margin-top:8px;">ร้าน "${app.shop_name}" ได้รับการอนุมัติเรียบร้อยแล้ว</p> <button class="primary-btn" onclick="navigateTo('seller')" style="margin-top:16px;">ไปที่ Seller Dashboard</button> </div>`; } else if (app && app.status === 'rejected') { statusDiv.innerHTML = ` <div style="background:var(--gray-100);border-radius:var(--radius-md);padding:20px;margin-bottom:20px;border-left:4px solid var(--danger);"> <p style="color:var(--danger);font-weight:700;">คำขอถูกปฏิเสธ — สามารถยื่นใหม่ได้อีกครั้ง</p> </div> ${renderSellerApplyForm()}`; } else { // No application yet statusDiv.innerHTML = renderSellerApplyForm(); } } catch (e) { // API not connected — show static form statusDiv.innerHTML = renderSellerApplyForm(); } if (window.lucide) lucide.createIcons(); } function renderSellerApplyForm() { return ` <form onsubmit="handleSellerApply(event)"> <div class="form-group"> <label>ชื่อร้านค้า *</label> <input type="text" id="seller-shop-name" placeholder="ชื่อร้านค้าของคุณ" required> </div> <div class="form-group"> <label>รายละเอียดร้านค้า *</label> <textarea id="seller-shop-desc" placeholder="เล่าเรื่องร้านค้าของคุณ ขายอะไร ประเภทสินค้า" required></textarea> </div> <div class="form-group"> <label>เบอร์ติดต่อ *</label> <input type="tel" id="seller-phone" placeholder="08X-XXX-XXXX" required> </div> <div class="form-group"> <label>ที่อยู่ร้านค้า</label> <textarea id="seller-address" placeholder="ที่อยู่หรือตำแหน่ง (ไม่บังคับ)"></textarea> </div> <div style="background:var(--gray-100);border-radius:var(--radius-md);padding:14px;margin-bottom:20px;font-size:13px;color:var(--dark-muted);"> <b>ข้อกำหนดการสมัคร:</b> ทีมงานจะตรวจสอบคำขอของคุณภายใน 1-3 วันทำการ เมื่อได้รับการอนุมัติแล้วท่านจะสามารถเข้าถึง Seller Dashboard เพื่อเริ่มลงขายสินค้าได้ทันที </div> <button type="submit" class="primary-btn" style="width:100%;"><i data-lucide="send"></i> ส่งคำขอสมัคร</button> </form>`; } async function handleSellerApply(e) { e.preventDefault(); const shopName = document.getElementById('seller-shop-name')?.value.trim(); const desc = document.getElementById('seller-shop-desc')?.value.trim(); const phone = document.getElementById('seller-phone')?.value.trim(); const address = document.getElementById('seller-address')?.value.trim(); // Fallback for when DB not connected: show pending state locally showToast('ส่งคำขอเรียบร้อยแล้ว (อยู่ระหว่างการพิจารณา)', 'success'); const sd = document.getElementById('seller-apply-status'); if (sd) sd.innerHTML = ` <div style="text-align:center;padding:40px;background:var(--gray-100);border-radius:var(--radius-md);"> <i data-lucide="clock" style="width:48px;height:48px;color:var(--warning);margin-bottom:12px;"></i> <h4 style="color:var(--warning)">อยู่ระหว่างการพิจารณา</h4> <p style="color:var(--gray-500);margin-top:8px;">คำขอเปิดร้าน "${shopName}" อยู่ระหว่างการตรวจสอบอนุมัติจากผู้ดูแลระบบ</p> </div>`; if (window.lucide) lucide.createIcons(); } function deleteProduct(id) { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เฉพาะแอดมินเท่านั้นที่สามารถลบสินค้าได้', 'error'); return; } if (confirm('คุณต้องการลบสินค้านี้ออกจากคลังใช่หรือไม่?')) { STATE.products = STATE.products.filter(p => p.id !== id); localStorage.setItem('goodboyshop_products', JSON.stringify(STATE.products)); renderProducts(); const content = document.getElementById('admin-tab-content'); if (content) _renderAdminProductsTab(content); showToast('ลบสินค้าออกจากคลังสำเร็จ', 'info'); } } // Profile View function renderProfileView(tab = 'info') { const container = document.getElementById('profile-content'); if (!container) return; if (!STATE.currentUser) { openAuthModal('login'); return; } const user = STATE.currentUser; const role = user.role; const sellerStatus = user.seller_status || 'none'; const isApprovedSeller = (role === 'seller' && sellerStatus === 'approved') || role === 'admin'; const isPendingSeller = (role === 'seller' && sellerStatus === 'pending'); const isRejectedSeller = (sellerStatus === 'rejected'); let badgeHtml = ''; if (role === 'admin') { badgeHtml = `<span style="background:var(--primary);color:#fff;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;">Admin</span>`; } else if (isApprovedSeller) { badgeHtml = `<span style="background:var(--success);color:#fff;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;">🏪 ผู้ขาย (อนุมัติแล้ว)</span>`; } else if (isPendingSeller) { badgeHtml = `<span style="background:var(--warning);color:#fff;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;">⏳ รอการอนุมัติผู้ขาย</span>`; } else if (isRejectedSeller) { badgeHtml = `<span style="background:var(--danger);color:#fff;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;">❌ คำขอเปิดร้านถูกปฏิเสธ</span>`; } let mainContent = ''; if (tab === 'info') { mainContent = ` <h3>การตั้งค่าข้อมูลส่วนตัว</h3> <form onsubmit="handleProfileSave(event)"> <div class="form-group"> <label>ชื่อ-นามสกุล</label> <input type="text" id="prof-name" value="${user.name}" required> </div> <div class="form-group"> <label>อีเมล</label> <input type="email" value="${user.email}" readonly style="background:var(--gray-100);"> </div> <div class="form-group"> <label>เบอร์โทรศัพท์</label> <input type="text" id="prof-phone" value="${user.phone || '081-234-5678'}" required> </div> <div class="form-group"> <label>ที่อยู่สำหรับจัดส่ง</label> <textarea id="prof-address" required placeholder="ระบุบ้านเลขที่ ถนน แขวง/ตำบล เขต/อำเภอ จังหวัด รหัสไปรษณีย์">${user.address || '123/45 หมู่บ้านสุขใจ แขวงคลองตัน เขตคลองเตย กรุงเทพฯ 10110'}</textarea> </div> <button type="submit" class="primary-btn">บันทึกการเปลี่ยนแปลง</button> </form> `; } else if (tab === 'orders') { mainContent = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;"> <h3 style="margin:0;">ประวัติคำสั่งซื้อของฉัน</h3> <button class="coupon-btn" onclick="loadMyProfileOrders()" style="font-size:12px;padding:4px 10px;"> <i data-lucide="refresh-cw" style="width:12px;height:12px;display:inline-block;vertical-align:middle;margin-right:4px;"></i> รีเฟรช </button> </div> <div id="profile-orders-list"> <p style="color:var(--gray-500);">กำลังโหลดข้อมูลคำสั่งซื้อ...</p> </div> `; setTimeout(loadMyProfileOrders, 50); } else if (tab === 'coupons') { mainContent = ` <h3>คูปองของฉัน</h3> <div style="display:flex;gap:16px;flex-direction:column;margin-top:16px;"> <div style="border:2px dashed var(--primary);border-radius:var(--radius-md);padding:20px;display:flex;justify-content:space-between;align-items:center;background:var(--primary-light);"> <div> <h4 style="color:var(--primary);font-weight:800;font-size:18px;">GOODBOY10</h4> <p style="font-size:14px;color:var(--dark-muted);margin-top:4px;">ส่วนลด 10% สำหรับยอดสั่งซื้อตั้งแต่ 1,000 บาท (สำหรับลูกค้าใหม่)</p> </div> <button class="primary-btn" onclick="navigator.clipboard.writeText('GOODBOY10');showToast('คัดลอกโค้ดส่วนลดแล้ว', 'success');">คัดลอก</button> </div> </div> `; } else if (tab === 'seller-apply') { mainContent = renderSellerApplyTab(); } container.innerHTML = ` <div class="profile-layout"> <div class="profile-sidebar"> <div class="profile-avatar-section"> <img src="${user.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&auto=format&fit=crop&q=80'}" class="profile-avatar-img" alt="${user.name}"> <div class="profile-name">${user.name}</div> <div class="profile-email">${user.email}</div> ${badgeHtml ? `<div style="margin-top:8px;">${badgeHtml}</div>` : ''} </div> <ul class="profile-nav"> <li><a class="${tab === 'info' ? 'active' : ''}" onclick="renderProfileView('info')"><i data-lucide="user"></i> ข้อมูลส่วนตัว</a></li> <li><a class="${tab === 'orders' ? 'active' : ''}" onclick="renderProfileView('orders')"><i data-lucide="package"></i> ประวัติคำสั่งซื้อ</a></li> <li><a class="${tab === 'coupons' ? 'active' : ''}" onclick="renderProfileView('coupons')"><i data-lucide="ticket"></i> คูปอง</a></li> ${isApprovedSeller ? `<li><a onclick="navigateTo('seller')" style="color:var(--success);"><i data-lucide="store"></i> จัดการร้านค้า (Seller Dashboard)</a></li>` : `<li><a class="${tab === 'seller-apply' ? 'active' : ''}" onclick="renderProfileView('seller-apply')" style="color:var(--primary);"><i data-lucide="store"></i> ${isPendingSeller ? '⏳ สถานะคำขอผู้ขาย' : 'สมัครเป็นผู้ขาย'}</a></li>` } <li><a onclick="handleLogout()" style="color:var(--danger);"><i data-lucide="log-out"></i> ออกจากระบบ</a></li> </ul> </div> <div class="profile-main"> ${mainContent} </div> </div> `; if (window.lucide) lucide.createIcons(); } function handleProfileSave(e) { e.preventDefault(); const name = document.getElementById('prof-name').value.trim(); const phone = document.getElementById('prof-phone').value.trim(); const address = document.getElementById('prof-address').value.trim(); STATE.currentUser.name = name; STATE.currentUser.phone = phone; STATE.currentUser.address = address; // Update users array const userIndex = STATE.users.findIndex(u => u.email === STATE.currentUser.email); if (userIndex > -1) { STATE.users[userIndex] = { ...STATE.currentUser }; } localStorage.setItem('goodboyshop_user', JSON.stringify(STATE.currentUser)); renderAuthHeader(); renderProfileView('info'); showToast('บันทึกข้อมูลส่วนตัวเรียบร้อยแล้ว ✅', 'success'); } async function loadMyProfileOrders() { const listEl = document.getElementById('profile-orders-list'); if (!listEl) return; try { const res = await fetch('api/orders.php'); const data = await res.json(); if (data.success && Array.isArray(data.orders)) { if (data.orders.length === 0) { listEl.innerHTML = '<p style="color:var(--gray-500);text-align:center;padding:20px;">ยังไม่มีประวัติคำสั่งซื้อ</p>'; return; } const statusLabel = { pending: '⏳ รอดำเนินการ (รอตรวจสลิป)', shipping: '🚚 กำลังจัดส่ง', delivered: '✅ จัดส่งแล้ว', cancelled: '❌ ยกเลิก' }; const statusClass = { pending: 'status-pending', shipping: 'status-shipping', delivered: 'status-delivered', cancelled: 'status-cancelled' }; listEl.innerHTML = ` <table class="admin-table"> <thead> <tr> <th>รหัสคำสั่งซื้อ</th> <th>วันที่สั่ง</th> <th>ยอดชำระ</th> <th>สลิป</th> <th>สถานะ</th> </tr> </thead> <tbody> ${data.orders.map(o => ` <tr> <td><b>#${o.order_id}</b></td> <td style="font-size:12px;color:var(--gray-500);">${o.created_at ? new Date(o.created_at).toLocaleDateString('th-TH') : '-'}</td> <td style="color:var(--primary);font-weight:700;">฿${parseFloat(o.total_amount || 0).toLocaleString()}</td> <td> ${o.payment_slip ? `<button class="coupon-btn" onclick="viewSlipImage('${o.payment_slip}', ${o.order_id})" style="padding:4px 8px;font-size:11px;">ดูสลิป</button>` : '<span style="color:var(--gray-500);font-size:11px;">ไม่มีสลิป</span>'} </td> <td><span class="status-badge ${statusClass[o.status] || 'status-pending'}">${statusLabel[o.status] || o.status}</span></td> </tr> `).join('')} </tbody> </table> `; if (window.lucide) lucide.createIcons(); } else { listEl.innerHTML = '<p style="color:var(--danger);">ไม่สามารถโหลดประวัติคำสั่งซื้อได้</p>'; } } catch (e) { listEl.innerHTML = '<p style="color:var(--danger);">เกิดข้อผิดพลาดในการเชื่อมต่อ</p>'; } } // ─── Checkout State ────────────────────────────────────────────────────────── let checkoutSlipFile = null; let checkoutSlipPath = ''; // Checkout View function renderCheckoutView() { const container = document.getElementById('checkout-content'); if (!container) return; if (STATE.cart.length === 0) { navigateTo('cart'); return; } checkoutSlipFile = null; checkoutSlipPath = ''; const subtotal = STATE.cart.reduce((sum, i) => sum + (i.price * i.qty), 0); const shipping = subtotal >= 1000 ? 0 : 50; const total = subtotal + shipping; container.innerHTML = ` <h2 class="page-title">ชำระเงินสั่งซื้อสินค้า</h2> <div class="checkout-layout"> <div class="checkout-form"> <h3>1. เลือกวิธีการชำระเงิน</h3> <form id="checkout-form-element" onsubmit="handlePlaceOrder(event, ${total})"> <div class="payment-methods" id="payment-methods-list"> <label class="payment-option selected" onclick="onPaymentMethodChange('promptpay')"> <input type="radio" name="payment" value="promptpay" checked> <i data-lucide="qr-code" style="color:var(--primary);"></i> <span>สแกน QR Code พร้อมเพย์ (PromptPay)</span> </label> <label class="payment-option" onclick="onPaymentMethodChange('credit')"> <input type="radio" name="payment" value="credit"> <i data-lucide="credit-card"></i> <span>บัตรเครดิต / บัตรเดบิต (Visa, Mastercard)</span> </label> <label class="payment-option" onclick="onPaymentMethodChange('cod')"> <input type="radio" name="payment" value="cod"> <i data-lucide="truck"></i> <span>เก็บเงินปลายทาง (Cash on Delivery)</span> </label> </div> <!-- ส่วนอัปโหลดสลิปการโอน (ซ่อนไว้ถ้าเลือก COD) --> <div id="slip-upload-section" style="margin-top:24px;"> <h3 style="margin-bottom:14px;">2. แนบสลิปการโอนเงิน <span style="color:var(--danger)">*</span></h3> <div style="background:rgba(233,30,140,0.07);border:1px solid rgba(233,30,140,0.25);border-radius:var(--radius-md);padding:14px 16px;margin-bottom:14px;font-size:13px;line-height:1.7;"> 📌 <strong>วิธีชำระเงิน:</strong> โอนเงินผ่าน PromptPay หรือ QR Code แล้วถ่ายรูป/สกรีนช็อตสลิปแนบมาด้วย<br> 💳 <strong>เลขบัญชี / PromptPay:</strong> <span style="color:var(--primary);font-weight:700;">091-234-5678</span> (Good boy Shop)<br> ⚠️ แอดมินจะตรวจสอบสลิปและอนุมัติคำสั่งซื้อภายใน 30 นาที </div> <!-- Dropzone สลิป --> <input type="file" id="slip-file-input" accept="image/jpeg,image/png,image/webp,image/jpg" style="display:none;" onchange="handleSlipFileSelect(this)"> <div id="slip-dropzone" class="slip-upload-box" onclick="document.getElementById('slip-file-input').click()"> <div style="font-size:36px;margin-bottom:10px;">📎</div> <div style="font-weight:700;font-size:14px;color:var(--primary);margin-bottom:4px;">คลิกเพื่อแนบสลิปการโอนเงิน</div> <div style="font-size:12px;color:var(--gray-500);">รองรับ JPG, PNG, WEBP ขนาดไม่เกิน 5MB</div> </div> <div id="slip-preview-section" style="display:none;margin-top:12px;"> <div style="position:relative;display:inline-block;width:100%;"> <img id="slip-preview-img" src="" alt="สลิป" style="width:100%;max-height:260px;object-fit:contain;border-radius:var(--radius-md);border:2px solid var(--primary);"> <div style="margin-top:8px;display:flex;gap:8px;"> <button type="button" class="admin-img-btn-change" onclick="document.getElementById('slip-file-input').click()" style="flex:1;justify-content:center;"> <i data-lucide="camera" style="width:14px;height:14px;"></i> เปลี่ยนสลิป </button> <button type="button" class="admin-img-btn-delete" onclick="clearSlipUpload()" style="flex:1;justify-content:center;"> <i data-lucide="trash-2" style="width:14px;height:14px;"></i> ลบสลิป </button> </div> <div id="slip-file-info" style="font-size:12px;color:var(--gray-500);margin-top:4px;"></div> </div> </div> <div id="slip-error-msg" class="form-error-msg" style="display:none;">กรุณาแนบสลิปการโอนเงิน</div> </div> <button type="submit" id="checkout-submit-btn" class="primary-btn" style="width:100%;margin-top:28px;padding:16px;font-size:16px;"> 🛒 ยืนยันคำสั่งซื้อ (฿${total.toLocaleString()}) </button> </form> </div> <div class="cart-summary"> <h3>สรุปรายการสินค้า</h3> <div style="display:flex;flex-direction:column;gap:12px;margin-bottom:20px;max-height:280px;overflow-y:auto;"> ${STATE.cart.map(i => ` <div style="display:flex;gap:10px;align-items:center;"> <img src="${i.image}" alt="${i.name}" style="width:48px;height:48px;border-radius:8px;object-fit:cover;"> <div style="flex:1;font-size:13px;"> <div style="font-weight:600;">${i.name}</div> <div style="color:var(--gray-500);">x ${i.qty}</div> </div> <div style="font-weight:700;font-size:14px;">฿${(i.price * i.qty).toLocaleString()}</div> </div> `).join('')} </div> <div class="summary-row"> <span>ราคาสินค้า</span> <span>฿${subtotal.toLocaleString()}</span> </div> <div class="summary-row"> <span>ค่าจัดส่ง</span> <span>${shipping === 0 ? '<strong style="color:var(--success)">ฟรี</strong>' : `฿${shipping}`}</span> </div> <div class="summary-row total"> <span>ยอดรวมทั้งสิ้น</span> <span style="color:var(--primary);font-size:22px;font-weight:800;">฿${total.toLocaleString()}</span> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); // ผูก Event ให้ payment option ทำงานถูก document.querySelectorAll('input[name="payment"]').forEach(radio => { radio.addEventListener('change', () => onPaymentMethodChange(radio.value)); }); } function onPaymentMethodChange(method) { document.querySelectorAll('.payment-option').forEach(el => el.classList.remove('selected')); const radio = document.querySelector(`input[name="payment"][value="${method}"]`); if (radio) { radio.checked = true; radio.closest('.payment-option').classList.add('selected'); } const slipSection = document.getElementById('slip-upload-section'); if (slipSection) { slipSection.style.display = method === 'cod' ? 'none' : 'block'; } if (window.lucide) lucide.createIcons(); } function handleSlipFileSelect(input) { const errorEl = document.getElementById('slip-error-msg'); if (errorEl) { errorEl.style.display = 'none'; } if (!input.files || !input.files[0]) return; const file = input.files[0]; const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!validTypes.includes(file.type.toLowerCase())) { showToast('ประเภทไฟล์ไม่ถูกต้อง (รองรับเฉพาะ JPG, PNG, WEBP)', 'error'); input.value = ''; return; } if (file.size > 5 * 1024 * 1024) { showToast('ขนาดไฟล์ต้องไม่เกิน 5MB', 'error'); input.value = ''; return; } checkoutSlipFile = file; const reader = new FileReader(); reader.onload = (e) => { const previewImg = document.getElementById('slip-preview-img'); const previewSection = document.getElementById('slip-preview-section'); const dropzone = document.getElementById('slip-dropzone'); const infoEl = document.getElementById('slip-file-info'); if (previewImg) previewImg.src = e.target.result; if (previewSection) previewSection.style.display = 'block'; if (dropzone) dropzone.style.display = 'none'; if (infoEl) infoEl.textContent = `${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} MB)`; if (window.lucide) lucide.createIcons(); }; reader.readAsDataURL(file); } function clearSlipUpload() { checkoutSlipFile = null; checkoutSlipPath = ''; const input = document.getElementById('slip-file-input'); if (input) input.value = ''; const previewSection = document.getElementById('slip-preview-section'); const dropzone = document.getElementById('slip-dropzone'); if (previewSection) previewSection.style.display = 'none'; if (dropzone) dropzone.style.display = ''; } async function handlePlaceOrder(e, totalAmount) { e.preventDefault(); if (!STATE.currentUser) { showToast('กรุณาเข้าสู่ระบบก่อนสั่งซื้อสินค้า', 'error'); return; } const paymentRadio = document.querySelector('input[name="payment"]:checked'); const paymentMethod = paymentRadio ? paymentRadio.value : 'promptpay'; const isCOD = paymentMethod === 'cod'; const submitBtn = document.getElementById('checkout-submit-btn'); const originalBtnText = submitBtn ? submitBtn.innerHTML : '🛒 ยืนยันคำสั่งซื้อ'; // ตรวจสอบ: ถ้าไม่ใช่ COD ต้องมีสลิป if (!isCOD && !checkoutSlipFile) { const errorEl = document.getElementById('slip-error-msg'); if (errorEl) errorEl.style.display = 'block'; showToast('กรุณาแนบสลิปการโอนเงินก่อนยืนยันคำสั่งซื้อ', 'error'); document.getElementById('slip-dropzone')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); return; } if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '⏳ กำลังประมวลผล...'; } try { let slipPath = ''; // Step 1: Upload สลิปก่อน (ถ้าไม่ใช่ COD) if (!isCOD && checkoutSlipFile) { if (submitBtn) submitBtn.innerHTML = '⏳ กำลังอัปโหลดสลิป...'; const formData = new FormData(); formData.append('slip', checkoutSlipFile); const uploadRes = await fetch('api/upload_slip.php', { method: 'POST', body: formData }); const uploadData = await uploadRes.json(); if (!uploadData.success || !uploadData.path) { throw new Error(uploadData.message || 'ไม่สามารถอัปโหลดสลิปได้'); } slipPath = uploadData.path; } if (submitBtn) submitBtn.innerHTML = '⏳ กำลังบันทึกคำสั่งซื้อ...'; // Step 2: สร้างออเดอร์ใน Database const orderRes = await fetch('api/orders.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_method: paymentMethod, shipping_address: STATE.currentUser.address || 'ไม่ระบุที่อยู่', total_amount: totalAmount || STATE.cart.reduce((s, i) => s + i.price * i.qty, 0), payment_slip: slipPath, cart_items: STATE.cart.map(i => ({ id: i.id, qty: i.qty, price: i.price })) }) }); const orderData = await orderRes.json(); if (orderData.success) { STATE.cart = []; localStorage.removeItem('goodboyshop_cart'); updateBadges(); showToast(orderData.message || 'สั่งซื้อสำเร็จ! รอแอดมินอนุมัติ', 'success'); navigateTo('home'); } else { throw new Error(orderData.message || 'ไม่สามารถบันทึกคำสั่งซื้อได้'); } } catch (err) { console.error('Place Order Error:', err); showToast(err.message || 'เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; } } } // Hero Slider Functions let currentSlideIndex = 0; let sliderAutoplay = null; // Category name display mapping const categoryBtnLabels = { electronics: 'ดูอิเล็กทรอนิกส์', fashion: 'ดูแฟชั่น', cosmetics: 'ดูเครื่องสำอาง', home: 'ดูของใช้ในบ้าน', food: 'ดูอาหาร', sports: 'ดูกีฬา' }; async function initSlider() { const track = document.getElementById('slide-track'); const dotsContainer = document.getElementById('slider-dots'); if (!track) return; let slides = []; try { const res = await fetch('api/get_slider_products.php'); if (res.ok) { const data = await res.json(); slides = data.slides || []; } } catch (e) { console.warn('Slider API unavailable, using fallback.'); } if (slides.length === 0) { // Fallback: welcome banner track.innerHTML = ` <div class="slide" style="background-image:url('https://images.unsplash.com/photo-1607082348824-0a96f2a4b9da?w=1600&auto=format&fit=crop&q=80');"> <div class="slide-overlay"></div> <div class="slide-content"> <span class="slide-tag">🎉 ยินดีต้อนรับ</span> <h2 class="slide-title">Good boy Shop</h2> <p class="slide-desc">ร้านค้าออนไลน์รวมสินค้าคุณภาพ ราคาดี จัดส่งรวดเร็ว</p> <button class="slide-btn" onclick="filterByCategory('all')">ดูสินค้าทั้งหมด <i data-lucide="arrow-right" style="width:16px;height:16px;"></i></button> </div> </div>`; } else { track.innerHTML = slides.map(product => { const imgUrl = product.image_url || 'https://images.unsplash.com/photo-1607082348824-0a96f2a4b9da?w=1600&auto=format&fit=crop&q=80'; const tag = product.discount_pct > 0 ? `🔥 ลด ${product.discount_pct}%` : (product.badge_text || 'สินค้าแนะนำ'); const priceFormatted = Number(product.price).toLocaleString('th-TH'); const originalFormatted = product.original_price ? Number(product.original_price).toLocaleString('th-TH') : null; const priceDisplay = originalFormatted ? `<div class="slide-price-row"><span class="slide-price-current">฿${priceFormatted}</span><span class="slide-price-original">฿${originalFormatted}</span></div>` : `<div class="slide-price-row"><span class="slide-price-current">฿${priceFormatted}</span></div>`; const btnLabel = categoryBtnLabels[product.category_id] || 'ดูสินค้า'; return ` <div class="slide" style="background-image:url('${imgUrl}');"> <div class="slide-overlay"></div> <div class="slide-content"> <span class="slide-tag">${tag}</span> <h2 class="slide-title">${product.product_name}</h2> ${priceDisplay} <button class="slide-btn" onclick="navigateTo('product', ${product.product_id})">ดูสินค้านี้ <i data-lucide="arrow-right" style="width:16px;height:16px;"></i></button> </div> </div>`; }).join(''); } // Re-init lucide icons inside new slides if (window.lucide) lucide.createIcons(); const allSlides = track.querySelectorAll('.slide'); const totalSlides = allSlides.length; // Generate dots if (dotsContainer) { dotsContainer.innerHTML = Array.from({ length: totalSlides }, (_, i) => `<button class="slider-dot ${i === 0 ? 'active' : ''}" onclick="goToSlide(${i})"></button>` ).join(''); } document.getElementById('slider-prev')?.addEventListener('click', () => { currentSlideIndex = (currentSlideIndex - 1 + totalSlides) % totalSlides; updateSlider(totalSlides); }); document.getElementById('slider-next')?.addEventListener('click', () => { currentSlideIndex = (currentSlideIndex + 1) % totalSlides; updateSlider(totalSlides); }); // Start autoplay if (sliderAutoplay) clearInterval(sliderAutoplay); sliderAutoplay = setInterval(() => { currentSlideIndex = (currentSlideIndex + 1) % totalSlides; updateSlider(totalSlides); }, 5000); updateSlider(totalSlides); } function goToSlide(index) { const track = document.getElementById('slide-track'); const totalSlides = track ? track.querySelectorAll('.slide').length : 0; currentSlideIndex = index; updateSlider(totalSlides); } function updateSlider(totalSlides) { const track = document.getElementById('slide-track'); if (track) { track.style.transform = `translateX(-${currentSlideIndex * 100}%)`; } document.querySelectorAll('.slider-dot').forEach((dot, i) => { if (i === currentSlideIndex) dot.classList.add('active'); else dot.classList.remove('active'); }); } // Flash Sale Timer function initFlashTimer() { let hours = 5, minutes = 42, seconds = 19; setInterval(() => { seconds--; if (seconds < 0) { seconds = 59; minutes--; if (minutes < 0) { minutes = 59; hours--; if (hours < 0) hours = 24; } } const th = document.getElementById('timer-h'); const tm = document.getElementById('timer-m'); const ts = document.getElementById('timer-s'); if (th) th.textContent = String(hours).padStart(2, '0'); if (tm) tm.textContent = String(minutes).padStart(2, '0'); if (ts) ts.textContent = String(seconds).padStart(2, '0'); }, 1000); } // Live Search Autocomplete function initSearch() { const input = document.getElementById('search-input'); const btn = document.getElementById('search-btn'); const suggestions = document.getElementById('search-suggestions'); if (!input || !suggestions) return; input.addEventListener('input', (e) => { const val = e.target.value.toLowerCase().trim(); if (val.length === 0) { suggestions.classList.remove('active'); renderProducts(); return; } const matches = STATE.products.filter(p => p.name.toLowerCase().includes(val) || p.category.includes(val)); if (matches.length > 0) { suggestions.innerHTML = matches.slice(0, 5).map(m => ` <div class="suggestion-item" onclick="navigateTo('product', ${m.id});document.getElementById('search-suggestions').classList.remove('active');"> <i data-lucide="search"></i> <span class="suggestion-title">${m.name}</span> <span class="suggestion-category">${m.category}</span> </div> `).join(''); suggestions.classList.add('active'); if (window.lucide) lucide.createIcons(); } else { suggestions.classList.remove('active'); } renderProducts(); }); btn?.addEventListener('click', () => { renderProducts(); suggestions.classList.remove('active'); }); } // ─────── SELLER DASHBOARD VIEW ────────────────────────────────────────── async function renderSellerView() { const container = document.getElementById('seller-content'); if (!container) return; if (!STATE.currentUser) { container.innerHTML = ` <div class="empty-cart"> <i data-lucide="lock" style="width:72px;height:72px;color:var(--warning);"></i> <h2>กรุณาเข้าสู่ระบบ</h2> <p>คุณต้องเข้าสู่ระบบด้วยบัญชีผู้ขายที่ได้รับการอนุมัติแล้วเพื่อเข้าใช้งาน Seller Dashboard</p> <button class="primary-btn" onclick="openAuthModal('login')">เข้าสู่ระบบ</button> </div> `; if (window.lucide) lucide.createIcons(); return; } const role = STATE.currentUser.role; const sellerStatus = STATE.currentUser.seller_status || 'none'; const isApprovedSeller = (role === 'seller' && sellerStatus === 'approved') || role === 'admin'; if (!isApprovedSeller) { container.innerHTML = ` <div class="empty-cart"> <i data-lucide="shield-alert" style="width:72px;height:72px;color:var(--warning);"></i> <h2>คุณยังไม่ได้รับอนุมัติให้เป็นผู้ขาย</h2> <p>${sellerStatus === 'pending' ? 'คำขอของคุณอยู่ระหว่างการตรวจสอบจากผู้ดูแลระบบ โปรดรอการอนุมัติ' : 'คุณยังไม่ได้สมัครเป็นผู้ขาย หรือคำขอของคุณยังไม่ผ่านการอนุมัติ'}</p> <button class="primary-btn" onclick="navigateTo('profile');renderProfileView('seller-apply');">ดูสถานะ / สมัครเป็นผู้ขาย</button> </div> `; if (window.lucide) lucide.createIcons(); return; } container.innerHTML = ` <div class="admin-layout"> <div class="admin-sidebar"> <div class="admin-sidebar-title">🏪 Seller Center</div> <div class="admin-sidebar-sub">ระบบจัดการร้านค้าผู้ขาย</div> <ul class="admin-nav"> <li><a class="active" onclick="switchSellerTab('products')"><i data-lucide="package"></i> สินค้าของฉัน</a></li> <li><a onclick="switchSellerTab('orders')"><i data-lucide="shopping-bag"></i> รายการสั่งซื้อของร้าน</a></li> <li><a onclick="navigateTo('home')" style="color:var(--warning);margin-top:20px;"><i data-lucide="store"></i> กลับไปหน้าร้านค้า</a></li> </ul> </div> <div class="admin-main"> <div id="seller-tab-content"> <p style="color:var(--gray-500);">กำลังโหลดข้อมูลร้านค้า...</p> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); loadSellerDashboardData('products'); } async function loadSellerDashboardData(tab = 'products') { const content = document.getElementById('seller-tab-content'); if (!content) return; try { const res = await fetch('api/seller_dashboard.php'); const data = await res.json(); if (!data.success) { content.innerHTML = `<p style="color:var(--danger);">${data.message || 'เกิดข้อผิดพลาดในการโหลดข้อมูล'}</p>`; return; } const products = data.products || []; const orders = data.orders || []; if (tab === 'products') { content.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;"> <div> <h3>สินค้าของฉัน (${products.length} รายการ)</h3> <p style="color:var(--gray-500);font-size:14px;">สินค้าที่เพิ่มจะส่งให้ Admin ตรวจสอบอนุมัติก่อนแสดงในหน้าร้าน</p> </div> <button class="primary-btn" onclick="openSellerAddProductModal()">+ เพิ่มสินค้าใหม่</button> </div> ${products.length === 0 ? ` <div style="text-align:center;padding:40px;background:var(--gray-100);border-radius:var(--radius-md);"> <i data-lucide="box" style="width:48px;height:48px;color:var(--gray-500);margin-bottom:12px;"></i> <h4>ยังไม่มีรายการสินค้า</h4> <p style="color:var(--gray-500);margin-top:4px;">กดปุ่ม "+ เพิ่มสินค้าใหม่" เพื่อเริ่มลงขายสินค้าของคุณ</p> </div> ` : ` <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>รูปสินค้า</th> <th>ชื่อสินค้า</th> <th>ราคาขาย</th> <th>สต็อก</th> <th>สถานะตรวจสอบ</th> <th>วันที่ลงขาย</th> </tr> </thead> <tbody> ${products.map(p => { let appBadge = `<span class="status-badge status-pending">⏳ รอตรวจสอบ</span>`; if (p.approval_status === 'approved') appBadge = `<span class="status-badge status-delivered">✅ วางจำหน่ายแล้ว</span>`; if (p.approval_status === 'rejected') appBadge = `<span class="status-badge status-cancelled">❌ ไม่อนุมัติ</span>`; return ` <tr> <td><img src="${p.image_url || 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800'}" alt="${p.product_name}" style="width:44px;height:44px;border-radius:8px;object-fit:cover;"></td> <td><b>${p.product_name}</b></td> <td style="color:var(--primary);font-weight:700;">฿${Number(p.price).toLocaleString()}</td> <td>${p.stock || 0} ชิ้น</td> <td>${appBadge}</td> <td>${new Date(p.created_at).toLocaleDateString('th-TH')}</td> </tr> `; }).join('')} </tbody> </table> </div> `} `; } else if (tab === 'orders') { content.innerHTML = ` <div style="margin-bottom:20px;"> <h3>รายการสั่งซื้อสินค้าของร้าน (${orders.length} รายการ)</h3> <p style="color:var(--gray-500);font-size:14px;">รายการสั่งซื้อที่มีสินค้าของร้านคุณ</p> </div> ${orders.length === 0 ? ` <div style="text-align:center;padding:40px;background:var(--gray-100);border-radius:var(--radius-md);"> <i data-lucide="shopping-bag" style="width:48px;height:48px;color:var(--gray-500);margin-bottom:12px;"></i> <h4>ยังไม่มีรายการสั่งซื้อ</h4> <p style="color:var(--gray-500);margin-top:4px;">เมื่อมีลูกค้าสั่งซื้อสินค้าของร้าน รายการจะปรากฏที่นี่</p> </div> ` : ` <div style="overflow-x:auto;"> <table class="admin-table"> <thead> <tr> <th>รหัสสั่งซื้อ</th> <th>ลูกค้า</th> <th>ยอดรวม</th> <th>สถานะ</th> <th>วันที่สั่งซื้อ</th> </tr> </thead> <tbody> ${orders.map(o => ` <tr> <td><b>${o.order_id}</b></td> <td>${o.customer_name}</td> <td>฿${Number(o.total_amount).toLocaleString()}</td> <td><span class="status-badge status-${o.status}">${o.status}</span></td> <td>${new Date(o.created_at).toLocaleDateString('th-TH')}</td> </tr> `).join('')} </tbody> </table> </div> `} `; } if (window.lucide) lucide.createIcons(); } catch (e) { content.innerHTML = `<p style="color:var(--danger);">เกิดข้อผิดพลาดในการโหลดข้อมูล</p>`; } } function switchSellerTab(tab) { document.querySelectorAll('.admin-sidebar .admin-nav a').forEach(a => a.classList.remove('active')); if (event && event.currentTarget) event.currentTarget.classList.add('active'); loadSellerDashboardData(tab); } function openSellerAddProductModal() { const content = document.getElementById('auth-modal-content'); content.innerHTML = ` <div style="text-align:center;margin-bottom:20px;"> <div style="display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;background:linear-gradient(135deg,var(--primary),#ff8c42);border-radius:var(--radius-md);margin-bottom:12px;"> <i data-lucide="plus-circle" style="width:24px;height:24px;color:#fff;"></i> </div> <h2 style="font-family:var(--font-heading);font-size:20px;font-weight:700;">📦 เพิ่มสินค้าใหม่ (Seller)</h2> <p style="font-size:13px;color:var(--gray-500);margin-top:4px;">สินค้าจะถูกส่งให้ผู้ดูแลระบบตรวจสอบอนุมัติ</p> </div> <form onsubmit="handleSellerAddProductSubmit(event)"> <div class="form-group"> <label>ชื่อสินค้า *</label> <input type="text" id="spm-name" required placeholder="ระบุชื่อสินค้า"> </div> <div class="form-grid-2"> <div class="form-group"> <label>หมวดหมู่ *</label> <select id="spm-category"> <option value="electronics">อิเล็กทรอนิกส์</option> <option value="fashion">แฟชั่น</option> <option value="cosmetics">เครื่องสำอาง</option> <option value="home">ของใช้ในบ้าน</option> <option value="food">อาหาร-เครื่องดื่ม</option> <option value="sports">กีฬา-สุขภาพ</option> </select> </div> <div class="form-group"> <label>จำนวนสต็อก (ชิ้น) *</label> <input type="number" id="spm-stock" required min="1" value="10"> </div> </div> <div class="form-grid-2"> <div class="form-group"> <label>ราคาขาย (฿) *</label> <input type="number" id="spm-price" required min="1" placeholder="เช่น 490"> </div> <div class="form-group"> <label>ราคาเดิม / ก่อนลด (฿)</label> <input type="number" id="spm-original-price" min="1" placeholder="เช่น 990"> </div> </div> <div class="form-group"> <label>รูปภาพสินค้า (อัปโหลดจากเครื่อง) *</label> <input type="file" id="spm-image-file" accept="image/jpeg,image/png,image/webp" required onchange="handleSellerImagePreview(this)"> <small style="color:var(--gray-500);font-size:11px;display:block;margin-top:4px;">รองรับไฟล์ JPG, PNG, WEBP ขนาดไม่เกิน 5MB</small> <div id="spm-image-preview-container" style="display:none;margin-top:10px;text-align:center;background:var(--gray-100);padding:10px;border-radius:var(--radius-sm);border:1px dashed var(--gray-300);"> <img id="spm-image-preview" src="" alt="Preview" style="max-height:160px;max-width:100%;border-radius:var(--radius-sm);object-fit:contain;"> <div style="font-size:12px;color:var(--gray-500);margin-top:4px;" id="spm-image-name"></div> </div> </div> <div class="form-group"> <label>รายละเอียดสินค้า</label> <textarea id="spm-desc" placeholder="ระบุคุณสมบัติ จุดเด่น และรายละเอียดของสินค้า"></textarea> </div> <button type="submit" id="spm-submit-btn" class="primary-btn" style="width:100%;">ส่งสินค้าเพื่อขออนุมัติ</button> </form> `; openModal('auth-modal'); if (window.lucide) lucide.createIcons(); } function handleSellerImagePreview(input) { const container = document.getElementById('spm-image-preview-container'); const preview = document.getElementById('spm-image-preview'); const nameEl = document.getElementById('spm-image-name'); if (input.files && input.files[0]) { const file = input.files[0]; // Client-side file size check (5MB) if (file.size > 5 * 1024 * 1024) { showToast('ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB', 'error'); input.value = ''; if (container) container.style.display = 'none'; return; } const reader = new FileReader(); reader.onload = function (e) { if (preview) preview.src = e.target.result; if (nameEl) nameEl.textContent = `รูปตัวอย่าง: ${file.name} (${(file.size / 1024).toFixed(1)} KB)`; if (container) container.style.display = 'block'; }; reader.readAsDataURL(file); } else { if (container) container.style.display = 'none'; } } async function handleSellerAddProductSubmit(e) { e.preventDefault(); const name = document.getElementById('spm-name').value.trim(); const category_id = document.getElementById('spm-category').value; const stock = parseInt(document.getElementById('spm-stock').value) || 0; const price = parseFloat(document.getElementById('spm-price').value) || 0; const original_price = document.getElementById('spm-original-price').value ? parseFloat(document.getElementById('spm-original-price').value) : null; const fileInput = document.getElementById('spm-image-file'); const description = document.getElementById('spm-desc').value.trim(); const submitBtn = document.getElementById('spm-submit-btn'); if (!fileInput || !fileInput.files || !fileInput.files[0]) { showToast('กรุณาเลือกไฟล์รูปภาพสินค้า', 'error'); return; } const file = fileInput.files[0]; if (file.size > 5 * 1024 * 1024) { showToast('ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB', 'error'); return; } // Disable button to prevent double submits if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '<i data-lucide="loader" style="width:16px;height:16px;display:inline-block;vertical-align:middle;animation:spin 1s linear infinite;"></i> กำลังอัปโหลดรูปภาพ...'; if (window.lucide) lucide.createIcons(); } try { // Step 1: Upload image file first const formData = new FormData(); formData.append('image', file); const uploadRes = await fetch('api/upload_product_image.php', { method: 'POST', body: formData }); const uploadData = await uploadRes.json(); if (!uploadData.success || !uploadData.path) { showToast(uploadData.message || 'อัปโหลดรูปภาพไม่สำเร็จ กรุณาลองใหม่อีกครั้ง', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'ส่งสินค้าเพื่อขออนุมัติ'; } return; } const uploadedImagePath = uploadData.path; if (submitBtn) { submitBtn.textContent = 'กำลังบันทึกข้อมูลสินค้า...'; } // Step 2: Submit product info with uploaded image path const res = await fetch('api/seller_dashboard.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_name: name, category_id, stock, price, original_price, image_url: uploadedImagePath, description }) }); const data = await res.json(); if (data.success) { closeModal('auth-modal'); showToast(data.message || 'ส่งสินค้าเพื่อขออนุมัติเรียบร้อยแล้ว', 'success'); loadSellerDashboardData('products'); } else { showToast(data.message || 'เกิดข้อผิดพลาดในการบันทึกสินค้า', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'ส่งสินค้าเพื่อขออนุมัติ'; } } } catch (err) { showToast('เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์', 'error'); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'ส่งสินค้าเพื่อขออนุมัติ'; } } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings