File manager - Edit - /home/webapp69.cm.in.th/u69319090041/Shop41/vendor_panel/vendor_header.php
Back
<?php /** * vendor_panel/vendor_header.php * Shared header + sidebar สำหรับ Production-Ready Vendor Dashboard */ // ─── PHP 7.x Polyfills (รองรับ PHP เวอร์ชันต่ำกว่า 8.0) ─────────────────────── if (!function_exists('str_starts_with')) { function str_starts_with($haystack, $needle) { return $needle !== '' ? strncmp($haystack, $needle, strlen($needle)) === 0 : true; } } if (!function_exists('str_ends_with')) { function str_ends_with($haystack, $needle) { if ($needle === '') return true; $len = strlen($needle); return substr($haystack, -$len) === $needle; } } if (!function_exists('str_contains')) { function str_contains($haystack, $needle) { return $needle !== '' ? strpos($haystack, $needle) !== false : true; } } // ─── Flexible Config Path Detection ───────────────────────────────────────── // ตรวจสอบ path แบบยืดหยุ่น รองรับทั้งกรณีไฟล์อยู่ใน vendor_panel/ (1 ชั้น) หรือ vendor_panel/sub/ (2 ชั้น) $_configPaths = [ __DIR__ . '/../config/config.php', __DIR__ . '/../../config/config.php', ]; foreach ($_configPaths as $_cp) { if (file_exists($_cp)) { require_once $_cp; break; } } // ตรวจสอบ middleware path แบบยืดหยุ่น $_middlewarePaths = [ __DIR__ . '/../includes/middleware.php', __DIR__ . '/../../includes/middleware.php', ]; foreach ($_middlewarePaths as $_mp) { if (file_exists($_mp)) { require_once $_mp; break; } } // ─── Safe Echo Helper ──────────────────────────────────────────────────────── if (!function_exists('safe_echo')) { function safe_echo($val) { return htmlspecialchars((string)($val ?? ''), ENT_QUOTES, 'UTF-8'); } } // ─── Auth Guard ────────────────────────────────────────────────────────────── if (function_exists('requireVendor')) { requireVendor(); // ต้อง login + approved vendor เท่านั้น } elseif (!isset($_SESSION['user']) || ($_SESSION['user']['role'] ?? '') !== 'vendor') { $siteUrl = defined('SITE_URL') ? SITE_URL : ''; header('Location: ' . $siteUrl . '/public/login.php'); exit; } // ─── Vendor Info ───────────────────────────────────────────────────────────── $vendor = []; $vendorId = 0; if (function_exists('getCurrentVendorData')) { $vendor = getCurrentVendorData(); } if (function_exists('getCurrentVendorId')) { $vendorId = getCurrentVendorId(); } // Fallback: ดึงจาก session ถ้าไม่มีฟังก์ชัน if (empty($vendor) && isset($_SESSION['user'])) { $vendor = $_SESSION['user']; $vendorId = (int)($_SESSION['user']['id'] ?? 0); } $currentPage = basename($_SERVER['SCRIPT_NAME'] ?? 'index.php'); // ─── Database Connection (Flexible Fallback) ───────────────────────────────── $pdo = null; try { if (function_exists('getDBConnection')) { $pdo = getDBConnection(); } elseif (defined('DB_HOST') && defined('DB_NAME') && defined('DB_USER') && defined('DB_PASS')) { // Fallback: สร้าง PDO connection จาก constants โดยตรง $pdo = new PDO( 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASS, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] ); } } catch (PDOException $e) { // ไม่ Kill หน้าเว็บ เพียงแค่ log และข้ามส่วน DB ไป error_log('[vendor_header] DB Connection Error: ' . $e->getMessage()); $pdo = null; } // ─── Sidebar Counts ─────────────────────────────────────────────────────────── $pendingOrderCount = 0; $lowStockCount = 0; $lowStockThreshold = (int)($vendor['low_stock_threshold'] ?? 5); if ($pdo && $vendorId > 0) { try { // นับ pending orders (lowercase table/column) $stmtPending = $pdo->prepare("select count(*) from vendor_orders where vendor_id = ? and status = 'pending'"); $stmtPending->execute([$vendorId]); $pendingOrderCount = (int)$stmtPending->fetchColumn(); } catch (PDOException $e) { error_log('[vendor_header] pendingOrderCount Error: ' . $e->getMessage()); $pendingOrderCount = 0; } try { // นับสินค้าสต็อกต่ำ (lowercase table/column) $stmtLow = $pdo->prepare(" select count(distinct p.id) from products p left join product_variants pv on p.id = pv.product_id where p.vendor_id = ? and (p.stock <= ? or pv.stock <= ?) "); $stmtLow->execute([$vendorId, $lowStockThreshold, $lowStockThreshold]); $lowStockCount = (int)$stmtLow->fetchColumn(); } catch (PDOException $e) { error_log('[vendor_header] lowStockCount Error: ' . $e->getMessage()); $lowStockCount = 0; } } $siteUrl = defined('SITE_URL') ? SITE_URL : ''; ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🏪 <?php echo safe_echo($vendor['store_name'] ?? 'Vendor Panel'); ?> — Merchant Dashboard</title> <!-- Fonts & Icons --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Noto+Sans+Thai:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <!-- Bootstrap 5 & Flatpickr for Date Filter --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> <!-- Chart.js --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> <script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/th.js"></script> <script>const SITE_URL = '<?php echo safe_echo($siteUrl); ?>';</script> <style> :root { --font-family: 'Plus Jakarta Sans', 'Noto Sans Thai', sans-serif; --sidebar-bg: #0f172a; --sidebar-hover: #1e293b; --sidebar-active: #334155; --accent-color: #6366f1; --accent-hover: #4f46e5; --bg-light: #f8fafc; --card-border: #e2e8f0; --text-main: #1e293b; --text-muted: #64748b; } *, body { font-family: var(--font-family); color: var(--text-main); } body { background-color: var(--bg-light); margin: 0; padding: 0; } /* Sidebar Styling */ .vendor-sidebar { width: 250px; min-height: 100vh; background: var(--sidebar-bg); position: fixed; top: 0; left: 0; z-index: 100; display: flex; flex-direction: column; box-shadow: 4px 0 24px rgba(0,0,0,0.06); } .vendor-sidebar .brand { padding: 1.25rem 1.25rem 1rem; border-bottom: 1px solid rgba(255,255,255,0.08); } .vendor-sidebar .store-logo { width: 44px; height: 44px; border-radius: 12px; object-fit: cover; background: linear-gradient(135deg, #6366f1, #a855f7); display: flex; align-items: center; justify-content: center; font-size: 1.2rem; color: #fff; font-weight: 700; } .sidebar-nav { padding: 1rem 0.75rem; flex: 1; overflow-y: auto; } .sidebar-nav a { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.9rem; border-radius: 10px; margin-bottom: 4px; color: #94a3b8; text-decoration: none; font-size: 0.88rem; font-weight: 500; transition: all 0.2s ease; } .sidebar-nav a:hover { background: var(--sidebar-hover); color: #f8fafc; } .sidebar-nav a.active { background: var(--accent-color); color: #ffffff; font-weight: 600; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35); } .sidebar-nav .nav-section { font-size: 0.7rem; font-weight: 700; color: #64748b; letter-spacing: 0.08em; text-transform: uppercase; padding: 1rem 0.9rem 0.4rem; } /* Main Area */ .main-content { margin-left: 250px; min-height: 100vh; display: flex; flex-direction: column; } .topbar { background: #ffffff; border-bottom: 1px solid var(--card-border); padding: 0.85rem 1.75rem; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 90; backdrop-filter: blur(8px); background-color: rgba(255, 255, 255, 0.95); } .page-body { padding: 1.75rem; flex: 1; } /* Modern UI Cards */ .v-card { background: #ffffff; border-radius: 16px; border: 1px solid var(--card-border); box-shadow: 0 1px 3px rgba(0,0,0,0.02), 0 1px 2px rgba(0,0,0,0.04); transition: transform 0.15s ease, box-shadow 0.15s ease; } .v-card:hover { box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05); } .v-card-header { padding: 1.2rem 1.5rem; border-bottom: 1px solid #f1f5f9; font-weight: 700; font-size: 1rem; color: #0f172a; display: flex; align-items: center; justify-content: space-between; } .v-card-body { padding: 1.5rem; } /* KPI Stat Cards */ .kpi-card { background: #ffffff; border-radius: 16px; padding: 1.25rem 1.5rem; border: 1px solid var(--card-border); position: relative; overflow: hidden; } .kpi-title { font-size: 0.82rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; } .kpi-value { font-size: 1.65rem; font-weight: 700; color: #0f172a; margin-top: 0.25rem; margin-bottom: 0.1rem; } .kpi-subtext { font-size: 0.78rem; color: var(--text-muted); } .kpi-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 1.4rem; } /* Badges & Tables */ .table th { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; background: #f8fafc; padding: 0.85rem 1rem; border-bottom: 1px solid #e2e8f0; } .table td { padding: 0.9rem 1rem; vertical-align: middle; } .badge-status { padding: 0.35rem 0.75rem; border-radius: 9999px; font-weight: 600; font-size: 0.75rem; display: inline-flex; align-items: center; gap: 0.35rem; } .badge-status-pending { background: #fff7ed; color: #c2410c; border: 1px solid #ffedd5; } .badge-status-processing { background: #eff6ff; color: #1d4ed8; border: 1px solid #dbeafe; } .badge-status-shipped { background: #f0fdf4; color: #15803d; border: 1px solid #dcfce7; } .badge-status-completed { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; } .badge-status-cancelled { background: #fef2f2; color: #b91c1c; border: 1px solid #fee2e2; } .btn-vendor-primary { background: var(--accent-color); color: #ffffff; border: none; border-radius: 10px; padding: 0.55rem 1.25rem; font-weight: 600; font-size: 0.88rem; transition: all 0.15s ease; } .btn-vendor-primary:hover { background: var(--accent-hover); color: #ffffff; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25); } /* Print styles for Packing Slip & Shipping Label */ @media print { .vendor-sidebar, .topbar, .no-print, .btn, footer { display: none !important; } .main-content { margin-left: 0 !important; } .page-body { padding: 0 !important; } .printable-area { display: block !important; width: 100% !important; } } </style> </head> <body> <!-- ── SIDEBAR ─────────────────────────── --> <aside class="vendor-sidebar no-print"> <div class="brand"> <div class="d-flex align-items-center gap-3"> <?php if (!empty($vendor['store_logo'])): ?> <img src="<?php echo safe_echo($vendor['store_logo']); ?>" class="store-logo" onerror="this.onerror=null; this.style.display='none';"> <?php else: ?> <div class="store-logo"><?php echo mb_substr($vendor['store_name'] ?? 'V', 0, 1); ?></div> <?php endif; ?> <div class="overflow-hidden"> <div class="text-white fw-bold text-truncate" style="font-size:0.92rem;line-height:1.2"> <?php echo safe_echo($vendor['store_name'] ?? 'Merchant Panel'); ?> </div> <div class="text-white-50" style="font-size:0.72rem">Vendor Dashboard</div> </div> </div> </div> <nav class="sidebar-nav"> <div class="nav-section">ภาพรวม & วิเคราะห์</div> <a href="index.php" class="<?php echo $currentPage === 'index.php' ? 'active' : ''; ?>"> <span>📊</span> สรุปยอดขาย (Analytics) </a> <div class="nav-section">การจัดการร้านค้า</div> <a href="orders.php" class="<?php echo $currentPage === 'orders.php' ? 'active' : ''; ?>"> <span>🛒</span> ออเดอร์ (Orders) <?php if ($pendingOrderCount > 0): ?> <span class="badge bg-danger ms-auto rounded-pill"><?php echo (int)$pendingOrderCount; ?></span> <?php endif; ?> </a> <a href="products.php" class="<?php echo $currentPage === 'products.php' ? 'active' : ''; ?>"> <span>📦</span> สินค้า & คลังสต็อก <?php if ($lowStockCount > 0): ?> <span class="badge bg-warning text-dark ms-auto rounded-pill" title="สินค้าสต็อกต่ำ">⚠️ <?php echo (int)$lowStockCount; ?></span> <?php endif; ?> </a> <a href="categories.php" class="<?php echo $currentPage === 'categories.php' ? 'active' : ''; ?>"> <span>🏷️</span> หมวดหมู่สินค้า </a> <div class="nav-section">บัญชีและการเงิน</div> <a href="profile.php" class="<?php echo $currentPage === 'profile.php' ? 'active' : ''; ?>"> <span>⚙️</span> ตั้งค่าร้าน & ธนาคาร </a> <a href="<?php echo safe_echo($siteUrl); ?>/shop.php?slug=<?php echo urlencode($vendor['store_slug'] ?? ''); ?>" target="_blank"> <span>🌐</span> ดูหน้าร้านค้าของคุณ </a> </nav> <div style="padding:0.85rem 1.25rem; border-top:1px solid rgba(255,255,255,0.08)"> <a href="<?php echo safe_echo($siteUrl); ?>/login.php?action=logout" class="d-flex align-items-center gap-2 text-white-50 text-decoration-none" style="font-size:0.84rem"> 🚪 ออกจากระบบ </a> </div> </aside> <!-- ── MAIN CONTENT WRAPPER ───────────── --> <div class="main-content"> <div class="topbar no-print"> <div class="d-flex align-items-center gap-2"> <h6 class="mb-0 fw-bold text-dark">STORE DASHBOARD</h6> <span class="text-muted">|</span> <span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1 rounded-pill small">🟢 ร้านค้าพร้อมใช้งาน</span> </div> <div class="d-flex align-items-center gap-3"> <span class="text-secondary small fw-medium">👤 <?php echo safe_echo($_SESSION['user']['fullname'] ?? 'Vendor'); ?></span> </div> </div> <?php if (isset($_SESSION['flash_success'])): ?> <div class="alert alert-success alert-dismissible m-3 mb-0 rounded-3 border-0 shadow-sm no-print"> ✅ <?php echo htmlspecialchars((string)$_SESSION['flash_success'], ENT_QUOTES, 'UTF-8'); unset($_SESSION['flash_success']); ?> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> <?php endif; ?> <?php if (isset($_SESSION['flash_error'])): ?> <div class="alert alert-danger alert-dismissible m-3 mb-0 rounded-3 border-0 shadow-sm no-print"> ⚠️ <?php echo htmlspecialchars((string)$_SESSION['flash_error'], ENT_QUOTES, 'UTF-8'); unset($_SESSION['flash_error']); ?> <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> <?php endif; ?> <div class="page-body">
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.24 |
proxy
|
phpinfo
|
Settings