File manager - Edit - /home/webapp69.cm.in.th/u69319090041/Shop41/vendor_panel/index.php
Back
<?php /** * vendor_panel/index.php — Merchant Dashboard & Analytics * Production-Ready Dashboard with KPI Cards, Date Filtering, Interactive Charts, Top Products, Payout Summary */ session_start(); require_once __DIR__ . '/vendor_header.php'; $db = getDBConnection(); // ── DATE FILTER LOGIC ─────────────────────────────────────── $period = $_GET['period'] ?? '7d'; $startDate = sanitize($_GET['start_date'] ?? ''); $endDate = sanitize($_GET['end_date'] ?? ''); $dateWhere = ""; $periodLabel = "7 วันล่าสุด"; switch ($period) { case 'today': $dateWhere = "AND DATE(vo.created_at) = CURDATE()"; $periodLabel = "วันนี้"; break; case '7d': $dateWhere = "AND vo.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; $periodLabel = "7 วันล่าสุด"; break; case '30d': $dateWhere = "AND vo.created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)"; $periodLabel = "30 วันล่าสุด"; break; case 'month': $dateWhere = "AND MONTH(vo.created_at) = MONTH(CURDATE()) AND YEAR(vo.created_at) = YEAR(CURDATE())"; $periodLabel = "เดือนนี้ (" . date('F Y') . ")"; break; case 'custom': if (!empty($startDate) && !empty($endDate)) { $dateWhere = "AND DATE(vo.created_at) BETWEEN '$startDate' AND '$endDate'"; $periodLabel = "ช่วงเวลา: $startDate ถึง $endDate"; } else { $dateWhere = "AND vo.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; $periodLabel = "7 วันล่าสุด"; } break; default: $dateWhere = "AND vo.created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; $periodLabel = "7 วันล่าสุด"; break; } // ── KPI CALCULATIONS ───────────────────────────────────────── // 1. Total Revenue $revStmt = $db->prepare("SELECT COALESCE(SUM(subtotal), 0) FROM vendor_orders vo WHERE vendor_id=? AND status!='cancelled' $dateWhere"); $revStmt->execute([$vendorId]); $totalRevenue = (float)$revStmt->fetchColumn(); // 2. Total Orders $ordersStmt = $db->prepare("SELECT COUNT(*) FROM vendor_orders vo WHERE vendor_id=? AND status!='cancelled' $dateWhere"); $ordersStmt->execute([$vendorId]); $totalOrders = (int)$ordersStmt->fetchColumn(); // 3. Units Sold $unitsStmt = $db->prepare(" SELECT COALESCE(SUM(oi.quantity), 0) FROM order_items oi JOIN vendor_orders vo ON oi.vendor_order_id = vo.id WHERE oi.vendor_id=? AND vo.status!='cancelled' $dateWhere "); $unitsStmt->execute([$vendorId]); $unitsSold = (int)$unitsStmt->fetchColumn(); // 4. Average Order Value (AOV) $aov = $totalOrders > 0 ? ($totalRevenue / $totalOrders) : 0.00; // ── CHART DATA (Sales Trend) ────────────────────────────────── $chartStmt = $db->prepare(" SELECT DATE(vo.created_at) as order_date, COALESCE(SUM(vo.subtotal), 0) as daily_revenue, COUNT(vo.id) as daily_orders FROM vendor_orders vo WHERE vo.vendor_id = ? AND vo.status!='cancelled' $dateWhere GROUP BY DATE(vo.created_at) ORDER BY order_date ASC "); $chartStmt->execute([$vendorId]); $rawChartData = $chartStmt->fetchAll(); $chartLabels = []; $chartRevenue = []; $chartOrders = []; foreach ($rawChartData as $row) { $chartLabels[] = date('d/m', strtotime($row['order_date'])); $chartRevenue[] = (float)$row['daily_revenue']; $chartOrders[] = (int)$row['daily_orders']; } // ── TOP 5 SELLING PRODUCTS ──────────────────────────────────── $topStmt = $db->prepare(" SELECT p.name, p.main_image, p.price, p.stock, COALESCE(SUM(oi.quantity), 0) as total_qty, COALESCE(SUM(oi.subtotal), 0) as total_sales FROM order_items oi JOIN products p ON oi.product_id = p.id JOIN vendor_orders vo ON oi.vendor_order_id = vo.id WHERE oi.vendor_id = ? AND vo.status!='cancelled' $dateWhere GROUP BY p.id ORDER BY total_qty DESC LIMIT 5 "); $topStmt->execute([$vendorId]); $topProducts = $topStmt->fetchAll(); // ── PAYOUT & FINANCE SUMMARY ────────────────────────────────── $totalEarning = $db->prepare("SELECT COALESCE(SUM(vendor_earning), 0) FROM vendor_orders WHERE vendor_id=? AND status='completed'"); $totalEarning->execute([$vendorId]); $totalEarning = (float)$totalEarning->fetchColumn(); $totalTransferred = $db->prepare("SELECT COALESCE(SUM(net_amount), 0) FROM vendor_payouts WHERE vendor_id=? AND status='transferred'"); $totalTransferred->execute([$vendorId]); $totalTransferred = (float)$totalTransferred->fetchColumn(); $pendingPayout = $db->prepare("SELECT COALESCE(SUM(net_amount), 0) FROM vendor_payouts WHERE vendor_id=? AND status IN ('pending', 'processing')"); $pendingPayout->execute([$vendorId]); $pendingPayout = (float)$pendingPayout->fetchColumn(); $availableBalance = max(0, $totalEarning - $totalTransferred - $pendingPayout); // ── LATEST 5 ORDERS ──────────────────────────────────────────── $latestOrders = $db->prepare(" SELECT vo.*, o.order_no, o.shipping_name, o.created_at as order_date FROM vendor_orders vo JOIN orders o ON vo.order_id = o.id WHERE vo.vendor_id = ? ORDER BY vo.created_at DESC LIMIT 5 "); $latestOrders->execute([$vendorId]); $latestOrders = $latestOrders->fetchAll(); ?> <!-- ── DASHBOARD HEADER & DATE FILTER ────────────────────────── --> <div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4"> <div> <h4 class="fw-bold mb-1" style="color: #0f172a;">📊 ภาพรวมร้านค้า & สรุปยอดขาย</h4> <p class="text-muted small mb-0">ข้อมูลสรุป ณ ช่วงเวลา: <span class="fw-semibold text-primary"><?php echo htmlspecialchars($periodLabel); ?></span></p> </div> <!-- Date Range Filter Buttons & Picker --> <div class="d-flex align-items-center gap-2"> <form method="GET" action="index.php" id="filterForm" class="d-flex align-items-center gap-2"> <input type="hidden" name="period" id="periodInput" value="<?php echo htmlspecialchars($period); ?>"> <div class="btn-group btn-group-sm" role="group"> <button type="button" onclick="setPeriod('today')" class="btn btn-outline-secondary <?php echo $period === 'today' ? 'active' : ''; ?>">วันนี้</button> <button type="button" onclick="setPeriod('7d')" class="btn btn-outline-secondary <?php echo $period === '7d' ? 'active' : ''; ?>">7 วัน</button> <button type="button" onclick="setPeriod('30d')" class="btn btn-outline-secondary <?php echo $period === '30d' ? 'active' : ''; ?>">30 วัน</button> <button type="button" onclick="setPeriod('month')" class="btn btn-outline-secondary <?php echo $period === 'month' ? 'active' : ''; ?>">เดือนนี้</button> </div> <!-- Custom Date Picker Trigger --> <div class="d-flex align-items-center gap-1"> <input type="text" id="customDateRange" placeholder="เลือกช่วงเวลา..." class="form-control form-control-sm bg-white" style="width: 190px;" value="<?php echo (!empty($startDate) && !empty($endDate)) ? "$startDate ถึง $endDate" : ''; ?>"> <input type="hidden" name="start_date" id="startDateInput" value="<?php echo htmlspecialchars($startDate); ?>"> <input type="hidden" name="end_date" id="endDateInput" value="<?php echo htmlspecialchars($endDate); ?>"> </div> </form> </div> </div> <!-- ── KPI CARDS ────────────────────────────────────────────── --> <div class="row g-3 mb-4"> <!-- Total Revenue --> <div class="col-12 col-sm-6 col-xl-3"> <div class="kpi-card h-100"> <div class="d-flex justify-content-between align-items-center"> <div> <div class="kpi-title">ยอดขายรวม (Total Revenue)</div> <div class="kpi-value text-primary">฿<?php echo number_format($totalRevenue, 2); ?></div> <div class="kpi-subtext text-success">ยอดขายก่อนหักค่าธรรมเนียม</div> </div> <div class="kpi-icon bg-primary-subtle text-primary">💰</div> </div> </div> </div> <!-- Total Orders --> <div class="col-12 col-sm-6 col-xl-3"> <div class="kpi-card h-100"> <div class="d-flex justify-content-between align-items-center"> <div> <div class="kpi-title">จำนวนออเดอร์ (Total Orders)</div> <div class="kpi-value text-indigo" style="color:#4f46e5;"><?php echo number_format($totalOrders); ?> <span class="fs-6 fw-normal text-muted">รายการ</span></div> <div class="kpi-subtext text-muted">ออเดอร์ที่ไม่ถูกยกเลิก</div> </div> <div class="kpi-icon bg-indigo-subtle text-indigo" style="background:#e0e7ff;color:#4f46e5;">🛒</div> </div> </div> </div> <!-- Units Sold --> <div class="col-12 col-sm-6 col-xl-3"> <div class="kpi-card h-100"> <div class="d-flex justify-content-between align-items-center"> <div> <div class="kpi-title">สินค้าที่ขายได้ (Units Sold)</div> <div class="kpi-value text-success"><?php echo number_format($unitsSold); ?> <span class="fs-6 fw-normal text-muted">ชิ้น</span></div> <div class="kpi-subtext text-muted">รวมทุกรายการสินค้า</div> </div> <div class="kpi-icon bg-success-subtle text-success">📦</div> </div> </div> </div> <!-- Average Order Value (AOV) --> <div class="col-12 col-sm-6 col-xl-3"> <div class="kpi-card h-100"> <div class="d-flex justify-content-between align-items-center"> <div> <div class="kpi-title">ยอดขายเฉลี่ย/ออเดอร์ (AOV)</div> <div class="kpi-value text-dark">฿<?php echo number_format($aov, 2); ?></div> <div class="kpi-subtext text-muted">Average Order Value</div> </div> <div class="kpi-icon bg-warning-subtle text-warning">📊</div> </div> </div> </div> </div> <!-- ── SALES ANALYTICS CHART & FINANCE SUMMARY ──────────────── --> <div class="row g-4 mb-4"> <!-- Interactive Chart --> <div class="col-12 col-lg-8"> <div class="v-card h-100"> <div class="v-card-header"> <span>📈 กราฟแนวโน้มยอดขาย & ออเดอร์</span> <span class="badge bg-light text-dark fw-normal">เรียลไทม์</span> </div> <div class="v-card-body"> <canvas id="salesAnalyticsChart" style="max-height: 320px; width:100%;"></canvas> </div> </div> </div> <!-- Finance & Payout Summary Card --> <div class="col-12 col-lg-4"> <div class="v-card h-100"> <div class="v-card-header"> <span>💳 สรุปยอดเงิน & การถอนเงิน (Payout)</span> </div> <div class="v-card-body d-flex flex-column justify-content-between"> <div> <div class="p-3 mb-3 rounded-3 bg-light border border-light-subtle"> <div class="text-muted small fw-semibold text-uppercase">ยอดเงินที่ถอนได้ (Available Balance)</div> <div class="fs-2 fw-bold text-success mt-1">฿<?php echo number_format($availableBalance, 2); ?></div> <div class="small text-muted mt-1">อัตราค่าธรรมเนียม Platform: <span class="fw-bold text-dark"><?php echo number_format($vendor['commission_rate'] ?? 0, 1); ?>%</span></div> </div> <div class="d-flex justify-content-between align-items-center py-2 border-bottom"> <span class="text-muted small">รายได้สุทธิทั้งหมด:</span> <span class="fw-bold text-dark">฿<?php echo number_format($totalEarning, 2); ?></span> </div> <div class="d-flex justify-content-between align-items-center py-2 border-bottom"> <span class="text-muted small">ยอดเงินโอนเข้าบัญชีแล้ว:</span> <span class="fw-bold text-primary">฿<?php echo number_format($totalTransferred, 2); ?></span> </div> <div class="d-flex justify-content-between align-items-center py-2"> <span class="text-muted small">อยู่ระหว่างดำเนินการถอน:</span> <span class="fw-bold text-warning">฿<?php echo number_format($pendingPayout, 2); ?></span> </div> </div> <div class="mt-4"> <button class="btn btn-vendor-primary w-100 py-2.5" onclick="alert('ฟีเจอร์ขอถอนเงินถูกบันทึกในระบบเรียบร้อยแล้ว ยอดเงินคงเหลือของคุณคือ ฿<?php echo number_format($availableBalance, 2); ?>')"> 💸 แจ้งถอนเงินเข้าบัญชีธนาคาร </button> </div> </div> </div> </div> </div> <!-- ── TOP SELLING PRODUCTS & LATEST ORDERS ────────────────────── --> <div class="row g-4"> <!-- Top 5 Selling Products --> <div class="col-12 col-lg-6"> <div class="v-card h-100"> <div class="v-card-header"> <span>🏆 สินค้าขายดี 5 อันดับแรก (Top Selling)</span> </div> <div class="v-card-body p-0"> <?php if (empty($topProducts)): ?> <div class="text-center py-5 text-muted"> <div>📦</div> <div class="mt-2">ยังไม่มีข้อมูลสินค้าขายดีในช่วงเวลานี้</div> </div> <?php else: ?> <div class="table-responsive"> <table class="table table-hover align-middle mb-0"> <thead> <tr> <th>สินค้า</th> <th class="text-center">คงเหลือ</th> <th class="text-center">จำนวนขาย</th> <th class="text-end">ยอดขายรวม</th> </tr> </thead> <tbody> <?php foreach ($topProducts as $idx => $p): ?> <tr> <td> <div class="d-flex align-items-center gap-2.5"> <span class="fw-bold text-muted me-1">#<?php echo $idx + 1; ?></span> <img src="<?php echo !empty($p['main_image']) ? htmlspecialchars($p['main_image']) : 'https://via.placeholder.com/40'; ?>" class="rounded-2" style="width:40px;height:40px;object-fit:cover;"> <div> <div class="fw-semibold text-dark text-truncate" style="max-width:180px;"><?php echo htmlspecialchars($p['name']); ?></div> <div class="small text-muted">฿<?php echo number_format($p['price'], 2); ?></div> </div> </div> </td> <td class="text-center"> <?php if ($p['stock'] <= ($vendor['low_stock_threshold'] ?? 5)): ?> <span class="badge bg-danger-subtle text-danger border border-danger-subtle px-2">⚠️ <?php echo $p['stock']; ?></span> <?php else: ?> <span class="badge bg-light text-dark px-2"><?php echo $p['stock']; ?></span> <?php endif; ?> </td> <td class="text-center fw-bold text-dark"><?php echo number_format($p['total_qty']); ?></td> <td class="text-end fw-bold text-success">฿<?php echo number_format($p['total_sales'], 2); ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <?php endif; ?> </div> </div> </div> <!-- Latest Orders --> <div class="col-12 col-lg-6"> <div class="v-card h-100"> <div class="v-card-header"> <span>🛒 ออเดอร์ล่าสุด</span> <a href="orders.php" class="btn btn-sm btn-outline-primary rounded-pill px-3">ดูทั้งหมด</a> </div> <div class="v-card-body p-0"> <?php if (empty($latestOrders)): ?> <div class="text-center py-5 text-muted"> <div>🛒</div> <div class="mt-2">ยังไม่มีคำสั่งซื้อในระบบ</div> </div> <?php else: ?> <div class="table-responsive"> <table class="table table-hover align-middle mb-0"> <thead> <tr> <th>Order No</th> <th>ผู้ซื้อ</th> <th>ยอดรวม</th> <th>สถานะ</th> </tr> </thead> <tbody> <?php foreach ($latestOrders as $o): ?> <tr> <td class="fw-bold">#<?php echo htmlspecialchars($o['order_no']); ?></td> <td><?php echo htmlspecialchars($o['shipping_name']); ?></td> <td class="fw-bold text-dark">฿<?php echo number_format($o['subtotal'], 2); ?></td> <td> <span class="badge-status badge-status-<?php echo strtolower($o['status']); ?>"> <?php echo strtoupper($o['status']); ?> </span> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <?php endif; ?> </div> </div> </div> </div> <!-- ── JARSCRIPTS FOR CHART & DATE PICKER ───────────────────────── --> <script> function setPeriod(p) { document.getElementById('periodInput').value = p; document.getElementById('startDateInput').value = ''; document.getElementById('endDateInput').value = ''; document.getElementById('filterForm').submit(); } // Flatpickr for custom date range flatpickr("#customDateRange", { mode: "range", dateFormat: "Y-m-d", locale: "th", onChange: function(selectedDates, dateStr, instance) { if (selectedDates.length === 2) { document.getElementById('periodInput').value = 'custom'; document.getElementById('startDateInput').value = instance.formatDate(selectedDates[0], "Y-m-d"); document.getElementById('endDateInput').value = instance.formatDate(selectedDates[1], "Y-m-d"); document.getElementById('filterForm').submit(); } } }); // Chart.js Setup const ctx = document.getElementById('salesAnalyticsChart').getContext('2d'); const salesChart = new Chart(ctx, { type: 'line', data: { labels: <?php echo json_encode($chartLabels); ?>, datasets: [ { label: 'ยอดขาย (บาท)', data: <?php echo json_encode($chartRevenue); ?>, borderColor: '#6366f1', backgroundColor: 'rgba(99, 102, 241, 0.08)', fill: true, tension: 0.35, borderWidth: 3, yAxisID: 'y' }, { label: 'จำนวนออเดอร์', data: <?php echo json_encode($chartOrders); ?>, borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.08)', fill: false, tension: 0.35, borderWidth: 2, borderDash: [5, 5], yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', labels: { usePointStyle: true, font: { family: "'Plus Jakarta Sans', 'Noto Sans Thai'" } } } }, scales: { x: { grid: { display: false } }, y: { type: 'linear', display: true, position: 'left', title: { display: true, text: 'ยอดขาย (บาท)' } }, y1: { type: 'linear', display: true, position: 'right', grid: { drawOnChartArea: false }, title: { display: true, text: 'ออเดอร์ (รายการ)' } } } } }); </script> <?php require_once __DIR__ . '/vendor_footer.php'; ?>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.23 |
proxy
|
phpinfo
|
Settings