File manager - Edit - /home/webapp69.cm.in.th/u69319090039/Shop39/public/assets/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"> <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> </div> <!-- ══════════════════════════════════════════════ --> <!-- Summary Cards --> <!-- ══════════════════════════════════════════════ --> <div class="row g-3 mb-4"> <div class="col-6 col-md col-lg"> <a href="?status=pending" class="summary-card card border-0 shadow-sm bg-warning bg-opacity-10 text-warning-emphasis"> <div class="sc-num"><?= (int)($summary['pending_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-clock me-1"></i>Pending</div> </a> </div> <div class="col-6 col-md col-lg"> <a href="?status=preparing_all" class="summary-card card border-0 shadow-sm bg-warning text-dark bg-opacity-75"> <div class="sc-num"><?= (int)($summary['preparing_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-box-seam me-1"></i>Preparing</div> </a> </div> <div class="col-6 col-md col-lg"> <a href="?status=shipping" class="summary-card card border-0 shadow-sm" style="background:#6f42c1;color:#fff;"> <div class="sc-num"><?= (int)($summary['shipping_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-truck me-1"></i>Shipping</div> </a> </div> <div class="col-6 col-md col-lg"> <a href="?status=delivered" class="summary-card card border-0 shadow-sm bg-success text-white"> <div class="sc-num"><?= (int)($summary['delivered_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-box-arrow-in-down me-1"></i>Delivered</div> </a> </div> <div class="col-6 col-md col-lg"> <a href="?status=completed" class="summary-card card border-0 shadow-sm text-white" style="background:#0a3622;"> <div class="sc-num"><?= (int)($summary['completed_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-patch-check-fill me-1"></i>Completed</div> </a> </div> <div class="col-6 col-md col-lg"> <a href="?status=cancelled" class="summary-card card border-0 shadow-sm bg-danger text-white"> <div class="sc-num"><?= (int)($summary['cancelled_cnt'] ?? 0) ?></div> <div class="sc-lbl"><i class="bi bi-x-circle me-1"></i>Cancelled</div> </a> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- Search & Filter --> <!-- ══════════════════════════════════════════════ --> <div class="card border-0 shadow-sm rounded-3 mb-4"> <div class="card-body p-3"> <form method="GET" action="" class="d-flex flex-wrap gap-2 align-items-center"> <div class="search-bar input-group flex-grow-1"> <span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span> <input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="ค้นหาเลขออเดอร์ / ชื่อลูกค้า / Tracking..." value="<?= Security::e($search ?? '') ?>"> </div> <select name="status" class="form-select" style="max-width:220px;" onchange="this.form.submit()"> <option value="all" <?= (($statusFilter ?? 'all') === 'all') ? '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' : '' ?>>Preparing Order</option> <option value="shipping" <?= (($statusFilter ?? '') === 'shipping') ? 'selected' : '' ?>>Shipping</option> <option value="delivered" <?= (($statusFilter ?? '') === 'delivered') ? 'selected' : '' ?>>Delivered</option> <option value="completed" <?= (($statusFilter ?? '') === 'completed') ? 'selected' : '' ?>>Completed</option> <option value="cancelled" <?= (($statusFilter ?? '') === 'cancelled') ? 'selected' : '' ?>>Cancelled</option> </select> <button type="submit" class="btn btn-primary px-4 rounded-pill"><i class="bi bi-funnel me-1"></i>กรอง</button> <a href="/seller/orders" class="btn btn-outline-secondary rounded-pill"><i class="bi bi-arrow-clockwise"></i></a> </form> </div> </div> <!-- ══════════════════════════════════════════════ --> <!-- Orders Table --> <!-- ══════════════════════════════════════════════ --> <div class="card border-0 shadow-sm rounded-3"> <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"></i> <h5 class="mt-3 text-secondary">ไม่พบคำสั่งซื้อที่ตรงกัน</h5> <a href="/seller/orders" class="btn btn-outline-primary mt-2 rounded-pill">ดูคำสั่งซื้อทั้งหมด</a> </div> <?php else: ?> <div class="table-responsive"> <table class="table table-hover align-middle mb-0"> <thead class="table-light border-bottom"> <tr> <th class="ps-4">หมายเลขออเดอร์</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']; ?> <tr class="order-row" id="order-row-<?= $ord['id'] ?>"> <td class="ps-4 fw-bold text-primary">#<?= Security::e($ord['order_number']) ?></td> <td><?= Security::e($ord['customer_name']) ?></td> <td class="text-muted small"><?= Security::e($ord['customer_phone'] ?? '-') ?></td> <td class="fw-bold text-success">฿<?= number_format($ord['total_amount'], 2) ?></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 me-1 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> <div class="p-3 text-muted small border-top">แสดง <?= count($orders) ?> รายการ</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">กำลังโหลดข้อมูลออเดอร์...</p> </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-2"> <div class="col-6"> <div class="text-muted">บริษัทขนส่ง</div> <div class="fw-bold" id="info-shipping-company">-</div> </div> <div class="col-6"> <div class="text-muted">เลขพัสดุ (Tracking)</div> <div class="fw-bold text-primary" id="info-tracking-number">-</div> </div> </div> <!-- Edit Shipping Form (always visible for seller) --> <div id="shipping-form-area"> <hr class="my-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="Kerry Express">Kerry Express</option> <option value="Flash Express">Flash Express</option> <option value="J&T Express">J&T Express</option> <option value="Ninja Van">Ninja Van</option> <option value="DHL Express">DHL Express</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>บันทึกข้อมูลจัดส่ง </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; } document.getElementById('modal-spinner').classList.remove('d-none'); document.getElementById('modal-content').classList.add('d-none'); document.getElementById('modal-order-number').textContent = ''; manageModal.show(); await loadOrderDetail(currentOrderId); }); // ── Load order detail via AJAX async function loadOrderDetail(orderId) { try { const url = APP_URL + '/seller/orders/detail/' + orderId; const resp = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!resp.ok) { showToast('ไม่สามารถโหลดออเดอร์ได้ (HTTP ' + resp.status + ')', 'error'); return; } const text = await resp.text(); let data; try { data = JSON.parse(text); } catch (jsonErr) { console.error('Invalid JSON:', text.substring(0, 300)); showToast('API ส่งข้อมูลไม่ถูกต้อง', 'error'); return; } if (data.status !== 'success') { showToast(data.message || 'เกิดข้อผิดพลาด', 'error'); return; } currentOrderData = data; renderModalContent(data); } catch (err) { console.error('Fetch error:', err); showToast('ไม่สามารถเชื่อมต่อ API ได้', 'error'); } } // ── Render Modal Content function renderModalContent(data) { const o = data.order; const items = data.items || []; const history = data.history || []; 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 = 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 || '-'; document.getElementById('info-customer-email').textContent = o.customer_email || '-'; document.getElementById('info-total').textContent = '฿' + parseFloat(o.total_amount).toLocaleString('th-TH', {minimumFractionDigits: 2}); document.getElementById('info-address').textContent = o.shipping_address || '-'; // Shipping info const infoCompany = document.getElementById('info-shipping-company'); const infoTracking = document.getElementById('info-tracking-number'); if (infoCompany) infoCompany.textContent = o.shipping_company || '-'; if (infoTracking) infoTracking.textContent = o.tracking_number || '-'; const scSelect = document.getElementById('input-shipping-company'); if (scSelect && o.shipping_company) { [...scSelect.options].forEach(opt => { opt.selected = (opt.value === 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 = { 'a[href="?status=pending"] .sc-num': data.summary.pending_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=completed"] .sc-num': data.summary.completed_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; } const infoC = document.getElementById('info-shipping-company'); const infoT = document.getElementById('info-tracking-number'); if (infoC) infoC.textContent = data.order.shipping_company || '-'; if (infoT) infoT.textContent = data.order.tracking_number || '-'; 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'; }); }); // end window.load </script>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.46 |
proxy
|
phpinfo
|
Settings