File manager - Edit - /home/webapp69.cm.in.th/u69319090038/Shop/seller-center.php
Back
<?php require_once __DIR__ . '/config/config.php'; ?> <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="ศูนย์ผู้ขาย Sarin Shop — จัดการสินค้า คำสั่งซื้อ และลูกค้าของคุณ"> <title>ศูนย์ผู้ขาย — Sarin Shop</title> <link rel="stylesheet" href="./public/css/style.css"> <style> /* ── Seller Center Layout ───────────────────────────────── */ .sc-layout { display:grid; grid-template-columns:240px 1fr; gap:1.5rem; align-items:start; } @media(max-width:768px){ .sc-layout{ grid-template-columns:1fr; } .sc-sidebar{ display:none; } } .sc-sidebar { background:var(--card); border-radius:var(--r-lg); padding:1.25rem; box-shadow:var(--shadow-sm); position:sticky; top:80px; } .sc-nav-item { display:flex; align-items:center; gap:.75rem; padding:.75rem 1rem; border-radius:var(--r-md); cursor:pointer; font-weight:500; font-size:.9rem; color:var(--text-secondary); border:none; background:none; width:100%; text-align:left; transition:all .2s; margin-bottom:.25rem; } .sc-nav-item:hover { background:var(--bg); color:var(--text-primary); } .sc-nav-item.active { background:linear-gradient(135deg,var(--brand),#ff6b35); color:#fff; } .sc-nav-item .nav-icon { font-size:1.2rem; width:24px; text-align:center; } .sc-nav-badge { margin-left:auto; background:var(--brand); color:#fff; border-radius:99px; font-size:.7rem; font-weight:700; padding:2px 7px; } .sc-nav-item.active .sc-nav-badge { background:rgba(255,255,255,.3); } .sc-nav-divider { border:none; border-top:1px solid var(--border); margin:.75rem 0; } .sc-shop-info { text-align:center; padding-bottom:1rem; margin-bottom:1rem; border-bottom:1px solid var(--border); } .sc-shop-avatar { width:64px; height:64px; border-radius:50%; object-fit:cover; margin-bottom:.5rem; border:3px solid var(--brand); } .sc-shop-name { font-weight:700; font-size:.95rem; color:var(--text-primary); } .sc-shop-status { font-size:.75rem; color:var(--success); margin-top:.25rem; } /* ── Section ────────────────────────────────────────────── */ .sc-section { display:none; } .sc-section.active { display:block; } /* ── Stats Grid ─────────────────────────────────────────── */ .sc-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:1rem; margin-bottom:1.5rem; } .sc-stat-card { background:var(--card); border-radius:var(--r-lg); padding:1.25rem; box-shadow:var(--shadow-sm); display:flex; align-items:center; gap:1rem; } .sc-stat-icon { width:48px; height:48px; border-radius:var(--r-md); display:flex; align-items:center; justify-content:center; font-size:1.4rem; flex-shrink:0; } .sc-stat-icon.orange { background:rgba(255,107,0,.12); } .sc-stat-icon.green { background:rgba(34,197,94,.12); } .sc-stat-icon.blue { background:rgba(59,130,246,.12); } .sc-stat-icon.purple { background:rgba(168,85,247,.12); } .sc-stat-value { font-size:1.5rem; font-weight:800; color:var(--text-primary); line-height:1; } .sc-stat-label { font-size:.78rem; color:var(--text-muted); margin-top:.3rem; } /* ── Quick Actions ──────────────────────────────────────── */ .quick-actions { display:grid; grid-template-columns:repeat(4,1fr); gap:1rem; margin-bottom:1.5rem; } @media(max-width:600px){ .quick-actions{ grid-template-columns:repeat(2,1fr); } } .quick-action-btn { background:var(--card); border:2px solid var(--border); border-radius:var(--r-lg); padding:1.25rem .75rem; cursor:pointer; text-align:center; transition:all .25s; display:flex; flex-direction:column; align-items:center; gap:.5rem; } .quick-action-btn:hover { border-color:var(--brand); transform:translateY(-2px); box-shadow:0 8px 24px rgba(238,77,45,.15); } .quick-action-btn .qa-icon { font-size:2rem; } .quick-action-btn .qa-label { font-size:.85rem; font-weight:600; color:var(--text-primary); } /* ── Products ───────────────────────────────────────────── */ .product-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:1rem; } .product-card { background:var(--card); border-radius:var(--r-lg); overflow:hidden; box-shadow:var(--shadow-sm); transition:all .25s; } .product-card:hover { transform:translateY(-3px); box-shadow:var(--shadow-md); } .product-card-img { width:100%; height:160px; object-fit:cover; background:var(--bg); } .product-card-img-placeholder { width:100%; height:160px; background:linear-gradient(135deg,var(--bg),var(--border)); display:flex; align-items:center; justify-content:center; font-size:3rem; } .product-card-body { padding:1rem; } .product-card-name { font-weight:600; font-size:.9rem; margin-bottom:.35rem; color:var(--text-primary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .product-card-price { color:var(--brand); font-weight:700; font-size:1rem; } .product-card-stock { font-size:.78rem; color:var(--text-muted); margin-top:.25rem; } .product-card-actions { display:flex; gap:.5rem; margin-top:.75rem; } .product-badge { display:inline-block; font-size:.7rem; font-weight:700; padding:2px 8px; border-radius:99px; } .product-badge.active { background:rgba(34,197,94,.15); color:#16a34a; } .product-badge.inactive { background:rgba(239,68,68,.15); color:#dc2626; } /* ── Orders Table ───────────────────────────────────────── */ .orders-filter { display:flex; gap:.5rem; flex-wrap:wrap; margin-bottom:1rem; } .filter-btn { padding:.4rem 1rem; border-radius:99px; border:1.5px solid var(--border); background:none; font-size:.8rem; cursor:pointer; color:var(--text-secondary); transition:all .2s; font-weight:500; } .filter-btn.active, .filter-btn:hover { border-color:var(--brand); color:var(--brand); background:rgba(238,77,45,.06); } .order-card { background:var(--card); border-radius:var(--r-lg); padding:1.25rem; margin-bottom:.75rem; box-shadow:var(--shadow-sm); border-left:4px solid transparent; transition:all .2s; } .order-card:hover { box-shadow:var(--shadow-md); } .order-card.pending { border-left-color:#f59e0b; } .order-card.confirmed { border-left-color:#3b82f6; } .order-card.shipping { border-left-color:#8b5cf6; } .order-card.delivered { border-left-color:#22c55e; } .order-card.cancelled { border-left-color:#ef4444; } .order-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:.75rem; } .order-id { font-weight:700; font-size:.85rem; color:var(--text-muted); } .status-badge { padding:.3rem .8rem; border-radius:99px; font-size:.75rem; font-weight:700; } .status-badge.pending { background:#fef3c7; color:#92400e; } .status-badge.confirmed { background:#dbeafe; color:#1e40af; } .status-badge.shipping { background:#ede9fe; color:#5b21b6; } .status-badge.delivered { background:#dcfce7; color:#166534; } .status-badge.cancelled { background:#fee2e2; color:#991b1b; } .order-buyer { font-weight:600; font-size:.9rem; } .order-items-list { margin:.5rem 0; } .order-item-row { display:flex; align-items:center; gap:.5rem; font-size:.82rem; color:var(--text-secondary); padding:.25rem 0; } .order-footer { display:flex; align-items:center; justify-content:space-between; margin-top:.75rem; padding-top:.75rem; border-top:1px solid var(--border); } .order-total { font-weight:700; color:var(--brand); font-size:1rem; } .order-actions { display:flex; gap:.5rem; } /* ── Sales Chart ────────────────────────────────────────── */ .chart-wrap { background:var(--card); border-radius:var(--r-lg); padding:1.5rem; box-shadow:var(--shadow-sm); margin-bottom:1.5rem; } .chart-title { font-weight:700; font-size:1rem; margin-bottom:1.25rem; } .bar-chart { display:flex; align-items:flex-end; gap:.5rem; height:180px; padding-bottom:1.5rem; position:relative; } .bar-chart::after { content:''; position:absolute; bottom:1.5rem; left:0; right:0; height:1px; background:var(--border); } .bar-col { flex:1; display:flex; flex-direction:column; align-items:center; gap:.3rem; height:100%; justify-content:flex-end; } .bar-fill { width:100%; border-radius:var(--r-sm) var(--r-sm) 0 0; background:linear-gradient(180deg,var(--brand),#ff6b35); transition:height .6s cubic-bezier(.34,1.56,.64,1); min-height:4px; } .bar-label { font-size:.7rem; color:var(--text-muted); margin-top:.35rem; text-align:center; } .bar-value { font-size:.7rem; font-weight:700; color:var(--text-primary); } .report-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:1rem; margin-bottom:1.5rem; } .report-card { background:var(--card); border-radius:var(--r-lg); padding:1.25rem; text-align:center; box-shadow:var(--shadow-sm); } .report-card-icon { font-size:2rem; margin-bottom:.5rem; } .report-card-value { font-size:1.6rem; font-weight:800; color:var(--text-primary); } .report-card-label { font-size:.78rem; color:var(--text-muted); margin-top:.25rem; } /* ── Messages ───────────────────────────────────────────── */ .msg-layout { display:grid; grid-template-columns:260px 1fr; gap:1rem; height:520px; } @media(max-width:600px){ .msg-layout{ grid-template-columns:1fr; height:auto; } .msg-thread{ display:none; } .msg-thread.active{ display:flex; } } .msg-convs { background:var(--card); border-radius:var(--r-lg); overflow:hidden; box-shadow:var(--shadow-sm); } .msg-convs-header { padding:1rem; font-weight:700; font-size:.9rem; border-bottom:1px solid var(--border); } .msg-convs-list { overflow-y:auto; height:calc(100% - 52px); } .conv-item { display:flex; align-items:center; gap:.75rem; padding:.85rem 1rem; cursor:pointer; transition:background .15s; border-bottom:1px solid var(--border); } .conv-item:hover, .conv-item.active { background:rgba(238,77,45,.06); } .conv-avatar { width:40px; height:40px; border-radius:50%; object-fit:cover; flex-shrink:0; } .conv-name { font-weight:600; font-size:.85rem; } .conv-preview { font-size:.75rem; color:var(--text-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:140px; } .conv-unread { background:var(--brand); color:#fff; border-radius:99px; font-size:.7rem; font-weight:700; padding:1px 6px; margin-left:auto; flex-shrink:0; } .msg-thread { background:var(--card); border-radius:var(--r-lg); overflow:hidden; box-shadow:var(--shadow-sm); display:flex; flex-direction:column; } .msg-thread-header { padding:1rem 1.25rem; border-bottom:1px solid var(--border); font-weight:700; font-size:.9rem; display:flex; align-items:center; gap:.75rem; } .msg-thread-body { flex:1; overflow-y:auto; padding:1rem; display:flex; flex-direction:column; gap:.5rem; } .msg-bubble { max-width:70%; padding:.65rem 1rem; border-radius:1rem; font-size:.85rem; line-height:1.5; } .msg-bubble.buyer { background:var(--bg); border-radius:1rem 1rem 1rem .25rem; align-self:flex-start; } .msg-bubble.seller { background:linear-gradient(135deg,var(--brand),#ff6b35); color:#fff; border-radius:1rem 1rem .25rem 1rem; align-self:flex-end; } .msg-time { font-size:.7rem; color:var(--text-muted); margin-top:.2rem; } .msg-input-wrap { padding:1rem; border-top:1px solid var(--border); display:flex; gap:.5rem; } .msg-input { flex:1; border:1.5px solid var(--border); border-radius:var(--r-md); padding:.6rem 1rem; font-size:.85rem; background:var(--bg); color:var(--text-primary); resize:none; font-family:inherit; } .msg-empty { flex:1; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:.5rem; color:var(--text-muted); } /* ── Modal ──────────────────────────────────────────────── */ .sc-modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:1000; display:none; align-items:center; justify-content:center; padding:1rem; backdrop-filter:blur(4px); } .sc-modal-overlay.open { display:flex; } .sc-modal { background:var(--card); border-radius:var(--r-lg); padding:2rem; width:100%; max-width:520px; max-height:90vh; overflow-y:auto; box-shadow:0 24px 80px rgba(0,0,0,.3); position:relative; } .sc-modal-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:1.5rem; } .sc-modal-title { font-size:1.15rem; font-weight:700; } .sc-modal-close { background:none; border:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted); line-height:1; padding:.25rem; } .sc-modal-close:hover { color:var(--text-primary); } /* ── Section header ─────────────────────────────────────── */ .section-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:1.25rem; } .section-title { font-size:1.05rem; font-weight:700; } </style> </head> <body> <!-- ── Navbar ─────────────────────────────────────────────── --> <nav class="navbar"> <a href="./index.php" class="nav-logo"><span>🛒</span> Sarin Shop</a> <span style="color:rgba(255,255,255,.8);font-size:.85rem;font-weight:600;margin-left:.5rem;">| ศูนย์ผู้ขาย</span> <div class="nav-actions" style="margin-left:auto;"> <img id="nav-avatar" src="" alt="" class="nav-avatar" style="display:none;"> <span id="nav-username" style="color:#fff;font-weight:600;font-size:.9rem;"></span> <a href="./account.php" class="btn btn-sm" style="background:rgba(255,255,255,.15);color:#fff;margin-left:.75rem;">⚙️ บัญชี</a> <button id="logout-btn" class="btn btn-sm" style="background:rgba(255,255,255,.2);color:#fff;">ออกจากระบบ</button> </div> </nav> <main class="page-container" style="padding-top:1.5rem;"> <!-- Access Denied --> <div id="access-denied" class="access-denied" style="display:none;"> <div class="access-denied-icon">🔐</div> <h1 class="access-denied-title">ไม่มีสิทธิ์เข้าถึง</h1> <p class="access-denied-desc">หน้านี้สำหรับผู้ขายเท่านั้น</p> <a href="./account.php?upgrade=1" class="btn btn-primary btn-lg mt-3">🏪 สมัครเป็นผู้ขาย</a> <a href="./index.php" class="btn btn-outline mt-1">กลับหน้าหลัก</a> </div> <!-- Dashboard --> <div id="seller-dashboard" style="display:none;"> <div class="sc-layout"> <!-- ── Sidebar ── --> <aside class="sc-sidebar"> <div class="sc-shop-info"> <img id="sidebar-avatar" src="" alt="" class="sc-shop-avatar"> <div class="sc-shop-name" id="sidebar-shop-name">กำลังโหลด...</div> <div class="sc-shop-status">🟢 ร้านเปิดอยู่</div> </div> <button class="sc-nav-item active" data-section="dashboard" onclick="showSection('dashboard',this)"> <span class="nav-icon">📊</span> ภาพรวม </button> <button class="sc-nav-item" data-section="products" onclick="showSection('products',this)"> <span class="nav-icon">🛍️</span> สินค้าของฉัน </button> <button class="sc-nav-item" data-section="orders" onclick="showSection('orders',this)"> <span class="nav-icon">📦</span> คำสั่งซื้อ <span class="sc-nav-badge" id="nav-badge-orders" style="display:none;"></span> </button> <button class="sc-nav-item" data-section="reports" onclick="showSection('reports',this)"> <span class="nav-icon">📈</span> รายงาน </button> <button class="sc-nav-item" data-section="messages" onclick="showSection('messages',this)"> <span class="nav-icon">💬</span> ข้อความ <span class="sc-nav-badge" id="nav-badge-msgs" style="display:none;"></span> </button> <hr class="sc-nav-divider"> <button class="sc-nav-item" onclick="window.location.href='./index.php'"> <span class="nav-icon">🏠</span> หน้าหลัก </button> </aside> <!-- ── Content Area ── --> <div id="sc-content"> <!-- ════ DASHBOARD ════ --> <div class="sc-section active" id="section-dashboard"> <div style="margin-bottom:1.25rem;"> <h1 style="font-size:1.3rem;font-weight:800;">🏪 ศูนย์ผู้ขาย</h1> <p id="dash-subtitle" style="color:var(--text-muted);font-size:.9rem;margin-top:.25rem;"></p> </div> <!-- Stats --> <div class="sc-stats"> <div class="sc-stat-card"> <div class="sc-stat-icon orange">📦</div> <div><div class="sc-stat-value" id="stat-orders">-</div><div class="sc-stat-label">คำสั่งซื้อทั้งหมด</div></div> </div> <div class="sc-stat-card"> <div class="sc-stat-icon green">💰</div> <div><div class="sc-stat-value" id="stat-revenue">-</div><div class="sc-stat-label">รายได้รวม</div></div> </div> <div class="sc-stat-card"> <div class="sc-stat-icon blue">🛍️</div> <div><div class="sc-stat-value" id="stat-products">-</div><div class="sc-stat-label">สินค้าทั้งหมด</div></div> </div> <div class="sc-stat-card"> <div class="sc-stat-icon purple">💬</div> <div><div class="sc-stat-value" id="stat-msgs">-</div><div class="sc-stat-label">ข้อความใหม่</div></div> </div> </div> <!-- Quick Actions --> <div class="quick-actions"> <button class="quick-action-btn" onclick="showSection('products',document.querySelector('[data-section=products]'));openAddProduct()"> <div class="qa-icon">➕</div><div class="qa-label">เพิ่มสินค้าใหม่</div> </button> <button class="quick-action-btn" onclick="showSection('orders',document.querySelector('[data-section=orders]'))"> <div class="qa-icon">📋</div><div class="qa-label">จัดการคำสั่งซื้อ</div> </button> <button class="quick-action-btn" onclick="showSection('reports',document.querySelector('[data-section=reports]'))"> <div class="qa-icon">📊</div><div class="qa-label">รายงานยอดขาย</div> </button> <button class="quick-action-btn" onclick="showSection('messages',document.querySelector('[data-section=messages]'))"> <div class="qa-icon">💬</div><div class="qa-label">ข้อความลูกค้า</div> </button> </div> <!-- Recent Orders Preview --> <div class="card"> <div class="section-header"> <div class="section-title">📋 คำสั่งซื้อล่าสุด</div> <button class="btn btn-sm btn-outline" onclick="showSection('orders',document.querySelector('[data-section=orders]'))">ดูทั้งหมด →</button> </div> <div id="dash-recent-orders"> <div class="empty-state" style="padding:2rem;"> <div class="empty-icon">📦</div> <div class="empty-title">ยังไม่มีคำสั่งซื้อ</div> <div class="empty-desc">เพิ่มสินค้าและรอลูกค้ามาซื้อ!</div> </div> </div> </div> </div> <!-- ════ PRODUCTS ════ --> <div class="sc-section" id="section-products"> <div class="section-header"> <div class="section-title">🛍️ สินค้าของฉัน</div> <button class="btn btn-primary" id="add-product-btn" onclick="openAddProduct()">➕ เพิ่มสินค้า</button> </div> <div id="products-grid" class="product-grid"> <div class="empty-state" style="grid-column:1/-1;padding:3rem;"> <div class="empty-icon">🛍️</div> <div class="empty-title">ยังไม่มีสินค้า</div> <div class="empty-desc">เริ่มเพิ่มสินค้าชิ้นแรกของคุณ!</div> </div> </div> </div> <!-- ════ ORDERS ════ --> <div class="sc-section" id="section-orders"> <div class="section-title" style="margin-bottom:1rem;">📦 คำสั่งซื้อ</div> <div class="orders-filter"> <button class="filter-btn active" data-status="" onclick="filterOrders('',this)">ทั้งหมด</button> <button class="filter-btn" data-status="pending" onclick="filterOrders('pending',this)">⏳ รอยืนยัน</button> <button class="filter-btn" data-status="confirmed" onclick="filterOrders('confirmed',this)">✅ ยืนยันแล้ว</button> <button class="filter-btn" data-status="shipping" onclick="filterOrders('shipping',this)">🚚 กำลังส่ง</button> <button class="filter-btn" data-status="delivered" onclick="filterOrders('delivered',this)">📬 ส่งแล้ว</button> <button class="filter-btn" data-status="cancelled" onclick="filterOrders('cancelled',this)">❌ ยกเลิก</button> </div> <div id="orders-list"> <div class="empty-state" style="padding:3rem;"> <div class="empty-icon">📦</div> <div class="empty-title">ยังไม่มีคำสั่งซื้อ</div> </div> </div> </div> <!-- ════ REPORTS ════ --> <div class="sc-section" id="section-reports"> <div class="section-title" style="margin-bottom:1.25rem;">📈 รายงานยอดขาย</div> <div class="report-cards" id="report-cards"> <div class="report-card"><div class="report-card-icon">💰</div><div class="report-card-value" id="rpt-revenue">฿0</div><div class="report-card-label">รายได้รวม</div></div> <div class="report-card"><div class="report-card-icon">📦</div><div class="report-card-value" id="rpt-orders">0</div><div class="report-card-label">คำสั่งซื้อทั้งหมด</div></div> <div class="report-card"><div class="report-card-icon">🛍️</div><div class="report-card-value" id="rpt-products">0</div><div class="report-card-label">จำนวนสินค้า</div></div> </div> <div class="chart-wrap"> <div class="chart-title">📊 รายได้รายเดือน (6 เดือนล่าสุด)</div> <div class="bar-chart" id="revenue-chart"> <div style="color:var(--text-muted);font-size:.85rem;margin:auto;">ยังไม่มีข้อมูล</div> </div> </div> <div class="card"> <div class="section-title" style="margin-bottom:1rem;">🏆 สินค้าขายดี</div> <div id="top-products-list"> <div class="empty-state" style="padding:2rem;"> <div class="empty-icon">🛍️</div> <div class="empty-title">ยังไม่มีข้อมูล</div> </div> </div> </div> </div> <!-- ════ MESSAGES ════ --> <div class="sc-section" id="section-messages"> <div class="section-title" style="margin-bottom:1rem;">💬 ข้อความลูกค้า</div> <div class="msg-layout"> <!-- Conversations --> <div class="msg-convs"> <div class="msg-convs-header">💬 การสนทนา</div> <div class="msg-convs-list" id="convs-list"> <div style="padding:2rem;text-align:center;color:var(--text-muted);font-size:.85rem;">ยังไม่มีข้อความ</div> </div> </div> <!-- Thread --> <div class="msg-thread" id="msg-thread"> <div class="msg-empty"> <div style="font-size:3rem;">💬</div> <div>เลือกการสนทนาเพื่อดูข้อความ</div> </div> </div> </div> </div> </div><!-- /sc-content --> </div><!-- /sc-layout --> </div><!-- /seller-dashboard --> </main> <!-- ════ Modal: Add/Edit Product ════ --> <div class="sc-modal-overlay" id="product-modal"> <div class="sc-modal"> <div class="sc-modal-header"> <div class="sc-modal-title" id="product-modal-title">➕ เพิ่มสินค้าใหม่</div> <button class="sc-modal-close" onclick="closeProductModal()">×</button> </div> <form id="product-form"> <input type="hidden" id="product-edit-id" value=""> <div class="form-group mb-2"> <label class="form-label">ชื่อสินค้า <span class="req">*</span></label> <input type="text" id="pf-name" class="form-input" placeholder="ชื่อสินค้า" required> </div> <div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;" class="mb-2"> <div class="form-group"> <label class="form-label">ราคา (฿) <span class="req">*</span></label> <input type="number" id="pf-price" class="form-input" placeholder="0.00" min="0" step="0.01" required> </div> <div class="form-group"> <label class="form-label">จำนวน stock <span class="req">*</span></label> <input type="number" id="pf-stock" class="form-input" placeholder="0" min="0" required> </div> </div> <div class="form-group mb-2"> <label class="form-label">หมวดหมู่</label> <select id="pf-category" class="form-input"> <option value="">เลือกหมวดหมู่</option> <option>แฟชั่น</option><option>อิเล็กทรอนิกส์</option> <option>บ้านและสวน</option><option>อาหารและเครื่องดื่ม</option> <option>กีฬา</option><option>ความงาม</option> <option>หนังสือ</option><option>ของเล่น</option><option>อื่นๆ</option> </select> </div> <div class="form-group mb-2"> <label class="form-label">รูปภาพสินค้า (URL)</label> <input type="url" id="pf-image" class="form-input" placeholder="https://example.com/image.jpg"> <div id="pf-img-preview" style="margin-top:.5rem;display:none;"> <img id="pf-img-preview-img" src="" alt="preview" style="width:100%;max-height:180px;object-fit:cover;border-radius:var(--r-md);"> </div> </div> <div class="form-group mb-3"> <label class="form-label">รายละเอียดสินค้า</label> <textarea id="pf-desc" class="form-input" rows="3" placeholder="อธิบายสินค้าของคุณ..."></textarea> </div> <div style="display:flex;gap:.75rem;"> <button type="button" class="btn btn-outline" style="flex:1;" onclick="closeProductModal()">ยกเลิก</button> <button type="submit" class="btn btn-primary" style="flex:2;" id="product-save-btn">💾 บันทึกสินค้า</button> </div> </form> </div> </div> <!-- ════ Modal: Order Detail ════ --> <div class="sc-modal-overlay" id="order-modal"> <div class="sc-modal" style="max-width:480px;"> <div class="sc-modal-header"> <div class="sc-modal-title">📦 รายละเอียดคำสั่งซื้อ</div> <button class="sc-modal-close" onclick="closeOrderModal()">×</button> </div> <div id="order-modal-body"></div> </div> </div> <script type="module"> import { requireAuth, api, Auth } from './public/js/api.js'; import { showToast, setButtonLoading } from './public/js/ui.js'; window.showToast = showToast; // ── State ────────────────────────────────────────────────── let allOrders = []; let allProducts = []; let currentBuyerId = null; let statsData = null; // ── Show Section ──────────────────────────────────────────── window.showSection = function(name, btn) { document.querySelectorAll('.sc-section').forEach(s => s.classList.remove('active')); document.querySelectorAll('.sc-nav-item').forEach(n => n.classList.remove('active')); document.getElementById('section-' + name).classList.add('active'); if (btn) btn.classList.add('active'); }; // ── Animate Number ────────────────────────────────────────── function animateValue(el, end, prefix='', suffix='') { let start = 0; const step = end / (900 / 16); const iv = setInterval(() => { start = Math.min(start + step, end); el.textContent = prefix + Math.floor(start).toLocaleString('th-TH') + suffix; if (start >= end) clearInterval(iv); }, 16); } // ── Format currency ────────────────────────────────────────── function formatBaht(n) { return '฿' + Number(n).toLocaleString('th-TH', {minimumFractionDigits:0, maximumFractionDigits:0}); } // ── Status helpers ────────────────────────────────────────── const statusLabel = { pending:'⏳ รอยืนยัน', confirmed:'✅ ยืนยัน', shipping:'🚚 กำลังส่ง', delivered:'📬 ส่งแล้ว', cancelled:'❌ ยกเลิก' }; const statusNext = { pending:'confirmed', confirmed:'shipping', shipping:'delivered' }; // ══════════════════════════════════════════════════════════ // PRODUCTS // ══════════════════════════════════════════════════════════ window.openAddProduct = function() { document.getElementById('product-modal-title').textContent = '➕ เพิ่มสินค้าใหม่'; document.getElementById('product-edit-id').value = ''; document.getElementById('product-form').reset(); document.getElementById('pf-img-preview').style.display = 'none'; document.getElementById('product-modal').classList.add('open'); }; window.closeProductModal = function() { document.getElementById('product-modal').classList.remove('open'); }; // Image preview document.getElementById('pf-image').addEventListener('input', function() { const url = this.value.trim(); const wrap = document.getElementById('pf-img-preview'); if (url) { document.getElementById('pf-img-preview-img').src = url; wrap.style.display = 'block'; } else { wrap.style.display = 'none'; } }); // Submit product form document.getElementById('product-form').addEventListener('submit', async function(e) { e.preventDefault(); const btn = document.getElementById('product-save-btn'); const editId = document.getElementById('product-edit-id').value; const body = { name: document.getElementById('pf-name').value.trim(), price: parseFloat(document.getElementById('pf-price').value), stock: parseInt(document.getElementById('pf-stock').value), category: document.getElementById('pf-category').value, image_url: document.getElementById('pf-image').value.trim(), description: document.getElementById('pf-desc').value.trim(), }; if (!body.name || isNaN(body.price)) { showToast('กรุณากรอกชื่อและราคา','warning'); return; } setButtonLoading(btn, true, 'กำลังบันทึก...'); let res; if (editId) { body.id = parseInt(editId); res = await api.sellerUpdateProduct(body); } else { res = await api.sellerCreateProduct(body); } setButtonLoading(btn, false); if (!res || !res.ok) { showToast((res?.data?.message)||'เกิดข้อผิดพลาด','error'); return; } showToast(editId ? 'อัปเดตสินค้าสำเร็จ!' : 'เพิ่มสินค้าสำเร็จ!', 'success'); closeProductModal(); await loadProducts(); await loadStats(); }); // Open edit window.openEditProduct = function(p) { document.getElementById('product-modal-title').textContent = '✏️ แก้ไขสินค้า'; document.getElementById('product-edit-id').value = p.id; document.getElementById('pf-name').value = p.name; document.getElementById('pf-price').value = p.price; document.getElementById('pf-stock').value = p.stock; document.getElementById('pf-category').value = p.category || ''; document.getElementById('pf-image').value = p.image_url || ''; document.getElementById('pf-desc').value = p.description || ''; if (p.image_url) { document.getElementById('pf-img-preview-img').src = p.image_url; document.getElementById('pf-img-preview').style.display = 'block'; } else { document.getElementById('pf-img-preview').style.display = 'none'; } document.getElementById('product-modal').classList.add('open'); }; // Toggle active window.toggleProduct = async function(id, current) { const res = await api.sellerUpdateProduct({ id, is_active: current ? 0 : 1 }); if (!res || !res.ok) { showToast('เกิดข้อผิดพลาด','error'); return; } showToast(current ? 'ซ่อนสินค้าแล้ว' : 'เผยแพร่สินค้าแล้ว', 'success'); await loadProducts(); }; // Delete product window.deleteProduct = async function(id) { if (!confirm('ลบสินค้านี้? ไม่สามารถกู้คืนได้')) return; const res = await api.sellerDeleteProduct({ id }); if (!res || !res.ok) { showToast('เกิดข้อผิดพลาด','error'); return; } showToast('ลบสินค้าสำเร็จ','success'); await loadProducts(); await loadStats(); }; async function loadProducts() { const res = await api.sellerProducts(); if (!res || !res.ok) return; allProducts = res.data.products || []; renderProducts(allProducts); } function renderProducts(products) { const grid = document.getElementById('products-grid'); if (!products.length) { grid.innerHTML = `<div class="empty-state" style="grid-column:1/-1;padding:3rem;"> <div class="empty-icon">🛍️</div> <div class="empty-title">ยังไม่มีสินค้า</div> <div class="empty-desc">กด "เพิ่มสินค้า" เพื่อเริ่มต้น!</div> </div>`; return; } grid.innerHTML = products.map(p => ` <div class="product-card"> ${p.image_url ? `<img class="product-card-img" src="${p.image_url}" alt="${p.name}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"> <div class="product-card-img-placeholder" style="display:none;">🛍️</div>` : `<div class="product-card-img-placeholder">🛍️</div>`} <div class="product-card-body"> <div class="product-card-name" title="${p.name}">${p.name}</div> <div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.25rem;"> <span class="product-card-price">${formatBaht(p.price)}</span> <span class="product-badge ${p.is_active?'active':'inactive'}">${p.is_active?'เผยแพร่':'ซ่อน'}</span> </div> <div class="product-card-stock">คลัง: ${p.stock} | ขาย: ${p.sold_count}</div> <div class="product-card-actions"> <button class="btn btn-sm btn-outline" style="flex:1;font-size:.75rem;" onclick='openEditProduct(${JSON.stringify(p)})'>✏️ แก้ไข</button> <button class="btn btn-sm" style="flex:1;font-size:.75rem;background:${p.is_active?'rgba(239,68,68,.1)':'rgba(34,197,94,.1)'};color:${p.is_active?'#dc2626':'#16a34a'};" onclick="toggleProduct(${p.id},${p.is_active})">${p.is_active?'ซ่อน':'เผยแพร่'}</button> <button class="btn btn-sm" style="font-size:.75rem;background:rgba(239,68,68,.1);color:#dc2626;" onclick="deleteProduct(${p.id})">🗑️</button> </div> </div> </div> `).join(''); } // ══════════════════════════════════════════════════════════ // ORDERS // ══════════════════════════════════════════════════════════ window.filterOrders = function(status, btn) { document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); const filtered = status ? allOrders.filter(o => o.status === status) : allOrders; renderOrders(filtered); }; async function loadOrders() { const res = await api.sellerOrders(); if (!res || !res.ok) return; allOrders = res.data.orders || []; renderOrders(allOrders); renderDashOrders(allOrders.slice(0, 3)); const pending = allOrders.filter(o => o.status === 'pending').length; const badge = document.getElementById('nav-badge-orders'); if (pending > 0) { badge.textContent = pending; badge.style.display = ''; } else badge.style.display = 'none'; } function renderOrders(orders) { const wrap = document.getElementById('orders-list'); if (!orders.length) { wrap.innerHTML = `<div class="empty-state" style="padding:3rem;"><div class="empty-icon">📦</div><div class="empty-title">ไม่มีคำสั่งซื้อ</div></div>`; return; } wrap.innerHTML = orders.map(o => ` <div class="order-card ${o.status}"> <div class="order-header"> <span class="order-id">#${String(o.id).padStart(5,'0')} · ${new Date(o.created_at).toLocaleDateString('th-TH')}</span> <span class="status-badge ${o.status}">${statusLabel[o.status]||o.status}</span> </div> <div class="order-buyer">👤 ${o.buyer_name}</div> <div class="order-items-list"> ${(o.items||[]).map(i => `<div class="order-item-row">• ${i.name} × ${i.qty} <span style="margin-left:auto;">${formatBaht(i.unit_price * i.qty)}</span></div>`).join('')} </div> <div class="order-footer"> <div class="order-total">${formatBaht(o.total_amount)}</div> <div class="order-actions"> ${o.status !== 'delivered' && o.status !== 'cancelled' ? `<button class="btn btn-sm btn-primary" onclick="updateOrderStatus(${o.id},'${statusNext[o.status]}')"> ${o.status === 'pending' ? '✅ ยืนยัน' : o.status === 'confirmed' ? '🚚 ส่งสินค้า' : '📬 ส่งแล้ว'} </button> <button class="btn btn-sm btn-outline" style="color:#dc2626;border-color:#dc2626;" onclick="updateOrderStatus(${o.id},'cancelled')">ยกเลิก</button>` : ''} </div> </div> </div> `).join(''); } function renderDashOrders(orders) { const wrap = document.getElementById('dash-recent-orders'); if (!orders.length) { wrap.innerHTML = `<div class="empty-state" style="padding:2rem;"><div class="empty-icon">📦</div><div class="empty-title">ยังไม่มีคำสั่งซื้อ</div><div class="empty-desc">เพิ่มสินค้าและรอลูกค้ามาซื้อ!</div></div>`; return; } wrap.innerHTML = orders.map(o => ` <div style="display:flex;align-items:center;justify-content:space-between;padding:.75rem 0;border-bottom:1px solid var(--border);"> <div> <div style="font-weight:600;font-size:.85rem;">#${String(o.id).padStart(5,'0')} · ${o.buyer_name}</div> <div style="font-size:.78rem;color:var(--text-muted);">${new Date(o.created_at).toLocaleDateString('th-TH')}</div> </div> <div style="display:flex;align-items:center;gap:.75rem;"> <span class="order-total" style="font-size:.9rem;">${formatBaht(o.total_amount)}</span> <span class="status-badge ${o.status}" style="font-size:.7rem;">${statusLabel[o.status]}</span> </div> </div> `).join(''); } window.updateOrderStatus = async function(id, status) { const label = { confirmed:'ยืนยันคำสั่งซื้อ', shipping:'เริ่มส่งสินค้า', delivered:'ยืนยันส่งแล้ว', cancelled:'ยกเลิกคำสั่งซื้อ' }; if (!confirm(`${label[status]||status}?`)) return; const res = await api.sellerUpdateOrder({ id, status }); if (!res || !res.ok) { showToast('เกิดข้อผิดพลาด','error'); return; } showToast('อัปเดตสถานะสำเร็จ!','success'); await loadOrders(); }; window.closeOrderModal = function() { document.getElementById('order-modal').classList.remove('open'); }; // ══════════════════════════════════════════════════════════ // STATS / REPORTS // ══════════════════════════════════════════════════════════ async function loadStats() { const res = await api.sellerStats(); if (!res || !res.ok) return; const d = res.data; statsData = d; // Dashboard stats animateValue(document.getElementById('stat-orders'), d.total_orders); animateValue(document.getElementById('stat-revenue'), d.total_revenue, '฿'); animateValue(document.getElementById('stat-products'), d.total_products); animateValue(document.getElementById('stat-msgs'), d.unread_messages); // Report summary document.getElementById('rpt-revenue').textContent = formatBaht(d.total_revenue); document.getElementById('rpt-orders').textContent = d.total_orders; document.getElementById('rpt-products').textContent = d.total_products; // Messages badge const msgBadge = document.getElementById('nav-badge-msgs'); if (d.unread_messages > 0) { msgBadge.textContent = d.unread_messages; msgBadge.style.display=''; } else msgBadge.style.display = 'none'; // Bar chart renderBarChart(d.monthly || []); // Top products renderTopProducts(d.top_products || []); } function renderBarChart(monthly) { const chart = document.getElementById('revenue-chart'); if (!monthly.length) { chart.innerHTML = '<div style="color:var(--text-muted);font-size:.85rem;margin:auto;">ยังไม่มีข้อมูลยอดขาย</div>'; return; } const max = Math.max(...monthly.map(m => parseFloat(m.revenue)), 1); const thMonths = ['','ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.']; chart.innerHTML = monthly.map(m => { const pct = (parseFloat(m.revenue) / max * 100).toFixed(1); const mo = parseInt(m.month.split('-')[1]); return `<div class="bar-col"> <div class="bar-value">${formatBaht(m.revenue)}</div> <div class="bar-fill" style="height:${Math.max(pct,2)}%"></div> <div class="bar-label">${thMonths[mo]}</div> </div>`; }).join(''); } function renderTopProducts(products) { const wrap = document.getElementById('top-products-list'); if (!products.length) { wrap.innerHTML = `<div class="empty-state" style="padding:2rem;"><div class="empty-icon">🛍️</div><div class="empty-title">ยังไม่มีข้อมูล</div></div>`; return; } wrap.innerHTML = products.map((p, i) => ` <div style="display:flex;align-items:center;gap:1rem;padding:.75rem 0;border-bottom:1px solid var(--border);"> <div style="width:28px;height:28px;border-radius:50%;background:${i===0?'linear-gradient(135deg,#f59e0b,#d97706)':i===1?'linear-gradient(135deg,#6b7280,#4b5563)':i===2?'linear-gradient(135deg,#cd7c2f,#a05c20)':'var(--bg)'};display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:800;color:${i<3?'#fff':'var(--text-muted)'};">${i+1}</div> ${p.image_url ? `<img src="${p.image_url}" style="width:40px;height:40px;border-radius:var(--r-sm);object-fit:cover;">` : '<div style="width:40px;height:40px;border-radius:var(--r-sm);background:var(--bg);display:flex;align-items:center;justify-content:center;">🛍️</div>'} <div style="flex:1;"> <div style="font-weight:600;font-size:.85rem;">${p.name}</div> <div style="font-size:.75rem;color:var(--text-muted);">ขายแล้ว ${p.sold_count} ชิ้น</div> </div> <div style="font-weight:700;color:var(--brand);">${formatBaht(p.price)}</div> </div> `).join(''); } // ══════════════════════════════════════════════════════════ // MESSAGES // ══════════════════════════════════════════════════════════ async function loadConversations() { const res = await api.sellerConversations(); if (!res || !res.ok) return; const convs = res.data.conversations || []; renderConversations(convs); } function renderConversations(convs) { const list = document.getElementById('convs-list'); if (!convs.length) { list.innerHTML = '<div style="padding:2rem;text-align:center;color:var(--text-muted);font-size:.85rem;">ยังไม่มีข้อความ</div>'; return; } list.innerHTML = convs.map(c => ` <div class="conv-item ${currentBuyerId===c.buyer_id?'active':''}" onclick="openThread(${c.buyer_id},'${c.buyer_name}','${c.avatar_url||''}')"> <img class="conv-avatar" src="${c.avatar_url||`https://ui-avatars.com/api/?name=${c.buyer_name}&background=ee4d2d&color=fff`}" alt=""> <div style="min-width:0;"> <div class="conv-name">${c.buyer_name}</div> <div class="conv-preview">${c.last_msg||''}</div> </div> ${c.unread>0?`<span class="conv-unread">${c.unread}</span>`:''} </div> `).join(''); } window.openThread = async function(buyerId, buyerName, avatarUrl) { currentBuyerId = buyerId; const thread = document.getElementById('msg-thread'); thread.innerHTML = ` <div class="msg-thread-header"> <img src="${avatarUrl||`https://ui-avatars.com/api/?name=${buyerName}&background=ee4d2d&color=fff`}" style="width:36px;height:36px;border-radius:50%;"> ${buyerName} </div> <div class="msg-thread-body" id="thread-body"><div style="text-align:center;padding:2rem;color:var(--text-muted);">กำลังโหลด...</div></div> <div class="msg-input-wrap"> <textarea class="msg-input" id="reply-input" rows="2" placeholder="พิมพ์ข้อความ..." onkeydown="handleReplyKey(event)"></textarea> <button class="btn btn-primary" id="reply-btn" onclick="sendReply()">ส่ง</button> </div> `; const res = await api.sellerThread(buyerId); if (!res || !res.ok) return; const msgs = res.data.messages || []; const body = document.getElementById('thread-body'); if (!msgs.length) { body.innerHTML = '<div style="text-align:center;color:var(--text-muted);font-size:.85rem;padding:2rem;">ยังไม่มีข้อความ</div>'; } else { body.innerHTML = msgs.map(m => ` <div style="display:flex;flex-direction:column;align-items:${m.sender_role==='seller'?'flex-end':'flex-start'};"> <div class="msg-bubble ${m.sender_role}">${m.body}</div> <div class="msg-time">${new Date(m.created_at).toLocaleString('th-TH',{hour:'2-digit',minute:'2-digit'})}</div> </div> `).join(''); body.scrollTop = body.scrollHeight; } // Reload conversations to clear unread await loadConversations(); }; window.handleReplyKey = function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendReply(); } }; window.sendReply = async function() { if (!currentBuyerId) return; const input = document.getElementById('reply-input'); const body = input.value.trim(); if (!body) return; const btn = document.getElementById('reply-btn'); setButtonLoading(btn, true, '...'); const res = await api.sellerReply({ buyer_id: currentBuyerId, body }); setButtonLoading(btn, false); if (!res || !res.ok) { showToast('ส่งไม่สำเร็จ','error'); return; } input.value = ''; // Re-open thread to refresh const nameEl = document.querySelector('.msg-thread-header'); const name = nameEl ? nameEl.textContent.trim() : ''; openThread(currentBuyerId, name, ''); }; // ══════════════════════════════════════════════════════════ // INIT // ══════════════════════════════════════════════════════════ (async () => { const user = await requireAuth(); if (!user) return; if (user.role !== 'seller' && user.role !== 'admin') { document.getElementById('access-denied').style.display = 'flex'; return; } document.getElementById('seller-dashboard').style.display = 'block'; const avatar = user.avatar_url || `https://ui-avatars.com/api/?name=${user.username}&background=ee4d2d&color=fff`; document.getElementById('nav-avatar').src = avatar; document.getElementById('nav-avatar').style.display = 'block'; document.getElementById('nav-username').textContent = user.username; document.getElementById('sidebar-avatar').src = avatar; // Load profile for shop name const prof = await api.getProfile(); if (prof && prof.ok) { const shopName = prof.data.user.shop_name || ('ร้านของ ' + user.username); document.getElementById('sidebar-shop-name').textContent = shopName; document.getElementById('dash-subtitle').textContent = 'จัดการร้านค้า: ' + shopName; } // Load all data in parallel await Promise.all([loadStats(), loadProducts(), loadOrders(), loadConversations()]); // Logout document.getElementById('logout-btn').addEventListener('click', async () => { await api.logout(); Auth.logout(); showToast('ออกจากระบบแล้ว','info'); setTimeout(() => window.location.href = './auth.php', 800); }); // Close modals on overlay click document.getElementById('product-modal').addEventListener('click', e => { if (e.target === e.currentTarget) closeProductModal(); }); document.getElementById('order-modal').addEventListener('click', e => { if (e.target === e.currentTarget) closeOrderModal(); }); })(); </script> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.39 |
proxy
|
phpinfo
|
Settings