File manager - Edit - /home/webapp69.cm.in.th/u69319090029/Project SME/platform-admin.php
Back
<?php header('Content-Type: text/html; charset=utf-8'); require_once 'db.php'; if (session_status() === PHP_SESSION_NONE) { session_start(); } // Handle Super Admin Logout if (isset($_GET['action']) && $_GET['action'] === 'logout_super_admin') { unset($_SESSION['super_admin_logged_in']); session_destroy(); header("Location: login.php"); exit; } // Handle Fast Shop ID Impersonation for Super Admin if (isset($_GET['action']) && $_GET['action'] === 'impersonate' && isset($_GET['store_id'])) { if (!empty($_SESSION['super_admin_logged_in'])) { $store_id = intval($_GET['store_id']); try { $stmt = $pdo->prepare("SELECT store_id, store_name FROM tenants WHERE store_id = :store_id LIMIT 1"); $stmt->execute([':store_id' => $store_id]); $tenant = $stmt->fetch(); if ($tenant) { $userStmt = $pdo->prepare("SELECT id, username FROM users WHERE store_id = :store_id AND role = 'store_admin' LIMIT 1"); $userStmt->execute([':store_id' => $store_id]); $adminUser = $userStmt->fetch(); $_SESSION['is_impersonating'] = true; $_SESSION['impersonated_store_id'] = $store_id; $_SESSION['shop_admin_id'] = $adminUser['id'] ?? 9999; $_SESSION['shop_id'] = $store_id; $_SESSION['store_id'] = $store_id; $_SESSION['shop_admin_name'] = $adminUser['username'] ?? 'store_admin'; $_SESSION['shop_name'] = $tenant['store_name']; header("Location: store-admin.php"); exit; } else { $error_msg = "ไม่พบข้อมูลร้านค้ารหัส #{$store_id} ในระบบ"; } } catch (PDOException $e) { $error_msg = "เกิดข้อผิดพลาดในการสวมสิทธิ์ร้านค้า: " . $e->getMessage(); } } } // Handle Super Admin Login $error_msg = ''; $success_msg = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_super_admin'])) { $username = trim($_POST['admin_username'] ?? ''); $password = $_POST['admin_password'] ?? ''; try { $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND role = 'super_admin' LIMIT 1"); $stmt->execute([':username' => $username]); $user = $stmt->fetch(); if ($user && (password_verify($password, $user['password']) || $user['password'] === $password)) { // Re-hash password if stored as plain text if ($user['password'] === $password) { $rehash = password_hash($password, PASSWORD_DEFAULT); $upStmt = $pdo->prepare("UPDATE users SET password = :password WHERE id = :id"); $upStmt->execute([':password' => $rehash, ':id' => $user['id']]); } session_regenerate_id(true); $_SESSION['super_admin_logged_in'] = true; header("Location: platform-admin.php"); exit; } else { $error_msg = 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง'; } } catch (PDOException $e) { $error_msg = 'เกิดข้อผิดพลาดในการตรวจสอบสิทธิ์: ' . $e->getMessage(); } } // Handle Adding New Tenant if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_tenant']) && isset($_SESSION['super_admin_logged_in'])) { $store_name = trim($_POST['store_name'] ?? ''); $category = trim($_POST['category'] ?? ''); $address = trim($_POST['address'] ?? ''); $plan_id = intval($_POST['plan_id'] ?? 0); $admin_username = trim($_POST['admin_username'] ?? ''); $admin_password = $_POST['admin_password'] ?? ''; if (empty($store_name) || empty($admin_username) || empty($admin_password) || $plan_id === 0) { $error_msg = 'กรุณากรอกข้อมูลที่จำเป็นให้ครบถ้วน'; } else { try { $pdo->beginTransaction(); // Check if username already exists $checkStmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = :username"); $checkStmt->execute([':username' => $admin_username]); if ($checkStmt->fetchColumn() > 0) { throw new Exception("ชื่อผู้ใช้ '{$admin_username}' มีในระบบแล้ว กรุณาใช้ชื่ออื่น"); } // Insert Tenant with dynamic trial days (14 days for Starter/Standard, 7 days for Premium) $trial_days = (intval($plan_id) === 3) ? 7 : 14; $stmt = $pdo->prepare("INSERT INTO tenants (store_name, category, address, plan_id, status, trial_ends_at, promo_banner, policy_text, custom_logo_url) VALUES (:store_name, :category, :address, :plan_id, 'trial', DATE_ADD(NOW(), INTERVAL :days DAY), '', '', '')"); $stmt->execute([ ':store_name' => $store_name, ':category' => $category, ':address' => $address, ':plan_id' => $plan_id, ':days' => $trial_days ]); $store_id = $pdo->lastInsertId(); // Insert Store Admin User (Hashed Password) $hashed_admin_password = password_hash($admin_password, PASSWORD_DEFAULT); $stmtUser = $pdo->prepare("INSERT INTO users (username, password, role, store_id) VALUES (:username, :password, 'store_admin', :store_id)"); $stmtUser->execute([ ':username' => $admin_username, ':password' => $hashed_admin_password, ':store_id' => $store_id ]); // Seed default tables for the new store $enc_shop_name = urlencode($store_name); $stmtTable = $pdo->prepare("INSERT INTO tables_qr (store_id, table_number, qr_code_url) VALUES (:store_id, :table_number, :qr_code_url)"); $stmtTable->execute([ ':store_id' => $store_id, ':table_number' => 'Table 1', ':qr_code_url' => "menu.php?store_id={$store_id}&shop_name={$enc_shop_name}&table=1" ]); $stmtTable->execute([ ':store_id' => $store_id, ':table_number' => 'Table 2', ':qr_code_url' => "menu.php?store_id={$store_id}&shop_name={$enc_shop_name}&table=2" ]); $pdo->commit(); $success_msg = "สร้างร้านค้า '{$store_name}' และบัญชีแอดมินเรียบร้อยแล้ว!"; } catch (Exception $e) { $pdo->rollBack(); $error_msg = "เกิดข้อผิดพลาดในการสร้างร้านค้า: " . $e->getMessage(); } } } // Handle Deleting Tenant if (isset($_GET['action']) && $_GET['action'] === 'delete_tenant' && isset($_GET['store_id']) && isset($_SESSION['super_admin_logged_in'])) { $store_id = intval($_GET['store_id']); try { $pdo->beginTransaction(); // Delete users, menus, tables, tenant (cascaded automatically via foreign keys) $stmt = $pdo->prepare("DELETE FROM tenants WHERE store_id = :store_id"); $stmt->execute([':store_id' => $store_id]); $pdo->commit(); $success_msg = "ลบร้านค้าเรียบร้อยแล้ว"; } catch (PDOException $e) { $pdo->rollBack(); $error_msg = "เกิดข้อผิดพลาดในการลบร้านค้า: " . $e->getMessage(); } } // Handle Extending Tenant Trial Period if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['extend_trial']) && isset($_SESSION['super_admin_logged_in'])) { $store_id = intval($_POST['store_id'] ?? 0); $days = intval($_POST['extend_days'] ?? 30); if ($days <= 0) $days = 30; if ($store_id > 0) { try { $stmt = $pdo->prepare("UPDATE tenants SET trial_ends_at = DATE_ADD(COALESCE(CASE WHEN trial_ends_at < NOW() THEN NOW() ELSE trial_ends_at END, NOW()), INTERVAL :days DAY), status = CASE WHEN status = 'expired' OR status = 'pending' THEN 'trial' ELSE status END WHERE store_id = :store_id"); $stmt->execute([':days' => $days, ':store_id' => $store_id]); $success_msg = "✅ ขยายระยะเวลาทดลองใช้งานสำหรับร้านค้า #{$store_id} เพิ่มอีก {$days} วัน เรียบร้อยแล้ว!"; } catch (PDOException $e) { $error_msg = "เกิดข้อผิดพลาดในการขยายเวลาทดลองใช้งาน: " . $e->getMessage(); } } } // If not logged in, show Super Admin Login Screen if (!isset($_SESSION['super_admin_logged_in']) || $_SESSION['super_admin_logged_in'] !== true) { ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Super Admin Authorization - SME Restaurant OS</title> <link rel="stylesheet" href="style.css"> <style> :root { --primary: #0A192F; --accent: #00E5FF; --dark-bg: #0D1B2A; --text-light: #F4F6F9; --card-bg: rgba(255, 255, 255, 0.05); } body { background: linear-gradient(135deg, var(--primary) 0%, var(--dark-bg) 100%); color: var(--text-light); font-family: 'Sarabun', 'Inter', sans-serif; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .login-card { background: rgba(10, 25, 47, 0.7); border: 1px solid rgba(0, 229, 255, 0.2); border-radius: 16px; padding: 40px; width: 100%; max-width: 450px; box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); backdrop-filter: blur(10px); box-sizing: border-box; } .logo-header { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; justify-content: center; } .logo-header img { height: 50px; object-fit: contain; } .logo-header h2 { font-size: 18px; margin: 0; color: var(--accent); } .form-group { margin-bottom: 20px; } .form-label { display: block; margin-bottom: 8px; font-size: 14px; color: #A0AEC0; } .form-control { width: 100%; padding: 12px 16px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; color: #fff; box-sizing: border-box; font-size: 15px; transition: 0.3s; } .form-control:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 10px rgba(0, 229, 255, 0.2); } .btn-submit { width: 100%; padding: 14px; background: var(--accent); border: none; border-radius: 8px; color: #0A192F; font-weight: bold; font-size: 16px; cursor: pointer; transition: 0.3s; } .btn-submit:hover { background: #00B4D8; box-shadow: 0 0 15px rgba(0, 229, 255, 0.4); } .error-box { background: rgba(239, 68, 68, 0.1); border: 1.5px solid #EF4444; color: #FCA5A5; padding: 12px; border-radius: 8px; font-size: 14px; margin-bottom: 20px; text-align: center; } </style> </head> <body> <div class="login-card"> <div class="logo-header"> <img src="logo.png" alt="CMTC Logo" style="height: 40px; width: auto; object-fit: contain;"> <div> <h2>CMTC Tech Solution</h2> <small style="color: #CBD5E0;">SME Restaurant OS Platform</small> </div> </div> <h3 style="text-align: center; margin-bottom: 25px;">👑 เข้าสู่ระบบ Super Admin</h3> <?php if ($error_msg): ?> <div class="error-box">❌ <?php echo htmlspecialchars($error_msg); ?></div> <?php endif; ?> <form method="POST"> <input type="hidden" name="login_super_admin" value="1"> <div class="form-group"> <label class="form-label" for="admin_username">ชื่อบัญชีผู้ดูแลกลาง (Username)</label> <input type="text" name="admin_username" id="admin_username" class="form-control" placeholder="ป้อนชื่อผู้ใช้" required> </div> <div class="form-group"> <label class="form-label" for="admin_password">รหัสผ่านระบบกลาง (Password)</label> <input type="password" name="admin_password" id="admin_password" class="form-control" placeholder="ป้อนรหัสผ่าน" required> </div> <button type="submit" class="btn-submit">ยืนยันรหัสผ่านเพื่อเข้าใช้งาน</button> </form> <a href="index.php" style="display: block; text-align: center; margin-top: 20px; color: #CBD5E0; text-decoration: none; font-size: 14px;">🏠 กลับสู่หน้าแรกระบบ</a> </div> </body> </html> <?php exit; } // Handle updating plans -- runs BEFORE data-fetch so UI shows updated values immediately if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_plan']) && isset($_SESSION['super_admin_logged_in'])) { $plan_id = intval($_POST['plan_id'] ?? 0); $price = floatval($_POST['price'] ?? 0.00); $limit_tables = intval($_POST['limit_tables'] ?? 0); $description = trim($_POST['description'] ?? ''); if ($plan_id > 0 && $price >= 0 && $limit_tables > 0) { try { $stmt = $pdo->prepare("UPDATE plans SET price = :price, limit_tables = :limit_tables, description = :description WHERE id = :id"); $stmt->execute([ ':price' => $price, ':limit_tables' => $limit_tables, ':description' => $description, ':id' => $plan_id ]); $success_msg = "✅ อัปเดตแพ็กเกจสำเร็จแล้ว! ราคาและโควต้าโต๊ะถูกบันทึกเรียบร้อย"; } catch (PDOException $e) { $error_msg = "เกิดข้อผิดพลาดในการอัปเดตแผน: " . $e->getMessage(); } } else { $error_msg = "กรุณากรอกข้อมูลราคาและจำนวนโต๊ะให้ถูกต้อง"; } } // Handle Payment Approval if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['approve_payment']) && isset($_SESSION['super_admin_logged_in'])) { $payment_id = intval($_POST['payment_id'] ?? 0); if ($payment_id > 0) { try { $pdo->beginTransaction(); $stmtPay = $pdo->prepare("SELECT * FROM payments WHERE payment_id = :id"); $stmtPay->execute([':id' => $payment_id]); $payInfo = $stmtPay->fetch(); if ($payInfo && $payInfo['status'] === 'pending') { $stmtUpPay = $pdo->prepare("UPDATE payments SET status = 'approved' WHERE payment_id = :id"); $stmtUpPay->execute([':id' => $payment_id]); $stmtUpTenant = $pdo->prepare("UPDATE tenants SET status = 'active', plan_id = :plan_id WHERE store_id = :store_id"); $stmtUpTenant->execute([ ':plan_id' => $payInfo['plan_id'], ':store_id' => $payInfo['store_id'] ]); $pdo->commit(); $success_msg = "✅ อนุมัติการชำระเงินเรียบร้อยแล้ว! ร้านค้าได้รับสิทธิ์การใช้งานแพ็กเกจทันที"; } else { $pdo->rollBack(); $error_msg = "ไม่พบรายการชำระเงิน หรือรายการนี้ได้รับการอนุมัติไปแล้ว"; } } catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); $error_msg = "เกิดข้อผิดพลาดในการอนุมัติการชำระเงิน: " . $e->getMessage(); } } } // Handle Payment Rejection if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['reject_payment']) && isset($_SESSION['super_admin_logged_in'])) { $payment_id = intval($_POST['payment_id'] ?? 0); $reject_note = trim($_POST['reject_note'] ?? 'หลักฐานการชำระเงินไม่ถูกต้อง'); if ($payment_id > 0) { try { $stmtUpPay = $pdo->prepare("UPDATE payments SET status = 'rejected', note = :note WHERE payment_id = :id"); $stmtUpPay->execute([':note' => $reject_note, ':id' => $payment_id]); $success_msg = "⚠️ ปฏิเสธรายการชำระเงินเรียบร้อยแล้ว"; } catch (Exception $e) { $error_msg = "เกิดข้อผิดพลาดในการปฏิเสธการชำระเงิน: " . $e->getMessage(); } } } // Super Admin logged in - fetch statistics $total_tenants = $pdo->query("SELECT COUNT(*) FROM tenants")->fetchColumn(); $total_users = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(); $active_subscriptions_count = $pdo->query("SELECT COUNT(*) FROM tenants WHERE status = 'active'")->fetchColumn() ?: 0; $pending_payment_count = $pdo->query("SELECT COUNT(*) FROM payments WHERE status = 'pending'")->fetchColumn() ?: 0; // Fetch all payment logs for Verification view $payment_logs = $pdo->query("SELECT p.*, t.store_name, pl.name as plan_name, pl.price as plan_price FROM payments p JOIN tenants t ON p.store_id = t.store_id JOIN plans pl ON p.plan_id = pl.id ORDER BY p.payment_id DESC")->fetchAll(PDO::FETCH_ASSOC); // Fetch all system user accounts for modal viewer $user_list = $pdo->query("SELECT u.id, u.username, u.role, u.store_id, t.store_name, p.name as plan_name FROM users u LEFT JOIN tenants t ON u.store_id = t.store_id LEFT JOIN plans p ON t.plan_id = p.id ORDER BY u.id ASC")->fetchAll(PDO::FETCH_ASSOC); // Calculate recurring revenue based on plan prices $recurring_revenue = $pdo->query("SELECT SUM(p.price) FROM tenants t JOIN plans p ON t.plan_id = p.id WHERE t.status = 'active'")->fetchColumn() ?: 0.00; // Fetch all tenants $tenants = $pdo->query("SELECT t.*, p.name as plan_name, p.price as plan_price, u.username as admin_username FROM tenants t LEFT JOIN plans p ON t.plan_id = p.id LEFT JOIN users u ON u.store_id = t.store_id AND u.role = 'store_admin' ORDER BY t.store_id DESC")->fetchAll(PDO::FETCH_ASSOC); // Fetch plans (after update so values are fresh) $plans = $pdo->query("SELECT * FROM plans ORDER BY price ASC")->fetchAll(PDO::FETCH_ASSOC); // Fetch plan subscription count for Bar Chart $plan_chart_data = $pdo->query("SELECT p.name, COUNT(t.store_id) as count FROM plans p LEFT JOIN tenants t ON t.plan_id = p.id GROUP BY p.id ORDER BY p.price ASC")->fetchAll(PDO::FETCH_ASSOC); // Fetch tenant status distribution for Donut Chart $tenant_status_data = $pdo->query("SELECT status, COUNT(*) as count FROM tenants GROUP BY status")->fetchAll(PDO::FETCH_ASSOC); if (empty($tenant_status_data)) { $tenant_status_data = [ ['status' => 'active', 'count' => $total_tenants ?: 1] ]; } // Fetch order trends for Line Chart $order_chart_data = $pdo->query("SELECT DATE(created_at) as date_str, COUNT(*) as count FROM orders GROUP BY DATE(created_at) ORDER BY date_str ASC LIMIT 7")->fetchAll(PDO::FETCH_ASSOC); if (empty($order_chart_data)) { $order_chart_data = [ ['date_str' => date('m-d', strtotime('-6 days')), 'count' => 4], ['date_str' => date('m-d', strtotime('-5 days')), 'count' => 8], ['date_str' => date('m-d', strtotime('-4 days')), 'count' => 12], ['date_str' => date('m-d', strtotime('-3 days')), 'count' => 7], ['date_str' => date('m-d', strtotime('-2 days')), 'count' => 15], ['date_str' => date('m-d', strtotime('-1 days')), 'count' => 22], ['date_str' => date('m-d'), 'count' => 18] ]; } else { foreach ($order_chart_data as &$ocd) { $ocd['date_str'] = date('m-d', strtotime($ocd['date_str'])); } } // Super Admin Multi-Tenant Order Status Distribution Chart Data $super_admin_status_data = $pdo->query("SELECT status, COUNT(*) as count FROM orders GROUP BY status")->fetchAll(PDO::FETCH_ASSOC); $super_admin_total_orders = 0; foreach ($super_admin_status_data as $sasd) { $super_admin_total_orders += intval($sasd['count']); } ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Super Admin Console - SME Restaurant OS</title> <link rel="stylesheet" href="style.css"> <!-- Chart.js CDN --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- SweetAlert2 CDN --> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <!-- Navigation Sound & SweetAlert Engine --> <script src="js/nav_sound_swal.js"></script> <style> :root { --primary: #1e293b; /* Modern Navy Blue */ --accent: #00E5FF; --dark-bg: #0f172a; /* Deep Slate Background */ --card-bg: #1e293b; --border-glass: rgba(0, 229, 255, 0.15); --text-primary: #f8fafc; /* Crisp light text */ --text-secondary: #94a3b8; } body { background-color: var(--dark-bg); color: var(--text-primary); font-family: 'Sarabun', 'Inter', sans-serif; margin: 0; padding: 0; display: flex; min-height: 100vh; } /* Dashboard Layout */ .dashboard-wrapper { display: flex; width: 100%; min-height: 100vh; } /* Sidebar Styles & Dropdown Controls */ .sidebar { width: 280px; background: var(--primary); border-right: 1px solid rgba(255,255,255,0.06); display: flex; flex-direction: column; flex-shrink: 0; position: relative; z-index: 500; pointer-events: auto !important; } .sidebar-menu { pointer-events: auto !important; overflow-y: auto; } .sidebar-category-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; color: var(--accent); font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px; background: rgba(0, 229, 255, 0.05); border-radius: 6px; cursor: pointer !important; user-select: none; margin-top: 10px; margin-bottom: 4px; border: 1px solid rgba(0, 229, 255, 0.1); transition: all 0.2s ease; } .sidebar-category-header:hover { background: rgba(0, 229, 255, 0.12); } .sidebar-subgroup { display: flex; flex-direction: column; gap: 4px; padding-left: 8px; margin-bottom: 6px; } .menu-btn { pointer-events: auto !important; cursor: pointer !important; } .sidebar-header { padding: 30px 24px; border-bottom: 1px solid rgba(255,255,255,0.06); display: flex; align-items: center; gap: 12px; } .sidebar-header img { height: 40px; width: auto; object-fit: contain; } .sidebar-header h1 { font-size: 16px; margin: 0; font-weight: 800; color: var(--text-primary); } .sidebar-header p { font-size: 11px; margin: 2px 0 0 0; color: var(--text-secondary); } .sidebar-menu { padding: 24px 16px; display: flex; flex-direction: column; gap: 8px; flex-grow: 1; } .menu-btn { display: flex; align-items: center; gap: 12px; padding: 12px 16px; color: var(--text-secondary); text-decoration: none; font-size: 14px; font-weight: 700; border-radius: 8px; border: none; background: transparent; cursor: pointer; text-align: left; width: 100%; transition: all 0.2s ease; } .menu-btn:hover { background: rgba(255,255,255,0.04); color: var(--text-primary); } .menu-btn.active { background: var(--accent); color: #0A192F; } .sidebar-footer { padding: 20px; border-top: 1px solid rgba(255,255,255,0.06); } /* Main Content Panel */ .main-content { flex-grow: 1; padding: 40px; box-sizing: border-box; overflow-y: auto; } .main-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; } .main-header h2 { font-size: 22px; margin: 0; font-weight: 800; } /* Tab Sections */ .tab-sec { display: none; } .tab-sec.active { display: block; } /* Stats Grid */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px; margin-bottom: 35px; } .stat-card { background: var(--card-bg); border: 1px solid rgba(255,255,255,0.04); border-radius: 12px; padding: 24px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .stat-info h3 { margin: 0 0 6px 0; font-size: 13px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; } .stat-info div { font-size: 26px; font-weight: 800; color: var(--accent); } .stat-icon { font-size: 32px; } /* Visual Charts Wrapper */ .charts-row { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 40px; } @media (max-width: 992px) { .charts-row { grid-template-columns: 1fr; } } .chart-card { background: var(--card-bg); border: 1px solid rgba(255,255,255,0.04); border-radius: 12px; padding: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .chart-card h3 { margin-top: 0; margin-bottom: 20px; font-size: 15px; color: var(--accent); border-left: 4px solid var(--accent); padding-left: 10px; } .chart-container { position: relative; height: 300px; width: 100%; } /* Split Grid Layout */ .split-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 30px; } @media (max-width: 992px) { .split-grid { grid-template-columns: 1fr; } } .card { background: var(--card-bg); border: 1px solid rgba(255,255,255,0.04); border-radius: 12px; padding: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); margin-bottom: 30px; } .card h3 { margin-top: 0; margin-bottom: 20px; font-size: 16px; color: var(--accent); border-left: 4px solid var(--accent); padding-left: 10px; } /* Table Styles */ .table-wrapper { overflow-x: auto; } table { width: 100%; border-collapse: collapse; text-align: left; } th, td { padding: 12px 14px; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 13.5px; } th { color: var(--text-secondary); font-weight: 700; } tr:hover td { background: rgba(255,255,255,0.01); } /* Interactive controls */ .form-group { margin-bottom: 16px; } .form-label { display: block; margin-bottom: 6px; font-size: 13px; color: var(--text-secondary); font-weight: 700; } .form-control { width: 100%; padding: 10px 12px; background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.08); border-radius: 6px; color: #fff; box-sizing: border-box; font-family: inherit; } .form-control:focus { border-color: var(--accent); outline: none; } .btn-primary { background: var(--accent); color: #0A192F; border: none; padding: 12px 20px; font-weight: 800; border-radius: 6px; cursor: pointer; width: 100%; transition: 0.2s ease; } .btn-primary:hover { background: #00B4D8; } .btn-sm { padding: 6px 12px; font-size: 11px; border-radius: 4px; font-weight: bold; text-decoration: none; display: inline-block; } .btn-delete { background: rgba(244, 67, 54, 0.15); color: #f44336; } .btn-delete:hover { background: #f44336; color: #fff; } .btn-impersonate { background: rgba(0, 229, 255, 0.15); color: var(--accent); } .btn-impersonate:hover { background: var(--accent); color: #0A192F; } .badge { padding: 3px 6px; border-radius: 4px; font-size: 11px; font-weight: bold; } .badge-active { background: rgba(76, 175, 80, 0.15); color: #4CAF50; } .badge-plan { background: rgba(0, 229, 255, 0.1); color: var(--accent); border: 1px solid rgba(0, 229, 255, 0.2); } .alert-success { background: rgba(76, 175, 80, 0.1); border: 1px solid #4CAF50; color: #81C784; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 13.5px; } .alert-danger { background: rgba(244, 67, 54, 0.1); border: 1px solid #f44336; color: #E57373; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 13.5px; } html, body { overflow-x: hidden !important; max-width: 100vw !important; margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box !important; } /* Mobile & Smartphone Responsive UI System */ @media (max-width: 992px) { .mobile-topbar { display: flex !important; position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; width: 100% !important; z-index: 99999 !important; background: #1e293b !important; box-shadow: 0 4px 15px rgba(0,0,0,0.4) !important; padding: 10px 14px !important; } .dashboard-wrapper { flex-direction: column !important; overflow-x: hidden !important; width: 100% !important; max-width: 100vw !important; margin-top: 52px !important; } .sidebar { position: fixed !important; top: 52px !important; left: 0 !important; width: 100% !important; height: calc(100vh - 52px) !important; background: rgba(15, 23, 42, 0.98) !important; backdrop-filter: blur(12px) !important; z-index: 99998 !important; display: none !important; overflow-y: auto !important; box-sizing: border-box !important; padding: 15px !important; border-right: none !important; box-shadow: 0 20px 40px rgba(0,0,0,0.8); } .sidebar.mobile-show { display: flex !important; flex-direction: column !important; } .menu-btn { padding: 14px 16px !important; font-size: 14.5px !important; border-radius: 10px !important; margin-bottom: 6px !important; } .main-content { padding: 12px 10px 80px 10px !important; width: 100% !important; max-width: 100% !important; overflow-x: hidden !important; box-sizing: border-box !important; } .split-grid { grid-template-columns: 1fr !important; gap: 15px !important; width: 100% !important; max-width: 100% !important; } .stats-grid { grid-template-columns: 1fr 1fr !important; gap: 10px !important; width: 100% !important; } .stat-card { padding: 12px 10px !important; border-radius: 10px !important; min-width: 0 !important; box-sizing: border-box !important; } .stat-info { min-width: 0 !important; overflow: hidden !important; } .stat-info h3 { font-size: 11px !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; margin-bottom: 4px !important; } .stat-info div { font-size: 16px !important; font-weight: 800 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } .stat-icon { font-size: 20px !important; } .card { padding: 16px 12px !important; border-radius: 10px !important; box-sizing: border-box !important; max-width: 100% !important; overflow-x: hidden !important; } .card h3, .chart-card h3 { font-size: 14px !important; line-height: 1.4 !important; word-wrap: break-word !important; white-space: normal !important; } .charts-row { grid-template-columns: 1fr !important; } table { display: block; overflow-x: auto; white-space: nowrap; max-width: 100%; } } </style> </head> <body> <!-- Mobile Top Navigation Header Bar for Smartphones --> <div class="mobile-topbar" style="display: none; background: #1e293b; border-bottom: 1px solid rgba(255,255,255,0.08); padding: 10px 14px; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 1000;"> <div style="display: flex; align-items: center; gap: 8px; min-width: 0;"> <img src="logo.png" alt="Logo" style="height: 28px; width: auto;" onerror="this.style.display='none'"> <div style="min-width: 0;"> <div style="font-weight: 800; font-size: 13.5px; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">CMTC SaaS Admin</div> <div style="font-size: 10.5px; color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">Super Admin Console</div> </div> </div> <div style="display:flex; gap:6px; align-items:center;"> <button type="button" onclick="toggleNavSound()" class="nav-sound-toggle-btn" style="background: rgba(255,255,255,0.1); color: #94a3b8; border: 1px solid rgba(255,255,255,0.2); padding: 5px 10px; border-radius: 6px; font-weight: 800; font-size: 11px; cursor: pointer; white-space: nowrap;"> 🔊 เสียงนำทาง: ปิด </button> <button type="button" onclick="toggleMobileSidebar()" style="background: var(--accent); color: #0f172a; border: none; padding: 7px 12px; border-radius: 6px; font-weight: 800; font-size: 12px; cursor: pointer; display: flex; align-items: center; gap: 4px; flex-shrink: 0; white-space: nowrap;"> เมนูแอดมิน </button> </div> </div> <div class="dashboard-wrapper"> <!-- Sidebar Navigation Drawer --> <aside class="sidebar"> <a href="platform-admin.php" title="กลับสู่หน้าหลัก Super Admin" style="text-decoration: none; color: inherit; display: block;"> <div class="sidebar-header" style="cursor: pointer;"> <img src="logo.png" alt="Logo"> <div> <h1>CMTC SaaS Admin</h1> <p>ระบบบริหารจัดการหลังบ้าน</p> </div> </div> </a> <div style="padding: 0 15px 10px 15px;"> <button type="button" onclick="toggleNavSound()" class="nav-sound-toggle-btn" style="width: 100%; text-align: center; padding: 7px 12px; border-radius: 8px; font-size: 11.5px; font-weight: bold; cursor: pointer; transition: all 0.2s ease; border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.08); color: #94a3b8;">🔊 เสียงนำทาง: ปิด</button> </div> <nav class="sidebar-menu"> <!-- 1. System Analytics Category --> <div class="sidebar-category-header" onclick="toggleSidebarDropdown('group-p-dash')"> <span>📊 ระบบบริหารภาพรวม (Overview)</span> <span id="group-p-dash-arrow" style="font-size: 10px; transition: transform 0.2s;">▼</span> </div> <div id="group-p-dash" class="sidebar-subgroup" style="display: flex;"> <button class="menu-btn active" id="btn-overview" onclick="showTab('overview')"> <span>📊</span> Overview & Analytics </button> <a href="store-admin.php" class="menu-btn" style="text-decoration: none;"> <span>🏪</span> Store Admin </a> </div> <!-- 2. Tenants & Deep-Dive Category --> <div class="sidebar-category-header" onclick="toggleSidebarDropdown('group-p-tenants')"> <span>🏪 ร้านค้า & การตรวจสอบ (Stores & Payments)</span> <span id="group-p-tenants-arrow" style="font-size: 10px; transition: transform 0.2s;">▼</span> </div> <div id="group-p-tenants" class="sidebar-subgroup" style="display: flex;"> <button class="menu-btn" id="btn-tenants" onclick="showTab('tenants')"> <span>🏪</span> Tenant Management </button> <button class="menu-btn" id="btn-payments" onclick="showTab('payments')"> <span>💳</span> Payment Verification <?php if ($pending_payment_count > 0): ?> <span style="margin-left: auto; background: #EF4444; color: #fff; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 800; animation: pulse 1.5s infinite;"><?php echo $pending_payment_count; ?></span> <?php endif; ?> </button> <button class="menu-btn" id="btn-impersonate" onclick="showTab('impersonate')"> <span>🔑</span> Store Deep-Dive (Impersonate) </button> </div> <!-- 3. SaaS Plans Category --> <div class="sidebar-category-header" onclick="toggleSidebarDropdown('group-p-plans')"> <span>⚙️ แพ็กเกจ & ราคา (Pricing & Plans)</span> <span id="group-p-plans-arrow" style="font-size: 10px; transition: transform 0.2s;">▼</span> </div> <div id="group-p-plans" class="sidebar-subgroup" style="display: flex;"> <button class="menu-btn" id="btn-plans" onclick="showTab('plans')"> <span>⚙️</span> Plan & Pricing Manager </button> </div> </nav> <div class="sidebar-footer"> <a href="platform-admin.php?action=logout_super_admin" class="btn-sm btn-delete" style="text-align: center; display: block; padding: 10px;">🚪 ออกจากระบบ</a> </div> </aside> <!-- Main Workspace --> <main class="main-content"> <!-- Global Messages --> <?php if (!empty($success_msg)): ?> <div class="alert-success">✓ <?php echo htmlspecialchars($success_msg); ?></div> <?php endif; ?> <?php if (!empty($error_msg)): ?> <div class="alert-danger">❌ <?php echo htmlspecialchars($error_msg); ?></div> <?php endif; ?> <!-- 1. Overview & Analytics Tab --> <section id="overview-sec" class="tab-sec active"> <!-- Platform System Operational Status Bar --> <div style="background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9)); border: 1px solid var(--accent); border-radius: 12px; padding: 14px 20px; margin-bottom: 22px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;"> <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;"> <span style="background: rgba(16,185,129,0.2); color: #10B981; border: 1px solid #10B981; padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: bold;"> 🟢 System Health: 100% Operational </span> <span style="font-size: 12.5px; color: var(--text-secondary);">MariaDB Engine • BCRYPT Encryption • Multi-Tenant Isolation</span> </div> <div style="font-size: 13px; color: var(--accent); font-weight: bold;"> 📈 ARR Projection: ฿<?php echo number_format($recurring_revenue * 12, 2); ?> / ปี </div> </div> <div class="main-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;"> <h2>📊 Overview & Analytics (ภาพรวมและสถิติกลาง)</h2> <button type="button" onclick="showTab('payments')" style="background: rgba(0, 229, 255, 0.1); border: 1px solid rgba(0, 229, 255, 0.3); color: #00E5FF; padding: 10px 18px; border-radius: 8px; font-weight: bold; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 13.5px;"> <span style="font-size: 18px;">🔔</span> แจ้งเตือนชำระเงิน <?php if ($pending_payment_count > 0): ?> <span style="background: #EF4444; color: #fff; border-radius: 12px; padding: 2px 8px; font-size: 11.5px; font-weight: 800; box-shadow: 0 0 10px rgba(239, 68, 68, 0.6);"><?php echo $pending_payment_count; ?> รายการใหม่</span> <?php else: ?> <span style="color: var(--text-secondary); font-size: 11px; font-weight: normal;">(ไม่มีค้าง)</span> <?php endif; ?> </button> </div> <!-- Stats Row --> <div class="stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px;"> <div class="stat-card" style="border-top: 4px solid var(--accent);"> <div class="stat-info"> <h3>จำนวนร้านค้าทั้งหมด</h3> <div style="font-size: 24px; font-weight: 800; color: var(--accent);"><?php echo $total_tenants; ?> ร้านค้า</div> </div> <div class="stat-icon" style="color: var(--accent);">🏪</div> </div> <div class="stat-card" style="border-top: 4px solid #10B981;"> <div class="stat-info"> <h3>รายได้รวมแพลตฟอร์ม (MRR)</h3> <div style="font-size: 24px; font-weight: 800; color: #10B981;">฿<?php echo number_format($recurring_revenue, 2); ?></div> </div> <div class="stat-icon" style="color: #10B981;">💰</div> </div> <div class="stat-card" style="border-top: 4px solid #3B82F6;"> <div class="stat-info"> <h3>แพ็กเกจที่ใช้งานอยู่</h3> <div style="font-size: 24px; font-weight: 800; color: #3B82F6;"><?php echo $active_subscriptions_count; ?> ร้านค้า</div> </div> <div class="stat-icon" style="color: #3B82F6;">💳</div> </div> <div class="stat-card" style="border-top: 4px solid #EF4444; cursor: pointer;" onclick="showTab('payments')" title="คลิกเพื่อไปหน้าอนุมัติการชำระเงิน"> <div class="stat-info"> <h3>รออนุมัติชำระเงิน</h3> <div style="font-size: 24px; font-weight: 800; color: #EF4444; display: flex; align-items: center; gap: 8px;"> <span><?php echo $pending_payment_count; ?> รายการ</span> <?php if ($pending_payment_count > 0): ?> <span style="background: #EF4444; color: #fff; border-radius: 12px; padding: 2px 8px; font-size: 11px; font-weight: 800;">ใหม่</span> <?php endif; ?> </div> </div> <div class="stat-icon" style="color: #EF4444;">🔔</div> </div> </div> <!-- Charts row --> <div class="charts-row" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 20px; margin-top: 25px;"> <div class="chart-card card" style="background: #1e293b; border: 1px solid var(--border-glass); border-radius: 12px; padding: 20px;"> <h3 style="margin: 0 0 15px; color: var(--accent); font-weight: 800; font-size: 15px;">📊 สัดส่วนผู้สมัครแพ็กเกจ (Subscription Bar Chart)</h3> <div class="chart-container" style="height: 280px; position: relative;"> <canvas id="planBarChart"></canvas> </div> </div> <div class="chart-card card" style="background: #1e293b; border: 1px solid var(--border-glass); border-radius: 12px; padding: 20px;"> <h3 style="margin: 0 0 15px; color: #FFC000; font-weight: 800; font-size: 15px;">📈 แนวโน้มออเดอร์ในระบบ 7 วันย้อนหลัง (Orders Line Trend)</h3> <div class="chart-container" style="height: 280px; position: relative;"> <canvas id="growthLineChart"></canvas> </div> </div> <div class="chart-card card" style="background: #1e293b; border: 1px solid var(--border-glass); border-radius: 12px; padding: 20px;"> <h3 style="margin: 0 0 15px; color: #10B981; font-weight: 800; font-size: 15px;">🍩 สัดส่วนสถานะการใช้งานร้านค้า (Tenants Status Donut Chart)</h3> <div class="chart-container" style="height: 280px; position: relative;"> <canvas id="statusDoughnutChart"></canvas> </div> </div> <div class="chart-card card" style="background: #1e293b; border: 1px solid var(--border-glass); border-radius: 12px; padding: 20px;"> <h3 style="margin: 0 0 15px; color: #A855F7; font-weight: 800; font-size: 15px;">📊 อัตราส่วนสถานะออเดอร์ทั้งระบบ (All Stores Order Status Distribution)</h3> <div style="font-size: 12px; color: var(--text-secondary); margin-top: -10px; margin-bottom: 12px;">รวมออเดอร์ทุกร้านค้าในระบบ (รวม <?php echo number_format($super_admin_total_orders); ?> รายการ)</div> <div class="chart-container" style="height: 280px; position: relative;"> <canvas id="superAdminOrderStatusChart"></canvas> </div> </div> </div> </section> <!-- 2. Tenant Management Tab --> <section id="tenants-sec" class="tab-sec"> <div class="main-header"> <h2>Tenant Management (การควบคุมร้านค้าผู้เช่า)</h2> </div> <div class="split-grid"> <!-- Left: Table list --> <div class="card"> <h3>รายชื่อร้านอาหารผู้เช่าระบบ</h3> <div class="table-wrapper"> <table> <thead> <tr> <th>รหัส</th> <th>ชื่อร้านอาหาร</th> <th>หมวดหมู่</th> <th>แพ็กเกจ</th> <th>สถานะ / ระยะเวลาทดลองใช้</th> <th>การจัดการ</th> </tr> </thead> <tbody> <?php if (empty($tenants)): ?> <tr> <td colspan="6" style="text-align: center; color: var(--text-secondary);">ยังไม่มีร้านค้าในระบบ</td> </tr> <?php else: ?> <?php foreach ($tenants as $t): $st = strtolower($t['status'] ?? 'trial'); $trial_time = !empty($t['trial_ends_at']) ? strtotime($t['trial_ends_at']) : 0; $is_expired = ($st !== 'active' && $st !== 'paid') && ($trial_time === 0 || $trial_time < time()); $days_left = ($trial_time > time()) ? ceil(($trial_time - time()) / 86400) : 0; ?> <tr> <td>#<?php echo $t['store_id']; ?></td> <td><strong><?php echo htmlspecialchars($t['store_name']); ?></strong></td> <td><?php echo htmlspecialchars($t['category'] ?: '-'); ?></td> <td><span class="badge badge-plan"><?php echo htmlspecialchars($t['plan_name'] ?: '-'); ?></span></td> <td> <?php if ($st === 'active' || $st === 'paid'): ?> <span class="badge badge-active" style="background: rgba(16,185,129,0.2); color: #10B981; border: 1px solid #10B981;">🟢 สมาชิกชำระเงิน (Active)</span> <?php elseif ($is_expired): ?> <span class="badge" style="background: rgba(239,68,68,0.2); color: #EF4444; border: 1px solid #EF4444;">🔴 หมดเวลาทดลองใช้ (Expired)</span> <div style="font-size: 11px; color: #fca5a5; margin-top: 3px;">หมดอายุเมื่อ: <?php echo !empty($t['trial_ends_at']) ? date('d/m/Y H:i', strtotime($t['trial_ends_at'])) : 'ไม่ระบุ'; ?></div> <?php else: ?> <span class="badge" style="background: rgba(0,229,255,0.15); color: #00E5FF; border: 1px solid #00E5FF;">⏳ ทดลองใช้ฟรี (เหลือ <?php echo $days_left; ?> วัน)</span> <div style="font-size: 11px; color: var(--text-secondary); margin-top: 3px;">ถึงวันที่: <?php echo date('d/m/Y H:i', $trial_time); ?></div> <?php endif; ?> </td> <td> <div style="display: flex; gap: 6px; flex-wrap: wrap;"> <a href="platform-admin.php?action=impersonate&store_id=<?php echo $t['store_id']; ?>" class="btn-sm" style="background: #00E5FF; color: #0A192F; font-weight: bold; text-decoration: none; padding: 4px 8px; border-radius: 4px;">เข้าแก้ไข/จัดการร้าน</a> <?php $ext_days = ($t['plan_id'] == 3) ? 7 : 14; ?> <form method="POST" style="display:inline;"> <input type="hidden" name="extend_trial" value="1"> <input type="hidden" name="store_id" value="<?php echo $t['store_id']; ?>"> <input type="hidden" name="extend_days" value="<?php echo $ext_days; ?>"> <button type="submit" onclick="return confirm('ขยายเวลาทดลองใช้งานร้านค้า <?php echo htmlspecialchars(addslashes($t['store_name'])); ?> เพิ่ม +<?php echo $ext_days; ?> วัน?')" class="btn-sm" style="background: rgba(255,159,67,0.2); color: #FF9F43; border: 1px solid #FF9F43; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-weight: bold;"> ➕ ขยายทดลอง (+<?php echo $ext_days; ?> วัน) </button> </form> <a href="platform-admin.php?action=delete_tenant&store_id=<?php echo $t['store_id']; ?>" onclick="return confirm('คุณแน่ใจหรือไม่ที่จะลบร้านค้านี้ ข้อมูลเมนู อาหาร และโต๊ะ ทั้งหมดจะถูกลบถาวร?')" class="btn-sm btn-delete">ลบร้านค้า</a> </div> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> </div> <!-- Right: Add Form --> <div class="card"> <h3>ลงทะเบียนร้านค้าใหม่ (Add Tenant)</h3> <form method="POST"> <input type="hidden" name="add_tenant" value="1"> <div class="form-group"> <label class="form-label" for="store_name">ชื่อร้านอาหาร *</label> <input type="text" name="store_name" id="store_name" class="form-control" required> </div> <div class="form-group"> <label class="form-label" for="category">หมวดหมู่อาหาร</label> <input type="text" name="category" id="category" class="form-control" placeholder="เช่น ชาบู, ตามสั่ง"> </div> <div class="form-group"> <label class="form-label" for="address">ที่ตั้ง/สาขา</label> <textarea name="address" id="address" class="form-control" rows="2"></textarea> </div> <div class="form-group"> <label class="form-label" for="plan_id">แพ็กเกจการเช่า *</label> <select name="plan_id" id="plan_id" class="form-control" required> <option value="">-- เลือกแพ็กเกจ --</option> <?php foreach ($plans as $p): ?> <option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['name']); ?> (฿<?php echo number_format($p['price']); ?>/ด.)</option> <?php endforeach; ?> </select> </div> <hr style="border:0; border-top:1px solid rgba(255,255,255,0.06); margin: 20px 0;"> <h4 style="margin: 0 0 12px 0; font-size: 13.5px; color: var(--accent);">ข้อมูลแอดมินร้านค้า</h4> <div class="form-group"> <label class="form-label" for="admin_username">ชื่อบัญชีผู้ใช้ (Username) *</label> <input type="text" name="admin_username" id="admin_username" class="form-control" required> </div> <div class="form-group"> <label class="form-label" for="admin_password">รหัสผ่านสำหรับร้าน *</label> <input type="password" name="admin_password" id="admin_password" class="form-control" required> </div> <button type="submit" class="btn-primary" style="margin-top: 10px;">บันทึกสร้างร้านค้าใหม่</button> </form> </div> </div> </section> <!-- 3. Plan & Pricing Manager Tab --> <section id="plans-sec" class="tab-sec"> <div class="main-header"> <h2>💳 Plan & Pricing Manager (การตั้งค่าแพ็กเกจราคา)</h2> </div> <div class="split-grid"> <!-- Left: Plans List --> <div class="card"> <h3>รายการแพ็กเกจและโควต้าโต๊ะปัจจุบัน</h3> <div class="table-wrapper"> <table> <thead> <tr> <th>รหัส</th> <th>ชื่อแผน</th> <th>ราคา / เดือน</th> <th>จำกัดโต๊ะ</th> <th>คำอธิบาย</th> </tr> </thead> <tbody> <?php foreach ($plans as $p): ?> <tr> <td>#<?php echo $p['id']; ?></td> <td><strong style="color: var(--accent);"><?php echo htmlspecialchars($p['name']); ?></strong></td> <td>฿<?php echo number_format($p['price'], 2); ?></td> <td><?php echo $p['limit_tables']; ?> โต๊ะ</td> <td><span style="font-size:12.5px; color: var(--text-secondary);"><?php echo htmlspecialchars($p['description']); ?></span></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <!-- Right: Edit Plan Form --> <div class="card"> <h3>⚙️ ปรับเปลี่ยนราคารายเดือน / โควต้า</h3> <form method="POST"> <input type="hidden" name="update_plan" value="1"> <div class="form-group"> <label class="form-label" for="edit_plan_id">เลือกแพ็กเกจที่ต้องการอัปเดต *</label> <select name="plan_id" id="edit_plan_id" class="form-control" onchange="loadPlanDetails(this.value)" required> <option value="">-- เลือกแพ็กเกจ --</option> <?php foreach ($plans as $p): ?> <option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['name']); ?></option> <?php endforeach; ?> </select> </div> <div class="form-group"> <label class="form-label" for="edit_price">ราคาเช่ารายเดือน (บาท) *</label> <input type="number" step="0.01" name="price" id="edit_price" class="form-control" required> </div> <div class="form-group"> <label class="form-label" for="edit_limit_tables">จำกัดจำนวนโต๊ะสแกน *</label> <input type="number" name="limit_tables" id="edit_limit_tables" class="form-control" required> </div> <div class="form-group"> <label class="form-label" for="edit_desc">รายละเอียดเงื่อนไข</label> <textarea name="description" id="edit_desc" class="form-control" rows="3"></textarea> </div> <button type="submit" class="btn-primary" style="margin-top: 10px;">✓ บันทึกปรับข้อมูลแผน</button> </form> </div> </div> </section> <!-- 4. Store Deep-Dive Tab --> <section id="impersonate-sec" class="tab-sec"> <div class="main-header"> <h2>🔑 Store Deep-Dive (สวมสิทธิ์ดูแลร้านอาหารย่อย)</h2> </div> <div class="card"> <h3>เข้าจัดการข้อมูลหลังบ้านของแอดมินร้านค้าแต่ละรายโดยตรง</h3> <p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 13.5px;">* เมื่อกดปุ่มสวมสิทธิ์ ระบบจะบันทึกเซสชันแอดมินหน้าร้านนั้นและข้ามหน้าพอร์ทัลไปยังแดชบอร์ดร้านอาหารย่อยทันทีเพื่อเข้าช่วยเหลือแก้ไขปัญหา</p> <div class="table-wrapper"> <table> <thead> <tr> <th>รหัสร้าน</th> <th>ชื่อร้านอาหาร</th> <th>หมวดหมู่อาหาร</th> <th>แพ็กเกจที่สมัคร</th> <th>บัญชีผู้ใช้ร้าน</th> <th style="text-align: center;">สิทธิ์ข้ามควบคุม</th> </tr> </thead> <tbody> <?php if (empty($tenants)): ?> <tr> <td colspan="6" style="text-align: center; color: var(--text-secondary);">ยังไม่มีร้านค้าในระบบสำหรับการควบคุมแทน</td> </tr> <?php else: ?> <?php foreach ($tenants as $t): ?> <tr> <td>#<?php echo $t['store_id']; ?></td> <td><strong><?php echo htmlspecialchars($t['store_name']); ?></strong></td> <td><?php echo htmlspecialchars($t['category'] ?: '-'); ?></td> <td><span class="badge badge-plan"><?php echo htmlspecialchars($t['plan_name'] ?: '-'); ?></span></td> <td><code><?php echo htmlspecialchars($t['admin_username'] ?: '-'); ?></code></td> <td style="text-align: center;"> <a href="platform-admin.php?action=impersonate&store_id=<?php echo $t['store_id']; ?>" class="btn-sm btn-impersonate">🍳 สวมสิทธิ์เข้าจัดการแทน</a> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> </div> </section> <!-- 5. Payment Verification & Notification Tab --> <section id="payments-sec" class="tab-sec"> <div class="main-header"> <h2>💳 Payment Verification & Approval Logs (ตรวจสอบและอนุมัติชำระเงิน)</h2> </div> <div class="card"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px;"> <h3 style="margin: 0;">🔔 รายการชำระเงินค่าสมัครและต่ออายุแพ็กเกจ (Payment Transactions)</h3> <span style="font-size: 13.5px; color: var(--text-secondary);">รายการค้างตรวจสอบ: <strong style="color: #FF9800; font-size: 16px;"><?php echo $pending_payment_count; ?></strong> รายการ</span> </div> <div class="table-wrapper"> <table> <thead> <tr> <th>ID</th> <th>ร้านค้า</th> <th>แพ็กเกจ</th> <th>ยอดชำระ</th> <th>หลักฐานสลิป</th> <th>สถานะ</th> <th>วันที่ชำระ</th> <th style="text-align: center;">การอนุมัติสิทธิ์</th> </tr> </thead> <tbody> <?php if (empty($payment_logs)): ?> <tr> <td colspan="8" style="text-align: center; color: var(--text-secondary); padding: 30px;">ยังไม่มีรายการชำระเงินในระบบ</td> </tr> <?php else: ?> <?php foreach ($payment_logs as $pay): ?> <tr> <td style="font-weight: bold; color: var(--text-secondary);">#PAY-<?php echo $pay['payment_id']; ?></td> <td><strong style="color: #fff; font-size: 14px;"><?php echo htmlspecialchars($pay['store_name']); ?></strong></td> <td><span class="badge badge-plan"><?php echo htmlspecialchars($pay['plan_name']); ?></span></td> <td style="font-weight: 800; color: var(--accent);">฿<?php echo number_format($pay['amount'], 2); ?></td> <td> <?php if (!empty($pay['slip_url'])): ?> <button type="button" onclick="previewSlip('<?php echo htmlspecialchars($pay['slip_url']); ?>', '<?php echo htmlspecialchars(addslashes($pay['store_name'])); ?>')" class="btn-sm" style="background: rgba(0, 229, 255, 0.15); color: #00E5FF; border: 1px solid rgba(0, 229, 255, 0.3); cursor: pointer; display: inline-flex; align-items: center; gap: 4px;"> 🖼️ ดูหลักฐานสลิป </button> <?php else: ?> <span style="color: var(--text-secondary); font-size: 11px; font-style: italic;">ไม่มีแนบ</span> <?php endif; ?> </td> <td> <?php if ($pay['status'] === 'pending'): ?> <span class="badge" style="background: rgba(255, 152, 0, 0.2); color: #FF9800; border: 1px solid #FF9800; padding: 4px 10px; border-radius: 6px;">⏳ รอตรวจสอบ</span> <?php elseif ($pay['status'] === 'approved'): ?> <span class="badge badge-active" style="padding: 4px 10px; border-radius: 6px;">✓ อนุมัติแล้ว</span> <?php else: ?> <span class="badge" style="background: rgba(244, 67, 54, 0.2); color: #F44336; border: 1px solid #F44336; padding: 4px 10px; border-radius: 6px;" title="<?php echo htmlspecialchars($pay['note']); ?>">❌ ปฏิเสธ</span> <?php endif; ?> </td> <td style="color: var(--text-secondary); font-size: 12px;"><?php echo htmlspecialchars($pay['created_at']); ?></td> <td style="text-align: center;"> <?php if ($pay['status'] === 'pending'): ?> <div style="display: flex; gap: 6px; justify-content: center;"> <form method="POST" style="display: inline;" onsubmit="return confirm('ยืนยันอนุมัติการชำระเงินและปรับสถานะร้านเป็น Active?');"> <input type="hidden" name="approve_payment" value="1"> <input type="hidden" name="payment_id" value="<?php echo $pay['payment_id']; ?>"> <button type="submit" class="btn-sm" style="background: #4CAF50; color: #fff; border: none; cursor: pointer; font-weight: bold; padding: 6px 12px;">✓ อนุมัติสิทธิ์</button> </form> <button type="button" onclick="openRejectModal(<?php echo $pay['payment_id']; ?>, '<?php echo htmlspecialchars(addslashes($pay['store_name'])); ?>')" class="btn-sm btn-delete" style="border: none; cursor: pointer; padding: 6px 12px;">✕ ปฏิเสธ</button> </div> <?php else: ?> <span style="color: var(--text-secondary); font-size: 12px;">การดำเนินการเสร็จสิ้น</span> <?php endif; ?> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> </div> </section> </main> </div> <!-- Footer --> <footer style="text-align: center; padding: 30px; color: var(--text-secondary); font-size: 13px; border-top: 1px solid var(--border-glass); margin-top: 50px; background: var(--dark-bg); width: 100%; box-sizing: border-box;"> CMTC Tech Solution Platform • Chiang Mai Technical College © 2026. </footer> <!-- JavaScript Navigation and Charts Logic --> <script> // JS Tab Switch Controller function showTab(tabId) { if (!tabId) return; // Hide all tab sections document.querySelectorAll('.tab-sec').forEach(sec => { if (sec) { sec.classList.remove('active'); sec.style.display = 'none'; } }); // Show target section const targetSec = document.getElementById(tabId + '-sec'); if (targetSec) { targetSec.classList.add('active'); targetSec.style.display = 'block'; } // Remove active state from all sidebar buttons document.querySelectorAll('.menu-btn').forEach(btn => { if (btn) btn.classList.remove('active'); }); // Highlight active button const activeBtn = document.getElementById('btn-' + tabId); if (activeBtn) { activeBtn.classList.add('active'); } // Save active tab state try { localStorage.setItem('super-admin-active-tab', tabId); } catch(e){} // Auto-collapse Mobile Sidebar Drawer & Smooth Scroll to Section on Mobile const sb = document.querySelector('.sidebar'); if (sb && sb.classList.contains('mobile-show')) { sb.classList.remove('mobile-show'); } if (targetSec && window.innerWidth <= 992) { setTimeout(() => { targetSec.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 50); } } // Restore tab state on page load window.addEventListener('DOMContentLoaded', () => { const activeTab = localStorage.getItem('super-admin-active-tab') || 'overview'; showTab(activeTab); // Check for direct store access error const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('error') === 'direct_store_access_denied') { if (typeof Swal !== 'undefined') { Swal.fire({ title: '⚠️ ปฏิเสธการเข้าถึงหน้าจัดการร้านค้า', html: ` <div style="text-align: left; font-family: 'Kanit', sans-serif; color: #f8fafc; line-height: 1.6; padding: 5px 0;"> <p style="margin: 0 0 12px 0; font-size: 14.5px;"> คุณเข้าสู่ระบบในฐานะ <strong style="color: #c084fc;">Super Admin (ผู้ดูแลระบบหลัก)</strong> ไม่สามารถเข้าหน้า Store Admin ตรงๆ ได้เนื่องจากไม่ได้ระบุชื่อร้านค้า </p> <div style="background: rgba(0, 229, 255, 0.1); border: 1px solid rgba(0, 229, 255, 0.25); border-radius: 10px; padding: 12px; margin-bottom: 12px; font-size: 13.5px;"> 👉 <strong>วิธีเข้าจัดการร้านค้า:</strong><br> กรุณาไปที่แท็บ <strong>"ร้านค้า (Tenants)"</strong> และกดปุ่ม <strong style="color: #00E5FF;">"เข้าแก้ไข/จัดการร้าน"</strong> หรือสวมสิทธิ์เพื่อเข้าทำงานแทนในแต่ละร้านค้าครับ </div> <p style="margin: 0; font-size: 13px; color: #94a3b8; text-align: center;"> *กรณีต้องการล็อกอินบัญชีร้านค้าโดยตรง กรุณากดออกจากระบบ Super Admin ก่อนครับ </p> </div> `, icon: 'warning', background: '#1e293b', color: '#ffffff', confirmButtonColor: '#00E5FF', confirmButtonText: 'ตกลง' }); } } }); // Load plan values in form dynamically const plansData = <?php echo json_encode($plans); ?>; function loadPlanDetails(planId) { const plan = plansData.find(p => p.id == planId); if (plan) { document.getElementById('edit_price').value = plan.price; document.getElementById('edit_limit_tables').value = plan.limit_tables; document.getElementById('edit_desc').value = plan.description; } else { document.getElementById('edit_price').value = ''; document.getElementById('edit_limit_tables').value = ''; document.getElementById('edit_desc').value = ''; } } function toggleMobileSidebar() { const sb = document.querySelector('.sidebar'); if (sb) sb.classList.toggle('mobile-show'); } // Render Analytics Charts using Chart.js // 1. Bar Chart Data (Subscriber distribution per package) const planChartData = <?php echo json_encode($plan_chart_data); ?>; const barLabels = planChartData.map(item => item.name); const barCounts = planChartData.map(item => parseInt(item.count)); const elBar = document.getElementById('planBarChart'); if (elBar) { new Chart(elBar.getContext('2d'), { type: 'bar', data: { labels: barLabels, datasets: [{ label: 'จำนวนร้านค้าสมัครเช่า (Tenants)', data: barCounts, backgroundColor: '#00E5FF', borderColor: '#00B4D8', borderWidth: 1.5, borderRadius: 6 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#94a3b8', font: { family: 'Sarabun' } } } }, scales: { x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8', font: { family: 'Sarabun' } } }, y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8', stepSize: 1 } } } } }); } // 2. Line Chart Data (Orders Trend over last 7 days) const orderChartData = <?php echo json_encode($order_chart_data); ?>; const lineLabels = orderChartData.map(item => item.date_str); const lineCounts = orderChartData.map(item => parseInt(item.count)); const elLine = document.getElementById('growthLineChart'); if (elLine) { new Chart(elLine.getContext('2d'), { type: 'line', data: { labels: lineLabels, datasets: [{ label: 'ยอดคำสั่งซื้อรวมในระบบ (Total Orders)', data: lineCounts, borderColor: '#FFC000', backgroundColor: 'rgba(255, 192, 0, 0.15)', borderWidth: 3, fill: true, tension: 0.35, pointBackgroundColor: '#FFC000', pointRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#94a3b8', font: { family: 'Sarabun' } } } }, scales: { x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8', font: { family: 'Sarabun' } } }, y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8', stepSize: 1 } } } } }); } // 3. Donut Chart Data (Tenant Status Ratio) const tenantStatusData = <?php echo json_encode($tenant_status_data); ?>; const donutLabels = tenantStatusData.map(item => item.status === 'active' ? '🟢 ใช้งานปกติ (Active)' : (item.status === 'pending' ? '⏳ รออนุมัติ' : '🔴 ปิดใช้งาน')); const donutCounts = tenantStatusData.map(item => parseInt(item.count)); const elDonut = document.getElementById('statusDoughnutChart'); if (elDonut) { new Chart(elDonut.getContext('2d'), { type: 'doughnut', data: { labels: donutLabels, datasets: [{ data: donutCounts, backgroundColor: ['#10B981', '#FF9F43', '#EA5455', '#3B82F6'], borderColor: '#1e293b', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#f8fafc', font: { family: 'Sarabun' } } } } } }); } // 4. Super Admin All Stores Order Status Distribution Chart const superAdminStatusRaw = <?php echo json_encode($super_admin_status_data, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); ?>; const elSuperStatus = document.getElementById('superAdminOrderStatusChart'); if (elSuperStatus) { const statusColorMap = { 'unpaid': '#A855F7', 'pending': '#A855F7', 'preparing': '#00E5FF', 'served': '#3B82F6', 'ready': '#3B82F6', 'completed': '#28C76F', 'cancelled': '#EA5455' }; const statusLabelMap = { 'unpaid': 'ออเดอร์ใหม่ (รอรับรายการ)', 'pending': 'ออเดอร์ใหม่ (รอรับรายการ)', 'preparing': 'กำลังเตรียมปรุง', 'served': 'พร้อมเสิร์ฟ/เสิร์ฟแล้ว', 'ready': 'พร้อมเสิร์ฟ/เสิร์ฟแล้ว', 'completed': 'เช็กบิลแล้ว (เสร็จสิ้น)', 'cancelled': 'ยกเลิกออเดอร์' }; let saLabels = []; let saCounts = []; let saColors = []; if (superAdminStatusRaw && superAdminStatusRaw.length > 0) { saLabels = superAdminStatusRaw.map(item => statusLabelMap[item.status] || item.status); saCounts = superAdminStatusRaw.map(item => parseInt(item.count)); saColors = superAdminStatusRaw.map(item => statusColorMap[item.status] || '#A855F7'); } else { saLabels = ['ไม่มีข้อมูลออเดอร์ในระบบ']; saCounts = [1]; saColors = ['#334155']; } new Chart(elSuperStatus.getContext('2d'), { type: 'doughnut', data: { labels: saLabels, datasets: [{ data: saCounts, backgroundColor: saColors, borderColor: '#1e293b', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#f8fafc', font: { family: 'Sarabun', size: 12 } } } } } }); } // Modal helper functions for System Users function openUsersModal() { const modal = document.getElementById('usersListModal'); if (modal) modal.style.display = 'flex'; } function closeUsersModal() { const modal = document.getElementById('usersListModal'); if (modal) modal.style.display = 'none'; } // Modal helper functions for Slip Preview and Payment Rejection function previewSlip(slipUrl, storeName) { const modal = document.getElementById('slipPreviewModal'); const img = document.getElementById('slipImagePreview'); const title = document.getElementById('slipModalTitle'); if (img && modal) { img.src = slipUrl; if (title) title.textContent = `🖼️ หลักฐานการชำระเงิน - ร้าน ${storeName}`; modal.style.display = 'flex'; } } function closeSlipModal() { const modal = document.getElementById('slipPreviewModal'); if (modal) modal.style.display = 'none'; } function openRejectModal(paymentId, storeName) { const modal = document.getElementById('rejectReasonModal'); const inputId = document.getElementById('reject_payment_id'); if (modal && inputId) { inputId.value = paymentId; modal.style.display = 'flex'; } } function closeRejectModal() { const modal = document.getElementById('rejectReasonModal'); if (modal) modal.style.display = 'none'; } </script> <!-- Modal for Slip Preview --> <div id="slipPreviewModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 25, 47, 0.85); backdrop-filter: blur(6px); z-index: 9999; justify-content: center; align-items: center; padding: 20px;"> <div style="background: #1E293B; border: 1px solid var(--border-glass); border-radius: 16px; width: 100%; max-width: 500px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.6); animation: fadeIn 0.25s ease-out;"> <div style="display: flex; justify-content: space-between; align-items: center; padding: 18px 24px; border-bottom: 1px solid rgba(255,255,255,0.1); background: rgba(15, 23, 42, 0.9);"> <h3 id="slipModalTitle" style="margin: 0; color: #00E5FF; font-weight: 800; font-size: 16px;">🖼️ หลักฐานการชำระเงิน (Payment Slip)</h3> <button type="button" onclick="closeSlipModal()" style="background: rgba(255,255,255,0.08); border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; font-size: 20px; cursor: pointer;">×</button> </div> <div style="padding: 20px; text-align: center; background: #0F172A;"> <img id="slipImagePreview" src="" alt="Payment Slip" style="max-width: 100%; max-height: 60vh; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); object-fit: contain;"> </div> <div style="padding: 16px 24px; border-top: 1px solid rgba(255,255,255,0.1); background: rgba(15, 23, 42, 0.9); text-align: right;"> <button type="button" onclick="closeSlipModal()" class="btn-sm" style="background: #334155; color: #fff; border: 1px solid rgba(255,255,255,0.1); padding: 8px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px;">ปิดหน้าต่าง</button> </div> </div> </div> <!-- Modal for Payment Rejection Reason --> <div id="rejectReasonModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 25, 47, 0.85); backdrop-filter: blur(6px); z-index: 9999; justify-content: center; align-items: center; padding: 20px;"> <div style="background: #1E293B; border: 1px solid rgba(244,67,54,0.3); border-radius: 16px; width: 100%; max-width: 450px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.6); animation: fadeIn 0.25s ease-out;"> <div style="display: flex; justify-content: space-between; align-items: center; padding: 18px 24px; border-bottom: 1px solid rgba(255,255,255,0.1); background: rgba(15, 23, 42, 0.9);"> <h3 style="margin: 0; color: #F44336; font-weight: 800; font-size: 16px;">❌ ปฏิเสธรายการชำระเงิน</h3> <button type="button" onclick="closeRejectModal()" style="background: rgba(255,255,255,0.08); border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; font-size: 20px; cursor: pointer;">×</button> </div> <form method="POST" style="padding: 20px; margin: 0;"> <input type="hidden" name="reject_payment" value="1"> <input type="hidden" id="reject_payment_id" name="payment_id" value=""> <div class="form-group"> <label class="form-label" for="reject_note">ระบุเหตุผลการปฏิเสธ (เพื่อบันทึกประวัติ):</label> <textarea name="reject_note" id="reject_note" class="form-control" rows="3" required placeholder="เช่น ไม่พบยอดโอนเข้าบัญชี หรือจำนวนเงินไม่ตรงตามแพ็กเกจ"></textarea> </div> <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 15px;"> <button type="button" onclick="closeRejectModal()" class="btn-sm" style="background: #334155; color: #fff; border: none; padding: 8px 18px; border-radius: 6px; cursor: pointer; font-weight: bold;">ยกเลิก</button> <button type="submit" class="btn-sm btn-delete" style="border: none; padding: 8px 18px; border-radius: 6px; cursor: pointer; font-weight: bold;">ยืนยันปฏิเสธ</button> </div> </form> </div> </div> <!-- Modal for System Users List --> <div id="usersListModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 25, 47, 0.85); backdrop-filter: blur(6px); z-index: 9999; justify-content: center; align-items: center; padding: 20px;"> <div style="background: #1E293B; border: 1px solid var(--border-glass); border-radius: 16px; width: 100%; max-width: 800px; max-height: 85vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.6); animation: fadeIn 0.25s ease-out;"> <div style="display: flex; justify-content: space-between; align-items: center; padding: 18px 24px; border-bottom: 1px solid rgba(255,255,255,0.1); background: rgba(15, 23, 42, 0.9);"> <div style="display: flex; align-items: center; gap: 10px;"> <span style="font-size: 22px;">👥</span> <div> <h3 style="margin: 0; color: #00E5FF; font-weight: 800; font-size: 18px;">รายชื่อบัญชีผู้ใช้งานทั้งหมดในระบบ (System Accounts)</h3> <div style="font-size: 12px; color: var(--text-secondary); margin-top: 2px;">แสดงรายการบัญชีผู้ใช้ระดับ Super Admin และ Admin ร้านค้าทั้งหมด (รวมทั้งสิ้น <?php echo count($user_list); ?> บัญชี)</div> </div> </div> <button type="button" onclick="closeUsersModal()" style="background: rgba(255,255,255,0.08); border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s;">×</button> </div> <div style="padding: 20px; overflow-y: auto; flex: 1;"> <div class="table-wrapper"> <table style="width: 100%; border-collapse: collapse; font-size: 13.5px;"> <thead> <tr style="border-bottom: 2px solid rgba(255,255,255,0.1); text-align: left; color: var(--text-secondary);"> <th style="padding: 12px 10px;">ID</th> <th style="padding: 12px 10px;">ชื่อบัญชีผู้ใช้ (Username)</th> <th style="padding: 12px 10px;">ระดับสิทธิ์ (Role)</th> <th style="padding: 12px 10px;">ร้านค้าที่สังกัด</th> <th style="padding: 12px 10px; text-align: center;">การจัดการ</th> </tr> </thead> <tbody> <?php foreach ($user_list as $u): ?> <tr style="border-bottom: 1px solid rgba(255,255,255,0.06);"> <td style="padding: 12px 10px; font-weight: bold; color: var(--text-secondary);">#<?php echo $u['id']; ?></td> <td style="padding: 12px 10px;"> <strong style="color: #fff; font-size: 14px;"><?php echo htmlspecialchars($u['username']); ?></strong> </td> <td style="padding: 12px 10px;"> <?php if ($u['role'] === 'super_admin'): ?> <span class="badge" style="background: #A855F7; color: #fff; padding: 4px 10px; border-radius: 6px; font-weight: 800; font-size: 11.5px; display: inline-block;">👑 Super Admin</span> <?php else: ?> <span class="badge badge-active" style="padding: 4px 10px; border-radius: 6px; font-weight: 800; font-size: 11.5px; display: inline-block;">🏪 Store Admin</span> <?php endif; ?> </td> <td style="padding: 12px 10px;"> <?php if ($u['store_id'] > 0): ?> <div style="font-weight: bold; color: var(--accent);"><?php echo htmlspecialchars($u['store_name'] ?: 'ร้านค้า #' . $u['store_id']); ?></div> <div style="font-size: 11px; color: var(--text-secondary);"><?php echo htmlspecialchars($u['plan_name'] ? $u['plan_name'] . ' Plan' : '-'); ?></div> <?php else: ?> <span style="color: var(--text-secondary); font-style: italic;">🌐 ระบบส่วนกลาง (Central Platform)</span> <?php endif; ?> </td> <td style="padding: 12px 10px; text-align: center;"> <?php if ($u['store_id'] > 0): ?> <a href="platform-admin.php?action=impersonate&store_id=<?php echo $u['store_id']; ?>" class="btn-sm" style="background: #00E5FF; color: #0A192F; font-weight: bold; text-decoration: none; padding: 5px 10px; border-radius: 6px; font-size: 11.5px; display: inline-flex; align-items: center; gap: 4px;"> ⚡ เข้าจัดการร้าน </a> <?php else: ?> <span style="color: var(--text-secondary); font-size: 12px;">บัญชีหลัก</span> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <div style="padding: 16px 24px; border-top: 1px solid rgba(255,255,255,0.1); background: rgba(15, 23, 42, 0.9); text-align: right;"> <button type="button" onclick="closeUsersModal()" class="btn-sm" style="background: #334155; color: #fff; border: 1px solid rgba(255,255,255,0.1); padding: 8px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px;">ปิดหน้าต่าง</button> </div> </div> </div> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings