Spaces:
Running
Running
| import React, { useState, useEffect } from 'react'; | |
| import { | |
| Plus, | |
| Minus, | |
| ShoppingBag, | |
| Heart, | |
| Search, | |
| Filter, | |
| Grid, | |
| List | |
| } from 'lucide-react'; | |
| const ProductGrid = ({ | |
| products, | |
| activeCategory, | |
| onAddToCart, | |
| onProductClick | |
| }) => { | |
| const [searchQuery, setSearchQuery] = useState(''); | |
| const [gridView, setGridView] = useState(true); | |
| const [filteredProducts, setFilteredProducts] = useState([]); | |
| useEffect(() => { | |
| let result = products; | |
| // Filter by category | |
| if (activeCategory !== 'all') { | |
| result = result.filter(product => product.category === activeCategory); | |
| } | |
| // Filter by search query | |
| if (searchQuery) { | |
| result = result.filter(product => | |
| product.name.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| product.description.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ); | |
| } | |
| setFilteredProducts(result); | |
| }, [products, activeCategory, searchQuery]); | |
| const formatPrice = (price) => { | |
| return new Intl.NumberFormat('th-TH', { | |
| style: 'currency', | |
| currency: 'THB' | |
| }).format(price); | |
| }; | |
| return ( | |
| <div className="flex-1 overflow-y-auto p-4"> | |
| {/* Search and Filter */} | |
| <div className="mb-6 space-y-4"> | |
| <div className="flex flex-col sm:flex-row gap-4"> | |
| <div className="relative flex-1"> | |
| <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-pos-text-secondary w-5 h-5" /> | |
| <input | |
| type="text" | |
| placeholder="ค้นหาสินค้า..." | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| className="w-full pl-10 pr-4 py-3 rounded-xl border border-pos-border focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none transition-all" | |
| /> | |
| </div> | |
| <div className="flex gap-2"> | |
| <button | |
| onClick={() => setGridView(true)} | |
| className={`p-3 rounded-xl border ${gridView | |
| ? 'bg-primary-600 text-white border-primary-600' | |
| : 'border-pos-border text-pos-text-secondary hover:bg-pos-bg'}`} | |
| > | |
| <Grid className="w-5 h-5" /> | |
| </button> | |
| <button | |
| onClick={() => setGridView(false)} | |
| className={`p-3 rounded-xl border ${!gridView | |
| ? 'bg-primary-600 text-white border-primary-600' | |
| : 'border-pos-border text-pos-text-secondary hover:bg-pos-bg'}`} | |
| > | |
| <List className="w-5 h-5" /> | |
| </button> | |
| <button className="p-3 rounded-xl border border-pos-border text-pos-text-secondary hover:bg-pos-bg"> | |
| <Filter className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Products */} | |
| <div className={gridView ? "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" : "space-y-3"}> | |
| {filteredProducts.length > 0 ? ( | |
| filteredProducts.map((product) => ( | |
| <div | |
| key={product.id} | |
| onClick={() => onProductClick(product)} | |
| className="group bg-white rounded-2xl border border-pos-border overflow-hidden hover:shadow-xl transition-all duration-300 hover:border-primary-200 cursor-pointer" | |
| > | |
| <div className="relative aspect-square overflow-hidden"> | |
| <img | |
| src={product.image} | |
| alt={product.name} | |
| className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300" | |
| /> | |
| <div className="absolute top-2 right-2"> | |
| <button className="p-2 bg-white/90 rounded-full hover:bg-white shadow-sm transition-colors"> | |
| <Heart className="w-4 h-4 text-red-500" /> | |
| </button> | |
| </div> | |
| {product.discount > 0 && ( | |
| <div className="absolute top-2 left-2 bg-danger text-white px-2 py-1 rounded-lg text-xs font-bold"> | |
| -{product.discount}% | |
| </div> | |
| )} | |
| <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-4"> | |
| <p className="text-white text-xs font-medium">{product.categoryName}</p> | |
| </div> | |
| </div> | |
| <div className="p-3"> | |
| <h3 className="font-bold text-pos-text mb-1 truncate">{product.name}</h3> | |
| <div className="flex justify-between items-center"> | |
| <div className="flex flex-col"> | |
| {product.discount > 0 ? ( | |
| <> | |
| <span className="text-xs text-pos-text-secondary line-through"> | |
| {formatPrice(product.price)} | |
| </span> | |
| <span className="text-lg font-bold text-primary-600"> | |
| {formatPrice(product.price * (1 - product.discount/100))} | |
| </span> | |
| </> | |
| ) : ( | |
| <span className="text-lg font-bold text-pos-text"> | |
| {formatPrice(product.price)} | |
| </span> | |
| )} | |
| </div> | |
| <button | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| onAddToCart(product); | |
| className="p-2 bg-primary-50 text-primary-600 rounded-xl hover:bg-primary-600 hover:text-white transition-colors" | |
| > | |
| <ShoppingBag className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )) | |
| ) : ( | |
| <div className="col-span-full py-12 text-center"> | |
| <div className="w-20 h-20 bg-pos-bg rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <Search className="w-10 h-10 text-pos-text-secondary" /> | |
| </div> | |
| <h3 className="text-lg font-medium text-pos-text mb-2">ไม่พบสินค้า</h3> | |
| <p className="text-pos-text-secondary"> | |
| {searchQuery | |
| ? `ไม่พบผลลัพธ์สำหรับ "${searchQuery}"` | |
| : "โปรดเลือกหมวดหมู่หรือลองค้นหาใหม่"} | |
| </p> | |
| </div> | |
| )} | |
| </div> | |
| {/* Product count */} | |
| <div className="mt-4 text-center"> | |
| <p className="text-sm text-pos-text-secondary"> | |
| แสดง {filteredProducts.length} ของ {products.length} ผลิตภัณฑ์ | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ProductGrid; |