File manager - Edit - /home/webapp69.cm.in.th/u69319090039/Shop39/database/app/Views/seller/orders/index.php
Back
<?php /** * Seller Order Management Page * Full lifecycle: Pending → Order Accepted → Preparing → Shipping → Delivered → Completed */ // Status labels & badge classes $statusConfig = [ 'pending' => ['label' => 'Pending (รอยืนยัน)', 'badge' => 'badge-pending', 'icon' => 'bi-clock'], 'order_accepted' => ['label' => 'Order Accepted (รับออเดอร์)', 'badge' => 'badge-order-accepted', 'icon' => 'bi-check-circle'], 'paid' => ['label' => 'Paid (ชำระเงินแล้ว)', 'badge' => 'badge-order-accepted', 'icon' => 'bi-credit-card'], 'waiting_payment'=> ['label' => 'Waiting Payment (รอชำระ)', 'badge' => 'badge-pending', 'icon' => 'bi-wallet2'], 'preparing' => ['label' => 'Preparing Order (เตรียมสินค้า)','badge' => 'badge-preparing', 'icon' => 'bi-box-seam'], 'shipping' => ['label' => 'Shipping (กำลังส่ง)', 'badge' => 'badge-shipping', 'icon' => 'bi-truck'], 'delivered' => ['label' => 'Delivered (ส่งสำเร็จ)', 'badge' => 'badge-delivered', 'icon' => 'bi-box-arrow-in-down'], 'completed' => ['label' => 'Completed (สำเร็จ)', 'badge' => 'badge-completed', 'icon' => 'bi-patch-check-fill'], 'cancelled' => ['label' => 'Cancelled (ยกเลิก)', 'badge' => 'badge-cancelled', 'icon' => 'bi-x-circle'], 'refunded' => ['label' => 'Refunded (คืนเงิน)', 'badge' => 'badge-cancelled', 'icon' => 'bi-arrow-counterclockwise'], ]; // Next action definitions $nextActions = [ 'pending' => ['next' => 'order_accepted', 'btn' => 'btn-primary', 'label' => 'Accept Order', 'icon' => 'bi-check2-circle'], 'order_accepted' => ['next' => 'preparing', 'btn' => 'btn-warning text-dark','label' => 'Start Preparing', 'icon' => 'bi-box-seam'], 'paid' => ['next' => 'preparing', 'btn' => 'btn-warning text-dark','label' => 'Start Preparing', 'icon' => 'bi-box-seam'], 'preparing' => ['next' => 'shipping', 'btn' => 'btn-purple', 'label' => 'Ship Order', 'icon' => 'bi-truck', 'needs_shipping' => true], 'shipping' => ['next' => 'delivered', 'btn' => 'btn-info text-dark','label' => 'Confirm Delivery', 'icon' => 'bi-box-arrow-in-down'], 'delivered' => ['next' => 'completed', 'btn' => 'btn-success', 'label' => 'Complete Order', 'icon' => 'bi-patch-check-fill'], ]; ?> <style> /* ── Status Badges ── */ .badge-pending { background: #ffc107; color: #212529; } .badge-order-accepted { background: #0d6efd; color: #fff; } .badge-preparing { background: #fd7e14; color: #fff; } .badge-shipping { background: #6f42c1; color: #fff; } .badge-delivered { background: #198754; color: #fff; } .badge-completed { background: #0a3622; color: #fff; } .badge-cancelled { background: #dc3545; color: #fff; } /* ── Summary Cards ── */ .summary-card { border-radius: 12px; padding: 18px 20px; cursor: pointer; transition: transform .15s, box-shadow .15s; text-decoration: none; display: block; } .summary-card:hover { transform: translateY(-3px); box-shadow: 0 8px 24px rgba(0,0,0,.12) !important; } .summary-card .sc-num { font-size: 2rem; font-weight: 700; line-height: 1; } .summary-card .sc-lbl { font-size: .78rem; font-weight: 600; opacity: .85; } /* ── History Timeline ── */ .timeline { position: relative; padding-left: 28px; } .timeline::before { content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 2px; background: #e9ecef; } .timeline-item { position: relative; margin-bottom: 18px; } .timeline-dot { position: absolute; left: -24px; top: 4px; width: 14px; height: 14px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 2px #0d6efd; background: #0d6efd; } .timeline-dot.done { background: #198754; box-shadow: 0 0 0 2px #198754; } .timeline-dot.ship { background: #6f42c1; box-shadow: 0 0 0 2px #6f42c1; } .timeline-dot.cancel { background: #dc3545; box-shadow: 0 0 0 2px #dc3545; } /* ── Purple button ── */ .btn-purple { background: #6f42c1; color: #fff; border-color: #6f42c1; } .btn-purple:hover { background: #5a32a3; color: #fff; } /* ── Misc ── */ .order-row { cursor: default; } .search-bar { max-width: 380px; } .status-track-step { font-size: .75rem; text-align: center; opacity: .45; } .status-track-step.active { opacity: 1; } </style> <!-- ══════════════════════════════════════════════ --> <!-- Page Header --> <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2"> <div> <h4 class="fw-bold m-0"><i class="bi bi-receipt text-primary me-2"></i>จัดการคำสั่งซื้อในร้าน <span class="text-muted fw-normal fs-6">(Seller Orders)</span></h4> <p class="text-muted small mb-0">ตรวจสอบคำสั่งซื้อ จัดเตรียมสินค้า อัปเดตสถานะการจัดส่ง และพิมพ์ใบปะหน้าพัสดุ</p> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- 1. Summary Cards (Clean, Highlighted, Real-time) --> <!-- ══════════════════════════════════════════════ --> <div class="row g-3 mb-4"> <!-- New Orders (Red Highlight Badge) --> <div class="col-6 col-md-4 col-lg-2"> <a href="?status=new" class="summary-card card border-0 shadow-sm text-decoration-none position-relative overflow-hidden" style="background: linear-gradient(135deg, #741b27 0%, #a82435 100%); color:#fff;"> <div class="d-flex justify-content-between align-items-start"> <div class="sc-num fw-bold fs-2" id="sc-new-cnt"><?= (int)($summary['new_unread_cnt'] ?? 0) ?></div> <span class="badge bg-warning text-dark rounded-pill px-2 py-1 small"> <i class="bi bi-bell-fill"></i> </span> </div> <div class="sc-lbl text-white-50 mt-1 small fw-bold">🔔 คำสั่งซื้อใหม่</div> </a> </div> <!-- Preparing --> <div class="col-6 col-md-4 col-lg-2"> <a href="?status=preparing_all" class="summary-card card border-0 shadow-sm text-decoration-none bg-warning text-dark bg-opacity-10"> <div class="sc-num fw-bold fs-2 text-warning-emphasis"><?= (int)($summary['preparing_cnt'] ?? 0) ?></div> <div class="sc-lbl text-secondary mt-1 small fw-bold"><i class="bi bi-box-seam me-1"></i>รอเตรียมสินค้า</div> </a> </div> <!-- Shipping --> <div class="col-6 col-md-4 col-lg-2"> <a href="?status=shipping" class="summary-card card border-0 shadow-sm text-decoration-none" style="background:#f3e8ff; color:#6f42c1;"> <div class="sc-num fw-bold fs-2"><?= (int)($summary['shipping_cnt'] ?? 0) ?></div> <div class="sc-lbl mt-1 small fw-bold"><i class="bi bi-truck me-1"></i>กำลังจัดส่ง</div> </a> </div> <!-- Delivered --> <div class="col-6 col-md-4 col-lg-2"> <a href="?status=delivered" class="summary-card card border-0 shadow-sm text-decoration-none bg-success bg-opacity-10 text-success"> <div class="sc-num fw-bold fs-2"><?= (int)($summary['delivered_cnt'] ?? 0) ?></div> <div class="sc-lbl text-success-emphasis mt-1 small fw-bold"><i class="bi bi-check-circle me-1"></i>จัดส่งสำเร็จ</div> </a> </div> <!-- Cancelled --> <div class="col-6 col-md-4 col-lg-2"> <a href="?status=cancelled" class="summary-card card border-0 shadow-sm text-decoration-none bg-danger bg-opacity-10 text-danger"> <div class="sc-num fw-bold fs-2"><?= (int)($summary['cancelled_cnt'] ?? 0) ?></div> <div class="sc-lbl text-danger-emphasis mt-1 small fw-bold"><i class="bi bi-x-circle me-1"></i>ยกเลิก</div> </a> </div> <!-- Total Sales --> <div class="col-6 col-md-4 col-lg-2"> <div class="summary-card card border-0 shadow-sm bg-dark text-white"> <div class="sc-num fw-bold fs-4 text-truncate">฿<?= number_format($summary['total_sales'] ?? 0, 2) ?></div> <div class="sc-lbl text-white-50 mt-1 small fw-bold"><i class="bi bi-cash-stack me-1"></i>ยอดขายรวม</div> </div> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- 3. Search & Filter Section --> <!-- ══════════════════════════════════════════════ --> <div class="card border-0 shadow-sm rounded-4 mb-4"> <div class="card-body p-3 p-md-4"> <form method="GET" action="" class="row g-3 align-items-end"> <!-- Search Keyword --> <div class="col-12 col-md-4"> <label class="form-label small fw-bold text-muted mb-1"><i class="bi bi-search me-1"></i>ค้นหา</label> <div class="input-group"> <span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span> <input type="text" name="search" class="form-control border-start-0" placeholder="เลข Order / ชื่อลูกค้า / เบอร์โทร / Tracking..." value="<?= Security::e($search ?? '') ?>"> </div> </div> <!-- Status Filter --> <div class="col-6 col-md-2"> <label class="form-label small fw-bold text-muted mb-1"><i class="bi bi-funnel me-1"></i>สถานะ</label> <select name="status" class="form-select"> <option value="all" <?= (($statusFilter ?? 'all') === 'all') ? 'selected' : '' ?>>ทุกสถานะ</option> <option value="new" <?= (($statusFilter ?? '') === 'new') ? 'selected' : '' ?>>🔔 คำสั่งซื้อใหม่ (ยังไม่อ่าน)</option> <option value="pending" <?= (($statusFilter ?? '') === 'pending') ? 'selected' : '' ?>>Pending (รอยืนยัน)</option> <option value="order_accepted" <?= (($statusFilter ?? '') === 'order_accepted') ? 'selected' : '' ?>>Order Accepted</option> <option value="preparing" <?= (($statusFilter ?? '') === 'preparing') ? 'selected' : '' ?>>กำลังเตรียมสินค้า</option> <option value="shipping" <?= (($statusFilter ?? '') === 'shipping') ? 'selected' : '' ?>>กำลังจัดส่ง</option> <option value="delivered" <?= (($statusFilter ?? '') === 'delivered') ? 'selected' : '' ?>>จัดส่งสำเร็จ</option> <option value="completed" <?= (($statusFilter ?? '') === 'completed') ? 'selected' : '' ?>>สำเร็จ (Completed)</option> <option value="cancelled" <?= (($statusFilter ?? '') === 'cancelled') ? 'selected' : '' ?>>ยกเลิก (Cancelled)</option> </select> </div> <!-- Payment Filter --> <div class="col-6 col-md-2"> <label class="form-label small fw-bold text-muted mb-1"><i class="bi bi-credit-card me-1"></i>การชำระเงิน</label> <select name="payment" class="form-select"> <option value="all" <?= (($paymentFilter ?? 'all') === 'all') ? 'selected' : '' ?>>ทุกวิธี</option> <option value="cod" <?= (($paymentFilter ?? '') === 'cod') ? 'selected' : '' ?>>เก็บเงินปลายทาง (COD)</option> <option value="prepaid" <?= (($paymentFilter ?? '') === 'prepaid') ? 'selected' : '' ?>>ชำระต้นทาง (Prepaid)</option> <option value="paid" <?= (($paymentFilter ?? '') === 'paid') ? 'selected' : '' ?>>ชำระเงินแล้ว</option> <option value="pending_payment" <?= (($paymentFilter ?? '') === 'pending_payment') ? 'selected' : '' ?>>รอชำระเงิน</option> </select> </div> <!-- Date Filter --> <div class="col-6 col-md-2"> <label class="form-label small fw-bold text-muted mb-1"><i class="bi bi-calendar3 me-1"></i>ช่วงเวลา</label> <select name="date_filter" id="dateFilterSelect" class="form-select"> <option value="all" <?= (($dateFilter ?? 'all') === 'all') ? 'selected' : '' ?>>ทั้งหมด</option> <option value="today" <?= (($dateFilter ?? '') === 'today') ? 'selected' : '' ?>>วันนี้</option> <option value="7days" <?= (($dateFilter ?? '') === '7days') ? 'selected' : '' ?>>7 วันที่ผ่านมา</option> <option value="30days" <?= (($dateFilter ?? '') === '30days') ? 'selected' : '' ?>>30 วันที่ผ่านมา</option> <option value="custom" <?= (($dateFilter ?? '') === 'custom') ? 'selected' : '' ?>>กำหนดวันที่เอง</option> </select> </div> <!-- Buttons --> <div class="col-6 col-md-2 d-flex gap-2"> <button type="submit" class="btn btn-primary rounded-pill px-4 flex-grow-1 fw-bold"> <i class="bi bi-funnel me-1"></i>ค้นหา </button> <a href="<?= APP_URL ?>/seller/orders" class="btn btn-outline-secondary rounded-pill" title="ล้างตัวกรอง"> <i class="bi bi-arrow-clockwise"></i> </a> </div> <!-- Custom Date Inputs (Conditional) --> <div class="col-12 <?= ($dateFilter === 'custom') ? '' : 'd-none' ?>" id="customDateRow"> <div class="row g-2"> <div class="col-md-3"> <label class="form-label small text-muted">ตั้งแต่วันที่:</label> <input type="date" name="date_from" class="form-control form-control-sm" value="<?= Security::e($dateFrom ?? '') ?>"> </div> <div class="col-md-3"> <label class="form-label small text-muted">ถึงวันที่:</label> <input type="date" name="date_to" class="form-control form-control-sm" value="<?= Security::e($dateTo ?? '') ?>"> </div> </div> </div> </form> </div> </div> <script> document.getElementById('dateFilterSelect').addEventListener('change', function() { const customRow = document.getElementById('customDateRow'); if (this.value === 'custom') { customRow.classList.remove('d-none'); } else { customRow.classList.add('d-none'); } }); </script> <!-- ══════════════════════════════════════════════ --> <!-- 2. Orders Table (Desktop) & Cards (Mobile) --> <!-- ══════════════════════════════════════════════ --> <div class="card border-0 shadow-sm rounded-4 overflow-hidden"> <div class="card-body p-0"> <?php if (empty($orders)): ?> <div class="text-center py-5"> <i class="bi bi-inbox text-muted display-4 opacity-50"></i> <h5 class="mt-3 text-secondary fw-bold">ไม่พบคำสั่งซื้อที่ตรงกับเงื่อนไข</h5> <p class="text-muted small">ลองปรับเปลี่ยนคำค้นหาหรือตัวกรองด้านบน</p> <a href="<?= APP_URL ?>/seller/orders" class="btn btn-outline-primary mt-2 rounded-pill px-4">ดูคำสั่งซื้อทั้งหมด</a> </div> <?php else: ?> <!-- Desktop Table View --> <div class="table-responsive d-none d-md-block"> <table class="table table-hover align-middle mb-0"> <thead class="table-light border-bottom"> <tr class="small text-muted text-uppercase"> <th class="ps-4 py-3">เลขที่คำสั่งซื้อ</th> <th>ลูกค้า</th> <th>รายการสินค้า</th> <th>จำนวน</th> <th>ยอดรวม</th> <th>วิธีชำระเงิน</th> <th>สถานะ</th> <th>วันที่สั่งซื้อ</th> <th class="text-end pe-4">การจัดการ</th> </tr> </thead> <tbody> <?php foreach ($orders as $ord): $sc = $statusConfig[$ord['status']] ?? ['label' => $ord['status'], 'badge' => 'badge-cancelled', 'icon' => 'bi-question']; $isCod = ($ord['payment_method'] ?? 'prepaid') === 'cod'; $isUnread = empty($ord['is_read']); ?> <tr class="order-row <?= $isUnread ? 'table-warning bg-opacity-25' : '' ?>" id="order-row-<?= $ord['id'] ?>"> <td class="ps-4"> <div class="fw-bold text-primary">#<?= Security::e($ord['order_number']) ?></div> <?php if ($isUnread): ?> <span class="badge bg-danger rounded-pill px-2 py-0 small"><i class="bi bi-bell-fill me-1"></i>ใหม่</span> <?php endif; ?> </td> <td> <div class="fw-semibold text-dark"><?= Security::e($ord['customer_name']) ?></div> <div class="text-muted small"><i class="bi bi-telephone me-1"></i><?= Security::e($ord['customer_phone'] ?? '-') ?></div> </td> <td> <div class="small text-dark text-truncate" style="max-width: 180px;" title="<?= Security::e($ord['first_item_title'] ?? '') ?>"> <?= Security::e($ord['first_item_title'] ?? 'สินค้าในร้าน') ?> </div> <?php if (($ord['item_count'] ?? 1) > 1): ?> <span class="badge bg-light text-secondary border small">+ อีก <?= ($ord['item_count'] - 1) ?> รายการ</span> <?php endif; ?> </td> <td class="text-center"> <span class="badge bg-light text-dark border px-2 py-1"><?= (int)($ord['item_count'] ?? 1) ?></span> </td> <td> <div class="fw-bold text-dark fs-6">฿<?= number_format($ord['total_amount'], 2) ?></div> </td> <td> <?php if ($isCod): ?> <span class="badge bg-warning text-dark border border-warning px-2 py-1 rounded-pill small"> <i class="bi bi-cash me-1"></i>เก็บเงินปลายทาง (COD) </span> <div class="small text-muted mt-1" style="font-size: 11px;"> <?= ($ord['payment_status'] ?? '') === 'paid' ? '<span class="text-success fw-bold">✓ ชำระแล้ว</span>' : 'ลูกค้าชำระเงินกับขนส่ง' ?> </div> <?php else: ?> <span class="badge bg-primary-subtle text-primary border border-primary-subtle px-2 py-1 rounded-pill small"> <i class="bi bi-credit-card me-1"></i>โอนเงิน (Prepaid) </span> <div class="small text-success mt-1" style="font-size: 11px;">✓ ชำระเงินแล้ว</div> <?php endif; ?> </td> <td> <span class="badge px-3 py-2 rounded-pill <?= $sc['badge'] ?>" id="status-badge-<?= $ord['id'] ?>"> <i class="bi <?= $sc['icon'] ?> me-1"></i><?= $sc['label'] ?> </span> </td> <td class="small text-muted"><?= date('d/m/Y H:i', strtotime($ord['created_at'])) ?></td> <td class="text-end pe-4"> <div class="d-inline-flex align-items-center gap-1"> <button type="button" class="btn btn-sm btn-primary rounded-pill px-3 btn-manage-order" data-order-id="<?= $ord['id'] ?>" title="ดูรายละเอียดและจัดการ"> <i class="bi bi-gear-fill me-1"></i>จัดการออเดอร์ </button> <a href="<?= APP_URL ?>/seller/orders/print/<?= $ord['id'] ?>" target="_blank" class="btn btn-sm btn-outline-secondary rounded-circle p-2" style="width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;" title="พิมพ์ใบแพ็คสินค้า"> <i class="bi bi-printer"></i> </a> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <!-- Mobile Card View --> <div class="d-block d-md-none p-3"> <?php foreach ($orders as $ord): $sc = $statusConfig[$ord['status']] ?? ['label' => $ord['status'], 'badge' => 'badge-cancelled', 'icon' => 'bi-question']; $isCod = ($ord['payment_method'] ?? 'prepaid') === 'cod'; $isUnread = empty($ord['is_read']); ?> <div class="card border rounded-4 mb-3 p-3 shadow-sm <?= $isUnread ? 'border-danger' : '' ?>"> <div class="d-flex justify-content-between align-items-center mb-2"> <div> <span class="fw-bold text-primary">#<?= Security::e($ord['order_number']) ?></span> <?php if ($isUnread): ?> <span class="badge bg-danger rounded-pill ms-1 small">ใหม่</span> <?php endif; ?> </div> <span class="badge px-3 py-1 rounded-pill <?= $sc['badge'] ?>"> <?= $sc['label'] ?> </span> </div> <div class="mb-2"> <div class="fw-bold text-dark"><?= Security::e($ord['customer_name']) ?></div> <div class="small text-muted"><i class="bi bi-telephone me-1"></i><?= Security::e($ord['customer_phone'] ?? '-') ?></div> </div> <div class="d-flex justify-content-between align-items-center py-2 border-top border-bottom mb-3 small"> <div> <div class="text-muted">วิธีชำระ: <?= $isCod ? 'เก็บเงินปลายทาง (COD)' : 'โอนเงิน (Prepaid)' ?></div> <div class="text-muted">วันที่: <?= date('d/m/Y H:i', strtotime($ord['created_at'])) ?></div> </div> <div class="text-end"> <div class="text-muted">ยอดสุทธิ</div> <div class="fw-bold text-dark fs-5">฿<?= number_format($ord['total_amount'], 2) ?></div> </div> </div> <div class="d-flex gap-2"> <button type="button" class="btn btn-sm btn-primary rounded-pill flex-grow-1 fw-bold btn-manage-order" data-order-id="<?= $ord['id'] ?>"> <i class="bi bi-gear-fill me-1"></i>จัดการออเดอร์ </button> <a href="<?= APP_URL ?>/seller/orders/print/<?= $ord['id'] ?>" target="_blank" class="btn btn-sm btn-outline-secondary rounded-pill px-3"> <i class="bi bi-printer"></i> </a> </div> </div> <?php endforeach; ?> </div> <div class="p-3 text-muted small border-top bg-light d-flex justify-content-between align-items-center"> <span>แสดง <?= count($orders) ?> รายการ</span> <span class="text-success fw-bold"><i class="bi bi-shield-check me-1"></i>เชื่อมต่อฐานข้อมูลคำสั่งซื้อจริง</span> </div> <?php endif; ?> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- Manage Order Modal --> <!-- ══════════════════════════════════════════════ --> <div class="modal fade" id="manageOrderModal" tabindex="-1" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-scrollable"> <div class="modal-content border-0 shadow-lg rounded-4 overflow-hidden"> <div class="modal-header bg-primary text-white border-0 py-3"> <h5 class="modal-title fw-bold"><i class="bi bi-sliders me-2"></i>จัดการคำสั่งซื้อ <span id="modal-order-number"></span></h5> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button> </div> <div class="modal-body p-0"> <!-- Spinner --> <div id="modal-spinner" class="text-center py-5"> <div class="spinner-border text-primary" role="status"></div> <p class="mt-2 text-muted fw-semibold">กำลังโหลดข้อมูลออเดอร์...</p> </div> <!-- Error State --> <div id="modal-error" class="text-center py-5 px-4 d-none"> <div class="mb-3"> <span class="bg-danger bg-opacity-10 text-danger rounded-circle d-inline-flex align-items-center justify-content-center" style="width: 64px; height: 64px;"> <i class="bi bi-exclamation-triangle fs-2"></i> </span> </div> <h5 class="fw-bold text-danger mb-1">⚠️ ไม่สามารถโหลดข้อมูลคำสั่งซื้อได้</h5> <p class="text-muted small mb-3" id="modal-error-message">เกิดข้อผิดพลาดในการดึงข้อมูลจากเซิร์ฟเวอร์</p> <button type="button" class="btn btn-outline-primary rounded-pill px-4" id="btn-modal-retry"> <i class="bi bi-arrow-clockwise me-1"></i>ลองใหม่ </button> </div> <!-- Content (hidden until loaded) --> <div id="modal-content" class="d-none"> <div class="row g-0"> <!-- LEFT: Order Info, Items, History --> <div class="col-lg-7 border-end"> <!-- Status Workflow Banner --> <div id="workflow-banner" class="p-3 border-bottom bg-light"> <div class="d-flex align-items-center justify-content-between flex-wrap gap-2"> <div> <span class="text-muted small">สถานะปัจจุบัน:</span> <span id="current-status-badge" class="badge px-3 py-2 rounded-pill ms-2 fs-6"></span> </div> <div id="quick-action-area"></div> </div> </div> <!-- Order Info Grid --> <div class="p-4 border-bottom"> <h6 class="fw-bold mb-3 text-secondary text-uppercase small">ข้อมูลคำสั่งซื้อ</h6> <div class="row g-2 small"> <div class="col-sm-6"> <div class="text-muted">เลขที่ออเดอร์</div> <div class="fw-bold text-primary" id="info-order-number">-</div> </div> <div class="col-sm-6"> <div class="text-muted">วันที่สั่งซื้อ</div> <div class="fw-bold" id="info-order-date">-</div> </div> <div class="col-sm-6"> <div class="text-muted">ชื่อลูกค้า</div> <div class="fw-bold" id="info-customer-name">-</div> </div> <div class="col-sm-6"> <div class="text-muted">เบอร์โทรศัพท์</div> <div class="fw-bold" id="info-customer-phone">-</div> </div> <div class="col-sm-6"> <div class="text-muted">อีเมล</div> <div class="fw-bold" id="info-customer-email">-</div> </div> <div class="col-sm-6"> <div class="text-muted">ยอดรวมสุทธิ</div> <div class="fw-bold text-success fs-5" id="info-total">-</div> </div> <div class="col-12"> <div class="text-muted">ที่อยู่จัดส่ง</div> <div class="fw-bold" id="info-address">-</div> </div> </div> </div> <!-- Order Items --> <div class="p-4 border-bottom"> <h6 class="fw-bold mb-3 text-secondary text-uppercase small">รายการสินค้าในออเดอร์</h6> <div id="items-container"></div> </div> <!-- Status History Timeline --> <div class="p-4"> <h6 class="fw-bold mb-3 text-secondary text-uppercase small"><i class="bi bi-clock-history me-1"></i>ประวัติการอัปเดตออเดอร์</h6> <div id="history-container"> <p class="text-muted small">ยังไม่มีประวัติสถานะ</p> </div> </div> </div> <!-- RIGHT: Actions & Shipping --> <div class="col-lg-5"> <div class="p-4 h-100 bg-light bg-opacity-50"> <!-- Manual Status Change Card --> <div class="card border-0 shadow-sm rounded-3 mb-3"> <div class="card-body p-3"> <h6 class="fw-bold mb-2 text-dark"><i class="bi bi-arrow-repeat me-2 text-primary"></i>อัปเดตสถานะสินค้า (Manual)</h6> <p class="small text-muted mb-2">เลือกสถานะที่ต้องการเปลี่ยนโดยตรง:</p> <div class="mb-2"> <select class="form-select form-select-sm" id="manual-status-select"> <option value="pending">⏳ Pending (รอยืนยันคำสั่งซื้อ)</option> <option value="order_accepted">✅ Order Accepted (รับออเดอร์แล้ว)</option> <option value="preparing">📦 Preparing (กำลังแพ็ค / เตรียมจัดส่ง)</option> <option value="shipping">🚚 Shipping (กำลังจัดส่ง)</option> <option value="delivered">📥 Delivered (จัดส่งสำเร็จแล้ว)</option> <option value="completed">🎉 Completed (สำเร็จสมบูรณ์)</option> </select> </div> <button type="button" class="btn btn-sm btn-primary rounded-pill px-3 w-100 fw-semibold" id="btn-update-manual-status"> <i class="bi bi-check2-circle me-1"></i>บันทึกการเปลี่ยนสถานะ </button> </div> </div> <!-- Shipping Info Card --> <div class="card border-0 shadow-sm rounded-3 mb-3"> <div class="card-body p-3"> <h6 class="fw-bold mb-3"><i class="bi bi-truck me-2 text-purple"></i>ข้อมูลการจัดส่ง</h6> <div class="row g-2 small mb-3"> <div class="col-12 mb-1"> <div class="text-muted">บริษัทขนส่ง (Shipping Company)</div> <div class="fw-bold fs-6 text-dark" id="info-shipping-company">ยังไม่ได้ระบุ</div> </div> <div class="col-12"> <div class="text-muted mb-1">หมายเลขพัสดุ (Tracking Number)</div> <div id="info-tracking-container"> <span class="text-muted italic" id="info-tracking-empty">Tracking number has not been provided yet.</span> </div> </div> </div> <!-- Edit Shipping Form (always visible for seller) --> <div id="shipping-form-area" class="border-top pt-2"> <div class="mb-2"> <label class="form-label small fw-semibold mb-1">บริษัทขนส่ง</label> <select class="form-select form-select-sm" id="input-shipping-company"> <option value="">-- เลือกบริษัทขนส่ง --</option> <option value="Thailand Post">Thailand Post (ไปรษณีย์ไทย)</option> <option value="Flash Express">Flash Express</option> <option value="J&T Express">J&T Express</option> <option value="KEX">KEX / Kerry Express</option> <option value="DHL">DHL Express</option> <option value="FedEx">FedEx</option> <option value="Ninja Van">Ninja Van</option> <option value="Shopee Express">Shopee Express</option> <option value="Lazada Express">Lazada Express</option> <option value="อื่นๆ">อื่นๆ</option> </select> </div> <div class="mb-2"> <label class="form-label small fw-semibold mb-1">เลขพัสดุ (Tracking Number)</label> <input type="text" class="form-control form-control-sm font-monospace" id="input-tracking-number" placeholder="กรอกเลขพัสดุ เช่น TH123456789"> </div> <button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3 w-100" id="btn-save-shipping"> <i class="bi bi-floppy me-1"></i>บันทึกข้อมูลจัดส่ง (Save Shipping Information) </button> </div> </div> </div> <!-- Danger Zone: Cancel --> <div class="card border-danger border-0 shadow-sm rounded-3"> <div class="card-body p-3"> <h6 class="fw-bold mb-2 text-danger"><i class="bi bi-exclamation-triangle me-1"></i>ยกเลิกออเดอร์</h6> <p class="small text-muted mb-2">ยกเลิกออเดอร์นี้ หากสินค้าหมดหรือปัญหาอื่นๆ</p> <div class="mb-2"> <input type="text" class="form-control form-control-sm" id="cancel-reason" placeholder="เหตุผลการยกเลิก (ไม่บังคับ)"> </div> <button type="button" class="btn btn-sm btn-outline-danger rounded-pill px-3 w-100" id="btn-cancel-order"> <i class="bi bi-x-circle me-1"></i>ยกเลิกคำสั่งซื้อนี้ </button> </div> </div> </div> </div> </div> </div> <!-- /modal-content --> </div> </div> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- Confirmation Modal --> <!-- ══════════════════════════════════════════════ --> <div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static"> <div class="modal-dialog modal-dialog-centered modal-sm"> <div class="modal-content border-0 shadow rounded-4"> <div class="modal-body p-4 text-center"> <div class="mb-3"> <span class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center" style="width:64px;height:64px;"> <i class="bi bi-question-circle text-primary fs-2"></i> </span> </div> <h6 class="fw-bold mb-1">ยืนยันการเปลี่ยนสถานะ</h6> <p class="text-muted small mb-3" id="confirm-message">คุณต้องการเปลี่ยนสถานะออเดอร์นี้ใช่ไหม?</p> <div class="d-flex gap-2 justify-content-center"> <button class="btn btn-outline-secondary rounded-pill px-4" data-bs-dismiss="modal">ยกเลิก</button> <button class="btn btn-primary rounded-pill px-4" id="btn-confirm-action">ยืนยัน</button> </div> </div> </div> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- Toast Notification --> <!-- ══════════════════════════════════════════════ --> <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1100"> <div id="statusToast" class="toast align-items-center text-white border-0 rounded-3 shadow-lg" role="alert"> <div class="d-flex"> <div class="toast-body fw-semibold" id="toast-message">สำเร็จ!</div> <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button> </div> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- JS Logic --> <!-- ══════════════════════════════════════════════ --> <script> const APP_URL = '<?= APP_URL ?>'; const CSRF_TOKEN = '<?= $csrf_token ?>'; const STATUS_CONFIG = { pending: { label: 'Pending (รอยืนยัน)', badge: 'badge-pending', icon: 'bi-clock' }, order_accepted: { label: 'Order Accepted (รับออเดอร์)', badge: 'badge-order-accepted', icon: 'bi-check-circle' }, paid: { label: 'Paid (ชำระเงินแล้ว)', badge: 'badge-order-accepted', icon: 'bi-credit-card' }, waiting_payment: { label: 'Waiting Payment (รอชำระ)', badge: 'badge-pending', icon: 'bi-wallet2' }, preparing: { label: 'Preparing (เตรียมสินค้า)', badge: 'badge-preparing', icon: 'bi-box-seam' }, shipping: { label: 'Shipping (กำลังส่ง)', badge: 'badge-shipping', icon: 'bi-truck' }, delivered: { label: 'Delivered (ส่งสำเร็จ)', badge: 'badge-delivered', icon: 'bi-box-arrow-in-down' }, completed: { label: 'Completed (สำเร็จ)', badge: 'badge-completed', icon: 'bi-patch-check-fill' }, cancelled: { label: 'Cancelled (ยกเลิก)', badge: 'badge-cancelled', icon: 'bi-x-circle' }, refunded: { label: 'Refunded (คืนเงิน)', badge: 'badge-cancelled', icon: 'bi-arrow-counterclockwise' }, }; const NEXT_ACTIONS = { pending: { next: 'order_accepted', btnClass: 'btn-primary', label: '<i class="bi bi-check2-circle me-1"></i>Accept Order', needsShipping: false }, order_accepted: { next: 'preparing', btnClass: 'btn-warning text-dark', label: '<i class="bi bi-box-seam me-1"></i>Start Preparing', needsShipping: false }, paid: { next: 'preparing', btnClass: 'btn-warning text-dark', label: '<i class="bi bi-box-seam me-1"></i>Start Preparing', needsShipping: false }, preparing: { next: 'shipping', btnClass: 'btn-purple', label: '<i class="bi bi-truck me-1"></i>Ship Order', needsShipping: true }, shipping: { next: 'delivered', btnClass: 'btn-info text-dark', label: '<i class="bi bi-box-arrow-in-down me-1"></i>Confirm Delivery', needsShipping: false }, delivered: { next: 'completed', btnClass: 'btn-success', label: '<i class="bi bi-patch-check-fill me-1"></i>Complete Order', needsShipping: false }, }; let currentOrderId = null; let currentOrderData = null; let pendingAction = null; function escHtml(str) { return String(str || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); } // ── All Bootstrap-dependent code runs AFTER page fully loads (Bootstrap JS loaded in footer) window.addEventListener('load', function () { // ── Bootstrap instances const manageModalEl = document.getElementById('manageOrderModal'); const confirmModalEl = document.getElementById('confirmModal'); const toastEl = document.getElementById('statusToast'); if (!manageModalEl || !confirmModalEl || !toastEl) { console.error('[SellerOrders] Required modal/toast elements not found in DOM.'); return; } const manageModal = new bootstrap.Modal(manageModalEl); const confirmModal = new bootstrap.Modal(confirmModalEl); const toastInst = new bootstrap.Toast(toastEl, { delay: 3500 }); // ── Toast helper function showToast(message, type) { toastEl.className = 'toast align-items-center text-white border-0 rounded-3 shadow-lg bg-' + (type === 'success' ? 'success' : 'danger'); document.getElementById('toast-message').textContent = message; toastInst.show(); } // ── Open Manage Modal via event delegation document.addEventListener('click', async function (e) { const btn = e.target.closest('.btn-manage-order'); if (!btn) return; e.preventDefault(); currentOrderId = btn.getAttribute('data-order-id'); if (!currentOrderId) { console.error('No data-order-id on button'); return; } showModalLoading(); manageModal.show(); await loadOrderDetail(currentOrderId); }); function showModalLoading() { document.getElementById('modal-spinner').classList.remove('d-none'); document.getElementById('modal-error').classList.add('d-none'); document.getElementById('modal-content').classList.add('d-none'); document.getElementById('modal-order-number').textContent = ''; } function showModalError(msg) { document.getElementById('modal-spinner').classList.add('d-none'); document.getElementById('modal-content').classList.add('d-none'); const errContainer = document.getElementById('modal-error'); const errMsgEl = document.getElementById('modal-error-message'); if (errMsgEl) errMsgEl.textContent = msg || 'เกิดข้อผิดพลาดในการโหลดข้อมูลคำสั่งซื้อ'; if (errContainer) errContainer.classList.remove('d-none'); } // ── Retry button handler const btnModalRetry = document.getElementById('btn-modal-retry'); if (btnModalRetry) { btnModalRetry.addEventListener('click', async function() { if (currentOrderId) { showModalLoading(); await loadOrderDetail(currentOrderId); } }); } // ── Load order detail via AJAX async function loadOrderDetail(orderId) { try { const url = APP_URL + '/seller/orders/detail/' + encodeURIComponent(orderId); const resp = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); const text = await resp.text(); let data; try { data = JSON.parse(text); } catch (jsonErr) { console.error('Invalid JSON from server:', text); showModalError('API ส่งข้อมูลไม่ถูกต้อง (Invalid JSON format)'); showToast('API ส่งข้อมูลไม่ถูกต้อง', 'error'); return; } if (!resp.ok || data.status === 'error' || data.success === false) { const errMsg = data.message || 'ไม่สามารถโหลดข้อมูลคำสั่งซื้อได้ (HTTP ' + resp.status + ')'; showModalError(errMsg); showToast(errMsg, 'error'); return; } currentOrderData = data; renderModalContent(data); } catch (err) { console.error('Fetch error:', err); showModalError('ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้'); showToast('ไม่สามารถเชื่อมต่อ API ได้', 'error'); } } // ── Helper to generate carrier tracking URL on client side function getCarrierTrackingUrl(company, trackNo) { if (!trackNo) return ''; trackNo = trackNo.trim(); const comp = (company || '').toLowerCase().trim(); if (comp.includes('thailand post') || comp.includes('ไปรษณีย์')) { return 'https://track.thailandpost.co.th/?trackNumber=' + encodeURIComponent(trackNo); } if (comp.includes('flash')) { return 'https://www.flashexpress.co.th/tracking/?se=' + encodeURIComponent(trackNo); } if (comp.includes('j&t') || comp.includes('jt')) { return 'https://www.jtexpress.co.th/index/query/gzquery.html?bill_code=' + encodeURIComponent(trackNo); } if (comp.includes('kex') || comp.includes('kerry')) { return 'https://th.kerryexpress.com/th/track/?track=' + encodeURIComponent(trackNo); } if (comp.includes('dhl')) { return 'https://www.dhl.com/th-th/home/tracking.html?tracking-id=' + encodeURIComponent(trackNo); } if (comp.includes('fedex')) { return 'https://www.fedex.com/fedextrack/?trknbr=' + encodeURIComponent(trackNo); } return ''; } // ── Helper function to render Tracking Container function renderTrackingDisplay(company, trackNo, backendUrl) { const infoCompany = document.getElementById('info-shipping-company'); const trackContainer = document.getElementById('info-tracking-container'); if (infoCompany) { infoCompany.textContent = company ? company : 'ยังไม่ได้ระบุ'; } if (!trackContainer) return; if (!trackNo) { trackContainer.innerHTML = '<span class="text-muted fst-italic">Tracking number has not been provided yet.</span>'; return; } const trackUrl = backendUrl || getCarrierTrackingUrl(company, trackNo); if (trackUrl) { trackContainer.innerHTML = ` <div class="d-flex align-items-center gap-2 flex-wrap mt-1"> <a href="${escHtml(trackUrl)}" target="_blank" rel="noopener noreferrer" class="fw-bold text-primary fs-5 text-decoration-none font-monospace"> 🔗 ${escHtml(trackNo)} </a> <a href="${escHtml(trackUrl)}" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-primary rounded-pill px-3 fw-bold"> <i class="bi bi-box-arrow-up-right me-1"></i>Track Package </a> </div> `; } else { trackContainer.innerHTML = ` <div class="mt-1"> <span class="fw-bold text-dark fs-5 font-monospace">${escHtml(trackNo)}</span> </div> `; } } // ── Render Modal Content function renderModalContent(data) { const o = data.order || data.data || {}; const items = data.items || (data.data ? data.data.items : []) || []; const history = data.history || (data.data ? data.data.history : []) || []; const trackUrl = data.tracking_url || (data.data ? data.data.tracking_url : ''); document.getElementById('modal-order-number').textContent = '#' + (o.order_number || ''); // Info fields document.getElementById('info-order-number').textContent = '#' + (o.order_number || '-'); document.getElementById('info-order-date').textContent = o.created_at ? new Date(o.created_at).toLocaleString('th-TH') : '-'; document.getElementById('info-customer-name').textContent = o.customer_name || '-'; document.getElementById('info-customer-phone').textContent = o.customer_phone || o.phone || '-'; document.getElementById('info-customer-email').textContent = o.customer_email || o.email || '-'; document.getElementById('info-total').textContent = '฿' + parseFloat(o.total_amount || o.total || 0).toLocaleString('th-TH', {minimumFractionDigits: 2}); document.getElementById('info-address').textContent = o.shipping_address || '-'; // Render Shipping info & Tracking URL renderTrackingDisplay(o.shipping_company, o.tracking_number, trackUrl); const scSelect = document.getElementById('input-shipping-company'); if (scSelect && o.shipping_company) { [...scSelect.options].forEach(opt => { opt.selected = (opt.value === o.shipping_company || opt.text.includes(o.shipping_company)); }); } const trackInput = document.getElementById('input-tracking-number'); if (trackInput) trackInput.value = o.tracking_number || ''; // Status badge const sc = STATUS_CONFIG[o.status] || { label: o.status, badge: 'badge-cancelled', icon: 'bi-question' }; const badgeEl = document.getElementById('current-status-badge'); badgeEl.className = 'badge px-3 py-2 rounded-pill fs-6 ' + sc.badge; badgeEl.innerHTML = '<i class="bi ' + sc.icon + ' me-1"></i>' + sc.label; // Manual status dropdown pre-select const manualSel = document.getElementById('manual-status-select'); if (manualSel) manualSel.value = o.status; // Quick action button renderQuickAction(o.status); // Cancel button state const cancelBtn = document.getElementById('btn-cancel-order'); if (cancelBtn) cancelBtn.disabled = (o.status === 'cancelled' || o.status === 'completed' || o.status === 'refunded'); // Items list const itemsHtml = items.length === 0 ? '<p class="text-muted small">ไม่มีรายการสินค้า</p>' : items.map(item => { const imgSrc = item.primary_image ? APP_URL + '/public/' + item.primary_image.replace(/^\/+/, '') : null; const imgTag = imgSrc ? '<img src="' + imgSrc + '" class="rounded-2 border object-fit-cover me-3 flex-shrink-0" style="width:52px;height:52px;" onerror="this.style.display=\'none\'">' : '<span class="d-inline-flex bg-light rounded-2 border me-3 flex-shrink-0 align-items-center justify-content-center text-muted" style="width:52px;height:52px;"><i class="bi bi-image fs-4"></i></span>'; const variantTag = item.variant_info ? '<span class="badge bg-light text-secondary border small">' + escHtml(item.variant_info) + '</span> ' : ''; return '<div class="d-flex align-items-center mb-3 pb-3 border-bottom">' + imgTag + '<div class="flex-grow-1">' + '<div class="fw-semibold small">' + escHtml(item.title) + '</div>' + variantTag + '<div class="small text-muted">SKU: ' + escHtml(item.sku || '-') + ' • x' + item.quantity + '</div>' + '</div>' + '<div class="text-end ms-2">' + '<div class="fw-bold text-success">฿' + parseFloat(item.subtotal).toLocaleString('th-TH', {minimumFractionDigits: 2}) + '</div>' + '<div class="small text-muted">฿' + parseFloat(item.price).toLocaleString('th-TH', {minimumFractionDigits: 2}) + ' / ชิ้น</div>' + '</div>' + '</div>'; }).join(''); document.getElementById('items-container').innerHTML = itemsHtml; renderHistory(history); document.getElementById('modal-spinner').classList.add('d-none'); document.getElementById('modal-content').classList.remove('d-none'); } // ── Quick Action Button function renderQuickAction(status) { const area = document.getElementById('quick-action-area'); const action = NEXT_ACTIONS[status]; if (!action) { area.innerHTML = ''; return; } area.innerHTML = '<button type="button" class="btn ' + action.btnClass + ' rounded-pill px-4 fw-bold" id="btn-quick-action">' + action.label + '</button>'; document.getElementById('btn-quick-action').addEventListener('click', function () { if (action.needsShipping) { const comp = document.getElementById('input-shipping-company').value; const track = (document.getElementById('input-tracking-number').value || '').trim(); if (!comp) { showToast('กรุณาเลือกบริษัทขนส่ง', 'error'); return; } if (!track) { showToast('กรุณากรอกเลขพัสดุ (Tracking Number)', 'error'); return; } } pendingAction = { type: 'status', nextStatus: action.next }; document.getElementById('confirm-message').textContent = 'คุณต้องการเปลี่ยนสถานะออเดอร์ #' + currentOrderData.order.order_number + ' เป็น "' + (STATUS_CONFIG[action.next]?.label || action.next) + '" ใช่ไหม?'; confirmModal.show(); }); } // ── History Timeline function renderHistory(history) { const container = document.getElementById('history-container'); if (!history || history.length === 0) { container.innerHTML = '<p class="text-muted small">ยังไม่มีประวัติสถานะ</p>'; return; } const dotMap = { shipping: 'ship', completed: 'done', delivered: 'done', cancelled: 'cancel', refunded: 'cancel' }; container.innerHTML = '<div class="timeline">' + history.map(h => { const dot = dotMap[h.status_key] || ''; const dt = new Date(h.event_time).toLocaleString('th-TH'); const desc = h.description ? '<span class="text-muted small d-block">' + escHtml(h.description) + '</span>' : ''; return '<div class="timeline-item">' + '<div class="timeline-dot ' + dot + '"></div>' + '<div class="small text-muted">' + dt + '</div>' + '<div class="fw-semibold small">' + escHtml(h.status_name || h.status_key) + '</div>' + desc + '<div class="text-muted" style="font-size:.72rem;">Updated by: ' + escHtml(h.changed_by || 'Seller') + '</div>' + '</div>'; }).join('') + '</div>'; } // ── AJAX Status Update async function doUpdateStatus(newStatus, note) { note = note || ''; const body = new URLSearchParams({ csrf_token: CSRF_TOKEN, status: newStatus, note: note }); if (newStatus === 'shipping') { body.append('shipping_company', document.getElementById('input-shipping-company').value); body.append('tracking_number', (document.getElementById('input-tracking-number').value || '').trim()); } try { const resp = await fetch(APP_URL + '/seller/orders/update-status/' + currentOrderId, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body }); if (!resp.ok) { showToast('API Error: HTTP ' + resp.status, 'error'); return; } const data = await resp.json(); if (data.status !== 'success') { showToast(data.message || 'เกิดข้อผิดพลาด', 'error'); return; } // Update table row badge const sc = STATUS_CONFIG[data.order.status] || { label: data.order.status, badge: 'badge-cancelled', icon: 'bi-question' }; const rowBadge = document.getElementById('status-badge-' + currentOrderId); if (rowBadge) { rowBadge.className = 'badge px-3 py-2 rounded-pill ' + sc.badge; rowBadge.innerHTML = '<i class="bi ' + sc.icon + ' me-1"></i>' + sc.label; } // Update summary card counts if (data.summary) { const map = { '#sc-new-cnt': data.summary.new_unread_cnt, 'a[href="?status=new"] .sc-num': data.summary.new_unread_cnt, 'a[href="?status=preparing_all"] .sc-num': data.summary.preparing_cnt, 'a[href="?status=shipping"] .sc-num': data.summary.shipping_cnt, 'a[href="?status=delivered"] .sc-num': data.summary.delivered_cnt, 'a[href="?status=cancelled"] .sc-num': data.summary.cancelled_cnt, }; Object.entries(map).forEach(([sel, val]) => { const el = document.querySelector(sel); if (el) el.textContent = val || 0; }); } currentOrderData = { order: data.order, items: currentOrderData.items, history: data.history }; renderModalContent(currentOrderData); showToast(data.message || 'อัปเดตสถานะสำเร็จ', 'success'); } catch (err) { console.error('Status update error:', err); showToast('ไม่สามารถอัปเดตสถานะได้', 'error'); } } // ── Confirm Action button document.getElementById('btn-confirm-action').addEventListener('click', async function () { confirmModal.hide(); if (!pendingAction) return; if (pendingAction.type === 'status') { await doUpdateStatus(pendingAction.nextStatus, ''); } else if (pendingAction.type === 'cancel') { const reason = (document.getElementById('cancel-reason').value || '').trim(); await doUpdateStatus('cancelled', reason); } pendingAction = null; }); // ── Manual Status Update const btnManualStatus = document.getElementById('btn-update-manual-status'); if (btnManualStatus) { btnManualStatus.addEventListener('click', function () { const selectedStatus = document.getElementById('manual-status-select').value; if (!selectedStatus) return; if (selectedStatus === 'shipping') { const comp = document.getElementById('input-shipping-company').value; const track = (document.getElementById('input-tracking-number').value || '').trim(); if (!comp) { showToast('กรุณาเลือกบริษัทขนส่ง', 'error'); return; } if (!track) { showToast('กรุณากรอกเลขพัสดุ (Tracking Number)', 'error'); return; } } const targetLabel = (STATUS_CONFIG[selectedStatus] || {}).label || selectedStatus; pendingAction = { type: 'status', nextStatus: selectedStatus }; document.getElementById('confirm-message').textContent = 'คุณต้องการเปลี่ยนสถานะออเดอร์ #' + currentOrderData.order.order_number + ' เป็น "' + targetLabel + '" ใช่ไหม?'; confirmModal.show(); }); } // ── Save Shipping Info const btnSaveShipping = document.getElementById('btn-save-shipping'); if (btnSaveShipping) { btnSaveShipping.addEventListener('click', async function () { const comp = document.getElementById('input-shipping-company').value; const track = (document.getElementById('input-tracking-number').value || '').trim(); if (!comp && !track) { showToast('กรุณากรอกข้อมูลการจัดส่งก่อน', 'error'); return; } const body = new URLSearchParams({ csrf_token: CSRF_TOKEN, shipping_company: comp, tracking_number: track }); try { const resp = await fetch(APP_URL + '/seller/orders/update-shipping/' + currentOrderId, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body }); const data = await resp.json(); if (data.status !== 'success') { showToast(data.message || 'เกิดข้อผิดพลาด', 'error'); return; } // Re-render Tracking info in modal with clickable tracking link if (typeof renderTrackingDisplay === 'function') { renderTrackingDisplay(data.order.shipping_company, data.order.tracking_number, data.tracking_url); } else { const infoC = document.getElementById('info-shipping-company'); if (infoC) infoC.textContent = data.order.shipping_company || 'ยังไม่ได้ระบุ'; } currentOrderData.order = data.order; currentOrderData.history = data.history; renderHistory(data.history); showToast('บันทึกข้อมูลจัดส่งสำเร็จ', 'success'); } catch (err) { showToast('ไม่สามารถบันทึกข้อมูลได้', 'error'); } }); } // ── Cancel Order const btnCancelOrder = document.getElementById('btn-cancel-order'); if (btnCancelOrder) { btnCancelOrder.addEventListener('click', function () { pendingAction = { type: 'cancel' }; document.getElementById('confirm-message').textContent = 'คุณต้องการยกเลิกคำสั่งซื้อ #' + (currentOrderData?.order?.order_number || '') + ' ใช่ไหม? การกระทำนี้ไม่สามารถย้อนกลับได้'; document.getElementById('btn-confirm-action').className = 'btn btn-danger rounded-pill px-4'; confirmModal.show(); }); } // ── Reset confirm button style on close confirmModalEl.addEventListener('hidden.bs.modal', function () { document.getElementById('btn-confirm-action').className = 'btn btn-primary rounded-pill px-4'; }); // ── Real-time New Order Polling in Orders Page setInterval(function() { fetch(APP_URL + '/seller/api/check-new-orders', { headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(res => res.json()) .then(data => { if (!data.success) return; const count = parseInt(data.count) || 0; const sideBadge = document.getElementById('sellerSidebarOrderBadge'); const navBadge = document.getElementById('sellerNavOrderBadge'); const scNew = document.getElementById('sc-new-cnt'); if (sideBadge) { sideBadge.textContent = count; sideBadge.classList.toggle('d-none', count <= 0); } if (navBadge) { navBadge.textContent = count; navBadge.classList.toggle('d-none', count <= 0); } if (scNew) { scNew.textContent = count; } }) .catch(e => console.log('Polling check error:', e)); }, 15000); }); // end window.load </script>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings