File manager - Edit - /home/webapp69.cm.in.th/u69319090033/Shop/tools_backup/app_backup.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) { wrapper.innerHTML = ` <div style="position:relative;"> <button class="user-avatar-btn" onclick="toggleUserDropdown()"> <img src="${STATE.currentUser.avatar}" 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:200px;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 ${STATE.currentUser.role === 'admin' ? 'status-shipping' : 'status-delivered'}" style="margin-top:6px;"> ${STATE.currentUser.role === 'admin' ? '๐”‘ นเน”นเนฅฃฐ (Admin)' : '๐‘ค ชกฒด'} </span> </div> ${STATE.currentUser.role === 'admin' ? ` <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;">เนซเนฒเนญ”กด (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" disabled style="opacity:0.6;cursor:not-allowed;"> <option value="user" selected>ชกฒด—ฑเนงเน (User)</option> </select> <small style="color:var(--gray-500);font-size:11px;">ฑต Admin/Seller •เนญชฃเนฒเน”ขนเน”นเนฅฃฐเน€—เนฒฑเน</small> </div> <button type="submit" class="primary-btn" style="width:100%;">ฅ—ฐเน€ตขชกฑฃชกฒด</button> </form> `} `; openModal('auth-modal'); if (window.lucide) lucide.createIcons(); } function handleLoginSubmit(e) { e.preventDefault(); const email = document.getElementById('login-email').value.trim(); const password = document.getElementById('login-password').value.trim(); 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'); } } 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 role = 'user'; // Force user role โ€” admin/seller must be granted by admin panel only if (STATE.users.some(u => u.email === email)) { showToast('อีเมลตเน–นเนเนฒเนฅเนง ฃธ“ฒเนเนอีเมลญทเน', 'error'); return; } const newUser = { id: Date.now(), name, email, password, role, 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'); // Note: role is always 'user' โ€” no redirect to admin panel from registration renderProfileView('info'); } function handleLogout() { 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(); } // Categories Grid Rendering function renderCategories() { const grid = document.getElementById('category-grid'); if (!grid) return; grid.innerHTML = STATE.categories.map(c => ` <div class="cat-card ${c.id === STATE.currentCategory ? 'active' : ''}" data-id="${c.id}" onclick="filterByCategory('${c.id}')"> <div class="cat-icon" style="background:var(--primary-light);color:var(--primary);"> <i data-lucide="${c.icon}"></i> </div> <span class="cat-name">${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 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; } const totalSales = STATE.allOrders.reduce((sum, o) => sum + o.total, 0); 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('orders')"><i data-lucide="shopping-bag"></i> ฑ”ฒฃณชฑเนทเนญ (${STATE.allOrders.length})</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">${STATE.allOrders.length}</div> <div class="stat-label">ณชฑเนทเนญฃงก</div> <div class="stat-change stat-up">โ‘ +12 ฃฒขฒฃ</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> <h4 style="margin:24px 0 12px;font-size:16px;">ณชฑเนทเนญฅเนฒชธ”</h4> <table class="admin-table"> <thead> <tr> <th>รหัสณชฑเนทเนญ</th> <th>ฅนเนฒ</th> <th>ขญ”เน€ด</th> <th>ช–ฒฐ</th> <th>งฑ—ตเน</th> </tr> </thead> <tbody> ${STATE.allOrders.map(o => ` <tr> <td><b>${o.id}</b></td> <td>${o.customer}</td> <td>฿${o.total.toLocaleString()}</td> <td><span class="status-badge status-${o.status}">${o.status}</span></td> <td>${o.date}</td> </tr> `).join('')} </tbody> </table> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); } function switchAdminTab(tab) { const content = document.getElementById('admin-tab-content'); document.querySelectorAll('.admin-nav a').forEach(a => a.classList.remove('active')); event.currentTarget.classList.add('active'); if (tab === 'dashboard') renderAdminView(); if (tab === 'orders') { content.innerHTML = ` <h3>ฃฐฑ”ฒฃณชฑเนทเนญชดเนฒฅนเนฒ</h3> <p style="color:var(--gray-500);margin-bottom:20px;font-size:14px;">นเน”นเนฅฃฐชฒกฒฃ–•ฃงชญ เนเนเน ช–ฒฐฒฃฃฑชดเนฒเนฅฐณชฑเนทเนญเน”เน—ตเนตเน</p> <table class="admin-table"> <thead> <tr> <th>รหัส</th> <th>ฅนเนฒ</th> <th>ยอดรวม</th> <th>ช–ฒฐฑ”ชเน</th> <th>ฒฃฑ”ฒฃ</th> </tr> </thead> <tbody> ${STATE.allOrders.map(o => ` <tr> <td><b>${o.id}</b></td> <td>${o.customer}</td> <td>฿${o.total.toLocaleString()}</td> <td><span class="status-badge status-${o.status}">${o.status}</span></td> <td> <button class="coupon-btn" onclick="showToast('ฃฑช–ฒฐเน€เน ฑ”ชเนเนฅเนง', 'success')">ญฑเน€”•ช–ฒฐ</button> </td> </tr> `).join('')} </tbody> </table> `; } if (tab === 'products') { 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 => ` <tr> <td><img src="${p.image}" alt="${p.name}" style="width:44px;height:44px;border-radius:8px;object-fit:cover;"></td> <td style="max-width:200px;"><b>${p.name}</b></td> <td><span class="status-badge status-delivered">${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(); } 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> </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' : 'status-delivered'}">${u.role}</span></td> </tr> `).join('')} </tbody> </table> `; } if (window.lucide) lucide.createIcons(); } // โ”€โ”€โ”€ ADMIN PRODUCT MODAL (Add / Edit) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function renderProductModal(product = null) { if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('เน€ฒฐเนญ”กดเน€—เนฒฑเน—ตเนชฒกฒฃ–ฑ”ฒฃชดเนฒเน”เน', 'error'); return; } const isEdit = !!product; 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 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> <div class="form-grid-2"> <div class="form-group"> <label>ซกง”ซกนเน *</label> <select id="pm-category"> <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> <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" placeholder="เน€เน 990" value="${isEdit ? product.price : ''}"> </div> <div class="form-group"> <label>ฃฒฒเน€”ดก / เนญฅ” (฿)</label> <input type="number" id="pm-original-price" min="1" placeholder="เน€เน 1990" value="${isEdit ? (product.originalPrice || '') : ''}"> </div> </div> <div class="form-group"> <label>URL ฃน ฒชดเนฒ</label> <input type="url" id="pm-image" placeholder="https://images.unsplash.com/..." value="${isEdit ? product.image : ''}"> </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:4px;"> <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" class="primary-btn" style="flex:2;">${isEdit ? '๐’พ ฑ—ถฒฃเน€ฅตเนขเนฅ' : '+ เน€ดเนกชดเนฒเนซกเน'}</button> </div> </form> `; openModal('auth-modal'); if (window.lucide) lucide.createIcons(); } 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) return; renderProductModal(product); } function saveProductForm(e, productId) { e.preventDefault(); if (!STATE.currentUser || STATE.currentUser.role !== 'admin') { showToast('ไม่มีสิทธิ์ดำเนินการนี้', 'error'); return; } const name = document.getElementById('pm-name').value.trim(); const category = document.getElementById('pm-category').value; const badge = document.getElementById('pm-badge').value; const price = parseFloat(document.getElementById('pm-price').value); const originalPrice = document.getElementById('pm-original-price').value ? parseFloat(document.getElementById('pm-original-price').value) : null; const image = document.getElementById('pm-image').value.trim() || 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800'; const description = document.getElementById('pm-description').value.trim(); if (productId) { const p = STATE.products.find(p => p.id === productId); if (p) { p.name = name; p.category = category; p.badge = badge || null; if (badge === 'sale') p.badgeText = 'ลด 50%'; else if (badge === 'new') p.badgeText = 'มาใหม่'; else if (badge === 'hot') p.badgeText = 'ขายดี'; else p.badgeText = null; p.price = price; p.originalPrice = originalPrice; p.image = image; p.description = description; } showToast('แก้ไขข้อมูลสินค้าเรียบร้อยแล้ว', 'success'); } else { const newId = STATE.products.length ? Math.max(...STATE.products.map(p => p.id)) + 1 : 1; let badgeText = null; if (badge === 'sale') badgeText = 'ลด 50%'; else if (badge === 'new') badgeText = 'มาใหม่'; else if (badge === 'hot') badgeText = 'ขายดี'; STATE.products.push({ id: newId, category, name, description, price, originalPrice, rating: 5.0, sold: 0, badge: badge || null, badgeText, image, reviewsCount: 0 }); showToast('เพิ่มสินค้าใหม่เรียบร้อยแล้ว', 'success'); } localStorage.setItem('goodboyshop_products', JSON.stringify(STATE.products)); closeModal('auth-modal'); renderProducts(); const adminContent = document.getElementById('admin-tab-content'); if (adminContent && typeof _renderAdminProductsTab === 'function') { _renderAdminProductsTab(adminContent); } } function renderProfileView(tab = 'info') { const container = document.getElementById('profile-content'); if (!container) return; if (!STATE.currentUser) { openAuthModal('login'); return; } const user = STATE.currentUser; const isSeller = ['seller', 'admin'].includes(user.role); 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') { const myOrders = STATE.allOrders.filter(o => o.customer === user.name); mainContent = ` <h3>ฃฐงฑ•ดณชฑเนทเนญญฑ</h3> ${myOrders.length === 0 ? '<p style="color:var(--gray-500);">\u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e21\u0e35\u0e1b\u0e23\u0e30\u0e27\u0e31\u0e15\u0e34\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e0b\u0e37\u0e49\u0e2d</p>' : ` <table class="admin-table"> <thead><tr><th>รหัสณชฑเนทเนญ</th><th>งฑ—ตเน</th><th>ขญ”ชธ—ดเน</th><th>ช–ฒฐ</th></tr></thead> <tbody> ${myOrders.map(o => ` <tr> <td><b>${o.id}</b></td> <td>${o.date}</td> <td>฿${o.total.toLocaleString()}</td> <td><span class="status-badge status-${o.status}">${o.status}</span></td> </tr> `).join('')} </tbody> </table> `} `; } 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}" class="profile-avatar-img" alt="${user.name}"> <div class="profile-name">${user.name}</div> <div class="profile-email">${user.email}</div> ${isSeller ? `<div style="margin-top:6px;"><span style="background:var(--success);color:#fff;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;">${user.role === 'admin' ? 'Admin' : 'Seller'}</span></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> ${isSeller ? `<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> ชกฑฃเน€เนนเนฒข</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 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;">เน€ด”ฃเนฒเนฒญญเนฅเนญธ“เนฅ•ญฃเนกญเน€ฃฒ ฒขชดเนฒเนซเนฅนเนฒซฅฒขฅเนฒ</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; } 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="${STATE.currentUser.name}" required> </div> <div class="form-group"> <label>อีเมล</label> <input type="email" value="${STATE.currentUser.email}" readonly style="background:var(--gray-100);"> </div> <div class="form-group"> <label>เน€ญฃเนเน—ฃจฑ—เน</label> <input type="text" id="prof-phone" value="${STATE.currentUser.phone || '081-234-5678'}" required> </div> <div class="form-group"> <label>—ตเนญขนเนชณซฃฑฑ”ชเน</label> <textarea id="prof-address" required placeholder="ฃฐธเนฒเน€ฅ—ตเน – เนง/•ณฅ เน€•/ญณเน€ ญ ฑซงฑ” รหัสเนฃฉ“ตขเน">${STATE.currentUser.address || '123/45 –ชธธกงด— เนงฅญเน€•ข เน€•ฅญเน€•ข ฃธเน€—กซฒฃ 10110'}</textarea> </div> <button type="submit" class="primary-btn">ฑ—ถฒฃเน€ฅตเนขเนฅ</button> </form> `; } else if (tab === 'orders') { const myOrders = STATE.allOrders.filter(o => o.customer === STATE.currentUser.name); mainContent = ` <h3>ฃฐงฑ•ดณชฑเนทเนญญฑ</h3> ${myOrders.length === 0 ? '<p style="color:var(--gray-500);">ขฑเนกเนกตฃฐงฑ•ดณชฑเนทเนญ</p>' : ` <table class="admin-table"> <thead> <tr> <th>รหัสณชฑเนทเนญ</th> <th>งฑ—ตเน</th> <th>ขญ”ชธ—ด</th> <th>ช–ฒฐ</th> </tr> </thead> <tbody> ${myOrders.map(o => ` <tr> <td><b>${o.id}</b></td> <td>${o.date}</td> <td>฿${o.total.toLocaleString()}</td> <td><span class="status-badge status-${o.status}">${o.status}</span></td> </tr> `).join('')} </tbody> </table> `} `; } 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> `; } container.innerHTML = ` <div class="profile-layout"> <div class="profile-sidebar"> <div class="profile-avatar-section"> <img src="${STATE.currentUser.avatar}" class="profile-avatar-img" alt="${STATE.currentUser.name}"> <div class="profile-name">${STATE.currentUser.name}</div> <div class="profile-email">${STATE.currentUser.email}</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> <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'); } // Checkout View function renderCheckoutView() { const container = document.getElementById('checkout-content'); if (!container) return; if (STATE.cart.length === 0) { navigateTo('cart'); 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">ณฃฐเน€ดชฑเนทเนญชดเนฒ</h2> <div class="checkout-layout"> <div class="checkout-form"> <h3>1. —ตเนญขนเนฑ”ชเนชดเนฒ</h3> <form id="checkout-form-element" onsubmit="handlePlaceOrder(event)"> <div class="payment-methods"> <label class="payment-option selected"> <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"> <input type="radio" name="payment" value="credit"> <i data-lucide="credit-card"></i> <span>ฑ•ฃเน€ฃ”ด• / ฑ•ฃเน€”ด• (Visa, Mastercard)</span> </label> <label class="payment-option"> <input type="radio" name="payment" value="cod"> <i data-lucide="truck"></i> <span>เน€เนเน€ดฅฒข—ฒ (Cash on Delivery)</span> </label> </div> <button type="submit" class="primary-btn" style="width:100%;margin-top:32px;padding:16px;font-size:16px;"> ขทขฑณชฑเนทเนญ (฿${(subtotal + shipping).toLocaleString()}) </button> </form> </div> <div class="cart-summary"> <h3>ชฃธฃฒขฒฃชดเนฒ</h3> <div style="display:flex;flex-direction:column;gap:12px;margin-bottom:20px;max-height:260px;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:44px;height:44px;border-radius:6px;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:20px;">฿${(subtotal + shipping).toLocaleString()}</span> </div> </div> </div> `; if (window.lucide) lucide.createIcons(); } function handlePlaceOrder(e) { e.preventDefault(); const orderId = "ORD-" + Math.floor(1000 + Math.random() * 9000); const total = STATE.cart.reduce((sum, i) => sum + (i.price * i.qty), 0); STATE.allOrders.unshift({ id: orderId, customer: STATE.currentUser ? STATE.currentUser.name : "ฅนเนฒ—ฑเนงเน", total, status: "pending", date: new Date().toISOString().split('T')[0] }); localStorage.setItem('goodboyshop_orders', JSON.stringify(STATE.allOrders)); STATE.cart = []; localStorage.removeItem('goodboyshop_cart'); updateBadges(); showToast(`ชฑเนทเนญชณเน€ฃเน! ซกฒขเน€ฅณชฑเนทเนญ: ${orderId}`, 'success'); navigateTo('home'); } // 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'); }); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.42 |
proxy
|
phpinfo
|
Settings