File manager - Edit - /home/webapp69.cm.in.th/u69319090047/ufa888/app.js
Back
// Mock Product Database with high-quality product images and variants const PRODUCTS = [ // 1. Electronics { id: 'elec-1', name: 'SoundAura Pro Wireless Headphones', category: 'electronics', categoryTh: 'อุปกรณ์อิเล็กทรอนิกส์', description: 'สัมผัสประสบการณ์เสียงที่เหนือระดับด้วยหูฟังไร้สาย SoundAura Pro ที่มาพร้อมกับระบบตัดเสียงรบกวนภายนอก Active Noise Cancelling (ANC) อัจฉริยะ ไดรเวอร์ขับเสียงขนาด 40มม. ให้เบสที่ลึกและเสียงกลางที่คมชัด แบตเตอรี่ใช้งานได้ยาวนานถึง 40 ชั่วโมงต่อการชาร์จหนึ่งครั้ง พร้อมฟีเจอร์ชาร์จเร็วเพียง 10 นาที ฟังได้นานถึง 5 ชั่วโมง สวมใส่สบายด้วยเมมโมรี่โฟมเนื้อนุ่มระดับพรีเมียม', price: 3490, originalPrice: 5990, discount: 42, rating: 4.8, reviewsCount: 320, soldCount: 1420, stock: 45, isFlashSale: true, flashClaimed: 78, images: [ 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1484704849700-f032a568e944?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1583394838336-acd977736f90?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Matte Black', value: '#1a1a1a' }, { name: 'Sunset Orange', value: '#ff5c00' }, { name: 'Cloud White', value: '#f3f4f6' } ], sizes: ['Standard'], specs: [ { label: 'รุ่นบลูทูธ', value: 'Bluetooth 5.2' }, { label: 'พอร์ตชาร์จ', value: 'USB-C Fast Charge' }, { label: 'ระยะเวลาใช้งาน', value: 'สูงสุด 40 ชั่วโมง (ปิด ANC)' }, { label: 'การป้องกันน้ำ', value: 'IPX4 กันละอองน้ำและเหงื่อ' } ], reviews: [ { name: 'สมชาย ดารากร', date: '2026-07-15', rating: 5, comment: 'เสียงดีมากครับ ระบบตัดเสียงรบกวนเงียบกริบ สีส้มสวยสะดุดตาโดนใจมาก แนะนำเลย!' }, { name: 'กานดา รักดี', date: '2026-07-08', rating: 4, comment: 'วัสดุพรีเมียม เบสแน่น แต่ใส่ไปนานๆ แอบร้อนหูเล็กน้อย โดยรวมคุ้มค่าเงินมากค่ะ' } ] }, { id: 'elec-2', name: 'AuraWatch Active Pro Smartwatch', category: 'electronics', categoryTh: 'อุปกรณ์อิเล็กทรอนิกส์', description: 'สมาร์ทวอทช์เพื่อสุขภาพและกีฬาสำหรับไลฟ์สไตล์ยุคใหม่ หน้าจอ AMOLED สีสันสดใสขนาด 1.43 นิ้ว สว่างสู้แดดได้ดี มาพร้อมเซนเซอร์วัดอัตราการเต้นของหัวใจตลอด 24 ชั่วโมง วัดระดับออกซิเจนในเลือด (SpO2) และฟังก์ชันติดตามการนอนหลับอย่างละเอียด รองรับโหมดออกกำลังกายมากกว่า 100 โหมด มี GPS ในตัวที่แม่นยำ และกันน้ำลึก 5 ATM', price: 2190, originalPrice: 3990, discount: 45, rating: 4.7, reviewsCount: 198, soldCount: 950, stock: 28, isFlashSale: true, flashClaimed: 85, images: [ 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1508685096489-7aacd43bd3b1?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1434494878577-86c23bcb06b9?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Frost White', value: '#f9fafb' }, { name: 'Charcoal Black', value: '#374151' }, { name: 'Active Orange', value: '#ff5c00' } ], sizes: ['40mm', '44mm'], specs: [ { label: 'ประเภทหน้าจอ', value: 'AMOLED 1.43 นิ้ว' }, { label: 'แบตเตอรี่', value: 'ใช้งานทั่วไปได้นาน 12 วัน' }, { label: 'การกันน้ำ', value: '5 ATM (ลึกสูงสุด 50 เมตร)' }, { label: 'เซนเซอร์', value: 'Heart Rate, SpO2, Accelerometer, GPS' } ], reviews: [ { name: 'ประเสริฐ ยอดชาย', date: '2026-07-10', rating: 5, comment: 'จอสวยมาก สว่างชัดเจน แบตเตอรี่อึดตามที่โฆษณาไว้เลย ใส่เดินวิ่งจับ GPS ได้ไวดีครับ' } ] }, { id: 'elec-3', name: 'NeoClick Ergonomic Mechanical Keyboard', category: 'electronics', categoryTh: 'อุปกรณ์อิเล็กทรอนิกส์', description: 'คีย์บอร์ดกลไกไร้สายขนาด 75% ออกแบบตามหลักสรีรศาสตร์เพื่อความสบายในการพิมพ์และเล่นเกมเป็นเวลานาน สวิตช์เสียงนุ่มแบบ Linear Red Switch ที่รองรับการสลับสวิตช์แบบ Hot-Swappable โครงสร้างปะเก็น (Gasket Mount) ซับเสียง 3 ชั้นเพื่อเสียงการพิมพ์ที่ไพเราะ รองรับการเชื่อมต่อ 3 โหมด (Bluetooth, 2.4GHz Wireless, และสาย USB-C) ไฟ RGB ปรับได้ 18 โหมด', price: 1890, originalPrice: 2790, discount: 32, rating: 4.6, reviewsCount: 145, soldCount: 680, stock: 35, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1587829741301-dc798b83add3?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1618384887929-16ec33fab9ef?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1595225476474-87563907a212?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Retro Gray', value: '#9ca3af' }, { name: 'Sunset Theme', value: '#ff8c42' } ], sizes: ['Red Switch', 'Brown Switch'], specs: [ { label: 'จำนวนปุ่ม', value: '82 ปุ่ม (75% Layout)' }, { label: 'การเชื่อมต่อ', value: 'Bluetooth 5.0 / 2.4G / Type-C' }, { label: 'แบตเตอรี่', value: '4000mAh (สูงสุด 200 ชม. ปิดไฟ)' }, { label: 'คีย์แคป', value: 'PBT Double-shot โปรไฟล์ Cherry' } ], reviews: [ { name: 'วิชัย ชนะภัย', date: '2026-07-02', rating: 4, comment: 'งานประกอบดีมาก พิมพ์สนุก เสียงเพราะสไตล์สวิตช์แดง เชื่อมต่อบลูทูธเสถียรดีครับ' } ] }, // 2. Fashion { id: 'fash-1', name: 'Classic Urban Leather Jacket', category: 'fashion', categoryTh: 'เสื้อผ้าแฟชั่น', description: 'เสื้อแจ็คเก็ตหนังแท้สไตล์ไบเกอร์สุดคลาสสิก ตัดเย็บจากหนังแกะเกรดพรีเมียมที่มีความนุ่มพิเศษและทนทาน ซับในด้วยผ้าซาตินคุณภาพดีให้ความรู้สึกสวมใส่สบายไม่ร้อน ซิปโลหะ YKK แข็งแรงทนทาน ออกแบบทรงเข้ารูปพอดีตัว (Slim Fit) เพิ่มลุคเท่ ทันสมัย แมตช์ง่ายกับกางเกงยีนส์และเสื้อยืดทุกสไตล์', price: 2590, originalPrice: 4990, discount: 48, rating: 4.9, reviewsCount: 88, soldCount: 410, stock: 12, isFlashSale: true, flashClaimed: 92, images: [ 'https://images.unsplash.com/photo-1551028719-00167b16eac5?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1521223890158-f9f7c3d5d504?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1551488831-00ddcb6c6bd3?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Premium Black', value: '#111827' }, { name: 'Tan Brown', value: '#78350f' } ], sizes: ['S', 'M', 'L', 'XL'], specs: [ { label: 'วัสดุภายนอก', value: 'หนังแกะแท้ 100%' }, { label: 'วัสดุซับใน', value: 'ผ้าโพลีเอสเตอร์ผสมซาติน' }, { label: 'ประเภทซิป', value: 'YKK Metal Zipper' }, { label: 'การทำความสะอาด', value: 'ส่งซักแห้งพิเศษสำหรับเสื้อหนังเท่านั้น' } ], reviews: [ { name: 'ธนา นวกิจ', date: '2026-07-12', rating: 5, comment: 'หนังนุ่มมากกก กลิ่นหนังแท้หอม ใส่แล้วทรงสวยพอดีตัวเลยครับ ร้านส่งไว แพ็คดีมาก' } ] }, { id: 'fash-2', name: 'Streetwear Oversized Graphic Hoodie', category: 'fashion', categoryTh: 'เสื้อผ้าแฟชั่น', description: 'เสื้อฮู้ดดี้ทรงโอเวอร์ไซส์สไตล์สตรีทแฟชั่น ผลิตจากผ้าฝ้าย Terry Cotton 100% หนา 320 แกรม ให้สัมผัสหนานุ่มอบอุ่นแต่ระบายอากาศได้ดี พิมพ์ลายกราฟิกแนวสตรีทอาร์ตแบบนูนความละเอียดสูง ทนทานต่อการซัก ไม่แตกหรือหลุดง่าย มาพร้อมกระเป๋าจิงโจ้ด้านหน้าและฮู้ดขนาดใหญ่ปรับสายรูดได้', price: 890, originalPrice: 1590, discount: 44, rating: 4.7, reviewsCount: 154, soldCount: 820, stock: 50, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1556821840-3a63f95609a7?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1620799140408-edc6dcb6d633?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Off-White', value: '#f9fafb' }, { name: 'Slate Gray', value: '#4b5563' }, { name: 'Sunset Orange', value: '#ff5c00' } ], sizes: ['M', 'L', 'XL', '2XL'], specs: [ { label: 'วัสดุผ้า', value: 'French Terry Cotton 100%' }, { label: 'น้ำหนักผ้า', value: '320 gsm (ผ้าหนาปานกลาง-หนา)' }, { label: 'ลายพิมพ์', value: 'Premium Screen Print' }, { label: 'การซัก', value: 'ซักเครื่องโหมดถนอมผ้า กลับด้านก่อนซัก' } ], reviews: [ { name: 'นารีรัตน์ มั่นคง', date: '2026-07-06', rating: 5, comment: 'ผ้าหนานุ่มดีมากค่ะ สีส้มสดสวยมาก ลายสกรีนเนี้ยบไม่ลอกเลย ซื้อรอบสองแล้วชอบมาก' } ] }, { id: 'fash-3', name: 'Retro Classic White Sneakers', category: 'fashion', categoryTh: 'เสื้อผ้าแฟชั่น', description: 'รองเท้าผ้าใบสีขาวสไตล์มินิมอลย้อนยุค ผลิตจากหนังเทียมไมโครไฟเบอร์ (Microfiber Leather) ที่มีความทนทานสูง ป้องกันรอยขีดข่วนและทำความสะอาดง่าย พื้นรองเท้าทำจากยางธรรมชาติกันลื่นยึดเกาะได้ดี ด้านในรองรับแรงกระแทกด้วยแผ่นรอง Ortholite นุ่มสบายตลอดทั้งวัน ออกแบบเรียบง่ายใส่ได้ทั้งผู้ชายและผู้หญิง', price: 1290, originalPrice: 2290, discount: 43, rating: 4.6, reviewsCount: 204, soldCount: 1100, stock: 30, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1600185365483-26d7a4cc7519?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Pure White', value: '#ffffff' }, { name: 'White Orange', value: '#ffebd9' } ], sizes: ['38', '39', '40', '41', '42', '43'], specs: [ { label: 'วัสดุอัปเปอร์', value: 'Premium Microfiber Synthetic Leather' }, { label: 'วัสดุซับใน', value: 'ผ้าตาข่ายระบายอากาศ (Mesh Lining)' }, { label: 'แผ่นรองพื้น', value: 'Ortholite Cushioned Insole' }, { label: 'พื้นนอกรองเท้า', value: 'ยางพาราธรรมชาติกันลื่น (Vulcanized Rubber)' } ], reviews: [ { name: 'พงศกร เด่นดี', date: '2026-07-09', rating: 4, comment: 'รองเท้าทรงสวย สวมใส่สบายไม่บีบเท้า ขนาดพอดีตามตารางไซส์ที่ร้านแจ้งไว้เลยครับ' } ] }, // 3. Cosmetics { id: 'cosm-1', name: 'GlowHydra Vitamin C & Hyaluronic Serum', category: 'cosmetics', categoryTh: 'เครื่องสำอาง', description: 'เซรั่มบำรุงผิวหน้าสูตรเข้มข้น ผสานคุณค่าระดับพรีเมียมจากวิตามินซีบริสุทธิ์ 15% และไฮยาลูรอนิกแอซิด 4 โมเลกุล ช่วยปรับสภาพผิวให้สว่างกระจ่างใส ลดเลือนจุดด่างดำ รอยสิว และความหมองคล้ำ พร้อมเติมเต็มความชุ่มชื้นให้ผิวอิ่มฟู นุ่มเด้ง รูขุมขนกระชับ สูตรอ่อนโยนปราศจากน้ำหอม แอลกอฮอล์ และพาราเบน เหมาะสำหรับทุกสภาพผิวแม้ผิวแพ้ง่าย', price: 490, originalPrice: 990, discount: 50, rating: 4.8, reviewsCount: 410, soldCount: 2200, stock: 80, isFlashSale: true, flashClaimed: 64, images: [ 'https://images.unsplash.com/photo-1620916566398-39f1143ab7be?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1608248597279-f99d160bfcbc?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1570172619644-dfd03ed5d881?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Original', value: '#ffebd9' } ], sizes: ['30 ml', '50 ml'], specs: [ { label: 'ปริมาตรสุทธิ', value: '30 มล. / 50 มล.' }, { label: 'ส่วนผสมหลัก', value: 'L-Ascorbic Acid 15%, Hyaluronic Acid, Niacinamide' }, { label: 'ประเภทผิว', value: 'เหมาะสำหรับทุกสภาพผิว รวมถึงผิวบอบบาง' }, { label: 'วิธีใช้', value: 'ทาบางๆ ทั่วใบหน้าและลำคอ เป็นประจำเช้า-เย็น ก่อนทาครีมบำรุง' } ], reviews: [ { name: 'สิรินทร์ สมใจ', date: '2026-07-16', rating: 5, comment: 'ใช้แล้วหน้าใสขึ้นจริงค่ะ รอยสิวดูจางลงมาก เนื้อเซรั่มซึมไว ไม่เหนอะหนะ ชอบมาก' }, { name: 'รัชดา พรแก้ว', date: '2026-07-04', rating: 5, comment: 'แพ็คเกจหรูหรา ขวดหัวปั๊มใช้งานง่าย ผิวดูชุ่มชื้นอิ่มฟูขึ้นหลังใช้ 1 สัปดาห์' } ] }, { id: 'cosm-2', name: 'Velvet Matte Longwear Lipstick Set', category: 'cosmetics', categoryTh: 'เครื่องสำอาง', description: 'เซตลิปสติกเนื้อเวลเวทแมตต์ 3 เฉดสียอดนิยม ให้พิกเมนต์สีแน่นสดชัดในการปาดเพียงครั้งเดียว สัมผัสบางเบาดุจกำมะหยี่ ไม่ตกร่อง ไม่ทำให้ริมฝีปากแห้งตึงด้วยส่วนผสมบำรุงจากวิตามินอีและโจโจ้บาออยล์ สูตรกันน้ำ ติดทนนานตลอดวันถึง 12 ชั่วโมง โทนสีสุภาพส้มอิฐ แดงตุ่น และชมพูนู้ด สวยเพอร์เฟกต์ทุกโอกาส', price: 390, originalPrice: 790, discount: 50, rating: 4.7, reviewsCount: 180, soldCount: 890, stock: 40, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1586495777744-4413f21062fa?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1625093742435-6fa192b6fb10?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Brick Orange', value: '#c2410c' }, { name: 'Soft Rose', value: '#db2777' }, { name: 'Maple Red', value: '#991b1b' } ], sizes: ['Set of 3'], specs: [ { label: 'จำนวนสินค้า', value: '3 แท่ง/เซต (แท่งละ 3.5 กรัม)' }, { label: 'เนื้อผลิตภัณฑ์', value: 'เนื้อครีมกำมะหยี่กึ่งแมตต์ (Velvet Matte)' }, { label: 'คุณสมบัติเด่น', value: 'กันน้ำ ติดทนนาน บำรุงชุ่มชื้น' }, { label: 'สารสกัดบำรุง', value: 'Vitamin E, Jojoba Seed Oil, Shea Butter' } ], reviews: [ { name: 'กนกวรรณ จันทร์', date: '2026-07-14', rating: 5, comment: 'สีสวยมากกกก โดยเฉพาะโทนส้มอิฐคือดีต่อใจ ปากไม่แห้งผงเลยค่ะ ติดทนพอสมควรเลย' } ] }, // 4. Home & Living { id: 'home-1', name: 'PureMist Smart Ultrasonic Humidifier', category: 'home', categoryTh: 'ของใช้ภายในบ้าน', description: 'เครื่องพ่นสเปรย์อโรมาและเพิ่มความชื้นในอากาศระบบอัลตราโซนิกขนาด 2.5 ลิตร ช่วยสร้างบรรยากาศผ่อนคลายและลดอาการคอแห้ง ผิวแห้งในห้องแอร์ ทำงานเงียบเป็นพิเศษไร้เสียงรบกวนต่ำกว่า 25 เดซิเบล มาพร้อมไฟ LED ปรับแสงเปลี่ยนสีได้ 7 สี และระบบความปลอดภัยปิดเครื่องอัตโนมัติเมื่อน้ำหมด สามารถควบคุมระดับความแรงของไอน้ำได้ 3 ระดับ', price: 790, originalPrice: 1490, discount: 47, rating: 4.8, reviewsCount: 162, soldCount: 750, stock: 25, isFlashSale: true, flashClaimed: 52, images: [ 'https://images.unsplash.com/photo-1519183071298-a2962feb14f4?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Minimal White', value: '#ffffff' }, { name: 'Pastel Orange', value: '#ffe3d1' } ], sizes: ['2.5L Standard', '3.5L Remote Controller'], specs: [ { label: 'ความจุถังน้ำ', value: '2.5 ลิตร (พ่นยาวนาน 12-18 ชั่วโมง)' }, { label: 'กำลังไฟฟ้า', value: '25W / แรงดันไฟ 220V' }, { label: 'อัตราการพ่นหมอก', value: '200-250 มล./ชั่วโมง' }, { label: 'ฟังก์ชันอโรมา', value: 'รองรับน้ำมันหอมระเหยชนิดละลายน้ำได้' } ], reviews: [ { name: 'ธนภูมิ มีสุข', date: '2026-07-11', rating: 5, comment: 'ใช้งานดีมากครับ ไอน้ำพ่นละเอียดดี เครื่องทำงานเงียบหลับสบาย ไฟปรับเปลี่ยนสีได้สวยงาม' } ] }, { id: 'home-2', name: 'Aesthetic Ribbed Ceramic Vase', category: 'home', categoryTh: 'ของใช้ภายในบ้าน', description: 'แจกันเซรามิกแฮนด์เมดทรงลอนฟลูต สไตล์เจแปนดิ-สแกนดิเนเวียน (Japandi) เหมาะสำหรับจัดแต่งโต๊ะทำงาน ห้องนั่งเล่น หรือโต๊ะอาหาร ผลิตจากดินเผาพอร์ซเลนคุณภาพดี ผิวสัมผัสแบบแมตต์หยาบมีเอกลักษณ์เฉพาะตัว โทนสีอบอุ่นเข้ากับสไตล์บ้านมินิมอลได้อย่างลงตัว', price: 320, originalPrice: 590, discount: 46, rating: 4.6, reviewsCount: 95, soldCount: 430, stock: 18, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1578500494198-246f612d3b3d?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1612196808214-b8e1d6145a8c?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Sand Beige', value: '#f5efe6' }, { name: 'Terracotta Orange', value: '#c2410c' } ], sizes: ['Medium (20cm)', 'Large (25cm)'], specs: [ { label: 'วัสดุ', value: 'เซรามิกพอร์ซเลนแฮนด์เมดเคลือบด้าน' }, { label: 'ขนาดสินค้า', value: 'สูง 20 ซม. / 25 ซม. เส้นผ่านศูนย์กลางปากแจกัน 6 ซม.' }, { label: 'น้ำหนัก', value: 'ประมาณ 0.6 - 0.8 กก.' }, { label: 'สไตล์ตกแต่ง', value: 'Minimalist, Japandi, Wabi-Sabi, Nordic' } ], reviews: [ { name: 'ดวงใจ นวลศรี', date: '2026-07-03', rating: 4, comment: 'แจกันสีสวย ผิวสัมผัสอาร์ตมาก วางประดับดอกไม้แห้งแล้วห้องดูมินิมอลขึ้นเยอะค่ะ แพ็คของมาแน่นหนาดี' } ] }, // 5. Food & Drinks { id: 'food-1', name: 'Uji Matcha Premium Ceremonial Powder', category: 'food', categoryTh: 'อาหารและเครื่องดื่ม', description: 'ผงมัทฉะแท้ 100% เกรดพิธีการ (Ceremonial Grade) นำเข้าจากเมืองอูจิ จังหวัดเกียวโต ประเทศญี่ปุ่น ซึ่งผ่านกระบวนการคลุมพรางแสงใบชา 30 วันก่อนเก็บเกี่ยว และโม่ด้วยครกหินโบราณอย่างพิถีพิถัน ให้สีเขียวมรกตสดใส รสชาติเข้มข้นกลมกล่อม มีอูมามิสูง อัฟเตอร์เทสต์หวานนุ่มละมุน ไม่ขมฝาด เหมาะสำหรับการชงดื่มแบบดั้งเดิม (Usucha) หรือนำไปทำมัทฉะลาเต้ระดับพรีเมียม', price: 590, originalPrice: 950, discount: 38, rating: 4.9, reviewsCount: 156, soldCount: 620, stock: 30, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1536256263959-770b48d82b0a?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1582725944991-48208248bb77?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Standard Pack', value: '#15803d' } ], sizes: ['50g Tin Can', '100g Refill Pack'], specs: [ { label: 'แหล่งกำเนิด', value: 'เมืองอูจิ, เกียวโต, ประเทศญี่ปุ่น' }, { label: 'สายพันธุ์ชา', value: 'Yabukita, Okumidori ผสมผสานพิเศษ' }, { label: 'ปริมาณบรรจุ', value: 'กระป๋องเหล็ก 50 กรัม / ถุงซิปล็อค 100 กรัม' }, { label: 'การเก็บรักษา', value: 'เก็บในภาชนะทึบแสง แช่ตู้เย็นอุณหภูมิต่ำกว่า 5°C' } ], reviews: [ { name: 'ปิติ รัตนเดช', date: '2026-07-13', rating: 5, comment: 'มัทฉะหอมมากครับ สีเขียวเข้มสวยงาม ชงดื่มกับน้ำร้อนเปล่าๆ คือฟิน ได้รสอูมามิตามฉบับของแท้เกียวโต' } ] }, { id: 'food-2', name: 'Organic Cold Brew Coffee Concentrate', category: 'food', categoryTh: 'อาหารและเครื่องดื่ม', description: 'กาแฟโคลด์บรูว์สูตรเข้มข้นสกัดเย็นแบบออร์แกนิก ใช้เมล็ดกาแฟสายพันธุ์อาราบิก้าแท้ 100% คั่วกลางพิเศษจากแหล่งเพาะปลูกยั่งยืนบนดอยสะเก็ด จังหวัดเชียงใหม่ สกัดเย็นนาน 18 ชั่วโมงเพื่อรสชาติที่นุ่มนวล กรดต่ำ ไม่ระคายเคืองกระเพาะอาหาร มีกลิ่นอายโทนช็อกโกแลต คาราเมล และถั่วอัลมอนด์ สามารถผสมดื่มกับน้ำเปล่า นม หรือน้ำส้มได้ตามชอบ', price: 290, originalPrice: 490, discount: 41, rating: 4.7, reviewsCount: 112, soldCount: 510, stock: 40, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1517701604599-bb29b565090c?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1551046713-bc47f96256c1?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Bold Blend', value: '#451a03' } ], sizes: ['500 ml Bottle', '1000 ml Big Jug'], specs: [ { label: 'ชนิดเมล็ดกาแฟ', value: 'Arabica 100% Organic Single Origin' }, { label: 'ระดับการคั่ว', value: 'Medium Roast (คั่วกลาง)' }, { label: 'รสสัมผัส (Notes)', value: 'Chocolate, Toffee, Nutty, Sweet Aftertaste' }, { label: 'อัตราส่วนแนะนำ', value: 'หัวเชื้อกาแฟ 1 ส่วน ต่อน้ำหรือนม 2 ส่วน' } ], reviews: [ { name: 'วิภาวี สร้อยแสง', date: '2026-07-07', rating: 5, comment: 'ชงง่าย สะดวกมากค่ะในตอนเช้า รสชาตินุ่มกลมกล่อมมาก ไม่ขมแสบคอเหมือนเอสเปรสโซ่ทั่วไป' } ] }, // 6. Sports & Health { id: 'sport-1', name: 'ActiveFlow High-Density Yoga Mat', category: 'sports', categoryTh: 'กีฬาและสุขภาพ', description: 'เสื่อโยคะระดับมืออาชีพผลิตจากวัสดุ TPE โครงสร้างสองชั้นความหนาแน่นสูง 6 มม. มีคุณสมบัติยืดหยุ่นดีเยี่ยม รองรับและลดแรงกดทับบริเวณหัวเข่า ข้อมือ และข้อต่อได้เป็นอย่างดี พื้นผิวกันลื่นแบบลายคลื่นสองทิศทาง ช่วยยึดเกาะพื้นและผิวสัมผัสได้อย่างดีเยี่ยมแม้มีเหงื่อออกมาก ปราศจากสารพิษและกลิ่นยางเคมี พร้อมสายสะพายหิ้วพกพาสะดวก', price: 550, originalPrice: 1090, discount: 49, rating: 4.8, reviewsCount: 220, soldCount: 940, stock: 30, isFlashSale: true, flashClaimed: 90, images: [ 'https://images.unsplash.com/photo-1592432678016-e910b452f9a2?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1601925260368-ae2f83cf8b7f?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Warm Orange', value: '#ff5c00' }, { name: 'Ocean Teal', value: '#0f766e' }, { name: 'Lilac Purple', value: '#701a75' } ], sizes: ['6mm Standard', '8mm Extra Thick'], specs: [ { label: 'วัสดุเสื่อ', value: 'Eco-Friendly TPE (Thermoplastic Elastomer) 100%' }, { label: 'ขนาดสินค้า', value: '183 ซม. x 61 ซม. ความหนา 6 มม. / 8 มม.' }, { label: 'น้ำหนักเสื่อ', value: 'ประมาณ 0.95 กก.' }, { label: 'การดูแลรักษา', value: 'เช็ดทำความสะอาดด้วยผ้าชุบน้ำสบู่เจือจาง ผึ่งลมในที่ร่มให้แห้ง' } ], reviews: [ { name: 'สุกัญญา คงเดช', date: '2026-07-14', rating: 5, comment: 'เสื่อโยคะหนานุ่มดีค่ะ เล่นแล้วไม่เจ็บเข่า ยึดเกาะดีไม่ลื่นไถล สีส้มสวยเด่นถูกใจ' } ] }, { id: 'sport-2', name: 'IronGrip 20kg Adjustable Dumbbell Set', category: 'sports', categoryTh: 'กีฬาและสุขภาพ', description: 'ชุดดัมเบลปรับน้ำหนักได้รวม 20 กก. บรรจุในกล่องเคสจัดเก็บพกพาสะดวก ผลิตจากเหล็กหล่อแท้คุณภาพสูงชุบโครเมียมกันสนิม แข็งแรงทนทาน ในชุดประกอบด้วยแผ่นน้ำหนักหลายขนาด แกนดัมเบล 2 แกนพร้อมข้อต่อฟองน้ำสำหรับดัดแปลงเป็นบาร์เบลได้ยาวสูงสุด 1.2 เมตร และตัวล็อคเกลียวความปลอดภัยสูงช่วยยึดแผ่นน้ำหนักแน่นหนาขณะใช้งาน', price: 1390, originalPrice: 2490, discount: 44, rating: 4.7, reviewsCount: 140, soldCount: 550, stock: 15, isFlashSale: false, images: [ 'https://images.unsplash.com/photo-1638536532686-d610adfc8e5c?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1517838277536-f5f99be501cd?w=800&auto=format&fit=crop&q=80' ], colors: [ { name: 'Chrome Steel', value: '#d1d5db' } ], sizes: ['20kg Set', '30kg Set'], specs: [ { label: 'วัสดุแผ่นเหล็ก', value: 'เหล็กหล่อพรีเมียม ชุบโครเมียมเงางาม' }, { label: 'แกนจับดัมเบล', value: 'แกนเหล็กขนาด 1 นิ้ว หุ้มพลาสติกกันลื่นลายเพชร' }, { label: 'ฟังก์ชันพิเศษ', value: 'มาพร้อมแกนโฟมต่อเชื่อมสำหรับทำบาร์เบล' }, { label: 'อุปกรณ์ในเซต', value: 'แผ่นน้ำหนัก 12 แผ่น, แกนดัมเบล 2 แกน, แกนต่อ 1 ชิ้น, น็อตล็อค 4 ตัว' } ], reviews: [ { name: 'ทศพล เก่งกล้า', date: '2026-07-05', rating: 5, comment: 'คุ้มค่ามากๆ ครับชุดนี้ ปรับน้ำหนักได้เยอะ ประกอบเข้าบาร์เบลง่ายและแน่นดีมาก กล่องจัดเก็บเป็นระเบียบ' } ] } ]; // Globally accessible memory Database and States const STATE = { activeView: 'home', // 'home', 'detail', 'cart', 'profile', 'wishlist', 'admin' selectedProductId: null, cart: [], wishlist: [], couponApplied: null, discountAmount: 0, searchQuery: '', currentCategoryFilter: 'all', currentProfileTab: 'settings', // 'settings', 'orders', 'vouchers', 'addresses' currentAdminTab: 'dashboard', // 'dashboard', 'orders', 'products', 'users' // Auth status isLoggedIn: false, currentUser: null, // Holds currently logged-in user object reference // Memory Users Database users: [ { email: 'admin@ufa888.com', password: 'admin123', name: 'สมชาย ผู้ดูแลระบบ (Admin)', phone: '099-999-9999', role: 'admin', gender: 'male', birthdate: '1985-02-20', regDate: '2026-01-01', addresses: [], coupons: [], orders: [] }, { email: 'user@ufa888.com', password: 'user123', name: 'ณัฐพงษ์ จิตดี', phone: '089-765-4321', role: 'user', gender: 'male', birthdate: '1998-05-12', regDate: '2026-07-01', addresses: [ { id: 'addr-1', tag: 'ที่ทำงาน', name: 'ณัฐพงษ์ จิตดี', detail: 'บริษัท เทคสตาร์ตอัพ จำกัด (ชั้น 18) 123 อาคารปทุมวันสแควร์ ถนนพญาไท แขวงวังใหม่ เขตปทุมวัน กรุงเทพมหานคร 10330', phone: '089-765-4321', isDefault: true }, { id: 'addr-2', tag: 'บ้าน', name: 'ณัฐพงษ์ จิตดี', detail: '45/6 หมู่ 3 หมู่บ้านเปี่ยมสุข ซอยรามอินทรา 39 ถนนรามอินทรา แขวงอนุสาวรีย์ เขตบางเขน กรุงเทพมหานคร 10220', phone: '089-765-4321', isDefault: false } ], coupons: [ { code: 'ORANGE10', discountPercent: 10, minSpend: 500, desc: 'ส่วนลด 10% เมื่อช้อปครบ 500 บาท', expiry: 'หมดอายุ: 31 ส.ค. 2026' }, { code: 'WELCOME20', discountPercent: 20, minSpend: 1000, desc: 'ส่วนลดพิเศษ 20% ลูกค้าใหม่ ช้อปครบ 1,000 บาท', expiry: 'หมดอายุ: 31 ธ.ค. 2026' } ], orders: [ { id: 'ORD-987214', date: '2026-07-10 14:22', status: 'completed', statusText: 'จัดส่งสำเร็จแล้ว', items: [ { name: 'SoundAura Pro Wireless Headphones (Matte Black / Standard)', qty: 1, price: 3490 } ], total: 3490 }, { id: 'ORD-984321', date: '2026-07-18 10:15', status: 'shipping', statusText: 'กำลังจัดส่งพัสดุ', items: [ { name: 'GlowHydra Vitamin C & Hyaluronic Serum (30 ml)', qty: 2, price: 490 } ], total: 980 } ] } ], // Global administrative list of ALL customer orders allOrders: [ { id: 'ORD-987214', customerName: 'ณัฐพงษ์ จิตดี', customerEmail: 'user@ufa888.com', date: '2026-07-10 14:22', status: 'completed', statusText: 'จัดส่งสำเร็จแล้ว', items: 'SoundAura Pro Wireless Headphones (Matte Black / Standard) x1', total: 3490 }, { id: 'ORD-984321', customerName: 'ณัฐพงษ์ จิตดี', customerEmail: 'user@ufa888.com', date: '2026-07-18 10:15', status: 'shipping', statusText: 'กำลังจัดส่งพัสดุ', items: 'GlowHydra Vitamin C & Hyaluronic Serum (30 ml) x2', total: 980 } ] }; // Global Slide Carousel Controller let currentSlideIndex = 0; let slideIntervalId = null; // Initialize app when DOM loaded document.addEventListener('DOMContentLoaded', () => { initApp(); }); function initApp() { // Start count down timer for Flash Sale startFlashSaleCountdown(); // Auto-login default regular user for smoother mockup demo initial experience (optional but let's keep it logged out so the user can test the login/register flows easily!) // Or we can let them log in. Let's keep it logged out to show off the Login screen at first! updateHeaderAuthUI(); // Render initial views renderHomeView(); renderCategoriesMenu(); updateBadges(); // Register all global event listeners registerEventListeners(); // Start Carousel auto play startCarouselAutoPlay(); // Set Lucide Icons lucide.createIcons(); } // ------------------------------------------------------------- // EVENT LISTENERS REGISTER // ------------------------------------------------------------- function registerEventListeners() { // Navigation routing document.querySelectorAll('[data-link]').forEach(element => { element.addEventListener('click', (e) => { e.preventDefault(); const view = element.getAttribute('data-link'); // Route guards: redirect to login if profile/cart/wishlist is clicked when logged out if (!STATE.isLoggedIn && (view === 'profile' || view === 'wishlist')) { showToast('กรุณาเข้าสู่ระบบเพื่อใช้งานระบบบัญชี', 'warning'); openAuthModal(); return; } navigateTo(view); }); }); // Logo Click triggers Home View document.querySelector('.logo').addEventListener('click', (e) => { e.preventDefault(); navigateTo('home'); }); // Category navigation links inside header menu document.querySelectorAll('.nav-links a').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('active')); link.classList.add('active'); const cat = link.getAttribute('data-category'); // If user was viewing admin panel or profile, redirect to home when clicking product categories if (STATE.activeView !== 'home') { navigateTo('home'); } STATE.currentCategoryFilter = cat; renderHomeView(); // Auto scroll to recommended section if clicked category filter if(cat !== 'all') { const target = document.getElementById('recommended-heading'); if(target) target.scrollIntoView({ behavior: 'smooth' }); } }); }); // Mobile navigation toggle const mobileMenuBtn = document.getElementById('mobile-menu-btn'); const navMenu = document.getElementById('nav-menu'); mobileMenuBtn.addEventListener('click', () => { navMenu.classList.toggle('mobile-active'); }); // Sticky header box-shadow window.addEventListener('scroll', () => { const header = document.querySelector('header'); if (window.scrollY > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } }); // Search bar dynamic suggestion engine const searchInput = document.getElementById('search-input'); const searchSuggestions = document.getElementById('search-suggestions'); const searchBtn = document.getElementById('search-btn'); searchInput.addEventListener('input', (e) => { const val = e.target.value.trim().toLowerCase(); STATE.searchQuery = val; if (val.length > 0) { const matches = PRODUCTS.filter(p => p.name.toLowerCase().includes(val) || p.description.toLowerCase().includes(val) || p.categoryTh.toLowerCase().includes(val) ).slice(0, 5); if (matches.length > 0) { let html = ''; matches.forEach(p => { html += ` <div class="suggestion-item" data-id="${p.id}"> <i data-lucide="search" style="width: 16px; height: 16px;"></i> <span class="suggestion-title">${p.name}</span> <span class="suggestion-category">${p.categoryTh}</span> </div> `; }); searchSuggestions.innerHTML = html; searchSuggestions.classList.add('active'); lucide.createIcons(); // Add click events on items document.querySelectorAll('.suggestion-item').forEach(item => { item.addEventListener('click', () => { const id = item.getAttribute('data-id'); searchSuggestions.classList.remove('active'); searchInput.value = ''; navigateTo('detail', id); }); }); } else { searchSuggestions.innerHTML = `<div class="suggestion-item"><span class="suggestion-title">ไม่พบสินค้าที่ตรงกับการค้นหา</span></div>`; searchSuggestions.classList.add('active'); } } else { searchSuggestions.classList.remove('active'); } }); // Close suggestion box when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.search-container')) { searchSuggestions.classList.remove('active'); } }); searchBtn.addEventListener('click', () => { executeSearch(); }); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { executeSearch(); } }); function executeSearch() { const val = searchInput.value.trim(); if (val) { STATE.searchQuery = val.toLowerCase(); searchSuggestions.classList.remove('active'); // Go to home view and filter recommended items navigateTo('home'); const target = document.getElementById('recommended-heading'); if(target) target.scrollIntoView({ behavior: 'smooth' }); // Highlight search active tab on categories menu document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('active')); renderHomeView(); } } // Carousel buttons const prevBtn = document.getElementById('slider-prev'); const nextBtn = document.getElementById('slider-next'); if (prevBtn && nextBtn) { prevBtn.addEventListener('click', () => { navigateCarousel(-1); resetCarouselAutoPlay(); }); nextBtn.addEventListener('click', () => { navigateCarousel(1); resetCarouselAutoPlay(); }); } // Modal Close document.getElementById('modal-close-btn').addEventListener('click', closeModal); document.getElementById('modal-action-btn').addEventListener('click', closeModal); window.addEventListener('click', (e) => { const modal = document.getElementById('modal'); if (e.target === modal) closeModal(); }); // Auth modal tab switcher triggers document.getElementById('tab-login-btn').addEventListener('click', () => toggleAuthTab('login')); document.getElementById('tab-register-btn').addEventListener('click', () => toggleAuthTab('register')); document.getElementById('auth-modal-close-btn').addEventListener('click', closeAuthModal); // Auth Forms Submission Handlers document.getElementById('login-form').addEventListener('submit', handleLogin); document.getElementById('register-form').addEventListener('submit', handleRegister); } // ------------------------------------------------------------- // USER AUTHENTICATION & LOGIN REGISTRATION MODAL // ------------------------------------------------------------- function openAuthModal() { const authModal = document.getElementById('auth-modal'); if (authModal) { authModal.classList.add('show'); toggleAuthTab('login'); } } function closeAuthModal() { const authModal = document.getElementById('auth-modal'); if (authModal) { authModal.classList.remove('show'); // Clear forms inputs document.getElementById('login-form').reset(); document.getElementById('register-form').reset(); } } function toggleAuthTab(tab) { const loginTabBtn = document.getElementById('tab-login-btn'); const registerTabBtn = document.getElementById('tab-register-btn'); const loginFormGroup = document.getElementById('form-login-group'); const registerFormGroup = document.getElementById('form-register-group'); if (tab === 'login') { loginTabBtn.classList.add('active'); registerTabBtn.classList.remove('active'); loginFormGroup.classList.add('active'); registerFormGroup.classList.remove('active'); } else { registerTabBtn.classList.add('active'); loginTabBtn.classList.remove('active'); registerFormGroup.classList.add('active'); loginFormGroup.classList.remove('active'); } } function handleLogin(e) { e.preventDefault(); const email = document.getElementById('login-email').value.trim().toLowerCase(); const pass = document.getElementById('login-password').value; const matched = STATE.users.find(u => u.email === email && u.password === pass); if (matched) { STATE.isLoggedIn = true; STATE.currentUser = matched; closeAuthModal(); updateHeaderAuthUI(); updateBadges(); showToast(`ยินดีต้อนรับกลับมาคุณ ${matched.name}!`, 'success'); // If they are an Admin, navigate straight to Admin Dashboard if (matched.role === 'admin') { navigateTo('admin'); } else { // Regular user, go to home or re-render profile if (STATE.activeView === 'profile') { renderProfileView(); } else { navigateTo('home'); } } } else { showToast('อีเมลหรือรหัสผ่านไม่ถูกต้อง กรุณาลองใหม่อีกครั้ง', 'danger'); } } function handleRegister(e) { e.preventDefault(); const name = document.getElementById('reg-name').value.trim(); const email = document.getElementById('reg-email').value.trim().toLowerCase(); const phone = document.getElementById('reg-phone').value.trim(); const password = document.getElementById('reg-password').value; const confirmPassword = document.getElementById('reg-conf-password').value; if (password !== confirmPassword) { showToast('รหัสผ่านและการยืนยันรหัสผ่านไม่ตรงกัน', 'danger'); return; } const exists = STATE.users.some(u => u.email === email); if (exists) { showToast('อีเมลนี้ถูกใช้งานในระบบแล้ว กรุณาใช้อีเมลอื่น', 'danger'); return; } const dateStr = new Date().toISOString().substring(0, 10); const newUser = { email, password, name, phone, role: 'user', gender: 'other', birthdate: '', regDate: dateStr, addresses: [], coupons: [ { code: 'ORANGE10', discountPercent: 10, minSpend: 500, desc: 'ส่วนลด 10% เมื่อช้อปครบ 500 บาท', expiry: 'หมดอายุ: 31 ส.ค. 2026' } ], orders: [] }; // Add to state users STATE.users.push(newUser); // Automatically log in STATE.isLoggedIn = true; STATE.currentUser = newUser; closeAuthModal(); updateHeaderAuthUI(); updateBadges(); showToast(`ลงทะเบียนเข้าใช้งานสำเร็จ ยินดีต้อนรับคุณ ${name}`, 'success'); navigateTo('home'); } window.handleLogout = function() { STATE.isLoggedIn = false; STATE.currentUser = null; STATE.cart = []; // clear cart upon logout STATE.wishlist = []; // clear wishlist updateHeaderAuthUI(); updateBadges(); showToast('ออกจากระบบเรียบร้อยแล้ว', 'info'); navigateTo('home'); }; function updateHeaderAuthUI() { const authWrapper = document.getElementById('header-auth-wrapper'); if (!authWrapper) return; if (STATE.isLoggedIn) { // If Admin, show a specific visual border or label const isAdmin = STATE.currentUser.role === 'admin'; authWrapper.innerHTML = ` <div class="user-avatar-btn" id="header-avatar-btn" style="border: 2px solid ${isAdmin ? '#10b981' : 'var(--primary)'};"> <img src="${isAdmin ? 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=80&auto=format&fit=crop&q=80' : 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=80&auto=format&fit=crop&q=80'}" alt="Avatar" class="avatar-img"> <span class="avatar-name" style="max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${STATE.currentUser.name}</span> <i data-lucide="chevron-down" style="width:14px; height:14px;"></i> </div> <!-- User Dropdown Panel --> <div class="auth-dropdown" id="auth-dropdown"> ${isAdmin ? `<div class="auth-dropdown-item font-bold" onclick="navigateTo('admin')" style="color: var(--success);"><i data-lucide="shield-alert"></i> ระบบผู้ดูแลระบบ (Admin)</div>` : ''} <div class="auth-dropdown-item" onclick="navigateTo('profile')"><i data-lucide="user"></i> ข้อมูลโปรไฟล์</div> <div class="auth-dropdown-item" onclick="navigateTo('profile'); switchProfileTab('orders');"><i data-lucide="package"></i> รายการคำสั่งซื้อ</div> <div class="auth-dropdown-item" onclick="navigateTo('profile'); switchProfileTab('vouchers');"><i data-lucide="ticket"></i> คูปองส่วนลด</div> <div class="auth-dropdown-item" onclick="navigateTo('profile'); switchProfileTab('addresses');"><i data-lucide="map-pin"></i> สมุดที่อยู่</div> <hr style="border:none; border-top:1px solid var(--gray-200); margin: 6px 0;"> <div class="auth-dropdown-item text-danger" onclick="handleLogout()"><i data-lucide="log-out"></i> ออกจากระบบ</div> </div> `; // Add dropdown toggle click trigger const avatarBtn = document.getElementById('header-avatar-btn'); const dropdown = document.getElementById('auth-dropdown'); avatarBtn.addEventListener('click', (e) => { e.stopPropagation(); dropdown.classList.toggle('active'); }); document.addEventListener('click', () => { if(dropdown) dropdown.classList.remove('active'); }); } else { authWrapper.innerHTML = ` <button class="btn-auth-trigger" onclick="openAuthModal()"> <i data-lucide="log-in" style="width: 16px; height: 16px;"></i> <span>เข้าสู่ระบบ / สมัครสมาชิก</span> </button> `; } lucide.createIcons(); } // ------------------------------------------------------------- // TIMER SUB-SYSTEM (FLASH SALE COUNTDOWN) // ------------------------------------------------------------- function startFlashSaleCountdown() { const hoursVal = document.getElementById('hours'); const minutesVal = document.getElementById('minutes'); const secondsVal = document.getElementById('seconds'); // Set mock countdown target (e.g. 5 hours from load) let totalSeconds = 5 * 60 * 60 + 45 * 60; // 5 hours 45 mins const interval = setInterval(() => { if (totalSeconds <= 0) { clearInterval(interval); return; } totalSeconds--; const hrs = Math.floor(totalSeconds / 3600); const mins = Math.floor((totalSeconds % 3600) / 60); const secs = totalSeconds % 60; if (hoursVal) hoursVal.textContent = String(hrs).padStart(2, '0'); if (minutesVal) minutesVal.textContent = String(mins).padStart(2, '0'); if (secondsVal) secondsVal.textContent = String(secs).padStart(2, '0'); }, 1000); } // ------------------------------------------------------------- // CAROUSEL AUTOMATIC SLIDER CONTROL // ------------------------------------------------------------- function startCarouselAutoPlay() { slideIntervalId = setInterval(() => { navigateCarousel(1); }, 4500); } function resetCarouselAutoPlay() { clearInterval(slideIntervalId); startCarouselAutoPlay(); } function navigateCarousel(direction) { const track = document.getElementById('slide-track'); const dots = document.querySelectorAll('.slider-dot'); if(!track || dots.length === 0) return; const totalSlides = dots.length; currentSlideIndex = (currentSlideIndex + direction + totalSlides) % totalSlides; track.style.transform = `translateX(-${currentSlideIndex * 100}%)`; dots.forEach((dot, idx) => { dot.classList.toggle('active', idx === currentSlideIndex); }); } // ------------------------------------------------------------- // STATE ROUTING CONTROLLER // ------------------------------------------------------------- function navigateTo(viewName, productId = null) { // Intercept navigation to cart/profile if logged out if (!STATE.isLoggedIn && (viewName === 'profile' || viewName === 'wishlist' || viewName === 'admin')) { showToast('กรุณาเข้าสู่ระบบก่อนดำเนินการต่อ', 'warning'); openAuthModal(); return; } // Switch Active views in navigation classes const oldSection = document.querySelector('.view-section.active'); if (oldSection) { oldSection.classList.remove('active'); } STATE.activeView = viewName; if (productId) { STATE.selectedProductId = productId; } // Run renderer functions if (viewName === 'home') { renderHomeView(); } else if (viewName === 'detail') { renderDetailView(); } else if (viewName === 'cart') { renderCartView(); } else if (viewName === 'profile') { renderProfileView(); } else if (viewName === 'wishlist') { renderWishlistView(); } else if (viewName === 'admin') { // Double check admin guard if (STATE.currentUser && STATE.currentUser.role === 'admin') { renderAdminView(); } else { showToast('คุณไม่มีสิทธิ์ในการเข้าถึงหน้าแอดมิน', 'danger'); navigateTo('home'); return; } } // Add dynamic active views classes setTimeout(() => { const newSection = document.getElementById(`${viewName}-view`); if (newSection) { newSection.classList.add('active'); } window.scrollTo({ top: 0, behavior: 'smooth' }); }, 50); } // ------------------------------------------------------------- // RENDER: CATEGORIES LINKS IN HEADER & BODY // ------------------------------------------------------------- function renderCategoriesMenu() { const grid = document.getElementById('categories-grid'); if(!grid) return; const cats = [ { key: 'electronics', name: 'อุปกรณ์อิเล็กทรอนิกส์', count: `${PRODUCTS.filter(p => p.category==='electronics').length} รายการ`, icon: 'smartphone' }, { key: 'fashion', name: 'เสื้อผ้าแฟชั่น', count: `${PRODUCTS.filter(p => p.category==='fashion').length} รายการ`, icon: 'shirt' }, { key: 'cosmetics', name: 'เครื่องสำอาง', count: `${PRODUCTS.filter(p => p.category==='cosmetics').length} รายการ`, icon: 'sparkles' }, { key: 'home', name: 'ของใช้ภายในบ้าน', count: `${PRODUCTS.filter(p => p.category==='home').length} รายการ`, icon: 'home' }, { key: 'food', name: 'อาหารและเครื่องดื่ม', count: `${PRODUCTS.filter(p => p.category==='food').length} รายการ`, icon: 'coffee' }, { key: 'sports', name: 'กีฬาและสุขภาพ', count: `${PRODUCTS.filter(p => p.category==='sports').length} รายการ`, icon: 'dumbbells' } ]; let html = ''; cats.forEach(c => { html += ` <div class="category-card" onclick="filterByCategory('${c.key}')"> <div class="category-icon-wrapper"> <i data-lucide="${c.icon}"></i> </div> <span class="category-name">${c.name}</span> <span class="category-count">${c.count}</span> </div> `; }); grid.innerHTML = html; } window.filterByCategory = function(categoryKey) { STATE.currentCategoryFilter = categoryKey; // Set header category link active state document.querySelectorAll('.nav-links a').forEach(link => { const linkCat = link.getAttribute('data-category'); if (linkCat === categoryKey) { link.classList.add('active'); } else { link.classList.remove('active'); } }); renderHomeView(); const target = document.getElementById('recommended-heading'); if (target) target.scrollIntoView({ behavior: 'smooth' }); }; // ------------------------------------------------------------- // RENDER: HOME VIEW (FLASH SALES & RECOMMENDED) // ------------------------------------------------------------- function renderHomeView() { // 1. Render Flash Sale Grid const flashGrid = document.getElementById('flash-sale-grid'); if (flashGrid) { const flashProducts = PRODUCTS.filter(p => p.isFlashSale); let flashHtml = ''; flashProducts.forEach(p => { const isFav = STATE.wishlist.includes(p.id) ? 'active' : ''; flashHtml += ` <div class="product-card"> <div class="card-media" onclick="navigateTo('detail', '${p.id}')"> <div class="card-badges"> <span class="badge-flash">Flash Sale</span> <span class="badge-discount">-${p.discount}%</span> </div> <img src="${p.images[0]}" alt="${p.name}" class="product-img" loading="lazy"> </div> <button class="btn-wishlist ${isFav}" onclick="event.stopPropagation(); toggleWishlist('${p.id}')"> <i data-lucide="heart" style="width: 18px; height: 18px; fill: ${isFav ? '#ef4444' : 'none'};"></i> </button> <div class="card-details" onclick="navigateTo('detail', '${p.id}')"> <span class="product-category-lbl">${p.categoryTh}</span> <h3 class="product-title-lbl">${p.name}</h3> <div class="product-rating-row"> <div class="stars">${renderStars(p.rating)}</div> <span>(${p.reviewsCount})</span> <span class="sold-count">ขายแล้ว ${p.soldCount} ชิ้น</span> </div> <div class="price-row"> <span class="price-actual">฿${p.price.toLocaleString()}</span> <span class="price-original">฿${p.originalPrice.toLocaleString()}</span> </div> <div class="claim-progress-bar"> <div class="claim-progress-fill" style="width: ${p.flashClaimed}%"></div> </div> <div class="claim-text"> <span>มีคนจองแล้ว ${p.flashClaimed}%</span> <span style="color: var(--primary); font-weight: 700;">จำกัดเวลา</span> </div> <button class="btn-add-to-cart" onclick="event.stopPropagation(); quickAdd('${p.id}')"> <i data-lucide="shopping-bag" style="width: 14px; height: 14px;"></i> เพิ่มลงตะกร้า </button> </div> </div> `; }); flashGrid.innerHTML = flashHtml; } // 2. Render Recommended Grid const recGrid = document.getElementById('recommended-grid'); if (recGrid) { let filtered = PRODUCTS; // Filter by category if (STATE.currentCategoryFilter !== 'all') { filtered = filtered.filter(p => p.category === STATE.currentCategoryFilter); } // Filter by search query if (STATE.searchQuery) { filtered = filtered.filter(p => p.name.toLowerCase().includes(STATE.searchQuery) || p.description.toLowerCase().includes(STATE.searchQuery) ); } // Render Heading Title depending on filter state const recHeading = document.getElementById('recommended-heading'); if (recHeading) { if (STATE.searchQuery) { recHeading.innerHTML = `ผลการค้นหาสำหรับ <span>"${STATE.searchQuery}"</span> (${filtered.length} รายการ)`; } else if (STATE.currentCategoryFilter !== 'all') { const activeCatObj = PRODUCTS.find(p => p.category === STATE.currentCategoryFilter); const categoryName = activeCatObj ? activeCatObj.categoryTh : ''; recHeading.innerHTML = `หมวดหมู่สินค้า <span>"${categoryName}"</span>`; } else { recHeading.innerHTML = `สินค้า<span>แนะนำสำหรับคุณ</span>`; } } if (filtered.length > 0) { let recHtml = ''; filtered.forEach(p => { const isFav = STATE.wishlist.includes(p.id) ? 'active' : ''; recHtml += ` <div class="product-card"> <div class="card-media" onclick="navigateTo('detail', '${p.id}')"> <div class="card-badges"> <span class="badge-discount">-${p.discount}%</span> </div> <img src="${p.images[0]}" alt="${p.name}" class="product-img" loading="lazy"> </div> <button class="btn-wishlist ${isFav}" onclick="event.stopPropagation(); toggleWishlist('${p.id}')"> <i data-lucide="heart" style="width: 18px; height: 18px; fill: ${isFav ? '#ef4444' : 'none'};"></i> </button> <div class="card-details" onclick="navigateTo('detail', '${p.id}')"> <span class="product-category-lbl">${p.categoryTh}</span> <h3 class="product-title-lbl">${p.name}</h3> <div class="product-rating-row"> <div class="stars">${renderStars(p.rating)}</div> <span>(${p.reviewsCount})</span> <span class="sold-count">ขายแล้ว ${p.soldCount} ชิ้น</span> </div> <div class="price-row"> <span class="price-actual">฿${p.price.toLocaleString()}</span> <span class="price-original">฿${p.originalPrice.toLocaleString()}</span> </div> <button class="btn-add-to-cart" onclick="event.stopPropagation(); quickAdd('${p.id}')"> <i data-lucide="shopping-bag" style="width: 14px; height: 14px;"></i> เพิ่มลงตะกร้า </button> </div> </div> `; }); recGrid.innerHTML = recHtml; } else { recGrid.innerHTML = ` <div style="grid-column: span 5; text-align: center; padding: 60px 0; color: var(--gray-500);"> <i data-lucide="inbox" style="width: 48px; height: 48px; margin-bottom: 12px; color: var(--gray-300);"></i> <p style="font-size: 16px; font-weight: 500;">ขออภัย ไม่พบรายการสินค้าในเงื่อนไขการค้นหาของคุณ</p> </div> `; } } lucide.createIcons(); } // Helper to draw stars function renderStars(rating) { let starsHtml = ''; const fullStars = Math.floor(rating); const hasHalf = rating % 1 >= 0.5; for(let i=1; i<=5; i++) { if(i <= fullStars) { starsHtml += '<i data-lucide="star" style="width: 13px; height: 13px; fill: currentColor;"></i>'; } else if(i === fullStars + 1 && hasHalf) { starsHtml += '<i data-lucide="star-half" style="width: 13px; height: 13px; fill: currentColor;"></i>'; } else { starsHtml += '<i data-lucide="star" style="width: 13px; height: 13px;"></i>'; } } return starsHtml; } // ------------------------------------------------------------- // RENDER: PRODUCT DETAIL VIEW // ------------------------------------------------------------- let selectedColorIndex = 0; let selectedSizeIndex = 0; let quantityToBuy = 1; function renderDetailView() { const product = PRODUCTS.find(p => p.id === STATE.selectedProductId); const container = document.getElementById('detail-view-container'); if (!product || !container) return; // Reset temporary states selectedColorIndex = 0; selectedSizeIndex = 0; quantityToBuy = 1; let colorsHtml = ''; product.colors.forEach((col, idx) => { colorsHtml += ` <div class="color-option ${idx === 0 ? 'active' : ''}" style="background-color: ${col.value}" title="${col.name}" onclick="selectDetailColor(this, ${idx})"> </div> `; }); let sizesHtml = ''; product.sizes.forEach((sz, idx) => { sizesHtml += ` <div class="size-option ${idx === 0 ? 'active' : ''}" onclick="selectDetailSize(this, ${idx})"> ${sz} </div> `; }); let thumbsHtml = ''; product.images.forEach((img, idx) => { thumbsHtml += ` <div class="thumb ${idx === 0 ? 'active' : ''}" onclick="swapDetailMainImage(this, '${img}')"> <img src="${img}" alt="thumbnail"> </div> `; }); let specsHtml = ''; product.specs.forEach(s => { specsHtml += ` <tr style="border-bottom: 1px solid var(--gray-100);"> <td style="padding: 10px 0; font-weight: 600; width: 150px; font-size: 13.5px; color: var(--dark);">${s.label}</td> <td style="padding: 10px 0; font-size: 13.5px; color: var(--dark-muted);">${s.value}</td> </tr> `; }); let reviewsHtml = ''; product.reviews.forEach(r => { reviewsHtml += ` <div class="review-item"> <div class="review-header"> <span class="reviewer-name">${r.name}</span> <div style="display: flex; gap: 10px; align-items: center;"> <div class="stars">${renderStars(r.rating)}</div> <span class="review-date">${r.date}</span> </div> </div> <p class="review-comment">${r.comment}</p> </div> `; }); container.innerHTML = ` <div class="detail-back-btn" onclick="navigateTo('home')"> <i data-lucide="arrow-left" style="width: 18px; height: 18px;"></i> <span>กลับหน้าหลัก / หมวดหมู่${product.categoryTh}</span> </div> <div class="detail-container"> <!-- 1. Left image column --> <div class="gallery-wrapper"> <div class="gallery-main-container"> <img id="detail-main-img" src="${product.images[0]}" alt="${product.name}" class="gallery-main"> </div> <div class="gallery-thumbs"> ${thumbsHtml} </div> </div> <!-- 2. Right product info column --> <div class="detail-info"> <span class="detail-category">${product.categoryTh}</span> <h1 class="detail-title">${product.name}</h1> <div class="detail-meta"> <div class="detail-rating"> <div class="stars" style="color: var(--warning); display: flex;">${renderStars(product.rating)}</div> <span><b>${product.rating}</b></span> </div> <span>|</span> <span>รีวิว ${product.reviewsCount} รายการ</span> <span>|</span> <span>ขายแล้ว ${product.soldCount} ชิ้น</span> </div> <div class="detail-prices"> <span class="detail-price-actual">฿${product.price.toLocaleString()}</span> <span class="detail-price-original">฿${product.originalPrice.toLocaleString()}</span> <span class="detail-discount-badge">ลดพิเศษ ${product.discount}%</span> </div> <p class="detail-desc">${product.description}</p> <!-- Color variants selector --> <div class="option-group"> <div class="option-label">เลือกสี: <span id="selected-color-name" style="font-weight: 500; color: var(--primary);">${product.colors[0].name}</span></div> <div class="color-selector"> ${colorsHtml} </div> </div> <!-- Size variants selector --> <div class="option-group"> <div class="option-label">เลือกตัวเลือก/ขนาด:</div> <div class="size-selector"> ${sizesHtml} </div> </div> <!-- Quantity & Add buttons --> <div class="detail-actions-row"> <div class="quantity-adjuster"> <button onclick="changeDetailQty(-1)"><i data-lucide="minus" style="width: 14px; height: 14px;"></i></button> <input id="detail-qty-input" type="text" class="quantity-input" value="1" readonly> <button onclick="changeDetailQty(1)"><i data-lucide="plus" style="width: 14px; height: 14px;"></i></button> </div> <button class="btn-detail-add" onclick="addProductToCart(false)"> <i data-lucide="shopping-cart" style="width: 16px; height: 16px;"></i> ใส่ตะกร้า </button> <button class="btn-detail-buy" onclick="addProductToCart(true)"> ซื้อเลย </button> </div> </div> </div> <!-- 3. Specifications & Reviews tabs --> <div class="detail-tabs-container"> <div class="detail-tab-headers"> <button class="detail-tab-btn active" onclick="switchDetailTab(this, 'specs-panel')">ข้อมูลจำเพาะสินค้า</button> <button class="detail-tab-btn" onclick="switchDetailTab(this, 'reviews-panel')">รีวิวจากผู้ซื้อ (${product.reviewsCount})</button> </div> <div id="specs-panel" class="tab-content-panel active"> <table style="width: 100%; border-collapse: collapse;"> <tbody> ${specsHtml} </tbody> </table> </div> <div id="reviews-panel" class="tab-content-panel"> <div class="reviews-list"> ${reviewsHtml} </div> </div> </div> `; lucide.createIcons(); } window.swapDetailMainImage = function(thumbEl, imgSrc) { document.querySelectorAll('.thumb').forEach(t => t.classList.remove('active')); thumbEl.classList.add('active'); document.getElementById('detail-main-img').src = imgSrc; }; window.selectDetailColor = function(colorEl, idx) { const product = PRODUCTS.find(p => p.id === STATE.selectedProductId); if (!product) return; document.querySelectorAll('.color-option').forEach(c => c.classList.remove('active')); colorEl.classList.add('active'); selectedColorIndex = idx; document.getElementById('selected-color-name').textContent = product.colors[idx].name; }; window.selectDetailSize = function(sizeEl, idx) { document.querySelectorAll('.size-option').forEach(s => s.classList.remove('active')); sizeEl.classList.add('active'); selectedSizeIndex = idx; }; window.changeDetailQty = function(change) { const input = document.getElementById('detail-qty-input'); let val = parseInt(input.value) + change; if (val < 1) val = 1; if (val > 10) { val = 10; showToast('สั่งได้สูงสุด 10 ชิ้นต่อครั้ง', 'warning'); } input.value = val; quantityToBuy = val; }; window.switchDetailTab = function(btnEl, panelId) { document.querySelectorAll('.detail-tab-btn').forEach(b => b.classList.remove('active')); btnEl.classList.add('active'); document.querySelectorAll('.tab-content-panel').forEach(p => p.classList.remove('active')); document.getElementById(panelId).classList.add('active'); }; window.addProductToCart = function(instantBuy) { // Check authentication first if (!STATE.isLoggedIn) { showToast('กรุณาเข้าสู่ระบบก่อนหยิบสินค้าใส่ตะกร้า', 'warning'); openAuthModal(); return; } const product = PRODUCTS.find(p => p.id === STATE.selectedProductId); if (!product) return; const selColor = product.colors[selectedColorIndex].name; const selSize = product.sizes[selectedSizeIndex]; // Add to State cart addToCartState(product.id, selColor, selSize, quantityToBuy); if (instantBuy) { navigateTo('cart'); } else { showToast(`เพิ่ม ${product.name} ลงในตะกร้าแล้ว!`, 'success'); } }; // ------------------------------------------------------------- // CART STATE OPERATIONS & VIEWS // ------------------------------------------------------------- function quickAdd(productId) { // Check authentication if (!STATE.isLoggedIn) { showToast('กรุณาเข้าสู่ระบบก่อนหยิบสินค้าใส่ตะกร้า', 'warning'); openAuthModal(); return; } const product = PRODUCTS.find(p => p.id === productId); if (!product) return; const dColor = product.colors[0].name; const dSize = product.sizes[0]; addToCartState(productId, dColor, dSize, 1); showToast(`เพิ่ม ${product.name} ลงในตะกร้าแล้ว!`, 'success'); } function addToCartState(productId, color, size, qty) { const existing = STATE.cart.find(item => item.productId === productId && item.color === color && item.size === size ); if (existing) { existing.quantity += qty; } else { STATE.cart.push({ productId, color, size, quantity: qty }); } updateBadges(); } function updateBadges() { const cartCount = document.getElementById('cart-count'); const favCount = document.getElementById('wishlist-count'); const totalItems = STATE.cart.reduce((sum, item) => sum + item.quantity, 0); if (cartCount) { cartCount.textContent = totalItems; cartCount.style.display = totalItems > 0 ? 'flex' : 'none'; } if (favCount) { favCount.textContent = STATE.wishlist.length; favCount.style.display = STATE.wishlist.length > 0 ? 'flex' : 'none'; } } // ------------------------------------------------------------- // RENDER: SHOPPING CART VIEW // ------------------------------------------------------------- function renderCartView() { const container = document.getElementById('cart-view-container'); if (!container) return; if (STATE.cart.length === 0) { container.innerHTML = ` <div class="cart-empty-state"> <div class="cart-empty-icon"> <i data-lucide="shopping-cart"></i> </div> <h2>ตะกร้าสินค้าว่างเปล่า</h2> <p>เลือกซื้อสินค้าดีๆ มากมายจากโปรโมชันโดนใจได้เลยตอนนี้</p> <button class="btn-cart-shop" onclick="navigateTo('home')">กลับไปเลือกซื้อสินค้า</button> </div> `; lucide.createIcons(); return; } let itemsHtml = ''; let subtotal = 0; STATE.cart.forEach((item, idx) => { const prod = PRODUCTS.find(p => p.id === item.productId); if (!prod) return; const rowSubtotal = prod.price * item.quantity; subtotal += rowSubtotal; itemsHtml += ` <div class="cart-item-row"> <div class="cart-product-info"> <img src="${prod.images[0]}" alt="${prod.name}" class="cart-product-img"> <div class="cart-product-details"> <h4 class="cart-product-title" style="cursor: pointer;" onclick="navigateTo('detail', '${prod.id}')">${prod.name}</h4> <span class="cart-product-option">ตัวเลือก: ${item.color} | ${item.size}</span> </div> </div> <div class="cart-item-price">฿${prod.price.toLocaleString()}</div> <div> <div class="quantity-adjuster" style="height: 38px;"> <button onclick="changeCartQty(${idx}, -1)"><i data-lucide="minus" style="width: 12px; height: 12px;"></i></button> <input type="text" class="quantity-input" style="width: 32px;" value="${item.quantity}" readonly> <button onclick="changeCartQty(${idx}, 1)"><i data-lucide="plus" style="width: 12px; height: 12px;"></i></button> </div> </div> <div style="display: flex; align-items: center; justify-content: space-between;"> <span class="cart-item-subtotal">฿${rowSubtotal.toLocaleString()}</span> <button class="btn-cart-remove" onclick="removeCartItem(${idx})"> <i data-lucide="trash-2" style="width: 18px; height: 18px;"></i> </button> </div> </div> `; }); // Calculate coupon discount let discount = 0; if (STATE.couponApplied) { if (subtotal >= STATE.couponApplied.minSpend) { discount = Math.round(subtotal * (STATE.couponApplied.discountPercent / 100)); } else { // Spend less than requirement, revoke coupon STATE.couponApplied = null; showToast('ยอดสั่งซื้อต่ำกว่าเกณฑ์การใช้คูปอง', 'warning'); } } const shipping = subtotal > 1000 ? 0 : 50; // free shipping above 1000 baht const total = subtotal - discount + shipping; container.innerHTML = ` <div class="cart-layout"> <!-- 1. Left cart list items panel --> <div class="cart-items-panel"> <div class="cart-table-header"> <span>สินค้า</span> <span>ราคาต่อชิ้น</span> <span>จำนวน</span> <span style="text-align: right; padding-right: 18px;">ราคารวม</span> </div> <div class="cart-list"> ${itemsHtml} </div> </div> <!-- 2. Right order totals summary panel --> <div class="cart-summary-panel"> <h3 class="summary-title">สรุปยอดชำระเงิน</h3> <!-- Coupon box code input --> <div class="coupon-input-group"> <input id="coupon-code-input" type="text" class="coupon-input" placeholder="ใส่รหัสคูปอง (เช่น ORANGE10)" value="${STATE.couponApplied ? STATE.couponApplied.code : ''}"> <button class="btn-coupon-apply" onclick="applyCartCoupon()"> ${STATE.couponApplied ? 'ยกเลิก' : 'ใช้โค้ด'} </button> </div> ${STATE.couponApplied ? `<div style="font-size:12px; color: var(--success); font-weight: 600; margin-top:-10px;">ใช้คูปองลด ${STATE.couponApplied.discountPercent}% สำเร็จ!</div>` : ''} <div class="summary-rows"> <div class="summary-row"> <span>ยอดรวมสินค้า</span> <span>฿${subtotal.toLocaleString()}</span> </div> <div class="summary-row discount"> <span>ส่วนลดคูปอง</span> <span>-฿${discount.toLocaleString()}</span> </div> <div class="summary-row"> <span>ค่าจัดส่ง</span> <span>${shipping === 0 ? '<span style="color:var(--success); font-weight:700;">ฟรี</span>' : `฿${shipping.toLocaleString()}`}</span> </div> ${shipping > 0 ? `<div style="font-size:11px; color: var(--gray-500); margin-top:-8px; text-align:right;">ช้อปเพิ่มอีก ฿${(1000 - subtotal).toLocaleString()} เพื่อจัดส่งฟรี!</div>` : ''} <div class="summary-row total"> <span>ยอดสุทธิ</span> <span class="price">฿${total.toLocaleString()}</span> </div> </div> <button class="btn-checkout" onclick="executeCheckout(${total})"> <i data-lucide="shield-check" style="width: 18px; height: 18px;"></i> ดำเนินการชำระเงิน </button> </div> </div> `; lucide.createIcons(); } window.changeCartQty = function(idx, change) { let item = STATE.cart[idx]; if (!item) return; item.quantity += change; if (item.quantity < 1) item.quantity = 1; if (item.quantity > 10) { item.quantity = 10; showToast('สั่งได้สูงสุด 10 ชิ้นต่อครั้ง', 'warning'); } updateBadges(); renderCartView(); }; window.removeCartItem = function(idx) { const removedItem = STATE.cart[idx]; const prod = PRODUCTS.find(p => p.id === removedItem.productId); STATE.cart.splice(idx, 1); updateBadges(); renderCartView(); if(prod) { showToast(`ลบ ${prod.name} ออกจากตะกร้าแล้ว`, 'info'); } }; window.applyCartCoupon = function() { const input = document.getElementById('coupon-code-input'); const val = input.value.trim().toUpperCase(); if (STATE.couponApplied) { // Revoke STATE.couponApplied = null; renderCartView(); showToast('ยกเลิกคูปองส่วนลดแล้ว', 'info'); return; } if (!val) { showToast('กรุณากรอกรหัสคูปอง', 'warning'); return; } // Check user coupons or default active coupons let matched = null; if (STATE.currentUser && STATE.currentUser.coupons) { matched = STATE.currentUser.coupons.find(c => c.code === val); } // Allow static welcome coupon ORANGE10 as well if (!matched && val === 'ORANGE10') { matched = { code: 'ORANGE10', discountPercent: 10, minSpend: 500 }; } if (!matched) { showToast('รหัสคูปองไม่ถูกต้อง หรือคุณไม่มีคูปองนี้', 'danger'); return; } // Check min spend const subtotal = STATE.cart.reduce((sum, item) => { const prod = PRODUCTS.find(p => p.id === item.productId); return sum + (prod ? prod.price * item.quantity : 0); }, 0); if (subtotal < matched.minSpend) { showToast(`คูปองนี้ใช้ได้เมื่อสั่งซื้อครบ ฿${matched.minSpend.toLocaleString()} ขึ้นไป`, 'warning'); return; } STATE.couponApplied = matched; renderCartView(); showToast(`ใช้คูปองลด ${matched.discountPercent}% เรียบร้อย!`, 'success'); }; window.executeCheckout = function(totalPrice) { // Security check if (!STATE.isLoggedIn) { showToast('กรุณาเข้าสู่ระบบก่อนทำการสั่งซื้อสินค้า', 'warning'); openAuthModal(); return; } // Generate order id const ordId = 'ORD-' + Math.floor(100000 + Math.random() * 900000); const dateStr = new Date().toISOString().replace('T', ' ').substring(0, 16); // Construct new order items string summary for Admin List let itemsStrList = []; // Construct new order const newOrderItems = STATE.cart.map(item => { const prod = PRODUCTS.find(p => p.id === item.productId); itemsStrList.push(`${prod.name} (${item.color} / ${item.size}) x${item.quantity}`); return { name: `${prod.name} (${item.color} / ${item.size})`, qty: item.quantity, price: prod.price }; }); const newOrderUser = { id: ordId, date: dateStr, status: 'pending', statusText: 'รอการตรวจสอบและแพ็คของ', items: newOrderItems, total: totalPrice }; // Add to current user's personal order list if (STATE.currentUser.orders) { STATE.currentUser.orders.unshift(newOrderUser); } else { STATE.currentUser.orders = [newOrderUser]; } // Add to Global administrative orders database const globalOrder = { id: ordId, customerName: STATE.currentUser.name, customerEmail: STATE.currentUser.email, date: dateStr, status: 'pending', statusText: 'รอการตรวจสอบและแพ็คของ', items: itemsStrList.join(', '), total: totalPrice }; STATE.allOrders.unshift(globalOrder); // Clear cart STATE.cart = []; STATE.couponApplied = null; updateBadges(); // Show custom modal success openModal( 'success', 'ชำระเงินและสั่งซื้อสำเร็จ!', `รายการสั่งซื้อหมายเลข <b>${ordId}</b> จำนวน ฿${totalPrice.toLocaleString()} บันทึกเรียบร้อยแล้ว ท่านสามารถตรวจสอบสถานะการจัดส่งได้ที่หน้าโปรไฟล์ผู้ใช้` ); } // ------------------------------------------------------------- // RENDER: USER PROFILE VIEWS // ------------------------------------------------------------- function renderProfileView() { const navPanel = document.getElementById('profile-nav-panel'); const contentPanel = document.getElementById('profile-content-panel'); if(!navPanel || !contentPanel || !STATE.currentUser) return; const isAdmin = STATE.currentUser.role === 'admin'; // 1. Render Left navigation buttons navPanel.innerHTML = ` <div class="profile-user-summary"> <img src="${isAdmin ? 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=150&auto=format&fit=crop&q=80' : 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80'}" alt="Avatar" class="profile-user-avatar"> <h4 class="profile-user-name">${STATE.currentUser.name}</h4> <span class="profile-user-role">${isAdmin ? 'ระบบผู้ดูแลระบบ (Admin)' : 'สมาชิกระดับ VIP Member'}</span> </div> <ul class="profile-nav-list"> <li class="profile-nav-item ${STATE.currentProfileTab === 'settings' ? 'active' : ''}"> <button onclick="switchProfileTab('settings')"><i data-lucide="settings" style="width:16px; height:16px;"></i> ตั้งค่าบัญชี</button> </li> <li class="profile-nav-item ${STATE.currentProfileTab === 'orders' ? 'active' : ''}"> <button onclick="switchProfileTab('orders')"><i data-lucide="package" style="width:16px; height:16px;"></i> ประวัติสั่งซื้อ</button> </li> <li class="profile-nav-item ${STATE.currentProfileTab === 'vouchers' ? 'active' : ''}"> <button onclick="switchProfileTab('vouchers')"><i data-lucide="ticket" style="width:16px; height:16px;"></i> คูปองของฉัน</button> </li> <li class="profile-nav-item ${STATE.currentProfileTab === 'addresses' ? 'active' : ''}"> <button onclick="switchProfileTab('addresses')"><i data-lucide="map-pin" style="width:16px; height:16px;"></i> ที่อยู่จัดส่ง</button> </li> ${isAdmin ? ` <hr style="border:none; border-top:1px solid var(--gray-200); margin: 8px 0;"> <li class="profile-nav-item"> <button onclick="navigateTo('admin')" style="color: var(--success); font-weight:700;"><i data-lucide="shield-alert" style="color: var(--success);"></i> ไปหน้าแอดมิน</button> </li>` : ''} </ul> `; // 2. Render right tabs content if (STATE.currentProfileTab === 'settings') { contentPanel.innerHTML = ` <h3 class="profile-tab-title">การตั้งค่าข้อมูลส่วนตัว</h3> <form id="settings-form" onsubmit="saveProfileSettings(event)"> <div class="form-grid"> <div class="form-group"> <label for="prof-name">ชื่อ-นามสกุล</label> <input id="prof-name" type="text" value="${STATE.currentUser.name}" required> </div> <div class="form-group"> <label for="prof-email">อีเมล (ไม่สามารถเปลี่ยนได้)</label> <input id="prof-email" type="email" value="${STATE.currentUser.email}" disabled style="background-color: var(--gray-100); color: var(--gray-500); cursor: not-allowed;"> </div> <div class="form-group"> <label for="prof-phone">เบอร์โทรศัพท์</label> <input id="prof-phone" type="text" value="${STATE.currentUser.phone}" required> </div> <div class="form-group"> <label for="prof-gender">เพศ</label> <select id="prof-gender"> <option value="male" ${STATE.currentUser.gender === 'male' ? 'selected' : ''}>ชาย</option> <option value="female" ${STATE.currentUser.gender === 'female' ? 'selected' : ''}>หญิง</option> <option value="other" ${STATE.currentUser.gender === 'other' ? 'selected' : ''}>อื่นๆ / ไม่ระบุ</option> </select> </div> <div class="form-group"> <label for="prof-birthdate">วันเกิด</label> <input id="prof-birthdate" type="date" value="${STATE.currentUser.birthdate || ''}"> </div> </div> <button type="submit" class="btn-save-form">บันทึกข้อมูลส่วนตัว</button> </form> `; } else if (STATE.currentProfileTab === 'orders') { let ordersCards = ''; const userOrders = STATE.currentUser.orders || []; if (userOrders.length === 0) { ordersCards = ` <div style="text-align: center; padding: 40px 0; color: var(--gray-500);"> <i data-lucide="shopping-bag" style="width: 48px; height: 48px; margin-bottom: 12px; color: var(--gray-300);"></i> <p>ยังไม่มีประวัติการทำรายการคำสั่งซื้อ</p> </div> `; } else { userOrders.forEach(o => { let itemsList = ''; o.items.forEach(it => { itemsList += ` <div class="order-item-summary-row"> <span class="order-item-name-lbl">${it.name}</span> <span class="order-item-qty-lbl">x${it.qty}</span> <span class="order-item-price-lbl">฿${it.price.toLocaleString()}</span> </div> `; }); ordersCards += ` <div class="order-card"> <div class="order-card-header"> <div> <span class="order-id-lbl">หมายเลขคำสั่งซื้อ: ${o.id}</span> <span class="order-date-lbl"> | วันที่ทำรายการ: ${o.date}</span> </div> <span class="order-status-badge ${o.status}">${o.statusText || o.status}</span> </div> <div class="order-card-body"> <div class="order-items-summary"> ${itemsList} </div> </div> <div class="order-card-footer"> <div class="order-total-lbl">ยอดชำระเงินทั้งหมด: <span>฿${o.total.toLocaleString()}</span></div> <button class="btn-order-detail" onclick="showToast('สถานะพัสดุ: ${o.statusText}', 'info')"> ตรวจสอบพัสดุ </button> </div> </div> `; }); } contentPanel.innerHTML = ` <h3 class="profile-tab-title">ประวัติคำสั่งซื้อทั้งหมด</h3> <div class="order-list"> ${ordersCards} </div> `; } else if (STATE.currentProfileTab === 'vouchers') { let couponCards = ''; const userCoupons = STATE.currentUser.coupons || []; if (userCoupons.length === 0) { couponCards = ` <div style="grid-column: span 2; text-align: center; padding: 40px 0; color: var(--gray-500);"> <i data-lucide="ticket" style="width: 48px; height: 48px; margin-bottom: 12px; color: var(--gray-300);"></i> <p>ไม่มีคูปองส่วนลดที่ใช้งานได้ในขณะนี้</p> </div> `; } else { userCoupons.forEach(c => { couponCards += ` <div class="coupon-card"> <div class="coupon-info"> <span class="coupon-code">${c.code}</span> <span class="coupon-desc">${c.desc}</span> <span class="coupon-expiry">${c.expiry}</span> </div> <button class="btn-coupon-copy" onclick="copyCouponCode('${c.code}')"> คัดลอกรหัส </button> </div> `; }); } contentPanel.innerHTML = ` <h3 class="profile-tab-title">คูปองส่วนลดของฉัน</h3> <div class="coupons-grid"> ${couponCards} </div> `; } else if (STATE.currentProfileTab === 'addresses') { let addressesHtml = ''; const userAddresses = STATE.currentUser.addresses || []; if (userAddresses.length === 0) { addressesHtml = ` <div style="grid-column: span 2; text-align: center; padding: 30px 0; color: var(--gray-500);"> <p>ยังไม่มีการบันทึกที่อยู่จัดส่งสินค้า</p> </div> `; } else { userAddresses.forEach(a => { addressesHtml += ` <div class="address-card ${a.isDefault ? 'default' : ''}"> <div class="address-tag">${a.tag}</div> <div class="address-name">${a.name}</div> <div class="address-detail">${a.detail}</div> <div class="address-phone"><i data-lucide="phone" style="width: 12px; height:12px; display:inline-block; vertical-align:middle; margin-right:4px;"></i> ${a.phone}</div> <div class="address-actions"> ${!a.isDefault ? `<button class="btn-address-action" onclick="setDefaultAddress('${a.id}')">ตั้งเป็นที่อยู่หลัก</button>` : '<span style="font-size:12px; color:var(--primary); font-weight:700;">ที่อยู่จัดส่งหลัก</span>'} <button class="btn-address-action" onclick="showToast('แก้ไขข้อมูลที่อยู่ได้เร็วๆ นี้', 'warning')">แก้ไข</button> </div> </div> `; }); } contentPanel.innerHTML = ` <h3 class="profile-tab-title">สมุดที่อยู่จัดส่ง</h3> <div class="address-grid"> ${addressesHtml} </div> <button class="btn-save-form" style="margin-top:20px; display:flex; align-items:center; gap:8px;" onclick="showToast('ฟังก์ชันเพิ่มที่อยู่ใหม่เปิดให้ใช้งานในระบบโปรแกรมจริงเร็วๆ นี้', 'info')"> <i data-lucide="plus" style="width:16px; height: 16px;"></i> เพิ่มที่อยู่จัดส่งใหม่ </button> `; } lucide.createIcons(); } window.switchProfileTab = function(tabName) { STATE.currentProfileTab = tabName; renderProfileView(); }; window.saveProfileSettings = function(e) { e.preventDefault(); if (!STATE.currentUser) return; STATE.currentUser.name = document.getElementById('prof-name').value; STATE.currentUser.phone = document.getElementById('prof-phone').value; STATE.currentUser.gender = document.getElementById('prof-gender').value; STATE.currentUser.birthdate = document.getElementById('prof-birthdate').value; // Update local list matching user object const matchInDb = STATE.users.find(u => u.email === STATE.currentUser.email); if(matchInDb) { matchInDb.name = STATE.currentUser.name; matchInDb.phone = STATE.currentUser.phone; matchInDb.gender = STATE.currentUser.gender; matchInDb.birthdate = STATE.currentUser.birthdate; } updateHeaderAuthUI(); showToast('บันทึกข้อมูลส่วนตัวเรียบร้อยแล้ว!', 'success'); renderProfileView(); }; window.copyCouponCode = function(code) { navigator.clipboard.writeText(code).then(() => { showToast(`คัดลอกรหัสคูปอง ${code} เรียบร้อย! นำไปใส่ในช่องรหัสลดได้เลย`, 'success'); }).catch(err => { showToast('เกิดข้อผิดพลาดในการคัดลอกรหัส', 'danger'); }); }; window.setDefaultAddress = function(id) { if (!STATE.currentUser || !STATE.currentUser.addresses) return; STATE.currentUser.addresses.forEach(a => { a.isDefault = a.id === id; }); showToast('ตั้งเป็นที่อยู่จัดส่งหลักสำเร็จ', 'success'); renderProfileView(); }; // ------------------------------------------------------------- // WISHLIST STATE & RENDERING // ------------------------------------------------------------- function toggleWishlist(productId) { if (!STATE.isLoggedIn) { showToast('กรุณาเข้าสู่ระบบก่อนบันทึกรายการสินค้าโปรด', 'warning'); openAuthModal(); return; } const idx = STATE.wishlist.indexOf(productId); const prod = PRODUCTS.find(p => p.id === productId); if (idx === -1) { STATE.wishlist.push(productId); showToast(`เพิ่ม ${prod.name} ไปยังรายการโปรดแล้ว`, 'success'); } else { STATE.wishlist.splice(idx, 1); showToast(`ลบ ${prod.name} ออกจากรายการโปรดแล้ว`, 'info'); } updateBadges(); // Re-render views to reflect active class states if (STATE.activeView === 'home') { renderHomeView(); } else if (STATE.activeView === 'wishlist') { renderWishlistView(); } } function renderWishlistView() { const grid = document.getElementById('wishlist-grid'); if (!grid) return; if (STATE.wishlist.length === 0) { grid.innerHTML = ` <div class="wishlist-empty" style="grid-column: span 5;"> <div class="wishlist-empty-icon"> <i data-lucide="heart"></i> </div> <h2>ยังไม่มีรายการโปรด</h2> <p>กดไอคอนหัวใจที่สินค้าที่คุณสนใจ เพื่อบันทึกมาเก็บไว้ดูภายหลัง</p> <button class="btn-cart-shop" onclick="navigateTo('home')">กลับไปเลือกดูสินค้า</button> </div> `; lucide.createIcons(); return; } let html = ''; STATE.wishlist.forEach(id => { const p = PRODUCTS.find(prod => prod.id === id); if(!p) return; const isFav = 'active'; html += ` <div class="product-card"> <div class="card-media" onclick="navigateTo('detail', '${p.id}')"> <div class="card-badges"> <span class="badge-discount">-${p.discount}%</span> </div> <img src="${p.images[0]}" alt="${p.name}" class="product-img"> </div> <button class="btn-wishlist ${isFav}" onclick="event.stopPropagation(); toggleWishlist('${p.id}')"> <i data-lucide="heart" style="width: 18px; height: 18px; fill: #ef4444;"></i> </button> <div class="card-details" onclick="navigateTo('detail', '${p.id}')"> <span class="product-category-lbl">${p.categoryTh}</span> <h3 class="product-title-lbl">${p.name}</h3> <div class="product-rating-row"> <div class="stars">${renderStars(p.rating)}</div> <span>(${p.reviewsCount})</span> <span class="sold-count">ขายแล้ว ${p.soldCount} ชิ้น</span> </div> <div class="price-row"> <span class="price-actual">฿${p.price.toLocaleString()}</span> <span class="price-original">฿${p.originalPrice.toLocaleString()}</span> </div> <button class="btn-add-to-cart" onclick="event.stopPropagation(); quickAdd('${p.id}')"> <i data-lucide="shopping-bag" style="width: 14px; height: 14px;"></i> เพิ่มลงตะกร้า </button> </div> </div> `; }); grid.innerHTML = html; lucide.createIcons(); } // ------------------------------------------------------------- // ADMIN DASHBOARD CORE CONTROLLERS // ------------------------------------------------------------- function renderAdminView() { const adminNav = document.getElementById('admin-nav-panel'); const adminContent = document.getElementById('admin-content-panel'); if (!adminNav || !adminContent) return; // 1. Render Left Admin Nav Menu adminNav.innerHTML = ` <div class="admin-profile-summary"> <div class="admin-badge-indicator"><i data-lucide="shield-check"></i> Admin Mode</div> <h4 style="font-size:15px; font-weight:700; margin-top:8px;">${STATE.currentUser.name}</h4> </div> <ul class="admin-nav-list" style="list-style: none; display: flex; flex-direction: column; gap: 6px; padding: 12px 0;"> <li class="profile-nav-item ${STATE.currentAdminTab === 'dashboard' ? 'active' : ''}"> <button onclick="switchAdminTab('dashboard')"><i data-lucide="trending-up"></i> สรุปผลประกอบการ</button> </li> <li class="profile-nav-item ${STATE.currentAdminTab === 'orders' ? 'active' : ''}"> <button onclick="switchAdminTab('orders')"><i data-lucide="shopping-cart"></i> จัดการคำสั่งซื้อ (${STATE.allOrders.length})</button> </li> <li class="profile-nav-item ${STATE.currentAdminTab === 'products' ? 'active' : ''}"> <button onclick="switchAdminTab('products')"><i data-lucide="boxes"></i> จัดการสินค้าคลัง</button> </li> <li class="profile-nav-item ${STATE.currentAdminTab === 'users' ? 'active' : ''}"> <button onclick="switchAdminTab('users')"><i data-lucide="users"></i> ผู้ใช้งานระบบ (${STATE.users.length})</button> </li> <hr style="border:none; border-top:1px solid var(--gray-200); margin: 8px 0;"> <li class="profile-nav-item"> <button onclick="navigateTo('home')" style="color: var(--primary); font-weight:700;"><i data-lucide="external-link" style="color: var(--primary);"></i> กลับหน้าหลักร้านค้า</button> </li> </ul> `; // 2. Render sub-panel content based on current tab state if (STATE.currentAdminTab === 'dashboard') { renderAdminDashboard(adminContent); } else if (STATE.currentAdminTab === 'orders') { renderAdminOrders(adminContent); } else if (STATE.currentAdminTab === 'products') { renderAdminProducts(adminContent); } else if (STATE.currentAdminTab === 'users') { renderAdminUsers(adminContent); } lucide.createIcons(); } window.switchAdminTab = function(tabName) { STATE.currentAdminTab = tabName; renderAdminView(); }; function renderAdminDashboard(container) { // Compute total sales revenue from completed or paid orders const completedOrders = STATE.allOrders.filter(o => o.status === 'completed' || o.status === 'shipping' || o.status === 'pending'); const totalRevenue = completedOrders.reduce((sum, o) => sum + o.total, 0); // Count stats const totalOrdersCount = STATE.allOrders.length; const totalUsersCount = STATE.users.length; const totalProductsCount = PRODUCTS.length; container.innerHTML = ` <h3 class="admin-panel-title">ภาพรวมระบบร้านค้า UFA 888</h3> <!-- Summary Stats Card Grid --> <div class="admin-stats-grid"> <div class="stat-card orange-grad"> <div class="stat-label">ยอดขายสะสมสุทธิ</div> <div class="stat-value">฿${totalRevenue.toLocaleString()}</div> <div class="stat-desc">คำนวณจากคำสั่งซื้อที่ทำรายการทั้งหมด</div> <i class="stat-bg-icon" data-lucide="dollar-sign"></i> </div> <div class="stat-card"> <div class="stat-label">จำนวนคำสั่งซื้อรวม</div> <div class="stat-value">${totalOrdersCount} รายการ</div> <div class="stat-desc">รายการที่มีการชำระเงินเข้ามาสำเร็จ</div> <i class="stat-bg-icon" data-lucide="shopping-cart"></i> </div> <div class="stat-card"> <div class="stat-label">สมาชิกที่สมัครเข้าใช้งาน</div> <div class="stat-value">${totalUsersCount} บัญชี</div> <div class="stat-desc">ลูกค้าที่ลงทะเบียนในฐานข้อมูล</div> <i class="stat-bg-icon" data-lucide="users"></i> </div> <div class="stat-card"> <div class="stat-label">จำนวนสินค้าทั้งหมด</div> <div class="stat-value">${totalProductsCount} ชิ้น</div> <div class="stat-desc">รายการที่มีสถานะวางจำหน่ายหน้าร้าน</div> <i class="stat-bg-icon" data-lucide="box"></i> </div> </div> <!-- Graph placeholder aesthetic design layout --> <div style="margin-top: 30px; background-color: var(--light); padding: 24px; border-radius: var(--radius-md); border:1px solid var(--gray-200);"> <h4 style="font-size:15px; font-weight:700; margin-bottom:12px; display:flex; align-items:center; gap:8px;"> <i data-lucide="activity" style="color:var(--primary);"></i> กราฟรายงานผลการจำหน่ายสินค้าประจำเดือนนี้ </h4> <div style="height: 180px; display:flex; align-items:flex-end; justify-content:space-between; padding:20px 20px 0 20px; border-bottom:2px solid var(--gray-300);"> <div style="width: 8%; height: 25%; background: var(--primary); border-radius:4px 4px 0 0; text-align:center; color:white; font-size:11px;" title="สัปดาห์ 1: 25,000">W1</div> <div style="width: 8%; height: 45%; background: var(--primary); border-radius:4px 4px 0 0; text-align:center; color:white; font-size:11px;" title="สัปดาห์ 2: 45,000">W2</div> <div style="width: 8%; height: 80%; background: linear-gradient(0deg, var(--primary), #ec9f05); border-radius:4px 4px 0 0; text-align:center; color:white; font-size:11px;" title="สัปดาห์ 3: 80,000">W3</div> <div style="width: 8%; height: 95%; background: linear-gradient(0deg, var(--primary), #ec9f05); border-radius:4px 4px 0 0; text-align:center; color:white; font-size:11px;" title="สัปดาห์ 4: 95,000">W4</div> </div> <p style="font-size:12px; color: var(--gray-500); text-align:center; margin-top:8px;">ยอดขายปรับตัวขึ้นสูงสุดถึง +32% ในสัปดาห์นี้เนื่องจากแคมเปญโปรโมชันโค้ด ORANGE10</p> </div> `; } function renderAdminOrders(container) { let tableRows = ''; if(STATE.allOrders.length === 0) { tableRows = ` <tr> <td colspan="6" style="text-align:center; padding: 30px; color:var(--gray-500);">ยังไม่มีการชำระคำสั่งซื้อสินค้าในระบบ</td> </tr> `; } else { STATE.allOrders.forEach(o => { tableRows += ` <tr style="border-bottom:1px solid var(--gray-200);"> <td style="padding:12px; font-weight:600; font-size:13.5px;">${o.id}</td> <td style="padding:12px; font-size:13.5px;"> <div><b>${o.customerName}</b></div> <div style="font-size:11px; color:var(--gray-500);">${o.customerEmail}</div> </td> <td style="padding:12px; font-size:12px; color:var(--gray-500); max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${o.items}">${o.items}</td> <td style="padding:12px; font-size:13.5px; font-weight:700; color:var(--primary);">฿${o.total.toLocaleString()}</td> <td style="padding:12px;"> <select onchange="updateOrderStatus('${o.id}', this.value)" style="border: 1px solid var(--gray-300); border-radius:4px; padding:4px 8px; font-size:12.5px; font-weight:600;" class="${o.status}"> <option value="pending" ${o.status === 'pending' ? 'selected' : ''}>รอการตรวจสอบ</option> <option value="shipping" ${o.status === 'shipping' ? 'selected' : ''}>กำลังจัดส่ง</option> <option value="completed" ${o.status === 'completed' ? 'selected' : ''}>จัดส่งสำเร็จ</option> <option value="cancelled" ${o.status === 'cancelled' ? 'selected' : ''}>ยกเลิกคำสั่งซื้อ</option> </select> </td> </tr> `; }); } container.innerHTML = ` <h3 class="admin-panel-title">ระบบจัดการคำสั่งซื้อสินค้าลูกค้า</h3> <p style="font-size:13px; color:var(--gray-500); margin-bottom: 20px; margin-top:-10px;">ผู้ดูแลระบบสามารถตรวจสอบ แก้ไข สถานะการรับสินค้าและคำสั่งซื้อได้ที่นี่</p> <div style="overflow-x:auto;"> <table style="width:100%; border-collapse:collapse; border:1px solid var(--gray-200); text-align:left;"> <thead> <tr style="background-color:var(--gray-100); border-bottom:2px solid var(--gray-300);"> <th style="padding:12px; font-size:13px; font-weight:700; color:var(--dark-muted);">รหัสคำสั่งซื้อ</th> <th style="padding:12px; font-size:13px; font-weight:700; color:var(--dark-muted);">ข้อมูลลูกค้า</th> <th style="padding:12px; font-size:13px; font-weight:700; color:var(--dark-muted);">รายละเอียดสินค้า</th> <th style="padding:12px; font-size:13px; font-weight:700; color:var(--dark-muted);">ราคาสุทธิ</th> <th style="padding:12px; font-size:13px; font-weight:700; color:var(--dark-muted);">ปรับปรุงสถานะ</th> </tr> </thead> <tbody> ${tableRows} </tbody> </table> </div> `; } window.updateOrderStatus = function(orderId, newStatus) { // 1. Update in administrative orders database const matchedGlobal = STATE.allOrders.find(o => o.id === orderId); if(matchedGlobal) { matchedGlobal.status = newStatus; if(newStatus === 'pending') matchedGlobal.statusText = 'รอการตรวจสอบและแพ็คของ'; else if(newStatus === 'shipping') matchedGlobal.statusText = 'กำลังจัดส่งพัสดุ'; else if(newStatus === 'completed') matchedGlobal.statusText = 'จัดส่งสำเร็จแล้ว'; else if(newStatus === 'cancelled') matchedGlobal.statusText = 'คำสั่งซื้อยกเลิก'; } // 2. Find and update order status inside customer's personal data file STATE.users.forEach(u => { if(u.orders) { const matchedUserOrder = u.orders.find(o => o.id === orderId); if (matchedUserOrder) { matchedUserOrder.status = newStatus; if(newStatus === 'pending') matchedUserOrder.statusText = 'รอการตรวจสอบและแพ็คของ'; else if(newStatus === 'shipping') matchedUserOrder.statusText = 'กำลังจัดส่งพัสดุ'; else if(newStatus === 'completed') matchedUserOrder.statusText = 'จัดส่งสำเร็จแล้ว'; else if(newStatus === 'cancelled') matchedUserOrder.statusText = 'คำสั่งซื้อยกเลิก'; } } }); showToast(`อัปเดตสถานะคำสั่งซื้อหมายเลข ${orderId} สำเร็จ`, 'success'); renderAdminView(); }; function renderAdminProducts(container) { let prodRows = ''; PRODUCTS.forEach((p, index) => { prodRows += ` <tr style="border-bottom:1px solid var(--gray-200);"> <td style="padding:10px; font-size:13.5px; font-weight:600;">${p.id}</td> <td style="padding:10px; font-size:13.5px; display:flex; align-items:center; gap:8px;"> <img src="${p.images[0]}" alt="${p.name}" style="width:36px; height:36px; object-fit:cover; border-radius:4px; border:1px solid var(--gray-200);"> <span style="max-width:180px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${p.name}">${p.name}</span> </td> <td style="padding:10px; font-size:13px; color:var(--primary); font-weight:600;">${p.categoryTh}</td> <td style="padding:10px;"> <input type="number" value="${p.price}" onchange="editAdminProductPrice('${p.id}', this.value)" style="width:90px; border:1px solid var(--gray-300); border-radius:4px; padding:4px 6px; font-size:13px; font-weight:700;"> </td> <td style="padding:10px;"> <input type="number" value="${p.stock}" onchange="editAdminProductStock('${p.id}', this.value)" style="width:70px; border:1px solid var(--gray-300); border-radius:4px; padding:4px 6px; font-size:13px;"> </td> <td style="padding:10px;"> <button class="btn-address-action" onclick="deleteAdminProduct(${index})" style="color:var(--danger);"><i data-lucide="trash-2" style="width:16px; height:16px;"></i> ลบ</button> </td> </tr> `; }); container.innerHTML = ` <h3 class="admin-panel-title">ระบบจัดการสินค้าคลัง (Stock Control)</h3> <div style="display:grid; grid-template-columns: 2fr 1fr; gap:24px; align-items: flex-start;"> <!-- Products Stock Table --> <div style="overflow-x:auto; background-color:var(--white); border:1px solid var(--gray-200); border-radius:var(--radius-md); padding:16px;"> <table style="width:100%; border-collapse:collapse; text-align:left;"> <thead> <tr style="border-bottom:2px solid var(--gray-300);"> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">รหัสสินค้า</th> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">รายการสินค้า</th> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">หมวดหมู่</th> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">ราคา (บาท)</th> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">คลัง</th> <th style="padding:10px; font-size:13px; color:var(--dark-muted);">จัดการ</th> </tr> </thead> <tbody> ${prodRows} </tbody> </table> </div> <!-- Add New Product Form --> <div style="background-color:var(--white); border:1px solid var(--gray-200); border-radius:var(--radius-md); padding:20px;"> <h4 style="font-size:15px; font-weight:700; margin-bottom:14px; border-bottom:1px solid var(--gray-200); padding-bottom:8px;">เพิ่มสินค้าใหม่เข้าระบบ</h4> <form id="admin-add-product-form" onsubmit="handleAdminAddProduct(event)"> <div style="display:flex; flex-direction:column; gap:12px;"> <div class="form-group"> <label for="ap-name" style="font-size:12px;">ชื่อสินค้า</label> <input id="ap-name" type="text" placeholder="ชื่อสินค้าตัวอย่าง..." required style="padding:6px 10px; font-size:13px;"> </div> <div class="form-group"> <label for="ap-category" style="font-size:12px;">หมวดหมู่สินค้า</label> <select id="ap-category" style="padding:6px 10px; font-size:13px;"> <option value="electronics">อุปกรณ์อิเล็กทรอนิกส์</option> <option value="fashion">เสื้อผ้าแฟชั่น</option> <option value="cosmetics">เครื่องสำอาง</option> <option value="home">ของใช้ภายในบ้าน</option> <option value="food">อาหารและเครื่องดื่ม</option> <option value="sports">กีฬาและสุขภาพ</option> </select> </div> <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;"> <div class="form-group"> <label for="ap-price" style="font-size:12px;">ราคาตั้งขาย (฿)</label> <input id="ap-price" type="number" required style="padding:6px 10px; font-size:13px;"> </div> <div class="form-group"> <label for="ap-stock" style="font-size:12px;">จำนวนในคลัง</label> <input id="ap-stock" type="number" required style="padding:6px 10px; font-size:13px;"> </div> </div> <div class="form-group"> <label for="ap-desc" style="font-size:12px;">คำอธิบายรายละเอียด</label> <textarea id="ap-desc" rows="3" placeholder="รายละเอียดผลิตภัณฑ์..." style="padding:6px 10px; font-size:13px; resize:none;"></textarea> </div> <div class="form-group"> <label for="ap-img" style="font-size:12px;">ลิงก์รูปภาพสินค้า (URL)</label> <input id="ap-img" type="text" placeholder="https://images.unsplash.com..." value="https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&auto=format&fit=crop&q=80" style="padding:6px 10px; font-size:12px;"> </div> <button type="submit" class="btn-save-form" style="width:100%; padding:10px 0; font-size:13px;">เพิ่มสินค้าลงคลังสินค้า</button> </div> </form> </div> </div> `; } window.editAdminProductPrice = function(id, val) { const prod = PRODUCTS.find(p => p.id === id); if(prod) { prod.price = parseInt(val) || 0; showToast(`แก้ไขราคาสินค้า ${prod.name} เป็น ฿${prod.price.toLocaleString()} สำเร็จ`, 'success'); } }; window.editAdminProductStock = function(id, val) { const prod = PRODUCTS.find(p => p.id === id); if(prod) { prod.stock = parseInt(val) || 0; showToast(`แก้ไขสต็อกสินค้า ${prod.name} เป็น ${prod.stock} ชิ้น สำเร็จ`, 'success'); } }; window.deleteAdminProduct = function(index) { const deleted = PRODUCTS[index]; if(deleted) { PRODUCTS.splice(index, 1); showToast(`ลบสินค้า ${deleted.name} ออกจากระบบแล้ว`, 'info'); renderAdminView(); } }; window.handleAdminAddProduct = function(e) { e.preventDefault(); const name = document.getElementById('ap-name').value; const cat = document.getElementById('ap-category').value; const price = parseInt(document.getElementById('ap-price').value) || 0; const stock = parseInt(document.getElementById('ap-stock').value) || 0; const desc = document.getElementById('ap-desc').value; const img = document.getElementById('ap-img').value; // Convert key label Th let categoryTh = 'สินค้าทั่วไป'; if(cat === 'electronics') categoryTh = 'อุปกรณ์อิเล็กทรอนิกส์'; else if(cat === 'fashion') categoryTh = 'เสื้อผ้าแฟชั่น'; else if(cat === 'cosmetics') categoryTh = 'เครื่องสำอาง'; else if(cat === 'home') categoryTh = 'ของใช้ภายในบ้าน'; else if(cat === 'food') categoryTh = 'อาหารและเครื่องดื่ม'; else if(cat === 'sports') categoryTh = 'กีฬาและสุขภาพ'; const id = 'ap-' + Math.floor(1000 + Math.random() * 9000); const newProd = { id, name, category: cat, categoryTh, description: desc || 'ไม่มีรายละเอียดเพิ่มเติมสำหรับสินค้านี้', price, originalPrice: Math.round(price * 1.3), discount: 23, rating: 5.0, reviewsCount: 0, soldCount: 0, stock, isFlashSale: false, images: [img], colors: [{ name: 'Standard', value: '#d1d5db' }], sizes: ['Free size'], specs: [{ label: 'สถานะ', value: 'สินค้าเพิ่มโดยผู้ดูแลระบบ' }], reviews: [] }; PRODUCTS.unshift(newProd); showToast(`เพิ่มสินค้าใหม่ ${name} เข้าสู่ระบบสำเร็จแล้ว`, 'success'); // Clear forms document.getElementById('admin-add-product-form').reset(); // Refresh grids renderAdminView(); }; function renderAdminUsers(container) { let userRows = ''; STATE.users.forEach(u => { const isAdmin = u.role === 'admin'; userRows += ` <tr style="border-bottom:1px solid var(--gray-200);"> <td style="padding:12px; font-size:13.5px; font-weight:600; display:flex; align-items:center; gap:8px;"> <img src="${isAdmin ? 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=80&auto=format&fit=crop&q=80' : 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=80&auto=format&fit=crop&q=80'}" style="width:28px; height:28px; border-radius:50%; object-fit:cover;"> <span>${u.name}</span> </td> <td style="padding:12px; font-size:13.5px;">${u.email}</td> <td style="padding:12px; font-size:13px; color:var(--gray-500);">${u.phone}</td> <td style="padding:12px; font-size:12px; color:var(--gray-500);">${u.regDate}</td> <td style="padding:12px;"> <span style="font-size:11px; font-weight:700; text-transform:uppercase; padding:4px 8px; border-radius:12px; ${isAdmin ? 'background-color:#dbeafe; color:#2563eb;' : 'background-color:#f3f4f6; color:var(--dark-muted);'}"> ${u.role} </span> </td> </tr> `; }); container.innerHTML = ` <h3 class="admin-panel-title">รายชื่อผู้ใช้งานลงทะเบียนระบบ (User Directory)</h3> <p style="font-size:13px; color:var(--gray-500); margin-bottom: 20px; margin-top:-10px;">ฐานข้อมูลลูกค้าที่ลงทะเบียนเข้าใช้งานทั้งหมดบนระบบ UFA 888</p> <div style="overflow-x:auto;"> <table style="width:100%; border-collapse:collapse; border:1px solid var(--gray-200); text-align:left; background-color:var(--white);"> <thead> <tr style="background-color:var(--gray-100); border-bottom:2px solid var(--gray-300);"> <th style="padding:12px; font-size:13px; color:var(--dark-muted);">ชื่อผู้ใช้</th> <th style="padding:12px; font-size:13px; color:var(--dark-muted);">อีเมล</th> <th style="padding:12px; font-size:13px; color:var(--dark-muted);">เบอร์โทรศัพท์</th> <th style="padding:12px; font-size:13px; color:var(--dark-muted);">วันที่ลงทะเบียน</th> <th style="padding:12px; font-size:13px; color:var(--dark-muted);">สิทธิ์การใช้งาน</th> </tr> </thead> <tbody> ${userRows} </tbody> </table> </div> `; } // ------------------------------------------------------------- // TOAST NOTIFICATIONS Engine // ------------------------------------------------------------- function showToast(msg, type = 'info') { const container = document.getElementById('toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; let iconName = 'info'; if (type === 'success') iconName = 'check-circle-2'; if (type === 'warning') iconName = 'alert-triangle'; if (type === 'danger') iconName = 'x-circle'; toast.innerHTML = ` <i class="toast-icon" data-lucide="${iconName}"></i> <span class="toast-msg">${msg}</span> `; container.appendChild(toast); lucide.createIcons(); setTimeout(() => { toast.classList.add('show'); }, 10); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { toast.remove(); }, 400); }, 3500); } // ------------------------------------------------------------- // CUSTOM MODAL Engine // ------------------------------------------------------------- function openModal(type, title, desc) { const modal = document.getElementById('modal'); const iconWrapper = document.getElementById('modal-icon-wrapper'); const modalTitle = document.getElementById('modal-title'); const modalDesc = document.getElementById('modal-desc'); if(!modal) return; iconWrapper.className = `modal-icon ${type}`; let iconName = 'check-circle-2'; if (type === 'success') iconName = 'check-circle-2'; iconWrapper.innerHTML = `<i data-lucide="${iconName}" style="width: 32px; height: 32px;"></i>`; modalTitle.innerHTML = title; modalDesc.innerHTML = desc; modal.classList.add('show'); lucide.createIcons(); } function closeModal() { const modal = document.getElementById('modal'); if(modal) modal.classList.remove('show'); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.27 |
proxy
|
phpinfo
|
Settings