File manager - Edit - /home/webapp69.cm.in.th/u69319090009/shop/src/components/AdminDashboard.jsx
Back
import React, { useState, useEffect } from 'react'; const AdminDashboard = () => { const [activeTab, setActiveTab] = useState('products'); // 2. Safe State Management (localStorage) const [products, setProducts] = useState([]); const [orders, setOrders] = useState([]); const [users, setUsers] = useState([]); // Modal State for Add Product const [isModalOpen, setIsModalOpen] = useState(false); const [newProduct, setNewProduct] = useState({ name: '', price: '', stock: '', image: '', category: '' }); // Load initial data safely from localStorage on mount useEffect(() => { try { const storedProducts = JSON.parse(localStorage.getItem('products')) || []; setProducts(Array.isArray(storedProducts) ? storedProducts : []); } catch (error) { console.error('Failed to parse products from localStorage:', error); setProducts([]); } try { const storedOrders = JSON.parse(localStorage.getItem('orders')) || []; setOrders(Array.isArray(storedOrders) ? storedOrders : []); } catch (error) { console.error('Failed to parse orders from localStorage:', error); setOrders([]); } try { const storedUsers = JSON.parse(localStorage.getItem('users')) || []; setUsers(Array.isArray(storedUsers) ? storedUsers : []); } catch (error) { console.error('Failed to parse users from localStorage:', error); setUsers([]); } }, []); // --- Handlers for Products --- const handleAddProductSubmit = (e) => { e.preventDefault(); if (!newProduct.name || !newProduct.price) return; const productToAdd = { id: Date.now().toString(), name: newProduct.name, price: Number(newProduct.price) || 0, stock: Number(newProduct.stock) || 0, image: newProduct.image || 'https://via.placeholder.com/150', category: newProduct.category || 'ทั่วไป' }; const updatedProducts = [...products, productToAdd]; setProducts(updatedProducts); localStorage.setItem('products', JSON.stringify(updatedProducts)); // Reset Form & Close Modal setNewProduct({ name: '', price: '', stock: '', image: '', category: '' }); setIsModalOpen(false); }; const handleDeleteProduct = (productId) => { if (window.confirm('คุณต้องการลบสินค้านี้ใช่หรือไม่?')) { const updatedProducts = products.filter((p) => p.id !== productId); setProducts(updatedProducts); localStorage.setItem('products', JSON.stringify(updatedProducts)); } }; // --- Handlers for Orders --- const handleStatusChange = (orderId, newStatus) => { const updatedOrders = orders.map((order) => { if (order.id === orderId) { return { ...order, status: newStatus }; } return order; }); setOrders(updatedOrders); localStorage.setItem('orders', JSON.stringify(updatedOrders)); }; // --- Handlers for Users --- const handleDeleteUser = (userId) => { if (window.confirm('คุณต้องการลบผู้ใช้งานนี้ใช่หรือไม่?')) { const updatedUsers = users.filter((u) => u.id !== userId && u.username !== userId); setUsers(updatedUsers); localStorage.setItem('users', JSON.stringify(updatedUsers)); } }; return ( // 1. Exact wrapper to prevent navbar overlap and scroll issues <div className="min-h-screen bg-gray-100 text-gray-900 w-full pt-24 pb-12 px-4 md:px-8 font-sans"> <div className="max-w-7xl mx-auto space-y-6"> {/* Header Title */} <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> <div> <h1 className="text-3xl font-bold text-gray-900 tracking-tight">Admin Dashboard</h1> <p className="text-sm text-gray-500 mt-1">ผู้ดูแลระบบ: จัดการสินค้า, คำสั่งซื้อ และผู้ใช้งาน</p> </div> </div> {/* 3. Dashboard Features (Tabs System) */} <div className="flex border-b border-gray-200 bg-white rounded-t-xl px-4 pt-2 gap-2 shadow-sm"> <button type="button" onClick={() => setActiveTab('products')} className={`py-3 px-5 font-semibold text-sm transition-colors border-b-2 ${ activeTab === 'products' ? 'border-indigo-600 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700' }`} > 📦 จัดการสินค้า (Products) </button> <button type="button" onClick={() => setActiveTab('orders')} className={`py-3 px-5 font-semibold text-sm transition-colors border-b-2 ${ activeTab === 'orders' ? 'border-indigo-600 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700' }`} > 🛒 จัดการคำสั่งซื้อ (Orders) </button> <button type="button" onClick={() => setActiveTab('users')} className={`py-3 px-5 font-semibold text-sm transition-colors border-b-2 ${ activeTab === 'users' ? 'border-indigo-600 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700' }`} > 👥 จัดการผู้ใช้งาน (Users) </button> </div> {/* Main Content Area (Modern Card Layout) */} <div className="bg-white rounded-b-xl rounded-t-none md:rounded-xl shadow-md p-6 border border-gray-100"> {/* TAB 1: PRODUCTS MANAGEMENT */} {activeTab === 'products' && ( <div className="space-y-4"> <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pb-2 border-b border-gray-100"> <h2 className="text-xl font-semibold text-gray-800">รายการสินค้าทั้งหมด</h2> <button type="button" onClick={() => setIsModalOpen(true)} className="bg-indigo-600 hover:bg-indigo-700 text-white font-medium text-sm px-4 py-2 rounded-lg transition shadow-sm hover:shadow focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" > + เพิ่มสินค้าใหม่ (Add Product) </button> </div> {/* Products Table */} <div className="overflow-x-auto"> <table className="w-full text-left border-collapse"> <thead> <tr className="bg-gray-50 text-gray-600 text-xs uppercase font-semibold border-b border-gray-200"> <th className="py-3 px-4">ID</th> <th className="py-3 px-4">รูปภาพ</th> <th className="py-3 px-4">ชื่อสินค้า</th> <th className="py-3 px-4">ราคา (บาท)</th> <th className="py-3 px-4">คลังสินค้า (Stock)</th> <th className="py-3 px-4 text-center">จัดการ</th> </tr> </thead> <tbody className="divide-y divide-gray-100 text-sm text-gray-700"> {products?.length > 0 ? ( products.map((product) => ( <tr key={product.id || product.name} className="hover:bg-gray-50 transition"> <td className="py-3 px-4 font-mono text-xs text-gray-500">{product.id || '-'}</td> <td className="py-3 px-4"> <img src={product.image || 'https://via.placeholder.com/60'} alt={product.name} className="w-12 h-12 object-cover rounded-md border border-gray-200" onError={(e) => { e.target.src = 'https://via.placeholder.com/60?text=No+Img'; }} /> </td> <td className="py-3 px-4 font-medium text-gray-900">{product.name}</td> <td className="py-3 px-4 text-indigo-600 font-semibold"> ฿{Number(product.price).toLocaleString()} </td> <td className="py-3 px-4"> <span className={`inline-block px-2.5 py-1 text-xs font-semibold rounded-full ${ product.stock > 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }`} > {product.stock ?? 0} ชิ้น </span> </td> <td className="py-3 px-4 text-center"> <button type="button" onClick={() => handleDeleteProduct(product.id)} className="bg-red-50 hover:bg-red-100 text-red-600 text-xs font-medium px-3 py-1.5 rounded border border-red-200 transition hover:border-red-300" > ลบสินค้า </button> </td> </tr> )) ) : ( <tr> <td colSpan="6" className="text-center py-10 text-gray-400"> ไม่พบข้อมูลสินค้าในระบบ (No Products Data) </td> </tr> )} </tbody> </table> </div> </div> )} {/* TAB 2: ORDERS MANAGEMENT */} {activeTab === 'orders' && ( <div className="space-y-4"> <div className="pb-2 border-b border-gray-100"> <h2 className="text-xl font-semibold text-gray-800">รายการคำสั่งซื้อทั้งหมด</h2> </div> {/* Orders Table */} <div className="overflow-x-auto"> <table className="w-full text-left border-collapse"> <thead> <tr className="bg-gray-50 text-gray-600 text-xs uppercase font-semibold border-b border-gray-200"> <th className="py-3 px-4">Order ID</th> <th className="py-3 px-4">ข้อมูลผู้ซื้อ (Buyer Info)</th> <th className="py-3 px-4">ราคารวม</th> <th className="py-3 px-4">สถานะ (Status)</th> </tr> </thead> <tbody className="divide-y divide-gray-100 text-sm text-gray-700"> {orders?.length > 0 ? ( orders.map((order) => ( <tr key={order.id} className="hover:bg-gray-50 transition"> <td className="py-3 px-4 font-mono text-xs font-semibold text-indigo-600"> #{order.id} </td> <td className="py-3 px-4"> <div className="font-medium text-gray-900"> {order.customerName || order.buyer || order.user || 'ลูกค้าทั่วไป'} </div> <div className="text-xs text-gray-500"> {order.address || order.email || order.phone || '-'} </div> </td> <td className="py-3 px-4 font-bold text-gray-900"> ฿{Number(order.totalPrice || order.total || order.amount || 0).toLocaleString()} </td> <td className="py-3 px-4"> <select value={order.status || 'Pending'} onChange={(e) => handleStatusChange(order.id, e.target.value)} className={`text-xs font-semibold px-3 py-1.5 rounded-lg border focus:outline-none focus:ring-2 focus:ring-indigo-500 cursor-pointer ${ order.status === 'Delivered' || order.status === 'สำเร็จ' ? 'bg-green-50 text-green-700 border-green-300' : order.status === 'Shipped' || order.status === 'จัดส่งแล้ว' ? 'bg-blue-50 text-blue-700 border-blue-300' : 'bg-yellow-50 text-yellow-700 border-yellow-300' }`} > <option value="Pending">Pending (รอดำเนินการ)</option> <option value="Shipped">Shipped (จัดส่งแล้ว)</option> <option value="Delivered">Delivered (จัดส่งสำเร็จ)</option> <option value="Cancelled">Cancelled (ยกเลิก)</option> </select> </td> </tr> )) ) : ( <tr> <td colSpan="4" className="text-center py-10 text-gray-400"> ไม่พบรายการคำสั่งซื้อ (No Orders Data) </td> </tr> )} </tbody> </table> </div> </div> )} {/* TAB 3: USERS MANAGEMENT */} {activeTab === 'users' && ( <div className="space-y-4"> <div className="pb-2 border-b border-gray-100"> <h2 className="text-xl font-semibold text-gray-800">รายการผู้ใช้งานทั้งหมด</h2> </div> {/* Users Table */} <div className="overflow-x-auto"> <table className="w-full text-left border-collapse"> <thead> <tr className="bg-gray-50 text-gray-600 text-xs uppercase font-semibold border-b border-gray-200"> <th className="py-3 px-4">ชื่อผู้ใช้งาน (Username)</th> <th className="py-3 px-4">อีเมล (Email)</th> <th className="py-3 px-4">สิทธิ์การใช้งาน (Role)</th> <th className="py-3 px-4 text-center">จัดการ</th> </tr> </thead> <tbody className="divide-y divide-gray-100 text-sm text-gray-700"> {users?.length > 0 ? ( users.map((user, idx) => { const isAdmin = user.role?.toLowerCase() === 'admin' || user.username?.toLowerCase() === 'admin'; return ( <tr key={user.id || user.username || idx} className="hover:bg-gray-50 transition"> <td className="py-3 px-4 font-medium text-gray-900"> {user.username || user.name || 'User'} </td> <td className="py-3 px-4 text-gray-500">{user.email || '-'}</td> <td className="py-3 px-4"> <span className={`inline-block px-2.5 py-1 text-xs font-semibold rounded-full ${ isAdmin ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-700' }`} > {user.role || (isAdmin ? 'Admin' : 'User')} </span> </td> <td className="py-3 px-4 text-center"> {!isAdmin ? ( <button type="button" onClick={() => handleDeleteUser(user.id || user.username)} className="bg-red-50 hover:bg-red-100 text-red-600 text-xs font-medium px-3 py-1.5 rounded border border-red-200 transition hover:border-red-300" > ลบผู้ใช้ (Delete User) </button> ) : ( <span className="text-xs text-gray-400 italic">สงวนสิทธิ์ Admin</span> )} </td> </tr> ); }) ) : ( <tr> <td colSpan="4" className="text-center py-10 text-gray-400"> ไม่พบข้อมูลผู้ใช้งาน (No Users Data) </td> </tr> )} </tbody> </table> </div> </div> )} </div> </div> {/* Modal: Add Product */} {isModalOpen && ( <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"> <div className="bg-white rounded-xl shadow-xl max-w-md w-full p-6 space-y-4 relative"> <h3 className="text-lg font-bold text-gray-900 border-b pb-2">เพิ่มสินค้าใหม่</h3> <form onSubmit={handleAddProductSubmit} className="space-y-3"> <div> <label className="block text-xs font-semibold text-gray-700 mb-1">ชื่อสินค้า *</label> <input type="text" required placeholder="เช่น เสื้อยืดสีมินิมอล" value={newProduct.name} onChange={(e) => setNewProduct({ ...newProduct, name: e.target.value })} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> </div> <div> <label className="block text-xs font-semibold text-gray-700 mb-1">ราคา (บาท) *</label> <input type="number" required min="0" placeholder="เช่น 290" value={newProduct.price} onChange={(e) => setNewProduct({ ...newProduct, price: e.target.value })} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> </div> <div> <label className="block text-xs font-semibold text-gray-700 mb-1">จำนวนคลังสินค้า (Stock)</label> <input type="number" min="0" placeholder="เช่น 100" value={newProduct.stock} onChange={(e) => setNewProduct({ ...newProduct, stock: e.target.value })} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> </div> <div> <label className="block text-xs font-semibold text-gray-700 mb-1">URL รูปภาพ</label> <input type="url" placeholder="https://example.com/image.jpg" value={newProduct.image} onChange={(e) => setNewProduct({ ...newProduct, image: e.target.value })} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> </div> <div> <label className="block text-xs font-semibold text-gray-700 mb-1">หมวดหมู่</label> <input type="text" placeholder="เช่น เสื้อผ้า, ไอที" value={newProduct.category} onChange={(e) => setNewProduct({ ...newProduct, category: e.target.value })} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> </div> <div className="flex justify-end gap-2 pt-3 border-t"> <button type="button" onClick={() => setIsModalOpen(false)} className="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition" > ยกเลิก </button> <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-lg transition" > บันทึกสินค้า </button> </div> </form> </div> </div> )} </div> ); }; export default AdminDashboard;
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.27 |
proxy
|
phpinfo
|
Settings