File manager - Edit - /home/webapp69.cm.in.th/u69319090001/Shop 001/index.php
Back
<?php /** * หน้าแรกของระบบจองสินค้าออนไลน์ (Online Pre-order System) * ออกแบบด้วย Bootstrap 5 ในธีมสีแดงสดใส (Bright Red) ผสมผสานกับความมินิมอล */ session_start(); // สร้าง CSRF Token สำหรับความปลอดภัยของฟอร์ม if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } require_once 'db.php'; // ดึงข้อมูลสินค้าทั้งหมดและตัวเลือกสินค้า try { $stmt = $pdo->query("SELECT * FROM products ORDER BY id ASC"); $products = $stmt->fetchAll(); $stmt_v = $pdo->query("SELECT * FROM product_variants ORDER BY product_id, id ASC"); $all_variants = $stmt_v->fetchAll(); $variants_by_product = []; foreach ($all_variants as $v) { $variants_by_product[$v['product_id']][] = $v; } // ดึงหมวดหมู่ทั้งหมดสำหรับแท็บกรอง $categories_in_store = []; foreach ($products as $p) { $cat = !empty($p['category']) ? trim($p['category']) : 'ทั่วไป'; if (!in_array($cat, $categories_in_store)) { $categories_in_store[] = $cat; } } } catch (PDOException $e) { $products = []; $variants_by_product = []; $categories_in_store = []; $error_msg = "ไม่สามารถเชื่อมต่อฐานข้อมูลได้: " . $e->getMessage(); } ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pre-order System - ระบบจองสินค้าออนไลน์</title> <!-- Google Fonts: Noto Sans Thai & Inter --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Noto+Sans+Thai:wght@300;400;500;700&display=swap" rel="stylesheet"> <!-- Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- FontAwesome for Icons --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> :root { --primary-red: #e61e2b; /* แดงสดใส */ --hover-red: #c1101b; --light-red: #ffebeb; --dark-gray: #2d3748; --light-gray: #f7fafc; --border-color: #edf2f7; --font-family: 'Noto Sans Thai', 'Inter', sans-serif; } body { font-family: var(--font-family); background-color: #fcfcfc; color: var(--dark-gray); } /* Navigation Style */ .navbar { border-bottom: 3px solid var(--primary-red); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); background-color: #fff !important; } .navbar-brand { font-weight: 700; color: var(--primary-red) !important; font-size: 1.5rem; letter-spacing: -0.5px; } .navbar-brand i { margin-right: 8px; } /* Hero Banner */ .hero-section { background: linear-gradient(135deg, var(--primary-red) 0%, #ff5252 100%); color: white; padding: 4rem 1rem; text-align: center; border-radius: 0 0 2rem 2rem; margin-bottom: 3rem; box-shadow: 0 10px 20px rgba(230, 30, 43, 0.15); } .hero-title { font-weight: 700; font-size: 2.5rem; margin-bottom: 1rem; } .hero-subtitle { font-weight: 300; font-size: 1.1rem; opacity: 0.9; } /* Product Card Styles */ .product-card { background: #fff; border: 1px solid var(--border-color); border-radius: 16px; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02); height: 100%; display: flex; flex-direction: column; position: relative; } .product-card:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.08); border-color: rgba(230, 30, 43, 0.2); } .image-container { position: relative; background-color: var(--light-gray); height: 250px; display: flex; align-items: center; justify-content: center; overflow: hidden; } .product-image { max-height: 100%; max-width: 100%; object-fit: cover; transition: transform 0.5s ease; } .product-card:hover .product-image { transform: scale(1.05); } .price-badge { position: absolute; bottom: 15px; right: 15px; background-color: var(--primary-red); color: #fff; padding: 6px 14px; border-radius: 20px; font-weight: 700; box-shadow: 0 4px 6px rgba(230, 30, 43, 0.25); z-index: 2; } .product-body { padding: 1.5rem; display: flex; flex-direction: column; flex-grow: 1; } .product-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--dark-gray); } .product-desc { font-size: 0.9rem; color: #718096; margin-bottom: 1.25rem; line-height: 1.6; flex-grow: 1; } /* Video Section Inside Card */ .video-box { border-radius: 12px; overflow: hidden; background-color: #000; margin-bottom: 1.25rem; border: 1px solid var(--border-color); } /* Buttons & Forms */ .btn-red { background-color: var(--primary-red); color: #fff; font-weight: 600; border: none; padding: 10px 24px; border-radius: 10px; transition: all 0.2s; } .btn-red:hover, .btn-red:focus { background-color: var(--hover-red); color: #fff; box-shadow: 0 4px 12px rgba(230, 30, 43, 0.3); } .btn-outline-red { border: 2px solid var(--primary-red); color: var(--primary-red); font-weight: 600; background: transparent; padding: 8px 20px; border-radius: 10px; transition: all 0.2s; } .btn-outline-red:hover { background-color: var(--primary-red); color: white; } /* Modern Variant Chips */ .variant-chip { display: inline-block; border: 1px solid var(--border-color); background-color: #fff; color: var(--dark-gray); font-size: 0.8rem !important; font-weight: 500; border-radius: 8px; padding: 6px 12px; cursor: pointer; transition: all 0.2s ease; user-select: none; } .variant-chip:hover:not(.disabled) { border-color: var(--primary-red) !important; color: var(--primary-red) !important; background-color: var(--light-red) !important; } .variant-chip.active { border-color: var(--primary-red) !important; background-color: var(--primary-red) !important; color: #fff !important; box-shadow: 0 4px 10px rgba(230, 30, 43, 0.2); } .variant-chip.disabled { background-color: #f3f4f6 !important; border-color: #e5e7eb !important; color: #9ca3af !important; cursor: not-allowed; text-decoration: line-through; } /* Category Filter Buttons */ .category-filter-btn { border-radius: 20px; padding: 8px 18px; font-size: 0.9rem; font-weight: 600; color: #4a5568; background-color: #fff; border: 1px solid #e2e8f0; transition: all 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.02); cursor: pointer; } .category-filter-btn:hover { border-color: var(--primary-red); color: var(--primary-red); background-color: var(--light-red); } .category-filter-btn.active { background-color: var(--primary-red); color: #fff; border-color: var(--primary-red); box-shadow: 0 4px 10px rgba(230, 30, 43, 0.25); } .product-category-tag { display: inline-flex; align-items: center; font-size: 0.75rem; font-weight: 600; color: var(--primary-red); background-color: var(--light-red); padding: 3px 10px; border-radius: 12px; margin-bottom: 0.5rem; } /* Modern Quantity Selector */ .quantity-ctrl { display: flex; align-items: center; max-width: 130px; border: 1px solid var(--border-color); border-radius: 8px; overflow: hidden; background: #fff; } .quantity-ctrl button { background: none; border: none; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--dark-gray); font-weight: bold; transition: background 0.2s; } .quantity-ctrl button:hover:not(:disabled) { background-color: var(--light-gray); } .quantity-ctrl button:disabled { color: #cbd5e0; cursor: not-allowed; } .quantity-ctrl input { width: 42px; height: 32px; text-align: center; border: none; border-left: 1px solid var(--border-color); border-right: 1px solid var(--border-color); font-weight: 600; font-size: 0.9rem; color: var(--dark-gray); } .quantity-ctrl input:focus { outline: none; } /* Floating Cart Button */ .floating-cart { position: fixed; bottom: 30px; right: 30px; width: 60px; height: 60px; background: linear-gradient(135deg, var(--primary-red) 0%, #ff5252 100%); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 8px 24px rgba(230, 30, 43, 0.35); z-index: 1040; cursor: pointer; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); border: none; } .floating-cart:hover { transform: scale(1.1) translateY(-3px); box-shadow: 0 12px 30px rgba(230, 30, 43, 0.45); color: #fff; } .floating-cart i { font-size: 1.4rem; } .floating-cart .badge { position: absolute; top: -3px; right: -3px; background-color: #fff; color: var(--primary-red); font-size: 0.75rem; min-width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; box-shadow: 0 2px 5px rgba(0,0,0,0.2); padding: 2px; border: 2px solid var(--primary-red); } /* Cart Drawer Style */ .cart-item-card { border: 1px solid var(--border-color); border-radius: 12px; padding: 12px; margin-bottom: 12px; background: #fff; transition: all 0.2s ease; } .cart-item-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.03); border-color: rgba(230, 30, 43, 0.15); } .cart-item-img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; background-color: var(--light-gray); } /* Modal Custom Style */ .modal-content { border-radius: 20px; border: none; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); } .modal-header { background-color: var(--light-red); border-bottom: 1px solid #fed7d7; border-top-left-radius: 20px; border-top-right-radius: 20px; color: var(--primary-red); } .modal-title { font-weight: 700; } .form-label { font-weight: 600; color: var(--dark-gray); font-size: 0.95rem; } .form-control, .form-select { border-radius: 8px; padding: 10px 14px; border: 1px solid #cbd5e0; } .form-control:focus, .form-select:focus { border-color: var(--primary-red); box-shadow: 0 0 0 3px rgba(230, 30, 43, 0.15); } .stock-badge { font-size: 0.85rem; padding: 4px 10px; border-radius: 6px; font-weight: 500; } /* Toast & Alert Styling */ .alert-preorder { border-radius: 12px; font-weight: 500; } </style> </head> <body> <!-- Navbar --> <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top"> <div class="container"> <a class="navbar-brand" href="index.php"> <i class="fa-solid fa-cart-shopping-fast"></i> <span class="text-dark">RED</span>SHOP <span class="badge bg-danger fs-6">Pre-order</span> </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavbar"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="mainNavbar"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active fw-bold text-danger" href="index.php"><i class="fa-solid fa-store me-1"></i> สินค้าเปิดจอง</a> </li> <?php if (isset($_SESSION['user_id'])): ?> <li class="nav-item"> <a class="nav-link text-dark" href="my_orders.php"><i class="fa-solid fa-receipt me-1"></i> ประวัติการสั่งจองของฉัน</a> </li> <?php endif; ?> </ul> <div class="d-flex align-items-center gap-2"> <?php if (isset($_SESSION['user_id'])): ?> <div class="dropdown"> <button class="btn btn-outline-danger dropdown-toggle btn-sm d-flex align-items-center gap-2" type="button" data-bs-toggle="dropdown"> <i class="fa-solid fa-circle-user"></i> <span><?php echo htmlspecialchars($_SESSION['user_name']); ?></span> </button> <ul class="dropdown-menu dropdown-menu-end shadow-sm"> <li><h6 class="dropdown-header"><i class="fa-solid fa-phone me-1"></i> <?php echo htmlspecialchars($_SESSION['user_phone']); ?></h6></li> <li><a class="dropdown-item" href="my_orders.php"><i class="fa-solid fa-receipt me-2"></i> ประวัติการสั่งจอง</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item text-danger" href="logout.php"><i class="fa-solid fa-right-from-bracket me-2"></i> ออกจากระบบ</a></li> </ul> </div> <?php else: ?> <a href="login.php" class="btn btn-outline-danger btn-sm"><i class="fa-solid fa-right-to-bracket me-1"></i> เข้าสู่ระบบ</a> <a href="register.php" class="btn btn-red btn-sm"><i class="fa-solid fa-user-plus me-1"></i> สมัครสมาชิก</a> <?php endif; ?> <a href="admin.php" class="btn btn-outline-secondary btn-sm ms-2"><i class="fa-solid fa-user-gear me-1"></i> Admin</a> </div> </div> </div> </nav> <!-- Hero Banner --> <div class="container"> <div class="hero-section"> <h1 class="hero-title">ระบบสั่งจองสินค้าล่วงหน้าออนไลน์</h1> <p class="hero-subtitle">จองสินค้าคุณภาพเยี่ยมก่อนใคร มั่นใจในสินค้าลิขสิทธิ์แท้ 100%</p> </div> </div> <!-- Main Content --> <div class="container mb-5"> <?php if (isset($_SESSION['success_msg'])): ?> <div class="alert alert-success alert-dismissible fade show alert-preorder mb-4" role="alert"> <i class="fa-solid fa-circle-check me-2"></i> <?php echo $_SESSION['success_msg']; unset($_SESSION['success_msg']); ?> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> <script> localStorage.removeItem('redshop_cart'); </script> <?php endif; ?> <?php if (isset($_SESSION['error_msg'])): ?> <div class="alert alert-danger alert-dismissible fade show alert-preorder mb-4" role="alert"> <i class="fa-solid fa-circle-exclamation me-2"></i> <?php echo $_SESSION['error_msg']; unset($_SESSION['error_msg']); ?> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> <?php endif; ?> <?php if (empty($products)): ?> <div class="text-center py-5"> <i class="fa-regular fa-folder-open fa-3x text-muted mb-3"></i> <h4 class="text-muted">ไม่พบข้อมูลสินค้าชั่วคราว</h4> <p class="text-muted">โปรดกลับมาตรวจสอบใหม่อีกครั้งในภายหลัง</p> </div> <?php else: ?> <!-- Category Filter Tabs (หน้าบ้าน) --> <?php // รวมหมวดหมู่ที่มีสินค้า + หมวดหมู่แนะนำ $default_categories = ['เสื้อผ้าและเครื่องแต่งกาย', 'กระเป๋าและอุปกรณ์', 'ของสะสมและของที่ระลึก', 'เครื่องเขียนและอุปกรณ์การเรียน', 'ทั่วไป']; $all_display_categories = array_unique(array_merge($categories_in_store, $default_categories)); ?> <div class="category-filter-wrapper mb-4"> <div class="d-flex flex-wrap gap-2 justify-content-center"> <button type="button" class="category-filter-btn active" onclick="filterCategory('all', this)"> <i class="fa-solid fa-border-all me-1"></i> สินค้าทั้งหมด (<?php echo count($products); ?>) </button> <?php foreach ($all_display_categories as $cat_name): ?> <?php $count_in_cat = 0; foreach ($products as $p) { if (($p['category'] ?? 'ทั่วไป') === $cat_name) { $count_in_cat++; } } ?> <button type="button" class="category-filter-btn" onclick="filterCategory('<?php echo htmlspecialchars($cat_name); ?>', this)"> <i class="fa-solid fa-tag me-1"></i> <?php echo htmlspecialchars($cat_name); ?> <?php if ($count_in_cat > 0): ?> <span class="badge rounded-pill bg-light text-dark ms-1"><?php echo $count_in_cat; ?></span> <?php endif; ?> </button> <?php endforeach; ?> </div> </div> <div class="row g-4" id="products-grid"> <?php foreach ($products as $product): ?> <?php $prod_category = !empty($product['category']) ? $product['category'] : 'ทั่วไป'; // ตรวจสอบช่วงเวลาจอง $now = new DateTime(); $start = !empty($product['start_time']) ? new DateTime($product['start_time']) : null; $end = !empty($product['end_time']) ? new DateTime($product['end_time']) : null; $is_active = true; $status_badge = ''; $button_text = 'จองสินค้าล่วงหน้า'; $button_disabled = ''; if ($start && $now < $start) { $is_active = false; $status_badge = '<span class="badge bg-warning text-dark position-absolute top-0 start-0 m-3 z-3 shadow-sm"><i class="fa-solid fa-clock me-1"></i> เริ่มเปิดจอง ' . $start->format('d/m H:i') . '</span>'; $button_text = 'ยังไม่เปิดรับจอง'; $button_disabled = 'disabled'; } elseif ($end && $now > $end) { $is_active = false; $status_badge = '<span class="badge bg-secondary position-absolute top-0 start-0 m-3 z-3 shadow-sm"><i class="fa-solid fa-ban me-1"></i> ปิดรับจองแล้ว</span>'; $button_text = 'ปิดรับจองแล้ว'; $button_disabled = 'disabled'; } else { $status_badge = '<span class="badge bg-success position-absolute top-0 start-0 m-3 z-3 shadow-sm"><i class="fa-solid fa-fire me-1"></i> กำลังเปิดจอง</span>'; } ?> <div class="col-12 col-md-6 col-lg-4 product-item-col" data-category="<?php echo htmlspecialchars($prod_category); ?>"> <div class="product-card"> <!-- Image Container --> <div class="image-container"> <?php echo $status_badge; ?> <?php $img_src = !empty($product['image_url']) ? $product['image_url'] : 'assets/images/default.png'; $is_valid_img = !empty($product['image_url']) && (filter_var($product['image_url'], FILTER_VALIDATE_URL) || file_exists($product['image_url'])); ?> <?php if ($is_valid_img): ?> <img src="<?php echo htmlspecialchars($img_src); ?>" onerror="this.onerror=null; this.src='assets/images/default.png';" alt="<?php echo htmlspecialchars($product['name']); ?>" class="product-image"> <?php else: ?> <img src="assets/images/default.png" alt="<?php echo htmlspecialchars($product['name']); ?>" class="product-image" style="object-fit: contain; padding: 15px; background-color: #f8f9fa;"> <?php endif; ?> <div class="price-badge">฿<?php echo number_format($product['price'], 2); ?></div> </div> <!-- Card Body --> <div class="product-body"> <div> <span class="product-category-tag"> <i class="fa-solid fa-layer-group me-1"></i><?php echo htmlspecialchars($prod_category); ?> </span> </div> <h3 class="product-title"><?php echo htmlspecialchars($product['name']); ?></h3> <p class="product-desc"><?php echo htmlspecialchars($product['description']); ?></p> <!-- Video Review Section (แสดงเฉพาะเมื่อมีการระบุลิงก์วิดีโอจริง) --> <?php $has_video = !empty($product['video_url']) && trim($product['video_url']) !== '' && strpos($product['video_url'], 'dQw4w9WgXcQ') === false; ?> <?php if ($has_video): ?> <div class="video-box"> <?php if (strpos($product['video_url'], 'youtube.com') !== false || strpos($product['video_url'], 'youtu.be') !== false): ?> <!-- YouTube Embed --> <div class="ratio ratio-16x9"> <iframe src="<?php echo htmlspecialchars($product['video_url']); ?>" title="Video Review" allowfullscreen></iframe> </div> <?php else: ?> <!-- HTML5 Video --> <video src="<?php echo htmlspecialchars($product['video_url']); ?>" controls class="w-100"></video> <?php endif; ?> </div> <?php endif; ?> <!-- Booking Chips & Cart Actions --> <?php $product_variants = isset($variants_by_product[$product['id']]) ? $variants_by_product[$product['id']] : []; ?> <div class="mb-3"> <label class="form-label d-block mb-1" style="font-size: 0.9rem;">เลือกไซส์ / ตัวเลือก:</label> <div class="d-flex flex-wrap gap-2" id="variant-selector-<?php echo $product['id']; ?>"> <?php if (empty($product_variants)): ?> <span class="text-muted small">ไม่มีตัวเลือกสินค้า</span> <?php else: ?> <?php foreach ($product_variants as $v): ?> <?php $disabled_class = ''; $stock_desc = ''; $is_unlimited = (int)$v['is_unlimited'] === 1; $stock_qty = (int)$v['stock']; if (!$is_unlimited && $stock_qty <= 0) { $disabled_class = 'disabled'; $stock_desc = ' (หมด)'; } else if ($is_unlimited) { $stock_desc = ' (Made-to-Order)'; } else { $stock_desc = " ({$stock_qty})"; } ?> <button type="button" class="variant-chip <?php echo $disabled_class; ?>" data-variant-id="<?php echo $v['id']; ?>" data-variant-name="<?php echo htmlspecialchars($v['variant_name']); ?>" data-stock="<?php echo $stock_qty; ?>" data-unlimited="<?php echo $v['is_unlimited']; ?>" onclick="selectCardVariant(this, <?php echo $product['id']; ?>)" <?php echo ($disabled_class ? 'disabled' : ''); ?>> <?php echo htmlspecialchars($v['variant_name']) . $stock_desc; ?> </button> <?php endforeach; ?> <?php endif; ?> </div> </div> <div class="d-flex align-items-center justify-content-between mb-2"> <span class="form-label mb-0" style="font-size: 0.9rem;">จำนวน:</span> <div class="quantity-ctrl" id="qty-ctrl-<?php echo $product['id']; ?>"> <button type="button" onclick="adjustCardQty(<?php echo $product['id']; ?>, -1)" disabled>-</button> <input type="number" id="qty-input-<?php echo $product['id']; ?>" value="1" min="1" disabled readonly> <button type="button" onclick="adjustCardQty(<?php echo $product['id']; ?>, 1)" disabled>+</button> </div> </div> <div class="text-muted small mb-3 text-end" id="stock-info-<?php echo $product['id']; ?>" style="font-size: 0.8rem;"> กรุณาเลือกตัวเลือกสินค้า </div> <div class="row g-2 mt-auto"> <div class="col-6"> <button type="button" class="btn btn-outline-red w-100 btn-add-cart d-flex align-items-center justify-content-center gap-1 py-2" id="btn-cart-<?php echo $product['id']; ?>" data-active="<?php echo $is_active ? '1' : '0'; ?>" onclick="addCardToCart(<?php echo $product['id']; ?>, '<?php echo htmlspecialchars($product['name']); ?>', <?php echo $product['price']; ?>, '<?php echo htmlspecialchars($product['image_url']); ?>')" disabled> <i class="fa-solid fa-cart-plus"></i> <span style="font-size: 0.9rem;">ใส่ตะกร้า</span> </button> </div> <div class="col-6"> <button type="button" class="btn btn-red w-100 btn-buy-now d-flex align-items-center justify-content-center gap-1 py-2" id="btn-buy-<?php echo $product['id']; ?>" data-active="<?php echo $is_active ? '1' : '0'; ?>" onclick="buyCardNow(<?php echo $product['id']; ?>, '<?php echo htmlspecialchars($product['name']); ?>', <?php echo $product['price']; ?>, '<?php echo htmlspecialchars($product['image_url']); ?>')" disabled> <i class="fa-solid fa-bolt"></i> <span style="font-size: 0.9rem;">ซื้อทันที</span> </button> </div> </div> </div> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> <!-- Booking Modal --> <div class="modal fade" id="preorderModal" tabindex="-1" aria-labelledby="preorderModalLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="preorderModalLabel"><i class="fa-solid fa-cart-plus me-2"></i>รายละเอียดการจองสินค้า</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <!-- เปลี่ยนเป็น enctype="multipart/form-data" เพื่อรองรับการอัปโหลดสลิป --> <form action="process_booking.php" method="POST" id="bookingForm" enctype="multipart/form-data"> <div class="modal-body"> <!-- CSRF Token --> <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>"> <!-- Hidden input for JSON Cart Items --> <input type="hidden" name="cart_items" id="modal_cart_items"> <!-- 1. Single Item Checkout Fields --> <div id="single_item_checkout_fields"> <input type="hidden" name="product_id" id="modal_product_id"> <div class="mb-3"> <label class="form-label">สินค้าที่เลือก</label> <input type="text" class="form-control bg-light" id="modal_product_name" readonly> </div> <!-- Dynamic Variants (Sizes/Colors) --> <div class="mb-3"> <label for="variant_select" class="form-label">เลือกไซส์ / ตัวเลือกสินค้า <span class="text-danger">*</span></label> <select class="form-select" id="variant_select" name="variant_id"> <option value="">-- โปรดเลือกตัวเลือก --</option> </select> <div class="form-text text-danger d-none" id="stock_alert"> <i class="fa-solid fa-exclamation-triangle"></i> ตัวเลือกนี้สินค้าหมด </div> </div> </div> <!-- 2. Multi-Item Cart Checkout Fields --> <div id="cart_checkout_fields" class="d-none"> <label class="form-label">รายการสินค้าในรถเข็น</label> <div class="list-group mb-3 border rounded overflow-hidden" id="checkout_cart_items_list" style="max-height: 250px; overflow-y: auto;"> <!-- Cart items list will be populated here --> </div> </div> <!-- 3. Customer Details --> <div class="mb-3"> <label for="customer_name" class="form-label">ชื่อ-นามสกุล <span class="text-danger">*</span></label> <input type="text" class="form-control" id="customer_name" name="customer_name" value="<?php echo isset($_SESSION['user_name']) ? htmlspecialchars($_SESSION['user_name']) : ''; ?>" required placeholder="ระบุชื่อเพื่อการติดต่อและรับสินค้า"> </div> <div class="mb-3"> <label for="customer_phone" class="form-label">เบอร์โทรศัพท์ <span class="text-danger">*</span></label> <input type="tel" class="form-control" id="customer_phone" name="customer_phone" value="<?php echo isset($_SESSION['user_phone']) ? htmlspecialchars($_SESSION['user_phone']) : ''; ?>" pattern="[0-9]{9,10}" required placeholder="เช่น 0891234567"> <div class="form-text">กรอกเฉพาะตัวเลข 9-10 หลัก</div> </div> <!-- ช่องทางการชำระเงิน & อัปโหลดสลิป --> <div class="mb-3"> <label for="payment_method" class="form-label">ช่องทางการชำระเงิน <span class="text-danger">*</span></label> <select class="form-select" id="payment_method" name="payment_method" required onchange="handlePaymentMethodChange()"> <option value="Bank Transfer">🏦 โอนเงินผ่านบัญชีธนาคาร (SCB)</option> <option value="PromptPay">📱 PromptPay / พร้อมเพย์</option> </select> </div> <!-- แสดงข้อมูลธนาคาร (SCB) --> <div class="alert alert-info py-2 shadow-sm" style="font-size: 0.85rem;" id="bank_details"> <strong>บัญชีธนาคารสำหรับโอนเงิน:</strong><br> <i class="fa-solid fa-building-columns"></i> ธนาคารไทยพาณิชย์ (SCB) <br> <i class="fa-solid fa-money-check"></i> เลขที่บัญชี: <strong>123-4-56789-0</strong> <br> <i class="fa-solid fa-user"></i> ชื่อบัญชี: <strong>บจก. เรดช็อป ออนไลน์ (REDSHOP Co., Ltd.)</strong> </div> <!-- PromptPay QR Code section --> <div id="promptpay_section" class="d-none"> <!-- Countdown Timer --> <div id="qr_countdown_wrapper" class="text-center mb-3" style="display:none !important;"> <div class="alert alert-warning py-2 px-3 d-flex align-items-center justify-content-between" style="border-radius:12px;"> <div> <i class="fa-solid fa-clock me-1 text-warning"></i> <strong>QR Code หมดอายุใน</strong> </div> <div class="ms-2"> <span id="countdown_display" class="fs-5 fw-bold text-danger">10:00</span> </div> </div> </div> <!-- QR Code Display --> <div class="text-center mb-3"> <div class="card border-0 shadow-sm mx-auto" style="max-width: 320px; border-radius: 16px; overflow: hidden; background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%);"> <div class="card-body p-3"> <div class="bg-white rounded p-3"> <div style="font-size: 0.7rem; text-align: center; color: #1a1a2e; font-weight: 700; margin-bottom: 8px;">THAI QR PAYMENT</div> <div class="d-flex justify-content-center align-items-center mb-2" style="gap: 6px;"> <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/PromptPay_logo.svg/200px-PromptPay_logo.svg.png" alt="PromptPay" style="height:24px; object-fit:contain;" onerror="this.style.display='none'"> </div> <div id="qr_code_img_wrapper" class="d-flex justify-content-center"> <div class="d-flex align-items-center justify-content-center" style="width:200px;height:200px;"> <div class="spinner-border text-danger" role="status"><span class="visually-hidden">Loading...</span></div> </div> </div> <div class="mt-2 text-center"> <small class="text-muted" style="font-size:0.72rem;">สแกนด้วยแอปธนาคาร หรือ TrueMoney Wallet</small> </div> <div class="mt-1 text-center"> <span class="badge bg-danger" style="font-size:0.7rem;" id="qr_amount_badge">กรุณายืนยันยอดเงิน</span> </div> </div> <div class="text-white text-center mt-2" style="font-size:0.72rem; opacity:0.9;"> <i class="fa-solid fa-shield-halved me-1"></i>ปลอดภัย · มั่นใจทุกการจ่าย </div> </div> </div> </div> <!-- Countdown Timer (visible below QR) --> <div id="qr_timer_box" class="text-center mb-2 d-none"> <div class="d-inline-flex align-items-center gap-2 px-3 py-2 rounded-pill" style="background: #fff3cd; border: 1px solid #ffc107;"> <i class="fa-solid fa-hourglass-half text-warning"></i> <span class="fw-bold text-dark">QR หมดอายุใน: </span> <span id="countdown_display2" class="fw-bold text-danger fs-6">10:00</span> </div> </div> <div class="alert alert-success py-2 mb-2" style="font-size:0.82rem;"> <i class="fa-solid fa-circle-info me-1"></i> <strong>วิธีชำระเงิน:</strong> สแกน QR ด้วยแอปธนาคาร → โอนเงิน → ถ่ายรูปสลิป → แนบสลิปด้านล่าง </div> </div> <!-- แนบสลิป --> <div class="mb-3"> <label for="slip_image" class="form-label">แนบรูปภาพสลิปโอนเงิน <span class="text-danger">*</span></label> <input type="file" class="form-control" id="slip_image" name="slip_image" accept="image/png, image/jpeg" required> <div class="form-text">เฉพาะไฟล์รูปภาพ PNG, JPG, JPEG ขนาดไม่เกิน 5MB</div> </div> <div class="row"> <div class="col-6 mb-3" id="single_item_qty_block"> <label for="quantity" class="form-label">จำนวน <span class="text-danger">*</span></label> <input type="number" class="form-control" id="quantity" name="quantity" min="1" value="1"> <div class="form-text text-muted" id="max_stock_text">สต็อกคงเหลือ: -</div> </div> <div class="col mb-3 text-end d-flex flex-column justify-content-end"> <span class="text-muted small" id="total_price_label">ราคารวมทั้งสิ้น</span> <h3 class="text-danger font-weight-bold mb-0">฿<span id="total_price_display">0.00</span></h3> </div> </div> </div> <div class="modal-footer border-top-0"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">ยกเลิก</button> <button type="submit" class="btn btn-red" id="submit_booking_btn"> <i class="fa-solid fa-circle-check me-2"></i> ยืนยันการสั่งจอง </button> </div> </form> </div> </div> </div> <!-- Floating Cart Button --> <button class="floating-cart" type="button" data-bs-toggle="offcanvas" data-bs-target="#cartOffcanvas" aria-controls="cartOffcanvas"> <i class="fa-solid fa-cart-shopping"></i> <span class="badge" id="cart-badge-count">0</span> </button> <!-- Shopping Cart Drawer (Offcanvas) --> <div class="offcanvas offcanvas-end" tabindex="-1" id="cartOffcanvas" aria-labelledby="cartOffcanvasLabel" style="width: 400px; max-width: 100vw;"> <div class="offcanvas-header"> <h5 class="offcanvas-title" id="cartOffcanvasLabel"><i class="fa-solid fa-cart-shopping me-2"></i>ตะกร้าสินค้าของคุณ</h5> <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body d-flex flex-column"> <div id="cart-items-container" class="flex-grow-1 overflow-auto"> <!-- Cart items will be loaded dynamically using JS --> <div class="text-center py-5 text-muted"> <i class="fa-solid fa-cart-flatbed fa-3x mb-3"></i> <p>ไม่มีสินค้าในตะกร้า</p> </div> </div> <div class="border-top pt-3 mt-3"> <div class="d-flex justify-content-between mb-3"> <span class="fw-bold">ราคารวมทั้งหมด:</span> <span class="fw-bold text-danger fs-4">฿<span id="cart-grand-total">0.00</span></span> </div> <button type="button" class="btn btn-red w-100 py-2 fs-6 fw-bold" id="btn-checkout-cart" onclick="openCartCheckout()" disabled> <i class="fa-solid fa-credit-card me-2"></i>ดำเนินการชำระเงิน </button> </div> </div> </div> <!-- Footer --> <footer class="bg-white border-top py-4 text-center mt-5"> <div class="container"> <p class="text-muted mb-0">© 2026 REDSHOP Online Pre-order System. All rights reserved.</p> </div> </footer> <!-- Bootstrap 5 Bundle JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script> // State management for each product card const cardStates = {}; function getCardState(productId) { if (!cardStates[productId]) { cardStates[productId] = { variantId: null, variantName: '', stock: 0, isUnlimited: false, quantity: 1 }; } return cardStates[productId]; } // Handle variant selection on a product card function selectCardVariant(button, productId) { const selector = document.getElementById(`variant-selector-${productId}`); // Remove active class from all sibling chips selector.querySelectorAll('.variant-chip').forEach(chip => { chip.classList.remove('active'); }); // Add active class to clicked chip button.classList.add('active'); // Get variant info const vId = button.getAttribute('data-variant-id'); const vName = button.getAttribute('data-variant-name'); const stock = parseInt(button.getAttribute('data-stock')) || 0; const isUnlimited = parseInt(button.getAttribute('data-unlimited')) === 1; // Update state const state = getCardState(productId); state.variantId = vId; state.variantName = vName; state.stock = stock; state.isUnlimited = isUnlimited; state.quantity = 1; // reset quantity to 1 // Update quantity input and controls const qtyInput = document.getElementById(`qty-input-${productId}`); qtyInput.value = 1; qtyInput.disabled = false; const qtyCtrl = document.getElementById(`qty-ctrl-${productId}`); qtyCtrl.querySelectorAll('button').forEach(btn => btn.disabled = false); // Update stock info text const stockInfo = document.getElementById(`stock-info-${productId}`); if (isUnlimited) { stockInfo.innerHTML = '<span class="text-success"><i class="fa-solid fa-infinity"></i> สต็อกคงเหลือ: ไม่จำกัด (Made-to-Order)</span>'; } else { stockInfo.innerHTML = `<span class="text-dark"><i class="fa-solid fa-box"></i> สต็อกคงเหลือ: <strong>${stock}</strong> ชิ้น</span>`; } // Enable action buttons (เฉพาะสินค้าที่อยู่ในช่วงเปิดรับจอง) const btnCart = document.getElementById(`btn-cart-${productId}`); const btnBuy = document.getElementById(`btn-buy-${productId}`); if (btnCart && btnCart.getAttribute('data-active') === '1') { btnCart.disabled = false; } if (btnBuy && btnBuy.getAttribute('data-active') === '1') { btnBuy.disabled = false; } } // Adjust quantity on product card function adjustCardQty(productId, amount) { const state = getCardState(productId); if (!state.variantId) return; let newQty = state.quantity + amount; if (newQty < 1) newQty = 1; if (!state.isUnlimited && newQty > state.stock) { newQty = state.stock; } state.quantity = newQty; document.getElementById(`qty-input-${productId}`).value = newQty; } // Cart logic let cart = []; // Load cart from localStorage function loadCart() { const savedCart = localStorage.getItem('redshop_cart'); if (savedCart) { try { cart = JSON.parse(savedCart); } catch(e) { cart = []; } } updateCartUI(); } // Save cart to localStorage function saveCart() { localStorage.setItem('redshop_cart', JSON.stringify(cart)); updateCartUI(); } // Add item to cart function addCardToCart(productId, name, price, imageUrl) { const state = getCardState(productId); if (!state.variantId) { alert('โปรดเลือกไซส์ / ตัวเลือกสินค้าก่อน'); return; } // Check if item with same variant already in cart const existingIndex = cart.findIndex(item => item.variant_id == state.variantId); if (existingIndex > -1) { let newQty = cart[existingIndex].quantity + state.quantity; if (!state.isUnlimited && newQty > state.stock) { newQty = state.stock; } cart[existingIndex].quantity = newQty; } else { cart.push({ product_id: productId, product_name: name, price: price, image_url: imageUrl, variant_id: state.variantId, variant_name: state.variantName, quantity: state.quantity, stock: state.stock, is_unlimited: state.isUnlimited }); } saveCart(); // Micro-animation / visual cue on floating cart const floatCartBtn = document.querySelector('.floating-cart'); floatCartBtn.classList.add('animate__animated', 'animate__pulse'); setTimeout(() => { floatCartBtn.classList.remove('animate__animated', 'animate__pulse'); }, 1000); // Show offcanvas drawer const cartOffcanvasEl = document.getElementById('cartOffcanvas'); const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(cartOffcanvasEl); bsOffcanvas.show(); } function removeCartItem(index) { cart.splice(index, 1); saveCart(); } function adjustCartItemQty(index, amount) { const item = cart[index]; let newQty = item.quantity + amount; if (newQty < 1) newQty = 1; if (!item.is_unlimited && newQty > item.stock) { newQty = item.stock; } item.quantity = newQty; saveCart(); } function updateCartUI() { const badgeCount = document.getElementById('cart-badge-count'); const itemsContainer = document.getElementById('cart-items-container'); const grandTotalDisplay = document.getElementById('cart-grand-total'); const checkoutBtn = document.getElementById('btn-checkout-cart'); let totalItems = 0; let grandTotal = 0; itemsContainer.innerHTML = ''; if (cart.length === 0) { badgeCount.textContent = '0'; grandTotalDisplay.textContent = '0.00'; checkoutBtn.disabled = true; itemsContainer.innerHTML = ` <div class="text-center py-5 text-muted"> <i class="fa-solid fa-cart-flatbed fa-3x mb-3 text-secondary" style="opacity: 0.5;"></i> <p>ไม่มีสินค้าในตะกร้า</p> </div> `; return; } cart.forEach((item, index) => { totalItems += item.quantity; const subtotal = item.price * item.quantity; grandTotal += subtotal; const itemCard = document.createElement('div'); itemCard.className = 'cart-item-card d-flex gap-3 align-items-center'; const imgTag = item.image_url ? `<img src="${item.image_url}" class="cart-item-img" alt="${item.product_name}">` : `<div class="cart-item-img d-flex align-items-center justify-content-center text-muted"><i class="fa-regular fa-image"></i></div>`; itemCard.innerHTML = ` ${imgTag} <div class="flex-grow-1 min-w-0"> <h6 class="mb-0 text-truncate text-dark fw-bold">${item.product_name}</h6> <small class="text-muted d-block mb-1">ตัวเลือก: ${item.variant_name}</small> <div class="d-flex align-items-center justify-content-between"> <span class="text-danger fw-bold">฿${(item.price * item.quantity).toLocaleString('th-TH', {minimumFractionDigits: 2})}</span> <div class="quantity-ctrl"> <button type="button" onclick="adjustCartItemQty(${index}, -1)">-</button> <input type="number" value="${item.quantity}" readonly> <button type="button" onclick="adjustCartItemQty(${index}, 1)">+</button> </div> </div> </div> <button type="button" class="btn btn-sm btn-link text-danger p-0" onclick="removeCartItem(${index})" style="font-size: 1.1rem;"> <i class="fa-solid fa-trash-can"></i> </button> `; itemsContainer.appendChild(itemCard); }); badgeCount.textContent = totalItems; grandTotalDisplay.textContent = grandTotal.toLocaleString('th-TH', {minimumFractionDigits: 2}); checkoutBtn.disabled = false; } // Checkout Cart (Multi-item) function openCartCheckout() { if (cart.length === 0) return; // Configure modal for Cart mode document.getElementById('single_item_checkout_fields').classList.add('d-none'); document.getElementById('single_item_qty_block').classList.add('d-none'); document.getElementById('cart_checkout_fields').classList.remove('d-none'); // Set variant dropdown as not required document.getElementById('variant_select').required = false; document.getElementById('quantity').required = false; // Put cart data into hidden input document.getElementById('modal_cart_items').value = JSON.stringify(cart); // Populate list of checkout items const checkoutList = document.getElementById('checkout_cart_items_list'); checkoutList.innerHTML = ''; let total = 0; cart.forEach(item => { total += item.price * item.quantity; const li = document.createElement('div'); li.className = 'list-group-item d-flex justify-content-between align-items-center py-2'; li.innerHTML = ` <div> <div class="fw-bold" style="font-size: 0.9rem;">${item.product_name}</div> <small class="text-muted">ตัวเลือก: ${item.variant_name} x ${item.quantity}</small> </div> <span class="text-danger fw-bold" style="font-size: 0.9rem;">฿${(item.price * item.quantity).toLocaleString('th-TH', {minimumFractionDigits: 2})}</span> `; checkoutList.appendChild(li); }); document.getElementById('total_price_label').textContent = 'ราคารวมทั้งหมด'; document.getElementById('total_price_display').textContent = total.toLocaleString('th-TH', {minimumFractionDigits: 2}); // ปลดล็อคปุ่มยืนยันการสั่งจอง const submitBookingBtn = document.getElementById('submit_booking_btn'); if (submitBookingBtn) submitBookingBtn.disabled = false; // ปรับยอด PromptPay QR Code ถ้าเลือก PromptPay อยู่ if (typeof updatePromptPayQR === 'function') { updatePromptPayQR(total); } // ซ่อน Offcanvas ตะกร้าสินค้า และเปิด Modal ชำระเงิน const cartOffcanvasEl = document.getElementById('cartOffcanvas'); const bsOffcanvas = bootstrap.Offcanvas.getInstance(cartOffcanvasEl) || new bootstrap.Offcanvas(cartOffcanvasEl); // รอให้ offcanvas ซ่อนเสร็จเรียบร้อยก่อนเปิด Modal ป้องกัน backdrop ทับกัน cartOffcanvasEl.addEventListener('hidden.bs.offcanvas', function onHidden() { cartOffcanvasEl.removeEventListener('hidden.bs.offcanvas', onHidden); const preorderModalEl = document.getElementById('preorderModal'); const bsModal = bootstrap.Modal.getInstance(preorderModalEl) || new bootstrap.Modal(preorderModalEl); bsModal.show(); }); bsOffcanvas.hide(); } // Buy Now (Single item) function buyCardNow(productId, name, price, imageUrl) { const state = getCardState(productId); if (!state.variantId) { alert('โปรดเลือกไซส์ / ตัวเลือกสินค้าก่อน'); return; } // Configure modal for Single Item Buy Now document.getElementById('single_item_checkout_fields').classList.remove('d-none'); document.getElementById('single_item_qty_block').classList.remove('d-none'); document.getElementById('cart_checkout_fields').classList.add('d-none'); // Set variant dropdown as required const variantSelect = document.getElementById('variant_select'); variantSelect.required = true; document.getElementById('quantity').required = true; // Clear cart JSON hidden input document.getElementById('modal_cart_items').value = ''; // Populate single product fields document.getElementById('modal_product_id').value = productId; document.getElementById('modal_product_name').value = name; // Synchronously construct variant dropdown options based on the card chips variantSelect.innerHTML = '<option value="">-- โปรดเลือกไซส์ / ตัวเลือก --</option>'; const chips = document.querySelectorAll(`#variant-selector-${productId} .variant-chip`); chips.forEach(chip => { const opt = document.createElement('option'); opt.value = chip.getAttribute('data-variant-id'); opt.setAttribute('data-stock', chip.getAttribute('data-stock')); opt.setAttribute('data-unlimited', chip.getAttribute('data-unlimited')); const vName = chip.getAttribute('data-variant-name'); const isUnl = parseInt(chip.getAttribute('data-unlimited')) === 1; const stock = parseInt(chip.getAttribute('data-stock')) || 0; if (isUnl) { opt.textContent = `${vName} (Made-to-Order: ไม่จำกัดสต็อก)`; } else if (stock <= 0) { opt.textContent = `${vName} (สินค้าหมด)`; opt.disabled = true; } else { opt.textContent = `${vName} (คงเหลือ: ${stock} ชิ้น)`; } variantSelect.appendChild(opt); }); // Set selected variant variantSelect.value = state.variantId; // Set quantity const qtyInput = document.getElementById('quantity'); qtyInput.value = state.quantity; qtyInput.disabled = false; // Update stock limits in modal const maxStockText = document.getElementById('max_stock_text'); const stockAlert = document.getElementById('stock_alert'); const submitBookingBtn = document.getElementById('submit_booking_btn'); currentProductPrice = price; selectedVariantStock = state.stock; isUnlimitedVariant = state.isUnlimited; if (state.isUnlimited) { maxStockText.textContent = 'สต็อกคงเหลือ: ไม่จำกัด (Made-to-Order)'; qtyInput.removeAttribute('max'); stockAlert.classList.add('d-none'); submitBookingBtn.disabled = false; } else { maxStockText.textContent = `สต็อกคงเหลือ: ${state.stock} ชิ้น`; qtyInput.max = state.stock; if (state.stock <= 0) { stockAlert.classList.remove('d-none'); submitBookingBtn.disabled = true; } else { stockAlert.classList.add('d-none'); submitBookingBtn.disabled = false; } } // Update total price display document.getElementById('total_price_label').textContent = 'ราคารวมทั้งสิ้น'; const total = price * state.quantity; document.getElementById('total_price_display').textContent = total.toLocaleString('th-TH', {minimumFractionDigits: 2}); // Show preorder modal const preorderModalEl = document.getElementById('preorderModal'); const bsModal = new bootstrap.Modal(preorderModalEl); bsModal.show(); } let currentProductPrice = 0; let selectedVariantStock = 0; let isUnlimitedVariant = false; const preorderModal = document.getElementById('preorderModal'); const variantSelect = document.getElementById('variant_select'); const quantityInput = document.getElementById('quantity'); const totalPriceDisplay = document.getElementById('total_price_display'); const maxStockText = document.getElementById('max_stock_text'); const stockAlert = document.getElementById('stock_alert'); const submitBookingBtn = document.getElementById('submit_booking_btn'); // เมื่อ Modal เปิดขึ้นมาแบบ Manual (ถ้ามี) preorderModal.addEventListener('show.bs.modal', function (event) { const button = event.relatedTarget; if (!button) return; // ข้ามถ้าเปิดจากโค้ด (เช่น ปุ่ม Buy Now หรือ ตะกร้าสินค้า) const productId = button.getAttribute('data-id'); const productName = button.getAttribute('data-name'); const productPrice = parseFloat(button.getAttribute('data-price')); document.getElementById('modal_product_id').value = productId; document.getElementById('modal_product_name').value = productName; currentProductPrice = productPrice; // รีเซ็ตค่าฟิลด์ variantSelect.innerHTML = '<option value="">-- กำลังโหลดตัวเลือกสินค้า... --</option>'; variantSelect.disabled = true; quantityInput.value = 1; quantityInput.disabled = true; totalPriceDisplay.textContent = '0.00'; maxStockText.textContent = 'สต็อกคงเหลือ: -'; submitBookingBtn.disabled = true; stockAlert.classList.add('d-none'); isUnlimitedVariant = false; // เรียก API ดึงข้อมูล Variants fetch('get_variants.php?product_id=' + productId) .then(response => response.json()) .then(data => { if (data.status === 'success') { variantSelect.innerHTML = '<option value="">-- โปรดเลือกไซส์ / ตัวเลือก --</option>'; data.variants.forEach(variant => { const option = document.createElement('option'); option.value = variant.id; option.setAttribute('data-stock', variant.stock); option.setAttribute('data-unlimited', variant.is_unlimited); if (parseInt(variant.is_unlimited) === 1) { option.textContent = `${variant.variant_name} (Made-to-Order: ไม่จำกัดสต็อก)`; } else if (parseInt(variant.stock) <= 0) { option.textContent = `${variant.variant_name} (สินค้าหมด)`; option.disabled = true; } else { option.textContent = `${variant.variant_name} (คงเหลือ: ${variant.stock} ชิ้น)`; } variantSelect.appendChild(option); }); variantSelect.disabled = false; } else { variantSelect.innerHTML = `<option value="">เกิดข้อผิดพลาดในการโหลดตัวเลือก</option>`; } }) .catch(err => { console.error('Error fetching variants:', err); variantSelect.innerHTML = `<option value="">ไม่สามารถติดต่อเซิร์ฟเวอร์ได้</option>`; }); }); // เมื่อเปลี่ยนตัวเลือก Variant ใน Modal variantSelect.addEventListener('change', function() { const selectedOption = variantSelect.options[variantSelect.selectedIndex]; if (selectedOption && selectedOption.value !== "") { selectedVariantStock = parseInt(selectedOption.getAttribute('data-stock')) || 0; isUnlimitedVariant = parseInt(selectedOption.getAttribute('data-unlimited')) === 1; quantityInput.disabled = false; if (isUnlimitedVariant) { maxStockText.textContent = `สต็อกคงเหลือ: ไม่จำกัด (Made-to-Order)`; quantityInput.removeAttribute('max'); stockAlert.classList.add('d-none'); submitBookingBtn.disabled = false; } else { maxStockText.textContent = `สต็อกคงเหลือ: ${selectedVariantStock} ชิ้น`; quantityInput.max = selectedVariantStock; if (selectedVariantStock <= 0) { stockAlert.classList.remove('d-none'); submitBookingBtn.disabled = true; } else { stockAlert.classList.add('d-none'); submitBookingBtn.disabled = false; } if (parseInt(quantityInput.value) > selectedVariantStock) { quantityInput.value = selectedVariantStock; } } calculateTotalPrice(); } else { quantityInput.disabled = true; maxStockText.textContent = 'สต็อกคงเหลือ: -'; submitBookingBtn.disabled = true; totalPriceDisplay.textContent = '0.00'; } }); // เมื่อเปลี่ยนจำนวนสินค้า ใน Modal quantityInput.addEventListener('input', function() { const val = parseInt(quantityInput.value); if (!isUnlimitedVariant && val > selectedVariantStock) { quantityInput.value = selectedVariantStock; } else if (val < 1 || isNaN(val)) { quantityInput.value = 1; } calculateTotalPrice(); }); function calculateTotalPrice() { const quantity = parseInt(quantityInput.value) || 0; const total = currentProductPrice * quantity; totalPriceDisplay.textContent = total.toLocaleString('th-TH', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); // อัปเดต QR Code ถ้า PromptPay ถูกเลือก if (document.getElementById('payment_method').value === 'PromptPay') { updatePromptPayQR(total); } } // กรองแสดงสินค้าตามหมวดหมู่ function filterCategory(category, btnElement) { // สลับสถานะ active ของปุ่ม document.querySelectorAll('.category-filter-btn').forEach(btn => btn.classList.remove('active')); if (btnElement) { btnElement.classList.add('active'); } // ซ่อน/แสดงการ์ดสินค้าตามหมวดหมู่ const productCols = document.querySelectorAll('.product-item-col'); productCols.forEach(col => { const itemCat = col.getAttribute('data-category'); if (category === 'all' || itemCat === category) { col.style.display = ''; } else { col.style.display = 'none'; } }); } // ========================================== // PromptPay QR Code & Countdown Logic // ========================================== const PROMPTPAY_PHONE = '0970566414'; // เบอร์ PromptPay let countdownInterval = null; let countdownSeconds = 600; // 10 นาที /** * สร้าง PromptPay QR Payload ตามมาตรฐาน EMVCo / BOT Thailand */ function generatePromptPayPayload(phone, amount) { // ทำความสะอาดเบอร์ let id = phone.replace(/[^0-9]/g, ''); if (id.length === 10 && id[0] === '0') { id = '0066' + id.substring(1); } else { id = '0066' + id; } const f = (tag, val) => tag + String(val.length).padStart(2, '0') + val; const guid = 'A000000677010111'; const accountInfo = f('00', guid) + f('01', id); let payload = ''; payload += f('00', '01'); // Payload Format Indicator if (amount && amount > 0) { payload += f('01', '12'); // Dynamic QR } else { payload += f('01', '11'); // Static QR } payload += f('29', accountInfo); // Merchant Account Info payload += f('52', '0000'); // Merchant Category Code payload += f('53', '764'); // THB Currency if (amount && amount > 0) { const amtStr = parseFloat(amount).toFixed(2); payload += f('54', amtStr); // Transaction Amount } payload += f('58', 'TH'); // Country Code payload += f('59', 'PromptPay'); // Merchant Name payload += f('60', 'Bangkok'); // City payload += '6304'; // CRC Header // CRC-16 CCITT let crc = 0xFFFF; for (let i = 0; i < payload.length; i++) { crc ^= payload.charCodeAt(i) << 8; for (let j = 0; j < 8; j++) { crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1; crc &= 0xFFFF; } } payload += crc.toString(16).toUpperCase().padStart(4, '0'); return payload; } /** * อัปเดต QR Code รูปภาพ */ function updatePromptPayQR(amount) { const wrapper = document.getElementById('qr_code_img_wrapper'); const amountBadge = document.getElementById('qr_amount_badge'); // แสดง spinner ขณะโหลด wrapper.innerHTML = `<div class="d-flex align-items-center justify-content-center" style="width:200px;height:200px;"> <div class="spinner-border text-danger" role="status"><span class="visually-hidden">Loading...</span></div> </div>`; const payload = generatePromptPayPayload(PROMPTPAY_PHONE, amount > 0 ? amount : null); const qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + encodeURIComponent(payload) + '&margin=10&ecc=M'; const img = new Image(); img.onload = function() { wrapper.innerHTML = `<img src="${qrUrl}" alt="PromptPay QR Code" style="width:200px;height:200px;border-radius:8px;">`; }; img.onerror = function() { wrapper.innerHTML = `<div class="text-center text-muted py-3"><i class="fa-solid fa-qrcode fa-3x mb-2"></i><br><small>ไม่สามารถโหลด QR Code ได้<br>กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต</small></div>`; }; img.src = qrUrl; // อัปเดต badge จำนวนเงิน if (amount > 0) { amountBadge.textContent = '฿' + amount.toLocaleString('th-TH', {minimumFractionDigits: 2}) + ' บาท'; amountBadge.classList.remove('bg-secondary'); amountBadge.classList.add('bg-danger'); } else { amountBadge.textContent = 'กรุณายืนยันยอดเงิน'; amountBadge.classList.remove('bg-danger'); amountBadge.classList.add('bg-secondary'); } } /** * เริ่มนับถอยหลัง 10 นาที */ function startQRCountdown() { clearQRCountdown(); countdownSeconds = 600; const timerBox = document.getElementById('qr_timer_box'); timerBox.classList.remove('d-none'); updateCountdownDisplay(); countdownInterval = setInterval(() => { countdownSeconds--; updateCountdownDisplay(); if (countdownSeconds <= 0) { clearQRCountdown(); // QR หมดอายุ - แจ้งเตือนและ refresh QR const amount = parseFloat(totalPriceDisplay.textContent.replace(/,/g, '')) || 0; updatePromptPayQR(amount); startQRCountdown(); } }, 1000); } function clearQRCountdown() { if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } } function updateCountdownDisplay() { const mins = Math.floor(countdownSeconds / 60); const secs = countdownSeconds % 60; const display = String(mins).padStart(2, '0') + ':' + String(secs).padStart(2, '0'); const d1 = document.getElementById('countdown_display'); const d2 = document.getElementById('countdown_display2'); if (d1) d1.textContent = display; if (d2) d2.textContent = display; // เปลี่ยนสีเมื่อเหลือน้อย const isUrgent = countdownSeconds <= 60; [d1, d2].forEach(el => { if (el) { el.style.color = isUrgent ? '#dc3545' : '#856404'; if (isUrgent && countdownSeconds % 2 === 0) { el.style.opacity = '0.5'; } else { el.style.opacity = '1'; } } }); } /** * จัดการการเปลี่ยน payment method */ function handlePaymentMethodChange() { const method = document.getElementById('payment_method').value; const bankDetails = document.getElementById('bank_details'); const promptpaySection = document.getElementById('promptpay_section'); if (method === 'PromptPay') { bankDetails.classList.add('d-none'); promptpaySection.classList.remove('d-none'); // คำนวณยอดเงินปัจจุบัน const currentTotal = currentProductPrice * (parseInt(document.getElementById('quantity').value) || 1); updatePromptPayQR(currentTotal); startQRCountdown(); } else { bankDetails.classList.remove('d-none'); promptpaySection.classList.add('d-none'); clearQRCountdown(); document.getElementById('qr_timer_box').classList.add('d-none'); } } // เมื่อ Modal ปิด ให้หยุด countdown document.getElementById('preorderModal').addEventListener('hidden.bs.modal', function() { clearQRCountdown(); // reset payment method back to Bank Transfer const pmSelect = document.getElementById('payment_method'); pmSelect.value = 'Bank Transfer'; handlePaymentMethodChange(); }); // Initialize cart on page load window.addEventListener('DOMContentLoaded', () => { loadCart(); }); </script> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.24 |
proxy
|
phpinfo
|
Settings