File manager - Edit - /home/webapp69.cm.in.th/u69319090009/shop/script.js
Back
// Mock Products Data (Minimalist items with category tags & detailed descriptions) const DEFAULT_PRODUCTS = [ { id: 1, name: "Aura Minimalist Mug", desc: "แก้วเซรามิกทำมือ ผิวด้านเรียบเนียน เหมาะสำหรับเครื่องดื่มร้อนทุกชนิด", description: "แก้วเซรามิกทำมือ ผิวด้านเรียบเนียน สไตล์มินิมอลนอร์ดิก\n- ความจุ 350ml เหมาะสำหรับกาแฟสดหรือชาทนนความร้อนได้สูง\n- วัสดุเซรามิกทนความร้อน ปลอดภัยเข้าไมโครเวฟและเครื่องล้างจานได้\n- ด้ามจับถนัดมือ ผิวสัมผัสแมตต์นุ่มนวลไม่ลื่นมือ", price: 450, category: "แก้วและจาน", icon: "coffee", image: "", ownerId: "beam" }, { id: 2, name: "Eco Canvas Tote Bag", desc: "กระเป๋าผ้าแคนวาสออร์แกนิค แข็งแรงทนทาน ใส่ของได้จุใจสไตล์มินิมอล", description: "กระเป๋าผ้าแคนวาสออร์แกนิค 100% สายสะพายไหล่นุ่มสบาย\n- ขนาดใหญ่ใส่แล็ปท็อป 15 นิ้วและเอกสาร A4 ได้จุใจ\n- มีช่องซิปด้านในสำหรับเก็บกระเป๋าสตางค์และกุญแจ\n- เนื้อผ้าหนาพิเศษ 14 ออนซ์ เย็บย้ำจุดรับน้ำหนักอย่างดี", price: 790, category: "กระเป๋า", icon: "shopping-bag", image: "", ownerId: "beam" }, { id: 3, name: "Nordic Ceramic Vase", desc: "แจกันเซรามิกสไตล์นอร์ดิกทรงเรขาคณิต สำหรับตกแต่งบ้านให้ดูอบอุ่นและเรียบง่าย", description: "แจกันเซรามิกรูปทรงเรขาคณิต ดีไซน์นอร์ดิกมินิมอล\n- ช่วยเติมเต็มบรรยากาศห้องนอนและห้องรับแขกให้ดูอบอุ่นสบายตา\n- เหมาะสำหรับจัดดอกไม้สด ดอกไม้แห้ง หรือตั้งโชว์เดี่ยวๆ\n- ฐานกว้างตั้งได้อย่างมั่นคง ผิวเคลือบแมตต์เช็ดทำความสะอาดง่าย", price: 1200, category: "ของตกแต่งบ้าน", icon: "flower", image: "", ownerId: "beam" }, { id: 4, name: "Aura Noise-Cancelling Earbuds", desc: "หูฟังบลูทูธไร้สาย ดีไซน์มินิมอล เสียงคมชัด พร้อมระบบตัดเสียงรบกวนภายนอก", description: "หูฟังบลูทูธไร้สาย True Wireless พร้อมระบบ ANC ตัดเสียงรบกวนภายนอกได้ถึง 35dB\n- รองรับ Bluetooth 5.3 เชื่อมต่อรวดเร็วและเสถียร\n- แบตเตอรี่ใช้งานต่อเนื่องได้สูงสุด 8 ชั่วโมง (รวมกล่องชาร์จสูงสุด 30 ชั่วโมง)\n- กันน้ำและเหงื่อระดับ IPX5 เหมาะสำหรับการออกกำลังกายและใช้งานประจำวัน", price: 3490, category: "ไอที/Gadget", icon: "headphones", image: "", ownerId: "beam" }, { id: 5, name: "Sleek Desk Lamp", desc: "โคมไฟตั้งโต๊ะ LED ปรับความสว่างและอุณหภูมิสีได้ ถนอมสายตา", description: "โคมไฟตั้งโต๊ะไฟ LED ถนอมสายตา ไร้แสงกระพริบรบกวน\n- ปรับความสว่างได้ 5 ระดับ และอุณหภูมิสี 3 โหมด (Warm/Cool/Natural)\n- ก้านโคมไฟปรับพับงอได้ 180 องศา สะดวกต่อการจัดมุมแสง\n- ระบบสัมผัส Touch Control พร้อมพอร์ต USB ชาร์จมือถือในตัว", price: 1850, category: "ของตกแต่งบ้าน", icon: "lamp", image: "", ownerId: "beam" }, { id: 6, name: "Minimalist Quartz Watch", desc: "นาฬิกาข้อมือระบบควอตซ์ สายหนังแท้ ดีไซน์เรียบหรู ใส่ได้ทุกโอกาส", description: "นาฬิกาข้อมือผู้ชาย/ผู้หญิง ดีไซน์มินิมอลเรียบหรู\n- ตัวเรือนสแตนเลสสตีลบางเพียง 7mm หน้าปัดกระจก Mineral Crystal กันรอย\n- สายหนังแท้นิ่มระบายอากาศได้ดี สวมใส่สบายตลอดวัน\n- กันน้ำระดับ 3ATM (ล้างมือ/ฝนตกพรำๆ ได้สบาย)", price: 2600, category: "แฟชั่น/นาฬิกา", icon: "watch", image: "", ownerId: "beam" } ]; // Default Mock Orders Data const DEFAULT_ORDERS = [ { id: "AURA-23594", orderId: "AURA-23594", buyerId: "gugu", customerName: "gugu", phone: "0812345678", email: "gugu@example.com", address: "123/45 ถนนสุขุมวิท, กรุงเทพมหานคร 10110", items: [ { name: "Aura Minimalist Mug", price: 450, quantity: 1 } ], total: 450, status: "จัดส่งแล้ว", trackingNumber: "TH123456789", sellerId: "beam" } ]; // Default Promos Data const DEFAULT_PROMOS = { "DISCOUNT10": { discountPercent: 10, name: "ลด 10%" }, "AURA20": { discountPercent: 20, name: "ลด 20%" }, "SAVE100": { discountAmount: 100, name: "ลด 100 บาท" } }; // Helper for State Persistence to LocalStorage (useEffect pattern) function persistAllState() { localStorage.setItem("aura_users", JSON.stringify(state.users)); localStorage.setItem("aura_shops", JSON.stringify(state.shops)); localStorage.setItem("aura_products", JSON.stringify(state.products)); localStorage.setItem("aura_orders", JSON.stringify(state.orders)); localStorage.setItem("aura_wishlist", JSON.stringify(state.wishlist)); localStorage.setItem("aura_promos", JSON.stringify(state.promos)); localStorage.setItem("aura_settings", JSON.stringify(state.settings)); if (state.user.username) { localStorage.setItem("aura_username", state.user.username); localStorage.setItem("aura_user_id", state.user.id || state.user.username); } else { localStorage.removeItem("aura_username"); localStorage.removeItem("aura_user_id"); } } // Default Mock Users Data with explicit password fields const DEFAULT_USERS = { "beam": { id: "usr_beam", username: "beam", email: "beam@example.com", password: "123456", hasShop: true }, "gugu": { id: "usr_gugu", username: "gugu", email: "gugu@example.com", password: "123456", hasShop: false } }; // Initializer helper for default state fallback seed with Auto-Wipe Corrupted Data check function getInitialState(key, fallback) { try { const item = localStorage.getItem(key); if (!item) return fallback; const parsed = JSON.parse(item); // Auto-Wipe Corrupted LocalStorage Safety Check for 'aura_users' if (key === "aura_users") { const userList = Object.values(parsed); if (userList.length > 0) { const firstUserHasPassword = userList[0] && typeof userList[0].password === "string" && userList[0].password.length > 0; if (!firstUserHasPassword) { console.warn("Detected corrupted/legacy aura_users without password property. Auto-wiping localStorage for security sync."); localStorage.removeItem("aura_users"); return fallback; } } } return parsed; } catch (e) { return fallback; } } // App State (Lifted state with Lazy LocalStorage Initialization) const savedCurrentUser = (function () { try { const raw = localStorage.getItem("aura_currentUser"); return raw ? JSON.parse(raw) : null; } catch (e) { return null; } })(); let state = { users: getInitialState("aura_users", DEFAULT_USERS), shops: getInitialState("aura_shops", {}), user: { id: (savedCurrentUser && savedCurrentUser.username) || localStorage.getItem("aura_user_id") || localStorage.getItem("aura_username") || null, username: (savedCurrentUser && savedCurrentUser.username) || localStorage.getItem("aura_username") || null, fullName: (savedCurrentUser && savedCurrentUser.fullName) || (savedCurrentUser && savedCurrentUser.name) || "", email: (savedCurrentUser && savedCurrentUser.email) || "", phone: (savedCurrentUser && savedCurrentUser.phone) || "", isLoggedIn: !!((savedCurrentUser && savedCurrentUser.username) || localStorage.getItem("aura_username")), hasShop: false }, products: getInitialState("aura_products", DEFAULT_PRODUCTS), orders: getInitialState("aura_orders", DEFAULT_ORDERS), promos: getInitialState("aura_promos", DEFAULT_PROMOS), settings: getInitialState("aura_settings", { bannerTitle: "รับส่วนลดพิเศษ 10% เมื่อช้อปวันนี้!", bannerDesc: "รับโค้ดส่วนลด <strong>DISCOUNT10</strong> หรือ <strong>AURA20</strong> เพื่อใช้ลดราคาสินค้าในหน้าชำระเงิน" }), cart: getInitialState("aura_cart", []), wishlist: getInitialState("aura_wishlist", []), isDarkMode: localStorage.getItem("aura_theme") === "dark", lang: localStorage.getItem("aura_lang") || "th", appliedPromo: null, // { code: 'DISCOUNT10', discountAmount: 45 } searchQuery: "", selectedCategory: "all", pendingCancelOrderId: null, isSeller: false, shopInfo: null, currentView: "login-view", currentSellerTab: "seller-dashboard" }; // Theme Management Logic function applyTheme() { const root = document.documentElement; const themeIcon = document.getElementById("theme-toggle-icon"); if (state.isDarkMode) { root.classList.add("dark"); localStorage.setItem("aura_theme", "dark"); if (themeIcon) { themeIcon.setAttribute("data-lucide", "sun"); } } else { root.classList.remove("dark"); localStorage.setItem("aura_theme", "light"); if (themeIcon) { themeIcon.setAttribute("data-lucide", "moon"); } } if (window.lucide) { window.lucide.createIcons(); } } window.toggleTheme = function () { state.isDarkMode = !state.isDarkMode; applyTheme(); showToast(`เปลี่ยนเป็น${state.isDarkMode ? 'โหมดมืด (Dark Mode)' : 'โหมดสว่าง (Light Mode)'} เรียบร้อยแล้ว`, "info"); }; // Sync active user state from users state & shops state function syncCurrentUserState() { if (!state.user.username) { state.user.id = null; state.user.hasShop = false; state.isSeller = false; state.shopInfo = null; return; } state.user.id = state.user.username; // Check user data and shop data in state maps const userData = state.users[state.user.username]; const shopData = state.shops[state.user.username] || (userData ? userData.shop : null); if ((userData && userData.hasShop) || shopData || state.shops[state.user.username]) { state.user.hasShop = true; state.isSeller = true; state.shopInfo = shopData || state.shops[state.user.username]; } else { state.user.hasShop = false; state.isSeller = false; state.shopInfo = null; } // Always keep state synced to local storage (useEffect simulation) persistAllState(); } // Reset Seller and Dashboard Data when user changes or logs out function resetUserDashboardState() { state.isSeller = false; state.shopInfo = null; state.user.hasShop = false; state.cart = []; localStorage.removeItem("aura_cart"); persistAllState(); } // Seed initial localStorage items if missing if (!localStorage.getItem("aura_products")) { localStorage.setItem("aura_products", JSON.stringify(DEFAULT_PRODUCTS)); } if (!localStorage.getItem("aura_orders")) { localStorage.setItem("aura_orders", JSON.stringify(DEFAULT_ORDERS)); } // DOM Elements const views = document.querySelectorAll(".view-section"); const navLinks = document.querySelectorAll(".nav-link"); const cartBadge = document.getElementById("cart-badge-count"); const headerUsername = document.getElementById("header-username"); const logoutBtn = document.getElementById("logout-btn"); const loginForm = document.getElementById("login-form"); const usernameInput = document.getElementById("username-input"); const productsGrid = document.getElementById("products-grid-container"); const cartItemsList = document.getElementById("cart-items-list"); const summarySubtotal = document.getElementById("summary-subtotal"); const summaryTotal = document.getElementById("summary-total"); const checkoutTotal = document.getElementById("checkout-total-price"); const checkoutSummaryList = document.getElementById("checkout-items-summary"); const checkoutForm = document.getElementById("checkout-form"); const toastContainer = document.getElementById("toast-container"); // Elements for success view details const successOrderId = document.getElementById("success-order-id"); const successName = document.getElementById("success-name"); const successAddress = document.getElementById("success-address"); const successAmount = document.getElementById("success-amount"); // Seller Centre elements const dashboardTotalSales = document.getElementById("dashboard-total-sales"); const dashboardTotalOrders = document.getElementById("dashboard-total-orders"); const addProductForm = document.getElementById("add-product-form"); const sellerOrdersList = document.getElementById("seller-orders-list"); const pendingOrdersCountBadge = document.getElementById("pending-orders-count"); // Initializer document.addEventListener("DOMContentLoaded", () => { initApp(); }); function initApp() { applyLanguage(); // <--- เติมบรรทัดนี้ เพื่อให้เว็บแปลภาษาตั้งแต่เริ่มโหลด applyTheme(); applySettingsToUI(); setupEventListeners(); setupSellerDashboardTabs(); setupBuyerProfileTabs(); updateWishlistUI(); renderProducts(); // Check login state and navigate accordingly if (state.user.isLoggedIn) { syncCurrentUserState(); updateUserUI(); navigateTo("products-view"); } else { navigateTo("login-view"); } updateCartUI(); updateSellerDashboard(); } // Router - Single Page Application View Switcher function navigateTo(viewId) { // Sync user state before routing decisions syncCurrentUserState(); // If not logged in, force Login View (allow register-view) if (!state.user.isLoggedIn && viewId !== "login-view" && viewId !== "register-view") { showToast("กรุณาเข้าสู่ระบบก่อนช้อปปิ้ง", "warning"); navigateTo("login-view"); return; } // Strict Conditional Rendering in Seller Centre: // IF currentUser.hasShop === false -> FORCE render "Register Shop Form" (seller-register-view) if (viewId === "seller-view" && !state.user.hasShop) { viewId = "seller-register-view"; } // Toggle active classes on sections views.forEach(view => { if (view.id === viewId) { view.classList.add("active"); } else { view.classList.remove("active"); } }); // Update state state.currentView = viewId; if (viewId === "profile-view") { renderBuyerProfile(); } // Update navigation active states navLinks.forEach(link => { // Handle seller subviews active navbar link const target = link.getAttribute("data-target"); if (target === viewId || (target === "seller-view" && viewId === "seller-register-view")) { link.classList.add("active"); } else { link.classList.remove("active"); } }); // Scroll to top of view window.scrollTo({ top: 0, behavior: 'smooth' }); // Handle view-specific renders if (viewId === "cart-view") { renderCart(); } else if (viewId === "checkout-view") { renderCheckoutSummary(); } else if (viewId === "seller-view") { updateUserUI(); updateSellerDashboard(); renderSellerOrders(); renderSellerProducts(); } else if (viewId === "profile-view") { renderBuyerProfile(); } else if (viewId === "admin-view") { renderAdminOverview(); renderAdminProducts(); renderAdminOrders(); renderAdminUsers(); } // Dynamic Header Visibility based on Login / Register const appHeader = document.getElementById("app-header"); if (viewId === "login-view" || viewId === "register-view") { appHeader.style.display = "none"; } else { appHeader.style.display = "block"; } // Re-trigger Lucide Icons rendering for dynamically created icons if (window.lucide) { window.lucide.createIcons(); } } // Helper for Password Eye Toggle Visibility window.togglePasswordVisibility = function (inputId, btnEl) { const input = document.getElementById(inputId); if (!input) return; const isPassword = input.type === "password"; input.type = isPassword ? "text" : "password"; const icon = btnEl.querySelector("i"); if (icon) { icon.setAttribute("data-lucide", isPassword ? "eye-off" : "eye"); if (window.lucide) { window.lucide.createIcons(); } } }; // User Authentication Handler with Strict Validations function register(username, email, password, confirmPassword) { username = (username || "").trim(); email = (email || "").trim().toLowerCase(); password = (password || "").trim(); confirmPassword = (confirmPassword || "").trim(); const errorMsgEl = document.getElementById("register-error-msg"); const setError = (msg) => { if (errorMsgEl) { errorMsgEl.textContent = msg; errorMsgEl.style.display = "block"; } showToast(msg, "warning"); }; if (errorMsgEl) { errorMsgEl.style.display = "none"; errorMsgEl.textContent = ""; } // 1. Strict Validation checks if (!username || !email || !password || !confirmPassword) { setError("กรุณากรอกข้อมูลให้ครบถ้วนทุกช่อง"); return; } if (password.length < 6) { setError("รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร"); return; } if (password !== confirmPassword) { setError("รหัสผ่านและการยืนยันรหัสผ่านไม่ตรงกัน กรุณาตรวจสอบอีกครั้ง"); return; } // 2. Check Duplicates in aura_users let users = JSON.parse(localStorage.getItem("aura_users")) || state.users || {}; // Check username duplicate if (users[username]) { setError("ชื่อผู้ใช้นี้ถูกใช้งานแล้ว"); return; } // Check email duplicate const isEmailUsed = Object.values(users).some(u => u.email === email); if (isEmailUsed) { setError("อีเมลนี้ถูกใช้งานแล้ว"); return; } // Reset dashboard state when creating new user resetUserDashboardState(); // 3. Clean up User Object: Save ONLY necessary fields (DO NOT save confirmPassword) const newUser = { id: `usr_${Date.now()}`, username: username, email: email, password: password, hasShop: false, shop: null }; users[username] = newUser; state.users = users; localStorage.setItem("aura_users", JSON.stringify(users)); // 4. Auto login after register state.user.id = username; state.user.username = username; state.user.isLoggedIn = true; state.user.hasShop = false; localStorage.setItem("aura_username", username); localStorage.setItem("aura_user_id", username); localStorage.setItem("aura_currentUser", JSON.stringify(newUser)); syncCurrentUserState(); updateUserUI(); navigateTo("products-view"); showToast(`สมัครสมาชิกสำเร็จ! ยินดีต้อนรับคุณ ${username}`, "success"); } function resetUserDashboardState() { state.isSeller = false; state.shopInfo = null; state.user.hasShop = false; state.cart = []; localStorage.removeItem("aura_cart"); resetVoucherUI(); // <--- Add this line to clear voucher on logout/login persistAllState(); } function handleLogin(inputUsername, inputPassword) { const safeInputUsername = (inputUsername || "").trim(); const safeInputPassword = (inputPassword || "").trim(); const errorMsgEl = document.getElementById("login-error-msg"); // 1. Fetch Users Database from LocalStorage or state let users = JSON.parse(localStorage.getItem("aura_users")) || state.users || {}; // 🛠️ อุดรอยรั่วที่ 1: ซิงค์ความจำหลัก เพื่อไม่ให้คูปองและประวัติหายตอนสลับไอดี state.users = users; // 2. Strict Authentication Logic let foundUser = Object.values(users).find(u => (u.username === safeInputUsername || (u.email && u.email.toLowerCase() === safeInputUsername.toLowerCase())) && String(u.password).trim() === safeInputPassword ); // 🛠️ อุดรอยรั่วที่ 2: ระบบสร้างไอดี Admin อัตโนมัติ (เผื่อกรณีฐานข้อมูลโดนล้าง) if (!foundUser && safeInputUsername === "admin" && safeInputPassword === "password") { foundUser = { id: "usr_admin", username: "admin", email: "admin@aura.com", password: "password", hasShop: true, role: "admin" }; users["admin"] = foundUser; state.users = users; localStorage.setItem("aura_users", JSON.stringify(users)); } // Default mock fallback for initial seed user 'beam' or 'gugu' if (!foundUser && (safeInputUsername === "beam" || safeInputUsername === "gugu") && safeInputPassword === "123456") { foundUser = { id: safeInputUsername, username: safeInputUsername, email: `${safeInputUsername}@example.com`, password: "123456", hasShop: safeInputUsername === "beam" }; users[safeInputUsername] = foundUser; state.users = users; localStorage.setItem("aura_users", JSON.stringify(users)); } // 3. Error Handling & Ban Check if (!foundUser) { if (errorMsgEl) { errorMsgEl.textContent = "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง"; errorMsgEl.style.display = "block"; } showToast("ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง", "warning"); return false; } if (foundUser.isBanned) { const msg = state.lang === 'en' ? "Your account has been suspended" : "บัญชีของคุณถูกระงับการใช้งาน"; if (errorMsgEl) { errorMsgEl.textContent = msg; errorMsgEl.style.display = "block"; } showToast(msg, "error"); return false; } // 4. Success Logic if (errorMsgEl) { errorMsgEl.style.display = "none"; errorMsgEl.textContent = ""; } resetUserDashboardState(); state.user.id = foundUser.username; state.user.username = foundUser.username; state.user.fullName = foundUser.fullName || foundUser.name || foundUser.username; state.user.email = foundUser.email || `${foundUser.username}@example.com`; state.user.phone = foundUser.phone || "0891234567"; state.user.isLoggedIn = true; localStorage.setItem("aura_username", foundUser.username); localStorage.setItem("aura_user_id", foundUser.username); localStorage.setItem("aura_currentUser", JSON.stringify(foundUser)); syncCurrentUserState(); updateUserUI(); navigateTo("products-view"); showToast(`ยินดีต้อนรับคุณ ${state.user.username} เข้าสู่ระบบ!`, "success"); return true; } function handleLogout() { resetUserDashboardState(); state.user.id = null; state.user.username = null; state.user.fullName = ""; state.user.email = ""; state.user.phone = ""; state.user.isLoggedIn = false; localStorage.removeItem("aura_username"); localStorage.removeItem("aura_user_id"); localStorage.removeItem("aura_currentUser"); updateUserUI(); updateCartUI(); navigateTo("login-view"); if (usernameInput) usernameInput.value = ""; showToast("ออกจากระบบเรียบร้อยแล้ว", "info"); } function updateUserUI() { const currentUsername = state.user.username || ""; const currentUserData = state.users[currentUsername]; const profileImage = currentUserData ? currentUserData.profileImage : null; const headerAvatarImg = document.getElementById("header-avatar-img"); const headerAvatarIcon = document.getElementById("header-avatar-icon"); if (profileImage) { if (headerAvatarImg) { headerAvatarImg.src = profileImage; headerAvatarImg.style.display = "block"; } if (headerAvatarIcon) { headerAvatarIcon.style.display = "none"; } } else { if (headerAvatarImg) { headerAvatarImg.style.display = "none"; } if (headerAvatarIcon) { headerAvatarIcon.style.display = "block"; } } const updateAvatarCircle = (id) => { const circle = document.getElementById(id); if (circle) { circle.innerHTML = profileImage ? `<img src="${profileImage}" style="width: 100%; height: 100%; object-fit: cover;">` : `<i data-lucide="user"></i>`; } }; updateAvatarCircle("buyer-sidebar-avatar"); updateAvatarCircle("seller-sidebar-avatar"); headerUsername.textContent = currentUsername; const sidebarUser = document.getElementById("sidebar-username-display"); if (sidebarUser) sidebarUser.textContent = currentUsername; const dashUser = document.getElementById("dash-username-display"); if (dashUser) dashUser.textContent = currentUsername; const shopDisplay = document.getElementById("seller-shop-name-display"); if (shopDisplay) { if (state.user.hasShop && state.shopInfo && state.shopInfo.name) { shopDisplay.textContent = state.shopInfo.name; shopDisplay.style.display = "inline-block"; } else { shopDisplay.textContent = ""; shopDisplay.style.display = "none"; } } if (state.user.isLoggedIn) { logoutBtn.style.display = "flex"; } else { logoutBtn.style.display = "none"; } // === เพิ่มส่วนนี้เข้าไป เพื่อเช็กโชว์ปุ่ม Admin === const adminLink = document.getElementById("nav-admin-link"); if (adminLink) { if (currentUsername === "admin") { adminLink.style.display = "flex"; // โชว์ปุ่มถ้าเป็น admin } else { adminLink.style.display = "none"; // ซ่อนปุ่มถ้าเป็นคนอื่น } } updateVoucherUI(); } // Seller Dashboard Tabs Controller (Sidebar & Quick Actions synchronized) function setupSellerDashboardTabs() { const sidebarItems = document.querySelectorAll(".sidebar-nav-item"); const actionBtns = document.querySelectorAll(".action-tab-btn"); sidebarItems.forEach(btn => { btn.addEventListener("click", () => { const targetTab = btn.getAttribute("data-sidebar-tab"); switchSellerTab(targetTab); }); }); actionBtns.forEach(btn => { btn.addEventListener("click", () => { const targetTab = btn.getAttribute("data-action-tab"); switchSellerTab(targetTab); }); }); } window.switchSellerTab = function (targetTab) { const sidebarItems = document.querySelectorAll(".sidebar-nav-item"); const actionBtns = document.querySelectorAll(".action-tab-btn"); const subViews = document.querySelectorAll(".seller-sub-view"); sidebarItems.forEach(item => { if (item.getAttribute("data-sidebar-tab") === targetTab) { item.classList.add("active"); } else { item.classList.remove("active"); } }); actionBtns.forEach(btn => { if (btn.getAttribute("data-action-tab") === targetTab) { btn.classList.add("active"); } else { btn.classList.remove("active"); } }); subViews.forEach(view => { if (view.id === targetTab) { view.classList.add("active"); } else { view.classList.remove("active"); } }); state.currentSellerTab = targetTab; if (targetTab === "seller-orders") { renderSellerOrders(); } else if (targetTab === "seller-dashboard") { updateSellerDashboard(); } else if (targetTab === "seller-my-products") { renderSellerProducts(); } if (window.lucide) { window.lucide.createIcons(); } }; // Wishlist Management System window.toggleWishlist = function (productId) { if (!state.user.isLoggedIn) { showToast("กรุณาเข้าสู่ระบบก่อนใช้งานสิ่งที่อยากได้", "warning"); navigateTo("login-view"); return; } const index = state.wishlist.indexOf(productId); const product = state.products.find(p => p.id === productId); if (index > -1) { state.wishlist.splice(index, 1); if (product) showToast(`ลบ "${product.name}" ออกจากสิ่งที่อยากได้แล้ว`, "info"); } else { state.wishlist.push(productId); if (product) showToast(`เพิ่ม "${product.name}" ลงในสิ่งที่อยากได้แล้ว`, "success"); } persistAllState(); updateWishlistUI(); renderProducts(); }; function updateWishlistUI() { const badge = document.getElementById("wishlist-badge-count"); if (badge) { badge.textContent = state.wishlist.length; } } window.openWishlistModal = function () { const modal = document.getElementById("wishlist-modal"); const modalBody = document.getElementById("wishlist-modal-body"); if (!modal || !modalBody) return; const likedProducts = state.products.filter(p => state.wishlist.includes(p.id)); if (likedProducts.length === 0) { modalBody.innerHTML = ` <div style="text-align: center; padding: 40px 20px; color: var(--text-muted);"> <i data-lucide="heart" style="width: 48px; height: 48px; color: var(--text-light); margin-bottom: 12px; display: inline-block;"></i> <p style="font-weight: 500;" data-i18n="wishlist_empty_title">ยังไม่มีสินค้าในสิ่งที่อยากได้</p> <p style="font-size: 13px; margin-top: 4px;" data-i18n="wishlist_empty_desc">กดไอคอนหัวใจที่รูปสินค้าเพื่อบันทึกสินค้าที่คุณชอบ</p> </div> `; } else { modalBody.innerHTML = likedProducts.map(prod => { let mediaHtml = ''; if (prod.image && (prod.image.startsWith('http://') || prod.image.startsWith('https://') || prod.image.startsWith('/') || prod.image.startsWith('data:image'))) { mediaHtml = `<img src="${prod.image}" alt="${prod.name}">`; } else { mediaHtml = `<div class="wishlist-item-icon"><i data-lucide="${prod.icon || 'package'}"></i></div>`; } return ` <div class="wishlist-item-row"> ${mediaHtml} <div class="wishlist-item-info"> <div class="wishlist-item-name">${prod.name}</div> <div class="wishlist-item-price">฿${prod.price.toLocaleString()}</div> </div> <button class="btn btn-primary" style="padding: 6px 12px; font-size: 13px;" onclick="addToCart(${prod.id});"> <i data-lucide="shopping-bag" class="icon-left" style="width: 14px; height: 14px;"></i> <span data-i18n="wishlist_add_to_cart">ใส่ตะกร้า</span> </button> <button class="remove-item-btn" onclick="toggleWishlist(${prod.id}); openWishlistModal();" title="ลบออกจากรายการ"> <i data-lucide="trash-2" class="icon-sm"></i> </button> </div> `; }).join(""); } modal.classList.add("active"); applyLanguage(); if (window.lucide) window.lucide.createIcons(); }; window.closeWishlistModal = function () { const modal = document.getElementById("wishlist-modal"); if (modal) modal.classList.remove("active"); }; // Update Voucher Claim UI state based on logged in user's claimedVouchers function updateVoucherUI() { const btn = document.getElementById("btn-claim-voucher"); if (!btn) return; const username = state.user ? state.user.username : null; const currentUserData = (username && state.users) ? state.users[username] : null; const code = "DISCOUNT10"; if (currentUserData && currentUserData.claimedVouchers && currentUserData.claimedVouchers.includes(code)) { btn.innerHTML = `<i data-lucide="check-circle" class="icon-left"></i> <span data-i18n="btn_claimed">เก็บโค้ดแล้ว</span>`; btn.disabled = true; btn.style.backgroundColor = "#e2e8f0"; btn.style.color = "#475569"; btn.style.cursor = "not-allowed"; btn.style.boxShadow = "none"; } else { btn.innerHTML = `<i data-lucide="gift" class="icon-left"></i> <span data-i18n="claim_btn_text">รับโค้ดส่วนลด DISCOUNT10</span>`; btn.disabled = false; btn.style.backgroundColor = ""; btn.style.color = ""; btn.style.cursor = "pointer"; btn.style.boxShadow = ""; } applyLanguage(); if (window.lucide) window.lucide.createIcons(); } window.updateVoucherUI = updateVoucherUI; // Reset Voucher Claim State and Restore Button UI function resetVoucherUI() { state.appliedPromo = null; // Clear the saved promo code updateVoucherUI(); } window.resetVoucherUI = resetVoucherUI; // Claim Voucher Helper (บันทึกระดับผู้ใช้ลงใน state.users) window.claimVoucher = function (code) { if (!state.user || !state.user.isLoggedIn) { showToast("กรุณาเข้าสู่ระบบก่อนเก็บโค้ดส่วนลด", "warning"); navigateTo("login-view"); return; } const username = state.user.username; if (!state.users[username]) { state.users[username] = { username: username, claimedVouchers: [] }; } const currentUserData = state.users[username]; // Initialize the claimedVouchers array if it doesn't exist if (!currentUserData.claimedVouchers) { currentUserData.claimedVouchers = []; } // Prevent duplicate claiming if (currentUserData.claimedVouchers.includes(code)) { showToast(`คุณมีโค้ดส่วนลด "${code}" อยู่ในระบบแล้ว!`, "warning"); return; } // Save to database currentUserData.claimedVouchers.push(code); persistAllState(); // Apply promo to current active session state.appliedPromo = { code: code, discountPercent: (state.promos && state.promos[code]) ? (state.promos[code].discountPercent || 10) : 10 }; showToast(`เก็บโค้ดส่วนลด "${code}" สำเร็จแล้ว!`, "success"); const promoInput = document.getElementById("promo-code-input"); if (promoInput) promoInput.value = code; updateVoucherUI(); }; window.openProductDetailModal = function (productId) { const modal = document.getElementById("product-detail-modal"); const container = document.getElementById("product-detail-content"); if (!modal || !container) return; const product = state.products.find(p => p.id === productId); if (!product) return; let mediaHtml = ''; if (product.image && (product.image.startsWith('http://') || product.image.startsWith('https://') || product.image.startsWith('/') || product.image.startsWith('data:image'))) { mediaHtml = `<img src="${product.image}" alt="${product.name}">`; } else { mediaHtml = `<i data-lucide="${product.icon || 'package'}"></i>`; } const isLiked = state.wishlist.includes(product.id); const displayDescription = product.description || product.desc || "ไม่มีรายละเอียดเพิ่มเติมสำหรับสินค้าชิ้นนี้"; container.innerHTML = ` <div class="product-detail-layout"> <div class="product-detail-media"> ${mediaHtml} </div> <div class="product-detail-info"> ${product.category ? `<span class="product-detail-category">${product.category}</span>` : ''} <h2 class="product-detail-title">${product.name}</h2> <div class="product-detail-price">฿${product.price.toLocaleString()}</div> <div class="product-detail-desc">${displayDescription}</div> <div class="product-detail-actions"> <button class="btn btn-primary" onclick="addToCart(${product.id});" style="flex: 1;"> <i data-lucide="shopping-bag" class="icon-left"></i> เพิ่มใส่ตะกร้า </button> <button class="btn ${isLiked ? 'btn-danger' : 'btn-text'}" onclick="toggleWishlist(${product.id}); openProductDetailModal(${product.id});" style="border: 1px solid var(--border);"> <i data-lucide="heart" class="icon-left" style="${isLiked ? 'fill: white;' : ''}"></i> ${isLiked ? 'ลบออกจากสิ่งที่อยากได้' : 'เพิ่มในสิ่งที่อยากได้'} </button> </div> </div> </div> `; modal.classList.add("active"); if (window.lucide) window.lucide.createIcons(); }; window.closeProductDetailModal = function () { const modal = document.getElementById("product-detail-modal"); if (modal) modal.classList.remove("active"); }; // Products Rendering with Search and Category Filtering function renderProducts() { if (!productsGrid) return; let filtered = state.products; // Search filter if (state.searchQuery.trim()) { const query = state.searchQuery.trim().toLowerCase(); filtered = filtered.filter(p => p.name.toLowerCase().includes(query) || (p.desc && p.desc.toLowerCase().includes(query)) || (p.description && p.description.toLowerCase().includes(query))); } // Category filter if (state.selectedCategory && state.selectedCategory !== "all") { filtered = filtered.filter(p => p.category === state.selectedCategory); } if (filtered.length === 0) { productsGrid.innerHTML = ` <div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; background: var(--white); border-radius: var(--radius-lg); border: 1px dashed var(--border);"> <i data-lucide="search-x" style="width: 48px; height: 48px; color: var(--text-light); margin-bottom: 12px; display: inline-block;"></i> <h3 style="font-size: 16px; font-weight: 600; color: var(--text-main);">ไม่พบสินค้าที่ค้นหา</h3> <p style="font-size: 14px; color: var(--text-muted); margin-top: 4px;">ลองเปลี่ยนคำค้นหาหรือเลือกหมวดหมู่สินค้าอื่น</p> </div> `; } else { productsGrid.innerHTML = filtered.map(prod => { let mediaHtml = ''; if (prod.image && (prod.image.startsWith('http://') || prod.image.startsWith('https://') || prod.image.startsWith('/') || prod.image.startsWith('data:image'))) { mediaHtml = `<img src="${prod.image}" class="product-image" alt="${prod.name}">`; } else { mediaHtml = `<i data-lucide="${prod.icon || 'package'}" class="product-icon"></i>`; } const isLiked = state.wishlist.includes(prod.id); return ` <div class="product-card"> <button class="wishlist-toggle-btn ${isLiked ? 'liked' : ''}" onclick="toggleWishlist(${prod.id})" title="${isLiked ? 'ลบออกจากสิ่งที่อยากได้' : 'เพิ่มในสิ่งที่อยากได้'}"> <i data-lucide="heart" style="width: 18px; height: 18px;"></i> </button> <div class="product-image-container clickable-product-img" onclick="openProductDetailModal(${prod.id})"> <div class="product-image-bg"></div> ${mediaHtml} </div> <div class="product-details"> <h3 class="product-title clickable-product-img" onclick="openProductDetailModal(${prod.id})">${prod.name}</h3> <p class="product-desc">${prod.desc || prod.description || ''}</p> <div style="margin-bottom: 14px;"> <button class="btn-detail-quickview" onclick="openProductDetailModal(${prod.id})"> <i data-lucide="eye" class="icon-left" style="width: 13px; height: 13px;"></i> ดูรายละเอียด </button> </div> <div class="product-footer"> <span class="product-price">฿${prod.price.toLocaleString()}</span> <button class="add-to-cart-btn" onclick="addToCart(${prod.id})" aria-label="เพิ่มใส่ตะกร้า"> <i data-lucide="plus"></i> </button> </div> </div> </div> `; }).join(""); } if (window.lucide) { window.lucide.createIcons(); } } // Cart Management System window.addToCart = function (productId) { if (!state.user.isLoggedIn) { navigateTo("login-view"); return; } const product = state.products.find(p => p.id === productId); if (!product) return; const cartItemIndex = state.cart.findIndex(item => item.product.id === productId); if (cartItemIndex > -1) { state.cart[cartItemIndex].quantity += 1; } else { state.cart.push({ product: product, quantity: 1 }); } saveCart(); updateCartUI(); showToast(`เพิ่ม ${product.name} ลงในตะกร้าแล้ว`, "success"); }; function saveCart() { localStorage.setItem("aura_cart", JSON.stringify(state.cart)); } function updateCartUI() { const totalCount = state.cart.reduce((sum, item) => sum + item.quantity, 0); cartBadge.textContent = totalCount; // Toggle badge animation on update cartBadge.classList.remove("pulse"); void cartBadge.offsetWidth; // Trigger reflow to restart animation cartBadge.classList.add("pulse"); // Update pending orders count badge for Seller Centre too const pendingCount = state.orders.filter(o => o.status === "Pending").length; const sidebarBadge = document.getElementById("sidebar-pending-count"); if (sidebarBadge) { if (pendingCount > 0) { sidebarBadge.textContent = pendingCount; sidebarBadge.style.display = "inline-flex"; } else { sidebarBadge.style.display = "none"; } } } function renderCart() { if (state.cart.length === 0) { cartItemsList.innerHTML = ` <div class="empty-cart-state"> <i data-lucide="shopping-cart"></i> <h3 data-i18n="cart_empty_title">ไม่มีสินค้าในตะกร้าของคุณ</h3> <p data-i18n="cart_empty_desc">กลับไปเลือกชมสินค้าตกแต่งบ้านชิ้นโปรดของคุณได้เลย</p> <button class="btn btn-primary" style="margin-top: 20px;" onclick="navigateTo('products-view')"> <span data-i18n="cart_shop_now">ช้อปสินค้าตอนนี้</span> </button> </div> `; document.getElementById("cart-summary-card").style.opacity = "0.5"; document.getElementById("btn-go-to-checkout").disabled = true; } else { document.getElementById("cart-summary-card").style.opacity = "1"; document.getElementById("btn-go-to-checkout").disabled = false; cartItemsList.innerHTML = state.cart.map(item => { let imgHtml = ''; if (item.product.image && (item.product.image.startsWith('http://') || item.product.image.startsWith('https://') || item.product.image.startsWith('/') || item.product.image.startsWith('data:image'))) { imgHtml = `<img src="${item.product.image}" class="product-image" style="width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius-sm);">`; } else { imgHtml = `<i data-lucide="${item.product.icon || 'package'}"></i>`; } return ` <div class="cart-item-card"> <div class="cart-item-image"> ${imgHtml} </div> <div class="cart-item-info"> <h4 class="cart-item-title">${item.product.name}</h4> <span class="cart-item-price">฿${item.product.price.toLocaleString()}</span> </div> <div class="cart-qty-ctrl"> <button class="qty-btn" onclick="updateQty(${item.product.id}, -1)"> <i data-lucide="minus" class="icon-sm"></i> </button> <span class="qty-val">${item.quantity}</span> <button class="qty-btn" onclick="updateQty(${item.product.id}, 1)"> <i data-lucide="plus" class="icon-sm"></i> </button> </div> <button class="remove-item-btn" onclick="removeCartItem(${item.product.id})" title="ลบรายการ"> <i data-lucide="trash-2" class="icon-sm"></i> </button> </div> `; }).join(""); } calculateTotals(); applyLanguage(); if (window.lucide) { window.lucide.createIcons(); } } window.updateQty = function (productId, delta) { const itemIndex = state.cart.findIndex(item => item.product.id === productId); if (itemIndex > -1) { state.cart[itemIndex].quantity += delta; if (state.cart[itemIndex].quantity <= 0) { state.cart.splice(itemIndex, 1); } saveCart(); updateCartUI(); renderCart(); } }; window.removeCartItem = function (productId) { const itemIndex = state.cart.findIndex(item => item.product.id === productId); if (itemIndex > -1) { const name = state.cart[itemIndex].product.name; state.cart.splice(itemIndex, 1); saveCart(); updateCartUI(); renderCart(); showToast(`ลบ ${name} ออกจากตะกร้าเรียบร้อย`, "info"); } }; function calculateTotals() { const subtotal = state.cart.reduce((sum, item) => sum + (item.product.price * item.quantity), 0); summarySubtotal.textContent = `฿${subtotal.toLocaleString()}`; summaryTotal.textContent = `฿${subtotal.toLocaleString()}`; } // Render Payment Interface Based on selectedPaymentMethod function renderPaymentInterface(selectedMethod) { const panel = document.getElementById("payment-details-panel"); if (!panel) return; if (!selectedMethod || selectedMethod === "COD" || selectedMethod === "เก็บเงินปลายทาง") { panel.classList.remove("active"); panel.innerHTML = ""; return; } panel.classList.add("active"); if (selectedMethod === "PromptPay/QR Code" || selectedMethod === "PromptPay") { panel.innerHTML = ` <div class="qr-payment-box"> <div class="qr-code-wrapper"> <img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=AURA-PROMPTPAY-MOCK" class="qr-code-img" alt="PromptPay QR Code"> </div> <p class="qr-instruction-text">สแกน QR Code ด้วยแอปธนาคารเพื่อชำระเงิน</p> <small style="color: var(--text-muted); font-size: 12px;">ยอดชำระจะได้รับการยืนยันอัตโนมัติเมื่อทำรายการเสร็จสิ้น</small> </div> `; } else if (selectedMethod === "Credit/Debit Card" || selectedMethod === "Credit Card") { panel.innerHTML = ` <div class="card-form-grid"> <div class="input-group col-span-2" style="margin-bottom: 0;"> <label style="font-size: 13px; font-weight: 500;">หมายเลขบัตร (Card Number)</label> <input type="text" placeholder="xxxx xxxx xxxx xxxx" maxlength="19" autocomplete="off" style="padding: 10px 12px; font-size: 14px;"> </div> <div class="input-group col-span-2" style="margin-bottom: 0;"> <label style="font-size: 13px; font-weight: 500;">ชื่อบนบัตร (Name on Card)</label> <input type="text" placeholder="เช่น SOMCHAI JAIDEE" autocomplete="off" style="padding: 10px 12px; font-size: 14px; text-transform: uppercase;"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-size: 13px; font-weight: 500;">วันหมดอายุ (MM/YY)</label> <input type="text" placeholder="MM/YY" maxlength="5" autocomplete="off" style="padding: 10px 12px; font-size: 14px;"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-size: 13px; font-weight: 500;">CVV</label> <input type="password" placeholder="123" maxlength="4" autocomplete="off" style="padding: 10px 12px; font-size: 14px;"> </div> </div> `; } else if (selectedMethod === "Mobile Banking") { panel.innerHTML = ` <div> <label style="display: block; font-size: 13px; font-weight: 600; color: var(--text-main); margin-bottom: 10px;"> เลือกธนาคารสำหรับชำระเงิน (Select Bank): </label> <div class="bank-options-grid"> <button type="button" class="bank-select-btn selected" onclick="selectBank(this, 'K PLUS')"> <div class="bank-icon-box bank-kbank">KBANK</div> <span>K PLUS</span> </button> <button type="button" class="bank-select-btn" onclick="selectBank(this, 'SCB EASY')"> <div class="bank-icon-box bank-scb">SCB</div> <span>SCB EASY</span> </button> <button type="button" class="bank-select-btn" onclick="selectBank(this, 'Krungthai NEXT')"> <div class="bank-icon-box bank-ktb">KTB</div> <span>Krungthai NEXT</span> </button> <button type="button" class="bank-select-btn" onclick="selectBank(this, 'Bualuang mBanking')"> <div class="bank-icon-box bank-bbl">BBL</div> <span>Bualuang</span> </button> </div> </div> `; } if (window.lucide) window.lucide.createIcons(); } window.selectBank = function (btnEl, bankName) { const allBtns = document.querySelectorAll(".bank-select-btn"); allBtns.forEach(b => b.classList.remove("selected")); btnEl.classList.add("selected"); showToast(`เลือกชำระผ่าน ${bankName}`, "info"); }; // Checkout Summary & Auto-fill Saved Address & Promo Code Calculation function renderCheckoutSummary() { const subtotal = state.cart.reduce((sum, item) => sum + (item.product.price * item.quantity), 0); const subtotalEl = document.getElementById("checkout-subtotal-price"); if (subtotalEl) subtotalEl.textContent = `฿${subtotal.toLocaleString()}`; let discountAmount = 0; if (state.appliedPromo) { if (state.appliedPromo.discountPercent) { discountAmount = Math.round((subtotal * state.appliedPromo.discountPercent) / 100); } else if (state.appliedPromo.discountAmount) { discountAmount = state.appliedPromo.discountAmount; } } const discountRow = document.getElementById("checkout-discount-row"); const discountPriceEl = document.getElementById("checkout-discount-price"); const discountCodeNameEl = document.getElementById("applied-discount-code-name"); if (discountAmount > 0 && discountRow && discountPriceEl && discountCodeNameEl) { discountRow.style.display = "flex"; discountPriceEl.textContent = `-฿${discountAmount.toLocaleString()}`; discountCodeNameEl.textContent = state.appliedPromo.code; } else if (discountRow) { discountRow.style.display = "none"; } const finalTotal = Math.max(0, subtotal - discountAmount); checkoutTotal.textContent = `฿${finalTotal.toLocaleString()}`; checkoutSummaryList.innerHTML = state.cart.map(item => ` <div class="checkout-item-summary-line"> <span class="title">${item.product.name} (x${item.quantity})</span> <span class="qty-price">฿${(item.product.price * item.quantity).toLocaleString()}</span> </div> `).join(""); // Auto fill promo input if code present const promoInput = document.getElementById("promo-code-input"); if (promoInput && state.appliedPromo) { promoInput.value = state.appliedPromo.code; } // Trigger payment interface panel render based on active radio const activeRadio = document.querySelector('input[name="payment-method"]:checked'); if (activeRadio) { renderPaymentInterface(activeRadio.value); } // 1. Fetch User Addresses strictly filtered by logged-in userId const savedDropdown = document.getElementById("checkout-saved-address-dropdown"); const selectBox = document.getElementById("saved-address-select-box"); const currentUserId = state.user.id || state.user.username || "beam"; let userAddresses = (addressesState || []).filter(addr => addr.userId === currentUserId); if (savedDropdown && selectBox) { if (userAddresses.length === 0) { selectBox.style.display = "none"; } else { selectBox.style.display = "block"; savedDropdown.innerHTML = userAddresses.map((addr, idx) => ` <option value="${idx}"> ${addr.name} (${addr.phone}) - ${addr.address || addr.detail || ''} ${addr.province || ''} ${addr.zipCode || addr.zipcode || ''} ${addr.isDefault ? '[ที่อยู่หลัก]' : ''} </option> `).join("") + `<option value="manual">+ ป้อนที่อยู่ใหม่ด้วยตนเอง (Manual Override)</option>`; // Helper to fill checkout form inputs (Complete State Mapping without hardcoded defaults) const applyAddressToForm = (addr) => { if (!addr) return; const nameInput = document.getElementById("shipping-name"); const phoneInput = document.getElementById("shipping-phone"); const emailInput = document.getElementById("shipping-email"); const addressInput = document.getElementById("shipping-address"); const provinceInput = document.getElementById("shipping-province"); const zipcodeInput = document.getElementById("shipping-zipcode"); if (nameInput) nameInput.value = addr.name || ""; if (phoneInput) phoneInput.value = addr.phone || ""; if (emailInput && !emailInput.value) emailInput.value = `${state.user.username || 'user'}@example.com`; if (addressInput) addressInput.value = addr.address || addr.detail || ""; if (provinceInput) provinceInput.value = addr.province || ""; if (zipcodeInput) zipcodeInput.value = addr.zipCode || addr.zipcode || ""; }; // 2. Auto-Fill with Default Address on mount const defaultIndex = userAddresses.findIndex(a => a.isDefault); const initialIndex = defaultIndex > -1 ? defaultIndex : 0; savedDropdown.value = initialIndex; applyAddressToForm(userAddresses[initialIndex]); // 3. Address Selection Event Listener savedDropdown.onchange = function () { const val = savedDropdown.value; if (val === "manual") { // Manual Override: clear fields for custom entry const nameInput = document.getElementById("shipping-name"); const phoneInput = document.getElementById("shipping-phone"); const addressInput = document.getElementById("shipping-address"); const provinceInput = document.getElementById("shipping-province"); const zipcodeInput = document.getElementById("shipping-zipcode"); if (nameInput) nameInput.value = ""; if (phoneInput) phoneInput.value = ""; if (addressInput) addressInput.value = ""; if (provinceInput) provinceInput.value = ""; if (zipcodeInput) zipcodeInput.value = ""; } else { const selectedAddr = userAddresses[parseInt(val, 10)]; applyAddressToForm(selectedAddr); } }; } } } // Success Checkout Action & Auto-Redirect to Profile -> My Purchases function processCheckout(formData) { const cartItems = [...state.cart]; if (cartItems.length === 0) { showToast("ไม่มีสินค้าในตะกร้า", "warning"); return; } const subtotal = cartItems.reduce((sum, item) => sum + (item.product.price * item.quantity), 0); let discountAmount = 0; if (state.appliedPromo) { if (state.appliedPromo.discountPercent) { discountAmount = Math.round((subtotal * state.appliedPromo.discountPercent) / 100); } else if (state.appliedPromo.discountAmount) { discountAmount = state.appliedPromo.discountAmount; } } const cartTotal = Math.max(0, subtotal - discountAmount); const orderId = `#AURA-${Math.floor(10000 + Math.random() * 90000)}`; // 1. Relational IDs: buyerId & sellerId const currentUserId = state.user.id || state.user.username; const currentUsername = state.user.username; const firstCartProduct = cartItems[0]?.product; const sellerId = (firstCartProduct && firstCartProduct.ownerId) ? firstCartProduct.ownerId : "beam"; // Payment Method & Status Mapping const selectedPaymentMethod = formData.paymentMethod || "COD"; const status = (selectedPaymentMethod === "COD" || selectedPaymentMethod === "เก็บเงินปลายทาง") ? "ที่ต้องจัดส่ง" : "ที่ต้องชำระ"; const shippingAddress = `${formData.name} (${formData.phone}) - ${formData.address}, ${formData.province} ${formData.zipcode}`; // 2. Construct Complete Relational Order Object const newOrder = { id: orderId.replace('#', ''), orderId: orderId, buyerId: currentUserId, customerName: currentUsername, sellerId: sellerId, items: cartItems.map(item => ({ name: item.product.name, price: item.product.price, quantity: item.quantity })), subtotal: subtotal, discount: discountAmount, promoCode: state.appliedPromo ? state.appliedPromo.code : "", totalPrice: cartTotal, total: cartTotal, totalAmount: cartTotal, shippingAddress: shippingAddress, phone: formData.phone, email: formData.email, address: `${formData.address}, ${formData.province} ${formData.zipcode}`, paymentMethod: selectedPaymentMethod, status: status, // 'ที่ต้องจัดส่ง' (To Ship) or 'ที่ต้องชำระ' (To Pay) trackingNumber: "", createdAt: new Date().toISOString() }; // 3. Update Global State & LocalStorage Immediately state.orders.push(newOrder); localStorage.setItem("aura_orders", JSON.stringify(state.orders)); // Clear user's cart after successful placement state.cart = []; saveCart(); updateCartUI(); showToast(`สั่งซื้อสินค้า ${orderId} สำเร็จ!`, "success"); // 4. Auto-Redirect to User Profile -> "การซื้อของฉัน" (My Purchases) activeMenu = "การซื้อของฉัน"; currentBuyerOrderTab = "All"; navigateTo("profile-view"); } function renderSellerProducts() { const container = document.getElementById("seller-products-list"); if (!container) return; // Filter products strictly belonging to the currently logged-in user const currentUserId = state.user.id || state.user.username; const currentShopProducts = state.products.filter(product => product.ownerId === currentUserId); if (currentShopProducts.length === 0) { container.innerHTML = `<p class="form-desc">ยังไม่มีสินค้าในร้านค้าของคุณ</p>`; return; } container.innerHTML = currentShopProducts.map(prod => ` <div class="product-card"> <div class="product-image-container" style="aspect-ratio: 16/9; padding: 15px;"> ${prod.image ? `<img src="${prod.image}" class="product-image" alt="${prod.name}">` : `<i data-lucide="${prod.icon || 'package'}" class="product-icon" style="width: 50px; height: 50px;"></i>`} </div> <div class="product-details" style="padding: 16px;"> <h4 class="product-title" style="font-size: 15px;">${prod.name}</h4> <span class="product-price" style="font-size: 16px;">฿${prod.price.toLocaleString()}</span> </div> </div> `).join(""); if (window.lucide) { window.lucide.createIcons(); } } // Add Product from Seller Portal function handleAddProduct(name, price, imageUrl, desc, description) { const newId = state.products.length > 0 ? Math.max(...state.products.map(p => p.id)) + 1 : 1; const currentUserId = state.user.id || state.user.username; // Check if imageUrl is a standard Lucide icon name, or actual url let icon = "package"; let image = ""; if (imageUrl.trim().startsWith("http://") || imageUrl.trim().startsWith("https://") || imageUrl.trim().startsWith("/") || imageUrl.trim().startsWith("data:image")) { image = imageUrl.trim(); } else if (imageUrl.trim()) { icon = imageUrl.trim().toLowerCase(); } const cleanDesc = desc ? desc.trim() : ""; const cleanDescription = (description && description.trim()) ? description.trim() : cleanDesc; const newProduct = { id: newId, name: name.trim(), desc: cleanDesc, description: cleanDescription, price: parseFloat(price), icon: icon, image: image, ownerId: currentUserId }; state.products.push(newProduct); localStorage.setItem("aura_products", JSON.stringify(state.products)); renderProducts(); renderSellerProducts(); showToast(`บันทึกสินค้า "${newProduct.name}" เรียบร้อยแล้ว`, "success"); // Reset form and redirect to Seller My Products tab addProductForm.reset(); switchSellerTab("seller-my-products"); } // Seller Dashboard Analytics - Isolated by sellerId with Dynamic Sidebar Badge Calculation function updateSellerDashboard() { const currentUserId = state.user.id || state.user.username; const currentSellerOrders = state.orders.filter(order => (order.sellerId || order.shopOwnerId) === currentUserId); const totalSales = currentSellerOrders.reduce((sum, order) => sum + order.total, 0); const totalOrdersCount = currentSellerOrders.length; dashboardTotalSales.textContent = `฿${totalSales.toLocaleString()}`; dashboardTotalOrders.textContent = `${totalOrdersCount} ออเดอร์`; // Dynamic Badge Calculation for "คำสั่งซื้อ (Orders)" sidebar menu const activePendingOrders = currentSellerOrders.filter(o => o.status === "Pending" || o.status === "ที่ต้องจัดส่ง" || o.status === "ที่ต้องชำระ" ); const badgeCount = activePendingOrders.length; const sidebarBadge = document.getElementById("sidebar-pending-count"); if (sidebarBadge) { sidebarBadge.textContent = badgeCount; // Conditional Rendering: ONLY display badge if badgeCount > 0 if (badgeCount > 0) { sidebarBadge.style.display = "inline-block"; } else { sidebarBadge.style.display = "none"; } } } // Seller Order Management Renderer - Isolated by sellerId with Dynamic Status Badge & Simple Status Change function renderSellerOrders() { // Read latest orders from localStorage for real-time sync with buyer actions state.orders = JSON.parse(localStorage.getItem("aura_orders")) || state.orders || []; const currentUserId = state.user.id || state.user.username; const currentSellerOrders = state.orders.filter(order => (order.sellerId || order.shopOwnerId) === currentUserId); if (currentSellerOrders.length === 0) { sellerOrdersList.innerHTML = ` <div class="empty-orders-state"> <i data-lucide="clipboard" style="width: 48px; height: 48px; color: var(--text-light); margin-bottom: 12px;"></i> <p>ยังไม่มีรายการคำสั่งซื้อจากลูกค้าในขณะนี้</p> </div> `; } else { // Render orders list, newest orders first sellerOrdersList.innerHTML = [...currentSellerOrders].reverse().map(order => { const status = order.status; // Dynamic Status Badge Colors let badgeStyle = "background-color: #f1f5f9; color: #475569;"; // Default fallback gray if (status === "ยกเลิก" || status === "Cancelled") { badgeStyle = "background-color: #fee2e2; color: #b91c1c; font-weight: 600;"; // Red } else if (status === "ที่ต้องชำระ" || status === "ที่ต้องจัดส่ง" || status === "Pending" || status === "Unpaid") { badgeStyle = "background-color: #ffedd5; color: #c2410c; font-weight: 600;"; // Orange } else if (status === "จัดส่งแล้ว" || status === "สำเร็จแล้ว" || status === "Shipped" || status === "Completed") { badgeStyle = "background-color: #dcfce7; color: #15803d; font-weight: 600;"; // Green } // Simple Status Change & Action Box let actionHtml = ""; if (status === "ยกเลิก" || status === "Cancelled") { actionHtml = ` <div class="shipment-action-box" style="padding: 12px; background-color: #fef2f2; border: 1px solid #fecaca; border-radius: var(--radius-md); margin-top: 12px;"> <p style="color: #ef4444; font-size: 14px; font-weight: 600; margin: 0 0 4px 0; display: flex; align-items: center; gap: 6px;"> <i data-lucide="alert-circle" style="width: 16px; height: 16px;"></i> ลูกค้ายกเลิกคำสั่งซื้อนี้แล้ว </p> ${order.cancelReason ? `<div style="font-size: 13px; color: #7f1d1d;"><strong>เหตุผลที่ยกเลิก:</strong> ${order.cancelReason}</div>` : ''} </div> `; } else if (status === "ที่ต้องจัดส่ง" || status === "Pending" || status === "ที่ต้องชำระ") { actionHtml = ` <div class="shipment-action-box" style="margin-top: 12px; display: flex; gap: 10px; align-items: center;"> <button class="btn btn-primary" style="padding: 8px 16px; font-size: 13px; background-color: #ee4d2d; border-color: #ee4d2d;" onclick="sellerUpdateStatus('${order.id}', 'จัดส่งแล้ว')"> <i data-lucide="truck" class="icon-left" style="width: 14px; height: 14px;"></i> เปลี่ยนสถานะเป็น "จัดส่งแล้ว" </button> </div> `; } else { actionHtml = ` <div class="shipment-action-box" style="margin-top: 12px;"> <div class="tracking-info-display" style="font-size: 14px; color: var(--text-main); font-weight: 500;"> <i data-lucide="check-circle" class="icon-left" style="color: #10b981; width: 16px; height: 16px; display: inline-block; vertical-align: middle;"></i> สถานะสินค้า: <span style="font-weight: 700; color: #10b981;">${order.status}</span> </div> </div> `; } return ` <div class="order-manage-card"> <div class="order-card-header"> <span class="order-id-txt">#${order.id}</span> <span class="status-badge" style="${badgeStyle} padding: 4px 10px; border-radius: 9999px; font-size: 13px;">${order.status}</span> </div> <div class="order-details-grid"> <div class="order-customer-box"> <h4>ข้อมูลจัดส่ง</h4> <div class="customer-detail-line"><strong>ผู้รับ:</strong> ${order.customerName}</div> <div class="customer-detail-line"><strong>โทรศัพท์:</strong> ${order.phone}</div> <div class="customer-detail-line"><strong>ที่อยู่:</strong> ${order.address || order.shippingAddress || ''}</div> <div class="customer-detail-line"><strong>ชำระเงิน:</strong> ${order.paymentMethod || 'COD'}</div> </div> <div class="order-items-box"> <h4>รายการสินค้า</h4> <div class="order-items-list-inner"> ${order.items.map(item => ` <div class="seller-order-item-row"> <span>${item.name} (x${item.quantity})</span> <span>฿${(item.price * item.quantity).toLocaleString()}</span> </div> `).join("")} </div> <div class="order-total-row"> <span>ยอดรวมชำระ:</span> <span>฿${(order.total || order.totalPrice || 0).toLocaleString()}</span> </div> </div> </div> ${actionHtml} </div> `; }).join(""); } if (window.lucide) { window.lucide.createIcons(); } } // Simple Seller Status Update window.sellerUpdateStatus = function (orderId, newStatus) { const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder) { targetOrder.status = newStatus; localStorage.setItem("aura_orders", JSON.stringify(state.orders)); showToast(`อัปเดตสถานะคำสั่งซื้อ #${targetOrder.id} เป็น "${newStatus}" แล้ว`, "success"); renderSellerOrders(); updateSellerDashboard(); } }; // Helper for Seller: Update Tracking History Array window.updateTrackingHistory = function (event, orderId) { event.preventDefault(); const form = event.target; const select = form.querySelector(".tracking-msg-select"); const customInput = form.querySelector(".tracking-msg-custom"); let message = select.value; if (message === "custom") { message = customInput ? customInput.value.trim() : ""; } if (!message) { showToast("กรุณาระบุข้อความสถานะพัสดุ", "warning"); return; } const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder) { if (!targetOrder.trackingHistory) { targetOrder.trackingHistory = []; } targetOrder.trackingHistory.push({ timestamp: new Date().toISOString(), message: message }); localStorage.setItem("aura_orders", JSON.stringify(state.orders)); showToast("อัปเดตประวัติการจัดส่งพัสดุเรียบร้อยแล้ว", "success"); renderSellerOrders(); } }; // Ship Order controller window.shipOrder = function (event, orderId) { event.preventDefault(); const trackingInput = event.target.querySelector("input"); const trackingNumber = trackingInput.value.trim(); if (!trackingNumber) return; const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder) { targetOrder.status = "จัดส่งแล้ว"; targetOrder.trackingNumber = trackingNumber; if (!targetOrder.trackingHistory) targetOrder.trackingHistory = []; targetOrder.trackingHistory.push({ timestamp: new Date().toISOString(), message: `จัดส่งสินค้าแล้ว - หมายเลขพัสดุ: ${trackingNumber}` }); localStorage.setItem("aura_orders", JSON.stringify(state.orders)); renderSellerOrders(); updateSellerDashboard(); showToast(`จัดส่งสินค้า #${targetOrder.id} เรียบร้อยแล้ว (Tracking: ${trackingNumber})`, "success"); } }; let activeMenu = "ประวัติ"; let currentBuyerOrderTab = "all"; function setupBuyerProfileTabs() { // Sidebar nav & subnav click handlers const buyerNavItems = document.querySelectorAll(".buyer-nav-item, .buyer-subnav-item"); buyerNavItems.forEach(item => { item.addEventListener("click", () => { let menuName = item.getAttribute("data-buyer-menu") || item.getAttribute("data-buyer-sub") || item.getAttribute("data-buyer-nav"); if (menuName === "purchases") menuName = "การซื้อของฉัน"; if (menuName === "account") menuName = "ประวัติ"; if (menuName) { activeMenu = menuName; buyerNavItems.forEach(i => i.classList.remove("active")); item.classList.add("active"); renderBuyerProfile(); } }); }); } function bindOrderTabEvents() { const orderTabBtns = document.querySelectorAll(".order-tab-btn"); orderTabBtns.forEach(btn => { btn.addEventListener("click", () => { orderTabBtns.forEach(b => b.classList.remove("active")); btn.classList.add("active"); currentBuyerOrderTab = btn.getAttribute("data-order-tab"); renderBuyerOrders(); }); }); } // Global Order State Update Helper for Buyer Actions window.handleUpdateOrderStatus = function (orderId, newStatus) { const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder) { targetOrder.status = newStatus; if (!targetOrder.trackingHistory) targetOrder.trackingHistory = []; targetOrder.trackingHistory.push({ timestamp: new Date().toISOString(), message: `สถานะคำสั่งซื้อเปลี่ยนเป็น: ${newStatus}` }); localStorage.setItem("aura_orders", JSON.stringify(state.orders)); showToast(`อัปเดตสถานะคำสั่งซื้อ #${targetOrder.id} เป็น "${newStatus}" แล้ว`, "info"); renderBuyerOrders(); } }; window.handleBuyAgain = function (orderId) { const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder && targetOrder.items) { targetOrder.items.forEach(item => { const prod = state.products.find(p => p.name === item.name) || { id: Date.now(), name: item.name, price: item.price, icon: "package" }; const existingInCart = state.cart.find(c => c.product.name === prod.name); if (existingInCart) { existingInCart.quantity += item.quantity; } else { state.cart.push({ product: prod, quantity: item.quantity }); } }); saveCart(); updateCartUI(); showToast("เพิ่มสินค้าจากคำสั่งซื้อเดิมเข้าตะกร้าแล้ว", "success"); navigateTo("cart-view"); } }; // Order Cancellation Modal Handlers window.openCancelOrderModal = function (orderId) { state.pendingCancelOrderId = orderId; const modal = document.getElementById("cancel-order-modal"); const orderIdSpan = document.getElementById("cancel-modal-order-id"); const reasonSelect = document.getElementById("cancel-reason-select"); const reasonText = document.getElementById("cancel-reason-text"); if (orderIdSpan) orderIdSpan.textContent = `#${orderId}`; if (reasonSelect) reasonSelect.selectedIndex = 0; if (reasonText) reasonText.value = ""; if (modal) modal.classList.add("active"); }; window.closeCancelOrderModal = function () { state.pendingCancelOrderId = null; const modal = document.getElementById("cancel-order-modal"); if (modal) modal.classList.remove("active"); }; function confirmCancelOrderWithReason(reason) { if (!state.pendingCancelOrderId) return; const orderId = state.pendingCancelOrderId; const targetOrder = state.orders.find(o => o.id === orderId || o.orderId === orderId || o.id === orderId.replace('#', '')); if (targetOrder) { targetOrder.status = "ยกเลิก"; targetOrder.cancelReason = reason || "ยกเลิกโดยผู้ซื้อ"; localStorage.setItem("aura_orders", JSON.stringify(state.orders)); showToast(`ยกเลิกคำสั่งซื้อ #${targetOrder.id} เรียบร้อยแล้ว`, "info"); closeCancelOrderModal(); renderBuyerOrders(); } } function renderBuyerOrders() { const orderHistoryList = document.getElementById("buyer-order-history-list"); if (!orderHistoryList) return; // Read latest orders from localStorage for real-time sync with seller updates state.orders = JSON.parse(localStorage.getItem("aura_orders")) || state.orders || []; const currentUserId = state.user.id || state.user.username; const currentUsername = state.user.username; // Strict Buyer Data Isolation: Filter orders purchased strictly by active user const myPurchases = state.orders.filter(order => (order.buyerId && order.buyerId === currentUserId) || (order.customerName && order.customerName === currentUsername) ); // Dynamic Tab Filtering Logic on top of myPurchases let filteredOrders = myPurchases; if (currentBuyerOrderTab !== "All" && currentBuyerOrderTab !== "all") { if (currentBuyerOrderTab === "to-pay" || currentBuyerOrderTab === "ที่ต้องชำระ") { filteredOrders = myPurchases.filter(o => o.status === "ที่ต้องชำระ" || o.status === "Unpaid" || o.status === "Pending Payment"); } else if (currentBuyerOrderTab === "to-ship" || currentBuyerOrderTab === "ที่ต้องจัดส่ง") { filteredOrders = myPurchases.filter(o => o.status === "ที่ต้องจัดส่ง" || o.status === "Pending"); } else if (currentBuyerOrderTab === "to-receive" || currentBuyerOrderTab === "ที่ต้องได้รับ") { filteredOrders = myPurchases.filter(o => o.status === "ที่ต้องได้รับ" || o.status === "Shipped" || o.status === "In Transit" || o.status === "จัดส่งแล้ว"); } else if (currentBuyerOrderTab === "completed" || currentBuyerOrderTab === "สำเร็จแล้ว") { filteredOrders = myPurchases.filter(o => o.status === "สำเร็จแล้ว" || o.status === "Completed" || o.status === "Delivered"); } else if (currentBuyerOrderTab === "cancelled" || currentBuyerOrderTab === "ยกเลิก") { filteredOrders = myPurchases.filter(o => o.status === "ยกเลิก" || o.status === "Cancelled"); } else { filteredOrders = myPurchases.filter(o => o.status === currentBuyerOrderTab); } } // Conditional Rendering: Empty State vs Order Cards if (filteredOrders.length === 0) { orderHistoryList.innerHTML = ` <div class="empty-orders-state" style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 80px 20px;"> <div style="background-color: #f1f5f9; padding: 24px; border-radius: 9999px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px;"> <i data-lucide="clipboard-list" style="width: 64px; height: 64px; color: #cbd5e1;"></i> </div> <p style="margin-top: 16px; color: #475569; font-weight: 500; font-size: 16px;" data-i18n="order_empty">ยังไม่มีการสั่งซื้อ</p> </div> `; } else { orderHistoryList.innerHTML = filteredOrders.map(order => { const rawStatus = order.status; let actionButtonsHtml = ""; if (rawStatus === "ที่ต้องชำระ" || rawStatus === "Unpaid" || rawStatus === "Pending Payment") { actionButtonsHtml = ` <button onclick="handleUpdateOrderStatus('${order.id}', 'ที่ต้องจัดส่ง')" class="btn" style="background-color: #ee4d2d; color: #fff; border: none; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ชำระเงิน</button> <button onclick="openCancelOrderModal('${order.id}')" class="btn" style="border: 1px solid #d1d5db; color: #ef4444; background: #fef2f2; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ยกเลิกคำสั่งซื้อ</button> `; } else if (rawStatus === "ที่ต้องจัดส่ง" || rawStatus === "Pending") { actionButtonsHtml = ` <button onclick="openCancelOrderModal('${order.id}')" class="btn" style="border: 1px solid #d1d5db; color: #ef4444; background: #fef2f2; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ยกเลิกคำสั่งซื้อ</button> `; } else if (rawStatus === "ที่ต้องได้รับ" || rawStatus === "Shipped" || rawStatus === "In Transit" || rawStatus === "จัดส่งแล้ว") { actionButtonsHtml = ` <button onclick="handleUpdateOrderStatus('${order.id}', 'สำเร็จแล้ว')" class="btn" style="background-color: #ee4d2d; color: #fff; border: none; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ฉันได้ตรวจสอบและยอมรับสินค้า</button> <button onclick="handleUpdateOrderStatus('${order.id}', 'คืนเงิน/คืนสินค้า')" class="btn" style="border: 1px solid #d1d5db; color: #374151; background: transparent; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ขอคืนเงิน/คืนสินค้า</button> `; } else if (rawStatus === "สำเร็จแล้ว" || rawStatus === "Completed" || rawStatus === "Delivered") { actionButtonsHtml = ` <button onclick="handleBuyAgain('${order.id}')" class="btn" style="background-color: #ee4d2d; color: #fff; border: none; padding: 8px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500; cursor: pointer;">ซื้ออีกครั้ง</button> `; } // Cancellation reason display box let cancelReasonHtml = ""; if (rawStatus === "ยกเลิก" || rawStatus === "Cancelled") { cancelReasonHtml = ` <div style="margin-top: 12px; padding: 12px 16px; background-color: #fef2f2; border: 1px solid #fecaca; border-radius: var(--radius-sm); font-size: 13px; color: #b91c1c;"> <strong style="display: flex; align-items: center; gap: 4px; margin-bottom: 2px;"> <i data-lucide="info" style="width: 14px; height: 14px;"></i> เหตุผลที่ยกเลิก: </strong> <span>${order.cancelReason || 'ไม่ระบุเหตุผล'}</span> </div> `; } return ` <div class="order-manage-card" style="margin-bottom: 16px; border: 1px solid var(--border); border-radius: var(--radius-md); padding: 16px; background: var(--white);"> <div class="order-card-header" style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 12px;"> <span class="order-id-txt" style="font-weight: 600;">#${order.id}</span> <span class="status-badge ${order.status === 'Pending' || order.status === 'ที่ต้องจัดส่ง' ? 'pending' : 'shipped'}" style="font-weight: 600; color: #ee4d2d;"> ${order.status} </span> </div> <div class="order-items-list-inner" style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px;"> ${order.items.map(item => ` <div style="display: flex; justify-content: space-between; font-size: 14px;"> <span>${item.name} (x${item.quantity})</span> <span style="font-weight: 600;">฿${(item.price * item.quantity).toLocaleString()}</span> </div> `).join("")} </div> <div style="display: flex; justify-content: space-between; align-items: center; border-top: 1px dashed var(--border); padding-top: 12px;"> <span style="font-size: 13px; color: var(--text-muted);">${order.paymentMethod ? `ชำระโดย: ${order.paymentMethod}` : 'COD'}</span> <div style="font-size: 15px; font-weight: 700; color: #ee4d2d;"> ยอดรวม: ฿${(order.total || order.totalPrice || 0).toLocaleString()} </div> </div> ${cancelReasonHtml} ${actionButtonsHtml ? ` <div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; border-top: 1px solid #f1f5f9; padding-top: 12px;"> ${actionButtonsHtml} </div> ` : ''} </div> `; }).join(""); } applyLanguage(); if (window.lucide) { window.lucide.createIcons(); } } let addressesState = getInitialState("aura_addresses", [ { id: 1, name: "สมชาย ใจดี", phone: "0891234567", address: "123/45 ถนนสุขุมวิท, เขตวัฒนา", province: "กรุงเทพมหานคร", zipCode: "10110", isDefault: true, userId: "beam" }, { id: 2, name: "ชาตรี", phone: "0800647148", address: "176 เชียงใหม่ หมู่ 10 ต.หนองหาร อ.สันทราย", province: "เชียงใหม่", zipCode: "50290", isDefault: false, userId: "beam" } ]); let isAddingAddressState = false; let banksState = []; let isAddingBankState = false; function toggleAddAddress(isAdding) { isAddingAddressState = isAdding; renderBuyerProfile(); } function saveAddress(e) { e.preventDefault(); const name = document.getElementById("add-addr-name").value.trim(); const phone = document.getElementById("add-addr-phone").value.trim(); const address = document.getElementById("add-addr-detail").value.trim(); const province = document.getElementById("add-addr-province").value.trim(); const zipCode = document.getElementById("add-addr-zipcode").value.trim(); const currentUserId = state.user.id || state.user.username || "beam"; if (name && phone && address) { const userAddrs = addressesState.filter(a => a.userId === currentUserId); const newAddress = { id: Date.now(), name, phone, address, province, zipCode, isDefault: userAddrs.length === 0, userId: currentUserId }; addressesState.push(newAddress); localStorage.setItem("aura_addresses", JSON.stringify(addressesState)); isAddingAddressState = false; showToast("บันทึกที่อยู่เรียบร้อยแล้ว", "success"); renderBuyerProfile(); } } function toggleAddBank(isAdding) { isAddingBankState = isAdding; renderBuyerProfile(); } function saveBank(e) { e.preventDefault(); const bankName = document.getElementById("add-bank-name").value; const accountNum = document.getElementById("add-bank-account").value.trim(); const holderName = document.getElementById("add-bank-holder").value.trim(); if (bankName && accountNum && holderName) { banksState.push({ id: Date.now(), bankName, accountNum, holderName }); isAddingBankState = false; showToast("บันทึกบัญชีธนาคารเรียบร้อยแล้ว", "success"); renderBuyerProfile(); } } function renderBuyerProfile() { const container = document.getElementById("buyer-profile-content-area"); if (!container) return; const currentUsername = state.user.username || ""; const currentUserFullName = state.user.fullName || currentUsername; const currentUserEmail = state.user.email || (currentUsername ? `${currentUsername}@example.com` : ""); const currentUserPhone = state.user.phone || ""; const usernameDisplay = document.getElementById("buyer-profile-username-display"); if (usernameDisplay) usernameDisplay.textContent = currentUsername; const sellerStatusHtml = state.user.hasShop ? `<span data-i18n="prof_seller_approved">อนุมัติแล้ว (ร้านค้า: </span>${state.shopInfo ? state.shopInfo.name : ""})` : `<span data-i18n="prof_seller_none">ยังไม่ได้เปิดร้านค้า</span>`; // Switch/Case Conditional Rendering in Right Column Area switch (activeMenu) { case "ประวัติ": const profileImage = state.users[currentUsername] ? state.users[currentUsername].profileImage : null; const avatarHtml = profileImage ? `<img src="${profileImage}" style="width: 100%; height: 100%; object-fit: cover;">` : `<i data-lucide="user" style="width: 40px; height: 40px; color: var(--primary);"></i>`; container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 6px;" data-i18n="form_my_profile">ข้อมูลของฉัน</h3> <p style="font-size: 14px; color: var(--text-muted); margin-bottom: 24px; border-bottom: 1px solid var(--border); padding-bottom: 12px;" data-i18n="form_profile_desc">จัดการข้อมูลส่วนตัวเพื่อความปลอดภัยของบัญชี</p> <div style="display: flex; align-items: center; gap: 20px; margin-bottom: 24px;"> <div style="width: 80px; height: 80px; border-radius: 50%; background-color: var(--primary-light); border: 2px solid var(--primary-soft); display: flex; align-items: center; justify-content: center; overflow: hidden; flex-shrink: 0;"> ${avatarHtml} </div> <div> <label for="profile-image-upload" class="btn btn-text" style="border: 1px solid var(--border); padding: 6px 12px; font-size: 13px; cursor: pointer;"> <i data-lucide="upload" class="icon-left" style="width: 14px; height: 14px;"></i> <span data-i18n="btn_change_avatar">เปลี่ยนรูปโปรไฟล์</span> </label> <input type="file" id="profile-image-upload" accept="image/*" style="display: none;" onchange="handleProfileImageUpload(event)"> <p style="font-size: 12px; color: var(--text-light); margin-top: 6px;" data-i18n="desc_avatar_size">ขนาดไฟล์สูงสุด 1MB (.JPG, .PNG)</p> </div> </div> <form onsubmit="event.preventDefault(); showToast('บันทึกข้อมูลส่วนตัวเรียบร้อยแล้ว', 'success');" style="display: flex; flex-direction: column; gap: 18px; max-width: 580px;"> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="form_username">ชื่อผู้ใช้งาน (Username)</label> <input type="text" value="${currentUsername}" readonly style="background-color: #f1f5f9; cursor: not-allowed;"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="form_fullname">ชื่อ-นามสกุล (Full Name)</label> <input type="text" placeholder="กรอกชื่อ-นามสกุลของคุณ..." value="${currentUserFullName}"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="form_email">อีเมล (Email)</label> <input type="email" placeholder="เช่น yourname@email.com" value="${currentUserEmail}"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="form_phone">เบอร์โทรศัพท์ (Phone Number)</label> <input type="tel" placeholder="กรอกเบอร์โทรศัพท์..." value="${currentUserPhone}"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500; margin-bottom: 8px;" data-i18n="form_gender">เพศ (Gender)</label> <div style="display: flex; gap: 24px; font-size: 14px;"> <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;"> <input type="radio" name="gender" value="male" checked> <span data-i18n="form_male">ชาย</span> </label> <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;"> <input type="radio" name="gender" value="female"> <span data-i18n="form_female">หญิง</span> </label> <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;"> <input type="radio" name="gender" value="other"> <span data-i18n="form_other">อื่นๆ</span> </label> </div> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="form_dob">วัน/เดือน/ปี เกิด (Date of Birth)</label> <input type="date" value="1998-05-20"> </div> <button type="submit" class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d; width: 140px; margin-top: 10px;"> <span data-i18n="btn_save">บันทึก</span> </button> </form> </div> `; break; case "purchases": case "การซื้อของฉัน": container.innerHTML = ` <!-- 1. User Summary Card --> <div class="profile-summary-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; align-items: center; gap: 16px;"> <div class="avatar-circle" style="width: 54px; height: 54px; font-size: 20px; background-color: var(--primary-soft); color: var(--primary);"> <i data-lucide="user"></i> </div> <div> <h3 style="font-size: 18px; font-weight: 600;">${currentUsername}</h3> <div style="display: flex; gap: 8px; align-items: center; margin-top: 4px;"> <span style="font-size: 12px; background-color: #fef3c7; color: #d97706; padding: 2px 8px; border-radius: var(--radius-full); font-weight: 600;">Silver Member</span> <span style="font-size: 13px; color: var(--text-muted);"><span data-i18n="prof_seller_status">สถานะผู้ขาย: </span> ${sellerStatusHtml}</span> </div> </div> </div> </div> <!-- 2. Purchase History Card with Tab Bar --> <div class="order-status-tabs-container" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden;"> <div style="padding: 16px 20px 0;"> <h3 style="font-size: 16px; font-weight: 600; margin-bottom: 12px;" data-i18n="prof_purchase_history">ประวัติการสั่งซื้อของฉัน (My Purchase History)</h3> </div> <div class="order-status-tab-bar" style="display: flex; border-bottom: 1px solid var(--border); overflow-x: auto;"> <button class="order-tab-btn ${currentBuyerOrderTab === 'all' ? 'active' : ''}" data-order-tab="all" data-i18n="order_tab_all">All</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'to-pay' ? 'active' : ''}" data-order-tab="to-pay" data-i18n="order_tab_pay">ที่ต้องชำระ</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'to-ship' ? 'active' : ''}" data-order-tab="to-ship" data-i18n="order_tab_ship">ที่ต้องจัดส่ง</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'to-receive' ? 'active' : ''}" data-order-tab="to-receive" data-i18n="order_tab_recv">ที่ต้องได้รับ</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'completed' ? 'active' : ''}" data-order-tab="completed" data-i18n="order_tab_comp">สำเร็จแล้ว</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'cancelled' ? 'active' : ''}" data-order-tab="cancelled" data-i18n="order_tab_cancel">ยกเลิก</button> <button class="order-tab-btn ${currentBuyerOrderTab === 'returns' ? 'active' : ''}" data-order-tab="returns" data-i18n="order_tab_refund">คืนเงิน/คืนสินค้า</button> </div> <div id="buyer-orders-tab-content" style="padding: 20px;"> <div id="buyer-order-history-list"></div> </div> </div> `; bindOrderTabEvents(); renderBuyerOrders(); break; case "ที่อยู่": if (isAddingAddressState) { container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 6px;" data-i18n="prof_addr_add">เพิ่มที่อยู่ใหม่</h3> <p style="font-size: 14px; color: var(--text-muted); margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 12px;" data-i18n="prof_addr_desc">จัดการที่อยู่สำหรับจัดส่งสินค้า</p> <form onsubmit="saveAddress(event)" style="display: flex; flex-direction: column; gap: 16px; max-width: 580px;"> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">ชื่อ-นามสกุล ผู้รับ</label> <input type="text" id="add-addr-name" required placeholder="เช่น สมชาย ใจดี"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">เบอร์โทรศัพท์</label> <input type="tel" id="add-addr-phone" required placeholder="เช่น 0891234567"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">รายละเอียดที่อยู่</label> <textarea id="add-addr-detail" required rows="2" placeholder="บ้านเลขที่, ซอย, ถนน, แขวง/ตำบล, เขต/อำเภอ"></textarea> </div> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;"> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">จังหวัด</label> <input type="text" id="add-addr-province" required placeholder="เช่น กรุงเทพมหานคร"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">รหัสไปรษณีย์</label> <input type="text" id="add-addr-zipcode" required placeholder="เช่น 10110"> </div> </div> <div style="display: flex; gap: 12px; margin-top: 10px;"> <button type="submit" class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d;"><span data-i18n="btn_save">บันทึก</span></button> <button type="button" class="btn btn-text" onclick="toggleAddAddress(false)">ยกเลิก</button> </div> </form> </div> `; } else { const currentUserAddresses = addressesState.filter(addr => addr.userId === (state.user.id || state.user.username || "beam")); container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 20px;"> <div> <h3 style="font-size: 18px; font-weight: 600;" data-i18n="prof_addr_title">ที่อยู่ของฉัน</h3> <p style="font-size: 14px; color: var(--text-muted);" data-i18n="prof_addr_desc">จัดการที่อยู่สำหรับจัดส่งสินค้า</p> </div> <button class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d;" onclick="toggleAddAddress(true)"> <span data-i18n="prof_addr_add">+ เพิ่มที่อยู่ใหม่</span> </button> </div> ${currentUserAddresses.length === 0 ? ` <div style="padding: 40px; text-align: center; color: var(--text-muted); border: 1px dashed var(--border); border-radius: var(--radius-md);" data-i18n="prof_addr_empty"> ยังไม่มีที่อยู่ที่บันทึกไว้สำหรับบัญชีนี้ </div> ` : ` <div style="display: flex; flex-direction: column; gap: 12px;"> ${currentUserAddresses.map(addr => ` <div style="padding: 16px; border: 1px solid var(--border); border-radius: var(--radius-md); background-color: var(--background);"> <div style="font-weight: 600; margin-bottom: 4px;">${addr.name} (<span style="color: var(--text-muted); font-weight: normal;">${addr.phone}</span>)</div> <div style="font-size: 14px; color: var(--text-muted);">${addr.address} ${addr.province || ''} ${addr.zipCode || ''}</div> ${addr.isDefault ? `<span style="display: inline-block; font-size: 11px; background-color: #dbeafe; color: #2563eb; padding: 2px 8px; border-radius: 4px; margin-top: 8px; font-weight: 600;">ที่อยู่หลัก</span>` : ''} </div> `).join('')} </div> `} </div> `; } break; case "บัญชีธนาคาร&บัตร": if (isAddingBankState) { container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 6px;" data-i18n="prof_bank_title">เพิ่มบัญชีธนาคาร</h3> <p style="font-size: 14px; color: var(--text-muted); margin-bottom: 20px; border-bottom: 1px solid var(--border); padding-bottom: 12px;" data-i18n="prof_bank_desc">กรอกข้อมูลบัญชีธนาคารเพื่อรองรับการชำระเงิน</p> <form onsubmit="saveBank(event)" style="display: flex; flex-direction: column; gap: 16px; max-width: 580px;"> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">เลือกธนาคาร</label> <select id="add-bank-name" required style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-md);"> <option value="ธนาคารกสิกรไทย (KBANK)">ธนาคารกสิกรไทย (KBANK)</option> <option value="ธนาคารไทยพาณิชย์ (SCB)">ธนาคารไทยพาณิชย์ (SCB)</option> <option value="ธนาคารกรุงเทพ (BBL)">ธนาคารกรุงเทพ (BBL)</option> <option value="ธนาคารกรุงไทย (KTB)">ธนาคารกรุงไทย (KTB)</option> <option value="ธนาคารกรุงศรีอยุธยา (BAY)">ธนาคารกรุงศรีอยุธยา (BAY)</option> </select> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">เลขที่บัญชี</label> <input type="text" id="add-bank-account" required placeholder="เช่น 123-4-56789-0"> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;">ชื่อบัญชี</label> <input type="text" id="add-bank-holder" required placeholder="เช่น นายสมชาย ใจดี"> </div> <div style="display: flex; gap: 12px; margin-top: 10px;"> <button type="submit" class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d;"><span data-i18n="btn_save">บันทึก</span></button> <button type="button" class="btn btn-text" onclick="toggleAddBank(false)">ยกเลิก</button> </div> </form> </div> `; } else { container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 20px;"> <div> <h3 style="font-size: 18px; font-weight: 600;" data-i18n="prof_bank_title">บัญชีธนาคาร & บัตรของฉัน</h3> <p style="font-size: 14px; color: var(--text-muted);" data-i18n="prof_bank_desc">จัดการข้อมูลการชำระเงินและรับเงิน</p> </div> <button class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d;" onclick="toggleAddBank(true)"> <span data-i18n="prof_bank_add">+ เพิ่มบัญชีธนาคาร</span> </button> </div> ${banksState.length === 0 ? ` <div style="padding: 40px; text-align: center; color: var(--text-muted); border: 1px dashed var(--border); border-radius: var(--radius-md);" data-i18n="prof_bank_empty"> ยังไม่มีข้อมูลบัญชีธนาคารหรือบัตรชำระเงินที่บันทึกไว้ </div> ` : ` <div style="display: flex; flex-direction: column; gap: 12px;"> ${banksState.map(b => ` <div style="padding: 16px; border: 1px solid var(--border); border-radius: var(--radius-md); background-color: var(--background); display: flex; justify-content: space-between; align-items: center;"> <div> <div style="font-weight: 600; color: var(--text-main);">${b.bankName}</div> <div style="font-size: 14px; color: var(--text-muted);">เลขบัญชี: ${b.accountNum} (${b.holderName})</div> </div> <span style="font-size: 12px; color: #10b981; font-weight: 600;">เชื่อมต่อแล้ว</span> </div> `).join('')} </div> `} </div> `; } break; case "เปลี่ยนรหัสผ่าน": container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 6px;" data-i18n="prof_pass_title">เปลี่ยนรหัสผ่าน</h3> <p style="font-size: 14px; color: var(--text-muted); margin-bottom: 24px; border-bottom: 1px solid var(--border); padding-bottom: 12px;" data-i18n="prof_pass_desc">เพื่อความปลอดภัยของบัญชี กรุณาอย่าเปิดเผยรหัสผ่านแก่ผู้อื่น</p> <form onsubmit="event.preventDefault(); showToast('เปลี่ยนรหัสผ่านสำเร็จเรียบร้อยแล้ว', 'success'); event.target.reset();" style="display: flex; flex-direction: column; gap: 18px; max-width: 580px;"> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="prof_pass_curr">รหัสผ่านปัจจุบัน</label> <input type="password" required placeholder="กรอกรหัสผ่านปัจจุบัน..."> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="prof_pass_new">รหัสผ่านใหม่</label> <input type="password" required placeholder="กรอกรหัสผ่านใหม่อย่างน้อย 6 ตัวอักษร..."> </div> <div class="input-group" style="margin-bottom: 0;"> <label style="font-weight: 500;" data-i18n="prof_pass_confirm">ยืนยันรหัสผ่านใหม่</label> <input type="password" required placeholder="กรอกยืนยันรหัสผ่านใหม่..."> </div> <button type="submit" class="btn btn-primary" style="background-color: #ee4d2d; border-color: #ee4d2d; width: 180px; margin-top: 10px;"> <span data-i18n="prof_save_changes">บันทึกการเปลี่ยนแปลง</span> </button> </form> </div> `; break; case "การตั้งค่าความเป็นส่วนตัว": case "ตั้งค่าการแจ้งเตือน": container.innerHTML = ` <div class="form-container-card" style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px;"> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 6px;" data-i18n="prof_priv_title">การตั้งค่าความเป็นส่วนตัว & การแจ้งเตือน</h3> <p style="font-size: 14px; color: var(--text-muted); margin-bottom: 24px; border-bottom: 1px solid var(--border); padding-bottom: 12px;" data-i18n="prof_priv_desc">จัดการการยินยอมและการแจ้งเตือนของระบบ</p> <div style="display: flex; flex-direction: column; gap: 20px; max-width: 600px;"> <div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 12px; border-bottom: 1px solid var(--border);"> <div> <div style="font-weight: 600; font-size: 15px;" data-i18n="prof_priv_email">รับอีเมลข่าวสารและโปรโมชัน</div> <div style="font-size: 13px; color: var(--text-muted);" data-i18n="prof_priv_email_desc">รับข้อมูลส่วนลด สิทธิพิเศษ และอัปเดตสินค้าใหม่ทางอีเมล</div> </div> <label class="toggle-switch"> <input type="checkbox" checked onchange="showToast('อัปเดตการตั้งค่าแล้ว', 'info')"> <span class="toggle-slider"></span> </label> </div> <div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 12px; border-bottom: 1px solid var(--border);"> <div> <div style="font-weight: 600; font-size: 15px;" data-i18n="prof_priv_public">การเปิดเผยโปรไฟล์สาธารณะ</div> <div style="font-size: 13px; color: var(--text-muted);" data-i18n="prof_priv_public_desc">อนุญาตให้ผู้ใช้อื่นมองเห็นชื่อและรูปโปรไฟล์ของคุณ</div> </div> <label class="toggle-switch"> <input type="checkbox" checked onchange="showToast('อัปเดตการตั้งค่าแล้ว', 'info')"> <span class="toggle-slider"></span> </label> </div> <div style="display: flex; justify-content: space-between; align-items: center;"> <div> <div style="font-weight: 600; font-size: 15px;" data-i18n="prof_priv_app">แจ้งเตือนสถานะคำสั่งซื้อในแอป</div> <div style="font-size: 13px; color: var(--text-muted);" data-i18n="prof_priv_app_desc">การแจ้งเตือนทันทีเมื่อคำสั่งซื้อเปลี่ยนสถานะเป็นจัดส่งแล้ว</div> </div> <label class="toggle-switch"> <input type="checkbox" checked onchange="showToast('อัปเดตการตั้งค่าแล้ว', 'info')"> <span class="toggle-slider"></span> </label> </div> </div> </div> `; break; default: let translatedMenuName = activeMenu; if (activeMenu === "Order Settings") translatedMenuName = translations[state.lang]["sidebar_order_settings"] || "Order Settings"; else if (activeMenu === "notifications" || activeMenu === "การแจ้งเตือน") translatedMenuName = translations[state.lang]["sidebar_notifications"] || "Notifications"; else if (activeMenu === "Order Updates" || activeMenu === "อัปเดตคำสั่งซื้อ") translatedMenuName = translations[state.lang]["sidebar_order_updates"] || "Order Updates"; container.innerHTML = ` <div style="background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 48px; text-align: center; color: var(--text-muted);"> <i data-lucide="wrench" style="width: 48px; height: 48px; margin-bottom: 12px; display: inline-block; color: var(--text-light);"></i> <h3 style="font-size: 16px; font-weight: 600; color: var(--text-main); margin-bottom: 4px;"> <span data-i18n="prof_dev_prefix"></span>${translatedMenuName}<span data-i18n="prof_dev_suffix"></span> </h3> <p style="font-size: 14px;" data-i18n="prof_dev_desc">ระบบกำลังเตรียมพร้อมให้บริการเมนูนี้เร็วๆ นี้</p> </div> `; break; } applyLanguage(); if (window.lucide) { window.lucide.createIcons(); } } // Event Listeners function setupEventListeners() { // Navigation Action Toggles navLinks.forEach(link => { link.addEventListener("click", (e) => { e.preventDefault(); const target = link.getAttribute("data-target"); navigateTo(target); }); }); const userProfileBadge = document.getElementById("user-profile-badge"); if (userProfileBadge) { userProfileBadge.setAttribute("title", "เปิดโปรไฟล์ผู้ใช้"); userProfileBadge.addEventListener("click", () => { navigateTo("profile-view"); }); } document.getElementById("nav-logo").addEventListener("click", () => { navigateTo(state.user.isLoggedIn ? "products-view" : "login-view"); }); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { toggleTheme(); }); } // <--- เติม 3 บรรทัดนี้ เพื่อให้ปุ่มถูกคลิกได้ const langBtn = document.getElementById("lang-toggle-btn"); if (langBtn) { langBtn.addEventListener("click", toggleLanguage); } document.getElementById("header-cart-btn").addEventListener("click", () => { navigateTo("cart-view"); }); logoutBtn.addEventListener("click", () => { handleLogout(); }); // Login Form Submit loginForm.addEventListener("submit", (e) => { e.preventDefault(); const username = usernameInput ? usernameInput.value : ""; const passwordInput = document.getElementById("password-input"); const password = passwordInput ? passwordInput.value : ""; if (username.trim()) { handleLogin(username, password); } }); // Toggle Login / Register View Links const linkToRegister = document.getElementById("link-to-register"); if (linkToRegister) { linkToRegister.addEventListener("click", (e) => { e.preventDefault(); navigateTo("register-view"); }); } const linkToLogin = document.getElementById("link-to-login"); if (linkToLogin) { linkToLogin.addEventListener("click", (e) => { e.preventDefault(); navigateTo("login-view"); }); } // Register Form Submit const registerForm = document.getElementById("register-form"); if (registerForm) { registerForm.addEventListener("submit", (e) => { e.preventDefault(); const username = document.getElementById("reg-username").value; const email = document.getElementById("reg-email").value; const password = document.getElementById("reg-password").value; const confirmPassword = document.getElementById("reg-confirm-password").value; register(username, email, password, confirmPassword); }); } // Cart Navigation Toggles document.getElementById("btn-back-to-products").addEventListener("click", () => { navigateTo("products-view"); }); document.getElementById("btn-go-to-checkout").addEventListener("click", () => { navigateTo("checkout-view"); }); // Checkout Navigation Toggles document.getElementById("btn-back-to-cart").addEventListener("click", () => { navigateTo("cart-view"); }); // Wishlist Header Button & Modal Listeners const headerWishlistBtn = document.getElementById("header-wishlist-btn"); if (headerWishlistBtn) { headerWishlistBtn.addEventListener("click", () => { openWishlistModal(); }); } const btnCloseWishlist = document.getElementById("btn-close-wishlist"); if (btnCloseWishlist) { btnCloseWishlist.addEventListener("click", () => { closeWishlistModal(); }); } // Search & Category Tab Listeners const searchInput = document.getElementById("product-search-input"); const btnSearch = document.getElementById("btn-search-product"); if (searchInput) { searchInput.addEventListener("input", (e) => { state.searchQuery = e.target.value; renderProducts(); }); } if (btnSearch) { btnSearch.addEventListener("click", () => { state.searchQuery = searchInput ? searchInput.value : ""; renderProducts(); }); } const categoryTabsContainer = document.getElementById("category-tabs"); if (categoryTabsContainer) { categoryTabsContainer.addEventListener("click", (e) => { const tabBtn = e.target.closest(".category-tab"); if (!tabBtn) return; const category = tabBtn.getAttribute("data-category"); state.selectedCategory = category; categoryTabsContainer.querySelectorAll(".category-tab").forEach(b => b.classList.remove("active")); tabBtn.classList.add("active"); renderProducts(); }); } // Checkout Payment Methods Radio Card Active Sync & Dynamic Interface Switching const paymentOptions = document.querySelectorAll(".payment-option-card"); paymentOptions.forEach(card => { card.addEventListener("click", () => { paymentOptions.forEach(c => c.classList.remove("active")); card.classList.add("active"); const radio = card.querySelector('input[type="radio"]'); if (radio) { radio.checked = true; renderPaymentInterface(radio.value); } }); }); // Promo Code Input Listener on Checkout const btnApplyPromo = document.getElementById("btn-apply-promo"); if (btnApplyPromo) { btnApplyPromo.addEventListener("click", () => { const promoInput = document.getElementById("promo-code-input"); const msgEl = document.getElementById("promo-code-msg"); const code = promoInput ? promoInput.value.trim().toUpperCase() : ""; if (!code) { if (msgEl) { msgEl.textContent = "กรุณากรอกโค้ดส่วนลด"; msgEl.className = "promo-code-msg error"; } return; } if (state.promos && state.promos[code]) { const promo = state.promos[code]; // Expiry Check if (promo.expiryDate) { const expDate = new Date(promo.expiryDate); expDate.setHours(23, 59, 59, 999); if (new Date() > expDate) { state.appliedPromo = null; if (msgEl) { msgEl.textContent = translations[state.lang]["msg_promo_expired"] || "โค้ดส่วนลดนี้หมดอายุแล้ว"; msgEl.className = "promo-code-msg error"; } renderCheckoutSummary(); return; // Stop processing } } state.appliedPromo = { code: code, ...promo }; if (msgEl) { msgEl.textContent = `ใช้โค้ด ${code} (${promo.name}) สำเร็จ!`; msgEl.className = "promo-code-msg success"; } showToast(`ส่วนลด ${code} ทำงานเรียบร้อยแล้ว`, "success"); renderCheckoutSummary(); } else { state.appliedPromo = null; if (msgEl) { msgEl.textContent = "โค้ดส่วนลดไม่ถูกต้อง"; msgEl.className = "promo-code-msg error"; } renderCheckoutSummary(); } }); } // Cancellation Modal Listeners const btnCloseCancelModal = document.getElementById("btn-close-cancel-modal"); const btnCancelModalDismiss = document.getElementById("btn-cancel-modal-dismiss"); const cancelOrderForm = document.getElementById("cancel-order-form"); if (btnCloseCancelModal) btnCloseCancelModal.addEventListener("click", closeCancelOrderModal); if (btnCancelModalDismiss) btnCancelModalDismiss.addEventListener("click", closeCancelOrderModal); if (cancelOrderForm) { cancelOrderForm.addEventListener("submit", (e) => { e.preventDefault(); const reasonSelect = document.getElementById("cancel-reason-select"); const reasonText = document.getElementById("cancel-reason-text"); let finalReason = reasonSelect ? reasonSelect.value : ""; if (finalReason === "custom" || (reasonText && reasonText.value.trim())) { finalReason = reasonText.value.trim() || "อื่นๆ"; } confirmCancelOrderWithReason(finalReason); }); } // Checkout Form Submit with Payment Method extraction checkoutForm.addEventListener("submit", (e) => { e.preventDefault(); const selectedPaymentRadio = document.querySelector('input[name="payment-method"]:checked'); const formData = { name: document.getElementById("shipping-name").value, phone: document.getElementById("shipping-phone").value, email: document.getElementById("shipping-email").value, address: document.getElementById("shipping-address").value, province: document.getElementById("shipping-province").value, zipcode: document.getElementById("shipping-zipcode").value, paymentMethod: selectedPaymentRadio ? selectedPaymentRadio.value : "COD" }; processCheckout(formData); checkoutForm.reset(); }); // Success Screen Back Home Button document.getElementById("btn-success-home").addEventListener("click", () => { navigateTo("products-view"); }); // Product Detail Modal Close Listener const btnCloseProductDetail = document.getElementById("btn-close-product-detail"); if (btnCloseProductDetail) { btnCloseProductDetail.addEventListener("click", closeProductDetailModal); } // Seller Add Product Submit addProductForm.addEventListener("submit", (e) => { e.preventDefault(); const name = document.getElementById("product-name").value; const price = document.getElementById("product-price").value; const imageUrl = document.getElementById("product-image-url").value; const desc = document.getElementById("product-desc").value; handleAddProduct(name, price, imageUrl, desc, desc); }); // Seller Registration Form Submit const sellerRegisterForm = document.getElementById("seller-register-form"); if (sellerRegisterForm) { sellerRegisterForm.addEventListener("submit", (e) => { e.preventDefault(); const shopName = document.getElementById("reg-shop-name").value; const phone = document.getElementById("reg-shop-phone").value; const desc = document.getElementById("reg-shop-desc").value; const category = document.getElementById("reg-shop-category").value; const bankName = document.getElementById("reg-bank-name").value; const bankAccount = document.getElementById("reg-bank-account").value; const bankHolder = document.getElementById("reg-bank-holder").value; handleSellerRegistration(shopName, phone, desc, category, bankName, bankAccount, bankHolder); }); } } // Parent level callback: handleRegisterSuccess function handleRegisterSuccess(shopData) { const username = state.user.username; if (!username) return; // 1. Update lifted users state if (!state.users[username]) { state.users[username] = { id: username, username: username, email: "", password: "", hasShop: true, shop: shopData }; } else { state.users[username].hasShop = true; state.users[username].shop = shopData; } // 2. Update lifted shops state state.shops[username] = shopData; // 3. Persist lifted state to local storage localStorage.setItem("aura_users", JSON.stringify(state.users)); localStorage.setItem("aura_shops", JSON.stringify(state.shops)); // 4. Update current user state flags state.user.hasShop = true; state.isSeller = true; state.shopInfo = shopData; // 5. Trigger instant UI refresh and navigation to Seller Dashboard syncCurrentUserState(); updateUserUI(); navigateTo("seller-view"); } // Seller Registration Handler function handleSellerRegistration(shopName, phone, desc, category, bankName, bankAccount, bankHolder) { const shopData = { name: shopName.trim(), phone: phone.trim(), desc: desc.trim(), category: category, bank: { name: bankName, account: bankAccount.trim(), holder: bankHolder.trim() } }; showToast(`ยินดีต้อนรับร้านค้า "${shopData.name}" เข้าสู่ระบบผู้ขาย!`, "success"); // Reset form const sellerRegisterForm = document.getElementById("seller-register-form"); if (sellerRegisterForm) sellerRegisterForm.reset(); // Delegate state lifting update & view switch to parent handleRegisterSuccess handleRegisterSuccess(shopData); } // Toast Notifications Helper function showToast(message, type = "success") { const toast = document.createElement("div"); toast.className = `toast toast-${type}`; let iconName = "check-circle"; if (type === "warning") iconName = "alert-triangle"; if (type === "info") iconName = "info"; toast.innerHTML = ` <i data-lucide="${iconName}"></i> <span>${message}</span> `; toastContainer.appendChild(toast); if (window.lucide) { window.lucide.createIcons(); } // Auto dismiss after 3 seconds setTimeout(() => { toast.style.animation = "toastIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) reverse forwards"; setTimeout(() => { toast.remove(); }, 300); }, 3000); } // ========================================== // ADMIN DASHBOARD FUNCTIONS (Vanilla JS) // ========================================== // Tab Switching window.switchAdminTab = function (tabId) { const tabs = document.querySelectorAll("#admin-tabs .action-tab-btn"); tabs.forEach(btn => { if (btn.getAttribute("onclick")?.includes(tabId)) { btn.classList.add("active"); } else { btn.classList.remove("active"); } }); const subViews = document.querySelectorAll(".admin-sub-view"); subViews.forEach(view => { if (view.id === tabId) { view.style.display = "block"; view.classList.add("active"); } else { view.style.display = "none"; view.classList.remove("active"); } }); if (tabId === "admin-overview") renderAdminOverview(); if (tabId === "admin-products") renderAdminProducts(); if (tabId === "admin-orders") renderAdminOrders(); if (tabId === "admin-users") renderAdminUsers(); if (tabId === "admin-promos") renderAdminPromos(); if (tabId === "admin-settings") renderAdminSettings(); if (window.lucide) window.lucide.createIcons(); }; // 1. Dashboard Analytics (Overview) window.renderAdminOverview = function () { const totalSalesEl = document.getElementById("admin-total-sales"); const totalOrdersEl = document.getElementById("admin-total-orders"); const totalUsersEl = document.getElementById("admin-total-users"); if (!totalSalesEl || !totalOrdersEl || !totalUsersEl) return; const orders = state.orders || []; const validOrders = orders.filter(o => o.status !== "Cancelled" && o.status !== "ยกเลิก"); const totalSales = validOrders.reduce((sum, o) => sum + Number(o.totalPrice || o.total || o.amount || 0), 0); let totalUsersCount = 0; if (Array.isArray(state.users)) { totalUsersCount = state.users.length; } else if (state.users && typeof state.users === 'object') { totalUsersCount = Object.keys(state.users).length; } const itemUnit = state.lang === "en" ? "Items" : "รายการ"; const personUnit = state.lang === "en" ? "Users" : "คน"; totalSalesEl.textContent = `฿${totalSales.toLocaleString()}`; totalOrdersEl.textContent = `${orders.length} ${itemUnit}`; totalUsersEl.textContent = `${totalUsersCount} ${personUnit}`; applyLanguage(); }; // 2. Products Management & Search Filter window.renderAdminProducts = function () { const tbody = document.getElementById("admin-products-tbody"); if (!tbody) return; const searchInput = document.getElementById("admin-search-products"); const query = searchInput ? searchInput.value.trim().toLowerCase() : ""; let products = state.products || []; if (query) { products = products.filter(p => (p.name && p.name.toLowerCase().includes(query)) || (p.category && p.category.toLowerCase().includes(query)) ); } if (products.length === 0) { tbody.innerHTML = ` <tr> <td colSpan="6" style="text-align: center; padding: 30px; color: var(--text-muted);"> ไม่พบข้อมูลสินค้า </td> </tr> `; return; } tbody.innerHTML = products.map(prod => { let mediaHtml = ''; if (prod.image && (prod.image.startsWith('http') || prod.image.startsWith('/') || prod.image.startsWith('data:'))) { mediaHtml = `<img src="${prod.image}" alt="${prod.name}" style="width: 45px; height: 45px; object-fit: cover; border-radius: 6px; border: 1px solid var(--border);">`; } else { mediaHtml = `<div style="width: 45px; height: 45px; display: flex; align-items: center; justify-content: center; background: var(--background); border-radius: 6px;"><i data-lucide="${prod.icon || 'package'}"></i></div>`; } return ` <tr style="border-bottom: 1px solid var(--border);"> <td style="padding: 12px; font-family: monospace; font-size: 12px;">${prod.id}</td> <td style="padding: 12px;">${mediaHtml}</td> <td style="padding: 12px; font-weight: 500;">${prod.name}</td> <td style="padding: 12px;"><span style="font-size: 12px; background: var(--primary-soft); color: var(--primary); padding: 3px 8px; border-radius: 12px; font-weight: 500;">${prod.category || 'ทั่วไป'}</span></td> <td style="padding: 12px; font-weight: 600; color: var(--primary);">฿${Number(prod.price).toLocaleString()}</td> <td style="padding: 12px; text-align: center;"> <div style="display: flex; gap: 6px; justify-content: center;"> <button class="btn btn-text" style="padding: 4px 8px; font-size: 12px; border: 1px solid var(--border);" onclick="adminOpenEditProduct('${prod.id}')"> <i data-lucide="edit" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="admin_btn_edit">แก้ไข</span> </button> <button class="btn btn-danger" style="padding: 4px 8px; font-size: 12px;" onclick="adminDeleteProduct('${prod.id}')"> <i data-lucide="trash-2" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="admin_btn_delete">ลบ</span> </button> </div> </td> </tr> `; }).join(""); applyLanguage(); if (window.lucide) window.lucide.createIcons(); }; // Admin Add Product Logic window.adminOpenAddProduct = function() { document.getElementById("admin-add-product-form").reset(); const modal = document.getElementById("admin-add-product-modal"); if (modal) modal.classList.add("active"); }; window.closeAdminAddModal = function() { const modal = document.getElementById("admin-add-product-modal"); if (modal) modal.classList.remove("active"); }; window.handleAdminAddProductSubmit = function(event) { event.preventDefault(); const name = document.getElementById("admin-add-product-name").value.trim(); const price = Number(document.getElementById("admin-add-product-price").value); const category = document.getElementById("admin-add-product-category").value.trim(); const image = document.getElementById("admin-add-product-image").value.trim(); const newId = state.products.length > 0 ? Math.max(...state.products.map(p => p.id)) + 1 : 1; const newProduct = { id: newId, name: name, price: price, category: category, image: image || "", icon: "package", ownerId: state.user.username // Admin is the owner }; state.products.push(newProduct); persistAllState(); closeAdminAddModal(); renderAdminProducts(); renderProducts(); showToast("เพิ่มสินค้าใหม่สำเร็จแล้ว", "success"); }; // 3. Edit Product Feature window.adminOpenEditProduct = function (productId) { const product = (state.products || []).find(p => String(p.id) === String(productId)); if (!product) return; document.getElementById("admin-edit-product-id").value = product.id; document.getElementById("admin-edit-product-name").value = product.name || ""; document.getElementById("admin-edit-product-price").value = product.price || 0; document.getElementById("admin-edit-product-category").value = product.category || ""; document.getElementById("admin-edit-product-image").value = product.image || ""; const modal = document.getElementById("admin-edit-product-modal"); if (modal) modal.classList.add("active"); }; window.closeAdminEditModal = function () { const modal = document.getElementById("admin-edit-product-modal"); if (modal) modal.classList.remove("active"); }; window.handleAdminEditProductSubmit = function (event) { event.preventDefault(); const id = document.getElementById("admin-edit-product-id").value; const name = document.getElementById("admin-edit-product-name").value.trim(); const price = Number(document.getElementById("admin-edit-product-price").value); const category = document.getElementById("admin-edit-product-category").value.trim(); const image = document.getElementById("admin-edit-product-image").value.trim(); state.products = (state.products || []).map(p => { if (String(p.id) === String(id)) { return { ...p, name, price, category, image }; } return p; }); persistAllState(); closeAdminEditModal(); renderAdminProducts(); renderProducts(); // Also update main shop products grid showToast("บันทึกการแก้ไขสินค้าสำเร็จแล้ว", "success"); }; window.adminDeleteProduct = function (productId) { if (!confirm("คุณต้องการลบสินค้านี้ใช่หรือไม่?")) return; state.products = (state.products || []).filter(p => String(p.id) !== String(productId)); persistAllState(); renderAdminProducts(); renderProducts(); showToast("ลบสินค้าออกจากระบบแล้ว", "info"); }; // 4. Orders Management & Details Modal window.renderAdminOrders = function () { const tbody = document.getElementById("admin-orders-tbody"); if (!tbody) return; const searchInput = document.getElementById("admin-search-orders"); const query = searchInput ? searchInput.value.trim().toLowerCase() : ""; let orders = state.orders || []; if (query) { orders = orders.filter(o => (o.id && String(o.id).toLowerCase().includes(query)) || (o.orderId && String(o.orderId).toLowerCase().includes(query)) || (o.customerName && o.customerName.toLowerCase().includes(query)) || (o.buyerId && o.buyerId.toLowerCase().includes(query)) ); } if (orders.length === 0) { tbody.innerHTML = ` <tr> <td colSpan="5" style="text-align: center; padding: 30px; color: var(--text-muted);"> ไม่พบรายการคำสั่งซื้อ </td> </tr> `; return; } tbody.innerHTML = orders.map(order => { const orderId = order.id || order.orderId; const buyerName = order.customerName || order.buyerId || order.buyer || "ลูกค้าทั่วไป"; const totalPrice = Number(order.totalPrice || order.total || order.amount || 0); return ` <tr style="border-bottom: 1px solid var(--border);"> <td style="padding: 12px; font-family: monospace; font-weight: 600; color: var(--primary);">#${orderId}</td> <td style="padding: 12px; font-weight: 500;">${buyerName}</td> <td style="padding: 12px; font-weight: 600;">฿${totalPrice.toLocaleString()}</td> <td style="padding: 12px;"> <select onchange="adminChangeOrderStatus('${orderId}', this.value)" style="padding: 4px 8px; font-size: 12px; border-radius: 6px; border: 1px solid var(--border); font-family: var(--font);"> <option value="Pending" ${order.status === 'Pending' || order.status === 'รอดำเนินการ' ? 'selected' : ''}>Pending (รอดำเนินการ)</option> <option value="Shipped" ${order.status === 'Shipped' || order.status === 'จัดส่งแล้ว' ? 'selected' : ''}>Shipped (จัดส่งแล้ว)</option> <option value="Delivered" ${order.status === 'Delivered' || order.status === 'สำเร็จ' ? 'selected' : ''}>Delivered (สำเร็จ)</option> <option value="Cancelled" ${order.status === 'Cancelled' || order.status === 'ยกเลิก' ? 'selected' : ''}>Cancelled (ยกเลิก)</option> </select> </td> <td style="padding: 12px; text-align: center;"> <div style="display: flex; gap: 6px; justify-content: center;"> <button class="btn btn-primary" style="padding: 4px 10px; font-size: 12px;" onclick="adminViewOrderDetails('${orderId}')"> <i data-lucide="eye" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="admin_btn_details">ดูรายละเอียด</span> </button> <button class="btn btn-secondary" style="padding: 4px 10px; font-size: 12px;" onclick="adminPrintWaybill('${orderId}')"> <i data-lucide="printer" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="admin_btn_print">พิมพ์ใบปะหน้า</span> </button> </div> </td> </tr> `; }).join(""); applyLanguage(); if (window.lucide) window.lucide.createIcons(); }; window.adminChangeOrderStatus = function (orderId, newStatus) { state.orders = (state.orders || []).map(o => { if (String(o.id) === String(orderId) || String(o.orderId) === String(orderId)) { return { ...o, status: newStatus }; } return o; }); persistAllState(); renderAdminOverview(); showToast(`อัปเดตสถานะคำสั่งซื้อ #${orderId} เป็น "${newStatus}" เรียบร้อยแล้ว`, "success"); }; window.adminViewOrderDetails = function (orderId) { const order = (state.orders || []).find(o => String(o.id) === String(orderId) || String(o.orderId) === String(orderId)); const container = document.getElementById("admin-order-details-content"); if (!order || !container) return; const buyerName = order.customerName || order.buyerId || order.buyer || "ลูกค้าทั่วไป"; const phone = order.phone || "-"; const address = order.address || "-"; const paymentMethod = order.paymentMethod || order.payment || "เก็บเงินปลายทาง (COD)"; const items = order.items || []; const totalPrice = Number(order.totalPrice || order.total || order.amount || 0); const itemsHtml = items.map(item => ` <div style="display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px border-dashed var(--border); font-size: 14px;"> <span>${item.name || 'สินค้า'} x ${item.quantity || 1}</span> <span style="font-weight: 600;">฿${Number((item.price || 0) * (item.quantity || 1)).toLocaleString()}</span> </div> `).join("") || '<p style="color: var(--text-muted); font-size: 13px;">ไม่มีรายละเอียดสินค้าในออเดอร์นี้</p>'; container.innerHTML = ` <div style="display: flex; flex-direction: column; gap: 16px;"> <div style="background: var(--primary-light); padding: 12px 16px; border-radius: var(--radius-md); font-size: 14px;"> <div><strong>Order ID:</strong> #${order.id || order.orderId}</div> <div><strong>สถานะ:</strong> <span style="font-weight: 600; color: var(--primary);">${order.status || 'Pending'}</span></div> </div> <div style="font-size: 14px;"> <h4 style="font-weight: 600; margin-bottom: 6px; color: var(--text-main);">ข้อมูลผู้จัดส่ง (Shipping Information)</h4> <div><strong>ชื่อผู้รับ:</strong> ${buyerName}</div> <div><strong>เบอร์โทรศัพท์:</strong> ${phone}</div> <div><strong>ที่อยู่จัดส่ง:</strong> ${address}</div> <div><strong>วิธีการชำระเงิน:</strong> ${paymentMethod}</div> </div> <div> <h4 style="font-weight: 600; margin-bottom: 8px; color: var(--text-main);">รายการสินค้าที่สั่งซื้อ (Purchased Items)</h4> <div style="background: var(--background); padding: 12px; border-radius: var(--radius-md);"> ${itemsHtml} <div style="display: flex; justify-content: space-between; padding-top: 12px; margin-top: 8px; border-top: 2px solid var(--border); font-weight: 700; font-size: 16px;"> <span>ยอดรวมสุทธิ</span> <span style="color: var(--primary);">฿${totalPrice.toLocaleString()}</span> </div> </div> </div> <div style="display: flex; justify-content: flex-end; margin-top: 20px;"> <button class="btn btn-primary" onclick="adminPrintWaybill('${order.id || order.orderId}')" style="background-color: #0f172a; border-color: #0f172a;"> <i data-lucide="printer" class="icon-left" style="width: 16px; height: 16px;"></i> <span data-i18n="admin_btn_print">พิมพ์ใบปะหน้า</span> </button> </div> </div> `; const modal = document.getElementById("admin-order-details-modal"); if (modal) modal.classList.add("active"); }; window.closeAdminOrderDetailsModal = function () { const modal = document.getElementById("admin-order-details-modal"); if (modal) modal.classList.remove("active"); }; // 5. Users Management & Search Filter window.renderAdminUsers = function () { const tbody = document.getElementById("admin-users-tbody"); if (!tbody) return; const searchInput = document.getElementById("admin-search-users"); const query = searchInput ? searchInput.value.trim().toLowerCase() : ""; let usersList = []; if (Array.isArray(state.users)) { usersList = state.users; } else if (state.users && typeof state.users === 'object') { usersList = Object.values(state.users); } if (query) { usersList = usersList.filter(u => (u.username && u.username.toLowerCase().includes(query)) || (u.email && u.email.toLowerCase().includes(query)) ); } if (usersList.length === 0) { tbody.innerHTML = ` <tr> <td colSpan="4" style="text-align: center; padding: 30px; color: var(--text-muted);"> ไม่พบข้อมูลผู้ใช้งาน </td> </tr> `; return; } tbody.innerHTML = usersList.map(user => { const username = user.username || user.name || "User"; const email = user.email || "-"; const role = user.role || (username.toLowerCase() === 'admin' ? 'Admin' : 'User'); const isAdmin = role.toLowerCase() === 'admin' || username.toLowerCase() === 'admin'; return ` <tr style="border-bottom: 1px solid var(--border);"> <td style="padding: 12px; font-weight: 500;">${username}</td> <td style="padding: 12px; color: var(--text-muted);">${email}</td> <td style="padding: 12px;"> <span style="font-size: 12px; padding: 3px 8px; border-radius: 12px; font-weight: 600; ${isAdmin ? 'background: rgba(147, 51, 234, 0.1); color: #9333ea;' : 'background: var(--background); color: var(--text-muted);'}"> ${role} </span> </td> <td style="padding: 12px; text-align: center;"> ${!isAdmin ? ` <div style="display: flex; gap: 6px; justify-content: center;"> <button class="btn ${user.isBanned ? 'btn-primary' : 'btn-danger'}" style="padding: 4px 8px; font-size: 12px; border: 1px solid var(--border);" onclick="adminToggleBan('${username}')"> <i data-lucide="${user.isBanned ? 'unlock' : 'ban'}" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="${user.isBanned ? 'admin_btn_unban' : 'admin_btn_ban'}">${user.isBanned ? 'ปลดแบน' : 'ระงับบัญชี'}</span> </button> <button class="btn btn-text" style="padding: 4px 8px; font-size: 12px; border: 1px solid var(--border); color: #9333ea;" onclick="adminPromoteUser('${username}')"> <i data-lucide="shield" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="admin_btn_promote">ตั้งเป็น Admin</span> </button> </div> ` : '<span style="font-size: 12px; color: var(--text-muted); italic;">สงวนสิทธิ์ Admin</span>'} </td> </tr> `; }).join(""); applyLanguage(); if (window.lucide) window.lucide.createIcons(); }; window.adminDeleteUser = function (username) { if (!confirm(`คุณต้องการลบผู้ใช้งาน "${username}" ใช่หรือไม่?`)) return; if (Array.isArray(state.users)) { state.users = state.users.filter(u => u.username !== username); } else if (state.users && typeof state.users === 'object') { delete state.users[username]; } persistAllState(); renderAdminOverview(); renderAdminUsers(); showToast(`ลบผู้ใช้งาน "${username}" เรียบร้อยแล้ว`, "info"); }; window.adminToggleBan = function(username) { const user = state.users[username]; if (!user) return; const isCurrentlyBanned = user.isBanned || false; if (!confirm(`คุณต้องการ ${isCurrentlyBanned ? 'ปลดแบน' : 'ระงับบัญชี'} ผู้ใช้งาน "${username}" ใช่หรือไม่?`)) return; user.isBanned = !isCurrentlyBanned; persistAllState(); renderAdminUsers(); showToast(`${user.isBanned ? 'ระงับบัญชี' : 'ปลดแบน'} "${username}" เรียบร้อยแล้ว`, user.isBanned ? "warning" : "success"); }; window.adminPromoteUser = function(username) { if (!confirm(`คุณต้องการตั้ง "${username}" เป็น Admin ใช่หรือไม่?`)) return; if (state.users[username]) { state.users[username].role = "admin"; persistAllState(); renderAdminUsers(); showToast(`เลื่อนขั้น "${username}" เป็น Admin แล้ว`, "success"); } }; // ========================================== // Language System (TH / EN) // ========================================== // 2. พจนานุกรมคำแปล (Dictionary) const translations = { "th": { "nav_home": "หน้าแรก", "nav_cart": "ตะกร้าสินค้า", "nav_seller": "ระบบผู้ขาย (Seller Centre)", "nav_admin": "จัดการหลังบ้าน (Admin)", "toast_lang": "เปลี่ยนภาษาเรียบร้อยแล้ว", // Login & Register "login_title": "ยินดีต้อนรับสู่ Aura Shop", "login_subtitle": "เข้าสู่ระบบเพื่อเริ่มช้อปปิ้งสินค้าสไตล์มินิมอล", "login_username": "ชื่อผู้ใช้งาน (Username)", "login_ph_username": "ใส่ชื่อผู้ใช้งานของคุณ...", "login_password": "รหัสผ่าน (Password)", "login_ph_password": "ใส่รหัสผ่านของคุณ...", "login_btn": "เข้าสู่ระบบ", "login_no_account": "ยังไม่มีบัญชีผู้ใช้งาน?", "login_register_link": "สมัครสมาชิกที่นี่", "reg_title": "สร้างบัญชีผู้ใช้ใหม่", "reg_subtitle": "กรอกข้อมูลด้านล่างเพื่อเริ่มช้อปปิ้งสินค้าสไตล์มินิมอล", "reg_username": "ชื่อผู้ใช้งาน (Username)", "reg_ph_username": "ตั้งชื่อผู้ใช้งาน...", "reg_email": "อีเมล (Email)", "reg_ph_email": "เช่น yourname@email.com", "reg_password": "รหัสผ่าน (Password)", "reg_ph_password": "ตั้งรหัสผ่านอย่างน้อย 6 ตัวอักษร...", "reg_confirm": "ยืนยันรหัสผ่าน (Confirm Password)", "reg_ph_confirm": "กรอกรหัสผ่านอีกครั้ง...", "reg_btn": "สมัครสมาชิก", "reg_has_account": "มีบัญชีผู้ใช้งานอยู่แล้ว?", "reg_login_link": "เข้าสู่ระบบ", "section_title": "สินค้าแนะนำ", "section_subtitle": "คัดสรรดีไซน์เรียบง่าย ตอบโจทย์ทุกการใช้งาน", "promo_badge": "คูปองส่วนลดพิเศษ", "promo_title": "รับส่วนลดพิเศษ 10% เมื่อช้อปวันนี้!", "promo_desc": "รับโค้ดส่วนลด <strong>DISCOUNT10</strong> หรือ <strong>AURA20</strong> เพื่อใช้ลดราคาสินค้าในหน้าชำระเงิน", "claim_btn_text": "รับโค้ดส่วนลด DISCOUNT10", "btn_claim_voucher": "รับโค้ดส่วนลด DISCOUNT10", "btn_claimed": "เก็บโค้ดแล้ว", "btn_change_avatar": "เปลี่ยนรูปโปรไฟล์", "desc_avatar_size": "ขนาดไฟล์สูงสุด 1MB (.JPG, .PNG)", "search_placeholder": "ค้นหาสินค้าตามชื่อ...", "search_btn": "ค้นหา", "cat_all": "ทั้งหมด", "cat_mugs": "แก้ว & จาน", "cat_bags": "กระเป๋า", "cat_decor": "ของตกแต่งบ้าน", "cat_gadget": "ไอที / Gadget", "cat_fashion": "แฟชั่น / นาฬิกา", // Cart View "cart_title": "ตะกร้าสินค้าของคุณ", "cart_subtitle": "ตรวจสอบรายการและจำนวนสินค้าที่เลือก", "cart_continue": "ช้อปสินค้าต่อ", "cart_empty_title": "ไม่มีสินค้าในตะกร้าของคุณ", "cart_empty_desc": "กลับไปเลือกชมสินค้าตกแต่งบ้านชิ้นโปรดของคุณได้เลย", "cart_shop_now": "ช้อปสินค้าตอนนี้", "cart_summary_title": "สรุปยอดคำสั่งซื้อ", "cart_subtotal": "ยอดรวมสินค้า", "cart_shipping": "ค่าจัดส่ง", "cart_free": "ฟรี", "cart_total": "ยอดรวมสุทธิ", "cart_checkout_btn": "ดำเนินการชำระเงิน", // Seller Register View "seller_reg_title": "สมัครเปิดร้านค้า", "seller_reg_subtitle": "เริ่มต้นขายสินค้าออนไลน์กับเราในไม่กี่ขั้นตอน", "seller_shop_name": "ชื่อร้านค้า (Shop Name)", "seller_shop_name_ph": "เช่น Minimalist Decor Shop", "seller_shop_phone": "เบอร์โทรศัพท์ติดต่อ", "seller_shop_phone_ph": "เช่น 0891234567", "seller_shop_desc": "รายละเอียด/คำอธิบายร้านค้าสั้นๆ", "seller_shop_desc_ph": "อธิบายประเภทร้านค้าหรือสินค้าของคุณ...", "seller_shop_cat": "หมวดหมู่สินค้าหลัก", "seller_select_cat": "เลือกหมวดหมู่สินค้า", "seller_cat_clothes": "เสื้อผ้า / แฟชั่น", "seller_cat_gadget": "ไอที / ไอที Gadget", "seller_cat_home": "ของใช้ในบ้าน / ตกแต่งบ้าน", "seller_bank_info_header": "ข้อมูลบัญชีธนาคารสำหรับรับเงิน", "seller_bank_name": "ชื่อธนาคาร", "seller_select_bank": "เลือกธนาคาร", "seller_bank_kbank": "ธนาคารกสิกรไทย (KBank)", "seller_bank_scb": "ธนาคารไทยพาณิชย์ (SCB)", "seller_bank_bbl": "ธนาคารกรุงเทพ (BBL)", "seller_bank_ktb": "ธนาคารกรุงไทย (KTB)", "seller_bank_bay": "ธนาคารกรุงศรีอยุธยา (BAY)", "seller_bank_acc": "เลขที่บัญชี", "seller_bank_acc_ph": "เช่น 123-4-56789-0", "seller_bank_holder": "ชื่อบัญชี", "seller_bank_holder_ph": "เช่น สมชาย ใจดี", "seller_accept_terms": "ฉันยอมรับข้อตกลงและเงื่อนไขการเป็นผู้ขาย", "seller_open_shop_btn": "เปิดร้านค้าเลย", // Wishlist Modal "wishlist_modal_title": "สิ่งที่อยากได้ (Wishlist)", "wishlist_empty_title": "ยังไม่มีสินค้าในสิ่งที่อยากได้", "wishlist_empty_desc": "กดไอคอนหัวใจที่รูปสินค้าเพื่อบันทึกสินค้าที่คุณชอบ", "wishlist_add_to_cart": "ใส่ตะกร้า", // Profile View & Sidebar "profile_title": "บัญชีของฉัน & ประวัติการสั่งซื้อ", "profile_subtitle": "จัดการข้อมูลส่วนตัวและตรวจสอบสถานะคำสั่งซื้อของคุณ", "sidebar_account": "บัญชีของฉัน", "sidebar_profile": "ประวัติ", "sidebar_banks": "บัญชีธนาคาร&บัตร", "sidebar_address": "ที่อยู่", "sidebar_password": "เปลี่ยนรหัสผ่าน", "sidebar_privacy": "การตั้งค่าความเป็นส่วนตัว", "sidebar_noti_settings": "ตั้งค่าการแจ้งเตือน", "sidebar_order_settings": "ตั้งค่าคำสั่งซื้อ", "sidebar_purchases": "การซื้อของฉัน", "sidebar_notifications": "การแจ้งเตือน", "sidebar_order_updates": "อัปเดตคำสั่งซื้อ", "prof_dev_prefix": "หน้า ", "prof_dev_suffix": " กำลังอยู่ในช่วงพัฒนา", "prof_dev_desc": "ระบบกำลังเตรียมพร้อมให้บริการเมนูนี้เร็วๆ นี้", "prof_seller_status": "สถานะผู้ขาย: ", "prof_seller_none": "ยังไม่ได้เปิดร้านค้า", "prof_seller_approved": "อนุมัติแล้ว (ร้านค้า: ", "form_my_profile": "ข้อมูลของฉัน", "form_profile_desc": "จัดการข้อมูลส่วนตัวเพื่อความปลอดภัยของบัญชี", "form_username": "ชื่อผู้ใช้งาน (Username)", "form_fullname": "ชื่อ-นามสกุล (Full Name)", "form_email": "อีเมล (Email)", "form_phone": "เบอร์โทรศัพท์ (Phone Number)", "form_gender": "เพศ (Gender)", "form_male": "ชาย", "form_female": "หญิง", "form_other": "อื่นๆ", "form_dob": "วัน/เดือน/ปี เกิด (Date of Birth)", "btn_save": "บันทึก", "prof_purchase_history": "ประวัติการสั่งซื้อของฉัน (My Purchase History)", "prof_addr_title": "ที่อยู่ของฉัน", "prof_addr_desc": "จัดการที่อยู่สำหรับจัดส่งสินค้า", "prof_addr_add": "+ เพิ่มที่อยู่ใหม่", "prof_addr_empty": "ยังไม่มีที่อยู่ที่บันทึกไว้สำหรับบัญชีนี้", "prof_bank_title": "บัญชีธนาคาร & บัตรของฉัน", "prof_bank_desc": "จัดการข้อมูลการชำระเงินและรับเงิน", "prof_bank_add": "+ เพิ่มบัญชีธนาคาร", "prof_bank_empty": "ยังไม่มีข้อมูลบัญชีธนาคารหรือบัตรชำระเงินที่บันทึกไว้", "prof_pass_title": "เปลี่ยนรหัสผ่าน", "prof_pass_desc": "เพื่อความปลอดภัยของบัญชี กรุณาอย่าเปิดเผยรหัสผ่านแก่ผู้อื่น", "prof_pass_curr": "รหัสผ่านปัจจุบัน", "prof_pass_new": "รหัสผ่านใหม่", "prof_pass_confirm": "ยืนยันรหัสผ่านใหม่", "prof_save_changes": "บันทึกการเปลี่ยนแปลง", "prof_priv_title": "การตั้งค่าความเป็นส่วนตัว & การแจ้งเตือน", "prof_priv_desc": "จัดการการยินยอมและการแจ้งเตือนของระบบ", "prof_priv_email": "รับอีเมลข่าวสารและโปรโมชัน", "prof_priv_email_desc": "รับข้อมูลส่วนลด สิทธิพิเศษ และอัปเดตสินค้าใหม่ทางอีเมล", "prof_priv_public": "การเปิดเผยโปรไฟล์สาธารณะ", "prof_priv_public_desc": "อนุญาตให้ผู้ใช้อื่นมองเห็นชื่อและรูปโปรไฟล์ของคุณ", "prof_priv_app": "แจ้งเตือนสถานะคำสั่งซื้อในแอป", "prof_priv_app_desc": "การแจ้งเตือนทันทีเมื่อคำสั่งซื้อเปลี่ยนสถานะเป็นจัดส่งแล้ว", "order_tab_all": "ทั้งหมด", "order_tab_pay": "ที่ต้องชำระ", "order_tab_ship": "ที่ต้องจัดส่ง", "order_tab_recv": "ที่ต้องได้รับ", "order_tab_comp": "สำเร็จแล้ว", "order_tab_cancel": "ยกเลิกแล้ว", "order_tab_refund": "คืนเงิน/คืนสินค้า", "order_empty": "ยังไม่มีการสั่งซื้อ", // Admin Dashboard "admin_title": "🛠️ ระบบจัดการผู้ดูแลระบบ (Admin Dashboard)", "admin_subtitle": "จัดการข้อมูลสินค้า คำสั่งซื้อ ผู้ใช้งาน และสถิติภาพรวมของระบบ", "admin_tab_overview": "สถิติภาพรวม (Overview)", "admin_tab_products": "จัดการสินค้า (Products)", "admin_tab_orders": "จัดการคำสั่งซื้อ (Orders)", "admin_tab_users": "จัดการผู้ใช้งาน (Users)", "admin_total_sales": "ยอดขายรวมทั้งหมด (Total Sales)", "admin_total_orders": "จำนวนคำสั่งซื้อทั้งหมด (Total Orders)", "admin_total_users": "จำนวนผู้ใช้งานทั้งหมด (Total Users)", "admin_manage_products_title": "จัดการสินค้าในระบบ", "admin_manage_orders_title": "จัดการคำสั่งซื้อทั้งหมด", "admin_manage_users_title": "จัดการผู้ใช้งานในระบบ", "admin_search_product_ph": "ค้นหาตามชื่อสินค้าหรือหมวดหมู่...", "admin_search_order_ph": "ค้นตาม Order ID หรือชื่อผู้ซื้อ...", "admin_search_user_ph": "ค้นตาม Username หรือ Email...", "admin_th_id": "ID", "admin_th_image": "รูปภาพ", "admin_th_product_name": "ชื่อสินค้า", "admin_th_category": "หมวดหมู่", "admin_th_price": "ราคา", "admin_th_action": "จัดการ", "admin_th_buyer": "ชื่อผู้ซื้อ", "admin_th_total": "ยอดรวม", "admin_th_status": "สถานะ", "admin_th_username": "Username", "admin_th_email": "Email", "admin_th_role": "Role", "admin_btn_edit": "แก้ไข", "admin_btn_delete": "ลบ", "admin_btn_details": "ดูรายละเอียด", "admin_btn_delete_user": "ลบผู้ใช้งาน", "admin_btn_add_product": "เพิ่มสินค้าใหม่", "admin_btn_ban": "ระงับบัญชี", "admin_btn_unban": "ปลดแบน", "admin_btn_promote": "ตั้งเป็น Admin", "msg_banned": "บัญชีของคุณถูกระงับการใช้งาน", "admin_tab_promos": "🎟️ จัดการโค้ดส่วนลด (Vouchers)", "admin_tab_settings": "ตั้งค่าระบบ (Settings)", "admin_btn_export_csv": "ส่งออก CSV", "admin_btn_print": "พิมพ์ใบปะหน้า", "admin_add_promo": "เพิ่มโค้ดส่วนลดใหม่", "admin_manage_promos": "จัดการโค้ดส่วนลดทั้งหมด", "admin_promo_code": "โค้ด (Code)", "admin_promo_name": "ชื่อแคมเปญ", "admin_promo_discount": "ส่วนลด", "admin_promo_expiry": "วันหมดอายุ (Expiry Date)", "admin_promo_optional": "(ไม่ระบุ = ไม่มีวันหมดอายุ)", "promo_status_active": "ใช้งานได้", "promo_status_expired": "หมดอายุแล้ว", "promo_status_none": "ไม่มีกำหนด", "msg_promo_expired": "โค้ดส่วนลดนี้หมดอายุแล้ว", "admin_promo_code_label": "โค้ดส่วนลด (เช่น SUMMER20)", "admin_promo_name_label": "ชื่อแคมเปญ (เช่น ลด 20% ซัมเมอร์)", "admin_promo_type": "ประเภทส่วนลด", "admin_promo_type_percent": "เปอร์เซ็นต์ (%)", "admin_promo_type_amount": "จำนวนเงิน (บาท)", "admin_promo_value": "มูลค่าส่วนลด", "admin_btn_save_promo": "บันทึกโค้ดส่วนลด" }, "en": { "nav_home": "Home", "nav_cart": "Cart", "nav_seller": "Seller Centre", "nav_admin": "Admin Panel", "toast_lang": "Language changed successfully", // Login & Register "login_title": "Welcome to Aura Shop", "login_subtitle": "Log in to start shopping for minimalist products", "login_username": "Username", "login_ph_username": "Enter your username...", "login_password": "Password", "login_ph_password": "Enter your password...", "login_btn": "Login", "login_no_account": "Don't have an account?", "login_register_link": "Register here", "reg_title": "Create New Account", "reg_subtitle": "Fill in the details below to start shopping", "reg_username": "Username", "reg_ph_username": "Choose a username...", "reg_email": "Email", "reg_ph_email": "e.g., yourname@email.com", "reg_password": "Password", "reg_ph_password": "Set password (min. 6 chars)...", "reg_confirm": "Confirm Password", "reg_ph_confirm": "Enter password again...", "reg_btn": "Register", "reg_has_account": "Already have an account?", "reg_login_link": "Login", "section_title": "Recommended", "section_subtitle": "Minimalist design for everyday use", "promo_badge": "Special Voucher", "promo_title": "Get 10% Off Today!", "promo_desc": "Use code <strong>DISCOUNT10</strong> or <strong>AURA20</strong> at checkout.", "claim_btn_text": "Claim DISCOUNT10", "btn_claim_voucher": "Claim DISCOUNT10", "btn_claimed": "Claimed", "btn_change_avatar": "Change Avatar", "desc_avatar_size": "Max file size 1MB (.JPG, .PNG)", "search_placeholder": "Search products...", "search_btn": "Search", "cat_all": "All", "cat_mugs": "Mugs & Plates", "cat_bags": "Bags", "cat_decor": "Home Decor", "cat_gadget": "IT / Gadget", "cat_fashion": "Fashion / Watches", // Cart View "cart_title": "Your Shopping Cart", "cart_subtitle": "Review your selected items and quantities", "cart_continue": "Continue Shopping", "cart_empty_title": "Your cart is empty", "cart_empty_desc": "Go back and explore your favorite minimalist items.", "cart_shop_now": "Shop Now", "cart_summary_title": "Order Summary", "cart_subtotal": "Subtotal", "cart_shipping": "Shipping", "cart_free": "Free", "cart_total": "Total Amount", "cart_checkout_btn": "Proceed to Checkout", // Seller Register View "seller_reg_title": "Register Shop", "seller_reg_subtitle": "Start selling online with us in just a few steps.", "seller_shop_name": "Shop Name", "seller_shop_name_ph": "e.g. Minimalist Decor Shop", "seller_shop_phone": "Contact Phone", "seller_shop_phone_ph": "e.g. 0891234567", "seller_shop_desc": "Short Shop Description", "seller_shop_desc_ph": "Describe your shop or products...", "seller_shop_cat": "Main Product Category", "seller_select_cat": "Select Category", "seller_cat_clothes": "Clothing / Fashion", "seller_cat_gadget": "IT / Gadget", "seller_cat_home": "Home & Living", "seller_bank_info_header": "Bank Account for Receiving Payments", "seller_bank_name": "Bank Name", "seller_select_bank": "Select Bank", "seller_bank_kbank": "Kasikornbank (KBank)", "seller_bank_scb": "Siam Commercial Bank (SCB)", "seller_bank_bbl": "Bangkok Bank (BBL)", "seller_bank_ktb": "Krungthai Bank (KTB)", "seller_bank_bay": "Bank of Ayudhya (BAY)", "seller_bank_acc": "Account Number", "seller_bank_acc_ph": "e.g. 123-4-56789-0", "seller_bank_holder": "Account Holder Name", "seller_bank_holder_ph": "e.g. Somchai Jaidee", "seller_accept_terms": "I accept the Seller Terms and Conditions", "seller_open_shop_btn": "Open Shop Now", // Wishlist Modal "wishlist_modal_title": "Wishlist", "wishlist_empty_title": "No items in your wishlist yet", "wishlist_empty_desc": "Click the heart icon on products to save them.", "wishlist_add_to_cart": "Add to Cart", // Profile View & Sidebar "profile_title": "My Account & Purchase History", "profile_subtitle": "Manage your personal info and track orders", "sidebar_account": "My Account", "sidebar_profile": "Profile", "sidebar_banks": "Banks & Cards", "sidebar_address": "Addresses", "sidebar_password": "Change Password", "sidebar_privacy": "Privacy Settings", "sidebar_noti_settings": "Notification Settings", "sidebar_order_settings": "Order Settings", "sidebar_purchases": "My Purchases", "sidebar_notifications": "Notifications", "sidebar_order_updates": "Order Updates", "prof_dev_prefix": "", "prof_dev_suffix": " is under development", "prof_dev_desc": "We are preparing this feature for you soon.", "prof_seller_status": "Seller Status: ", "prof_seller_none": "Not yet opened a shop", "prof_seller_approved": "Approved (Shop: ", "form_my_profile": "My Profile", "form_profile_desc": "Manage your personal information for account security", "form_username": "Username", "form_fullname": "Full Name", "form_email": "Email", "form_phone": "Phone Number", "form_gender": "Gender", "form_male": "Male", "form_female": "Female", "form_other": "Other", "prof_dob": "Date of Birth", "btn_save": "Save", "prof_purchase_history": "My Purchase History", "prof_addr_title": "My Addresses", "prof_addr_desc": "Manage delivery addresses", "prof_addr_add": "+ Add New Address", "prof_addr_empty": "No saved addresses for this account yet", "prof_bank_title": "My Bank Accounts & Cards", "prof_bank_desc": "Manage payment and payout methods", "prof_bank_add": "+ Add Bank Account", "prof_bank_empty": "No saved bank accounts or payment cards yet", "prof_pass_title": "Change Password", "prof_pass_desc": "For account security, please do not share your password.", "prof_pass_curr": "Current Password", "prof_pass_new": "New Password", "prof_pass_confirm": "Confirm New Password", "prof_save_changes": "Save Changes", "prof_priv_title": "Privacy & Notification Settings", "prof_priv_desc": "Manage system consents and notifications", "prof_priv_email": "Receive news and promotion emails", "prof_priv_email_desc": "Receive discount codes, privileges, and product updates via email", "prof_priv_public": "Public Profile Visibility", "prof_priv_public_desc": "Allow other users to see your name and profile picture", "prof_priv_app": "In-App Order Status Notifications", "prof_priv_app_desc": "Instant notifications when order status changes to shipped", "order_tab_all": "All", "order_tab_pay": "To Pay", "order_tab_ship": "To Ship", "order_tab_recv": "To Receive", "order_tab_comp": "Completed", "order_tab_cancel": "Cancelled", "order_tab_refund": "Return/Refund", "order_empty": "No orders found in this category", // Admin Dashboard "admin_title": "🛠️ Admin Dashboard", "admin_subtitle": "Manage products, orders, users, and system analytics", "admin_tab_overview": "Overview", "admin_tab_products": "Products", "admin_tab_orders": "Orders", "admin_tab_users": "Users", "admin_total_sales": "Total Sales", "admin_total_orders": "Total Orders", "admin_total_users": "Total Users", "admin_manage_products_title": "Manage Products", "admin_manage_orders_title": "Manage Orders", "admin_manage_users_title": "Manage Users", "admin_search_product_ph": "Search by product name or category...", "admin_search_order_ph": "Search by Order ID or buyer name...", "admin_search_user_ph": "Search by Username or Email...", "admin_th_id": "ID", "admin_th_image": "Image", "admin_th_product_name": "Product Name", "admin_th_category": "Category", "admin_th_price": "Price", "admin_th_action": "Action", "admin_th_buyer": "Buyer Name", "admin_th_total": "Total Amount", "admin_th_status": "Status", "admin_th_username": "Username", "admin_th_email": "Email", "admin_th_role": "Role", "admin_btn_edit": "Edit", "admin_btn_delete": "Delete", "admin_btn_details": "View Details", "admin_btn_delete_user": "Delete User", "admin_btn_add_product": "Add Product", "admin_btn_ban": "Ban User", "admin_btn_unban": "Unban", "admin_btn_promote": "Make Admin", "msg_banned": "Your account has been suspended", "admin_tab_promos": "🎟️ Vouchers", "admin_tab_settings": "⚙️ Settings", "admin_btn_export_csv": "Export CSV", "admin_btn_print": "Print Waybill", "admin_add_promo": "Add New Promo Code", "admin_manage_promos": "Manage All Promo Codes", "admin_promo_code": "Code", "admin_promo_name": "Campaign Name", "admin_promo_discount": "Discount", "admin_promo_expiry": "Expiry Date", "admin_promo_optional": "(Leave blank for no expiry)", "promo_status_active": "Active", "promo_status_expired": "Expired", "promo_status_none": "No Expiry", "msg_promo_expired": "This promo code has expired.", "admin_promo_code_label": "Promo Code (e.g., SUMMER20)", "admin_promo_name_label": "Campaign Name (e.g., Summer Sale 20%)", "admin_promo_type": "Discount Type", "admin_promo_type_percent": "Percentage (%)", "admin_promo_type_amount": "Fixed Amount (฿)", "admin_promo_value": "Discount Value", "admin_btn_save_promo": "Save Promo Code" } }; // 3. ฟังก์ชันสลับภาษา window.toggleLanguage = function () { // สลับภาษาไปมา state.lang = state.lang === "th" ? "en" : "th"; localStorage.setItem("aura_lang", state.lang); // บันทึกความจำไว้ applyLanguage(); // สั่งแปล showToast(translations[state.lang]["toast_lang"], "info"); }; // 4. ฟังก์ชันวิ่งหาคำมาแปลตามหน้าเว็บ function applyLanguage() { // อัปเดตตัวหนังสือบนปุ่ม (TH / EN) const langText = document.getElementById("current-lang-text"); if (langText) langText.textContent = state.lang.toUpperCase(); // ค้นหาทุกจุดใน HTML ที่มีคำว่า data-i18n แล้วจับเปลี่ยนคำ const elements = document.querySelectorAll("[data-i18n]"); elements.forEach(el => { const key = el.getAttribute("data-i18n"); if (translations[state.lang] && translations[state.lang][key]) { const val = translations[state.lang][key]; if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") { el.placeholder = val; } else if (val.includes("<") && val.includes(">")) { el.innerHTML = val; } else { el.textContent = val; } } }); } // ผูกฟังก์ชันเข้ากับปุ่มสลับภาษา และเรียกทำงานเมื่อโหลดหน้าเว็บ document.addEventListener("DOMContentLoaded", function () { applyLanguage(); const langBtn = document.getElementById("lang-toggle-btn"); if (langBtn) { langBtn.addEventListener("click", window.toggleLanguage); } }); // Admin Promo Code Management Logic window.renderAdminPromos = function () { const tbody = document.getElementById("admin-promos-tbody"); if (!tbody) return; const promos = state.promos || {}; const promoKeys = Object.keys(promos); if (promoKeys.length === 0) { tbody.innerHTML = `<tr><td colSpan="5" style="text-align: center; padding: 30px; color: var(--text-muted);">ไม่พบโค้ดส่วนลดในระบบ</td></tr>`; return; } tbody.innerHTML = promoKeys.map(code => { const p = promos[code]; const discountText = p.discountPercent ? `${p.discountPercent}%` : `฿${p.discountAmount}`; let expiryText = `<span style="color: var(--text-muted);" data-i18n="promo_status_none">ไม่มีกำหนด</span>`; let isExpired = false; if (p.expiryDate) { const expDate = new Date(p.expiryDate); expDate.setHours(23, 59, 59, 999); // Expire at end of the day isExpired = new Date() > expDate; const formattedDate = expDate.toLocaleDateString(state.lang === 'th' ? 'th-TH' : 'en-US'); if (isExpired) { expiryText = `<span style="color: #ef4444; font-weight: 600;"><span data-i18n="promo_status_expired">หมดอายุแล้ว</span> (${formattedDate})</span>`; } else { expiryText = `<span style="color: #10b981; font-weight: 500;">${formattedDate}</span>`; } } return ` <tr style="border-bottom: 1px solid var(--border);"> <td style="padding: 12px; font-weight: 700; color: ${isExpired ? 'var(--text-light)' : 'var(--primary)'};">${code}</td> <td style="padding: 12px; ${isExpired ? 'color: var(--text-light); text-decoration: line-through;' : ''}">${p.name}</td> <td style="padding: 12px; font-weight: 600; ${isExpired ? 'color: var(--text-light);' : ''}">${discountText}</td> <td style="padding: 12px;">${expiryText}</td> <td style="padding: 12px; text-align: center;"> <button class="btn btn-danger" style="padding: 4px 8px; font-size: 12px;" onclick="adminDeletePromo('${code}')"> <i data-lucide="trash-2" style="width: 14px; height: 14px; margin-right: 4px;"></i> <span data-i18n="btn_delete">ลบ</span> </button> </td> </tr> `; }).join(""); applyLanguage(); if (window.lucide) window.lucide.createIcons(); }; window.handleAddPromoSubmit = function (e) { e.preventDefault(); const codeInput = document.getElementById("new-promo-code").value.trim().toUpperCase(); const nameInput = document.getElementById("new-promo-name").value.trim(); const typeInput = document.getElementById("new-promo-type").value; const valueInput = Number(document.getElementById("new-promo-value").value); const expiryInput = document.getElementById("new-promo-expiry").value; if (!codeInput || !nameInput || valueInput <= 0) return; if (state.promos[codeInput]) { showToast("มีโค้ดส่วนลดนี้ในระบบแล้ว!", "warning"); return; } state.promos[codeInput] = { name: nameInput }; if (typeInput === "percent") { state.promos[codeInput].discountPercent = valueInput; } else { state.promos[codeInput].discountAmount = valueInput; } if (expiryInput) { state.promos[codeInput].expiryDate = expiryInput; } persistAllState(); renderAdminPromos(); document.getElementById("admin-add-promo-form").reset(); showToast(`เพิ่มโค้ดส่วนลด ${codeInput} แล้ว`, "success"); }; window.adminDeletePromo = function (code) { if (!confirm(`คุณต้องการลบโค้ดส่วนลด ${code} ใช่หรือไม่?`)) return; delete state.promos[code]; persistAllState(); renderAdminPromos(); showToast(`ลบโค้ดส่วนลด ${code} แล้ว`, "info"); }; // Profile Picture Upload Handler window.handleProfileImageUpload = function(event) { const file = event.target.files[0]; if (!file) return; if (file.size > 1024 * 1024) { // Max 1MB showToast(state.lang === 'en' ? "File size is too large (Max 1MB)" : "ขนาดไฟล์ใหญ่เกินไป (สูงสุด 1MB)", "warning"); return; } const reader = new FileReader(); reader.onload = function(e) { const base64Image = e.target.result; const username = state.user.username; if (state.users[username]) { state.users[username].profileImage = base64Image; } persistAllState(); updateUserUI(); // Immediately updates header and sidebars renderBuyerProfile(); // Re-renders the profile tab to show the new avatar showToast(state.lang === 'en' ? "Avatar updated successfully" : "อัปเดตรูปโปรไฟล์เรียบร้อยแล้ว", "success"); }; reader.readAsDataURL(file); }; window.handleAvatarUpload = window.handleProfileImageUpload; // Phase 2: Admin Export CSV & Print Waybill & Settings Logic window.adminExportOrdersCSV = function() { const orders = state.orders || []; if (orders.length === 0) return showToast("ไม่มีข้อมูลออเดอร์สำหรับส่งออก", "warning"); const headers = ["Order ID", "Customer Name", "Phone", "Status", "Payment Method", "Total Price", "Date"]; const rows = orders.map(o => [ o.id || o.orderId, `"${o.customerName || o.buyerId || ''}"`, `"${o.phone || ''}"`, o.status, o.paymentMethod || 'COD', o.totalPrice || o.total || 0, o.createdAt ? new Date(o.createdAt).toLocaleDateString() : '' ]); const csvContent = "data:text/csv;charset=utf-8,\uFEFF" + [headers.join(","), ...rows.map(e => e.join(","))].join("\n"); const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", `aura_orders_${new Date().getTime()}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); showToast("ดาวน์โหลดไฟล์ CSV เรียบร้อยแล้ว", "success"); }; window.adminPrintWaybill = function(orderId) { const order = state.orders.find(o => String(o.id) === String(orderId) || String(o.orderId) === String(orderId)); if (!order) return; const printWindow = window.open('', '_blank', 'width=600,height=800'); printWindow.document.write(` <html> <head> <title>ใบปะหน้าพัสดุ #${order.id || order.orderId}</title> <style> body { font-family: sans-serif; padding: 20px; line-height: 1.6; } .box { border: 2px solid #000; padding: 20px; margin-bottom: 20px; border-radius: 8px; } .header { text-align: center; font-weight: bold; font-size: 24px; border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 20px; } .sender { border-bottom: 1px dashed #ccc; padding-bottom: 10px; margin-bottom: 10px; } .receiver { font-size: 18px; } .cod { font-size: 20px; font-weight: bold; text-align: center; margin-top: 20px; border: 2px solid #000; padding: 10px; } </style> </head> <body> <div class="box"> <div class="header">Aura Shop - ใบปะหน้าพัสดุ</div> <div class="sender"> <strong>ผู้ส่ง:</strong> Aura Shop (สำนักงานใหญ่)<br> โทร: 02-123-4567 </div> <div class="receiver"> <strong>ผู้รับ:</strong> ${order.customerName || order.buyerId}<br> <strong>เบอร์โทร:</strong> ${order.phone || '-'}<br> <strong>ที่อยู่:</strong><br> ${order.address || order.shippingAddress || '-'} </div> ${(!order.paymentMethod || order.paymentMethod === 'COD') ? `<div class="cod">เก็บเงินปลายทาง (COD): ฿${(order.totalPrice || order.total).toLocaleString()}</div>` : `<div style="text-align:center; margin-top:20px; font-weight:bold;">ชำระเงินแล้ว (${order.paymentMethod})</div>`} <div style="text-align: center; margin-top: 20px;">Order ID: #${order.id || order.orderId}</div> </div> <script>window.onload = function() { window.print(); window.close(); }</script> </body> </html> `); printWindow.document.close(); }; window.renderAdminSettings = function() { document.getElementById("setting-banner-title").value = state.settings.bannerTitle || ""; document.getElementById("setting-banner-desc").value = state.settings.bannerDesc || ""; }; window.handleSaveAdminSettings = function(e) { e.preventDefault(); state.settings.bannerTitle = document.getElementById("setting-banner-title").value.trim(); state.settings.bannerDesc = document.getElementById("setting-banner-desc").value.trim(); persistAllState(); applySettingsToUI(); showToast("บันทึกการตั้งค่าระบบเรียบร้อยแล้ว", "success"); }; function applySettingsToUI() { const titleEl = document.querySelector(".promo-banner-content h3"); const descEl = document.querySelector(".promo-banner-content p"); if (titleEl && state.settings.bannerTitle) { titleEl.textContent = state.settings.bannerTitle; titleEl.removeAttribute("data-i18n"); // Remove translation tag to allow dynamic text } if (descEl && state.settings.bannerDesc) { descEl.innerHTML = state.settings.bannerDesc; descEl.removeAttribute("data-i18n"); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.27 |
proxy
|
phpinfo
|
Settings