clean up stuff
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
'use client';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
// Core columns for performance and usability
|
||||
const columns = [
|
||||
'uuid',
|
||||
'sku',
|
||||
'brandName',
|
||||
'productName',
|
||||
'department',
|
||||
'category',
|
||||
'subcategory',
|
||||
'retailPrice',
|
||||
'salePrice',
|
||||
'imageUrl',
|
||||
];
|
||||
|
||||
export default function AdminProductsPage() {
|
||||
const [products, setProducts] = useState<any[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = useState(50);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Filter state
|
||||
const [brand, setBrand] = useState('');
|
||||
const [department, setDepartment] = useState('');
|
||||
const [category, setCategory] = useState('');
|
||||
const [subcategory, setSubcategory] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
fetch(`/api/products?page=${page}&limit=${limit}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setProducts(data.data || []);
|
||||
setTotal(data.total || 0);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err.message || 'Error fetching products');
|
||||
setLoading(false);
|
||||
});
|
||||
}, [page, limit]);
|
||||
|
||||
// Get unique filter options from current page's products
|
||||
const brandOptions = useMemo(() => Array.from(new Set(products.map(p => p.brandName).filter(Boolean))).sort(), [products]);
|
||||
const departmentOptions = useMemo(() => Array.from(new Set(products.map(p => p.department).filter(Boolean))).sort(), [products]);
|
||||
const categoryOptions = useMemo(() => Array.from(new Set(products.map(p => p.category).filter(Boolean))).sort(), [products]);
|
||||
const subcategoryOptions = useMemo(() => Array.from(new Set(products.map(p => p.subcategory).filter(Boolean))).sort(), [products]);
|
||||
|
||||
// Filter products before rendering
|
||||
const filteredProducts = useMemo(() => {
|
||||
return products.filter(p =>
|
||||
(!brand || p.brandName === brand) &&
|
||||
(!department || p.department === department) &&
|
||||
(!category || p.category === category) &&
|
||||
(!subcategory || p.subcategory === subcategory)
|
||||
);
|
||||
}, [products, brand, department, category, subcategory]);
|
||||
|
||||
// Reset to page 1 when filters change
|
||||
useEffect(() => { setPage(1); }, [brand, department, category, subcategory]);
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-4">Admin Products</h1>
|
||||
{error && <div className="text-red-600 mb-4">{error}</div>}
|
||||
{/* Filters */}
|
||||
<div className="flex flex-wrap gap-4 mb-4 items-end">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold mb-1">Brand</label>
|
||||
<select className="border rounded px-2 py-1 min-w-[120px]" value={brand} onChange={e => setBrand(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{brandOptions.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold mb-1">Department</label>
|
||||
<select className="border rounded px-2 py-1 min-w-[120px]" value={department} onChange={e => setDepartment(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{departmentOptions.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold mb-1">Category</label>
|
||||
<select className="border rounded px-2 py-1 min-w-[120px]" value={category} onChange={e => setCategory(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{categoryOptions.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold mb-1">Subcategory</label>
|
||||
<select className="border rounded px-2 py-1 min-w-[120px]" value={subcategory} onChange={e => setSubcategory(e.target.value)}>
|
||||
<option value="">All</option>
|
||||
{subcategoryOptions.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto border rounded bg-white">
|
||||
<table className="min-w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map(col => (
|
||||
<th key={col} className="px-2 py-2 border-b text-left font-semibold bg-zinc-50">{col}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading ? (
|
||||
<tr><td colSpan={columns.length} className="text-center py-8">Loading...</td></tr>
|
||||
) : filteredProducts.length === 0 ? (
|
||||
<tr><td colSpan={columns.length} className="text-center py-8">No products found.</td></tr>
|
||||
) : (
|
||||
filteredProducts.map((product, i) => (
|
||||
<tr key={product.uuid || i} className="border-b hover:bg-zinc-50">
|
||||
{columns.map(col => (
|
||||
<td key={col} className="px-2 py-1 max-w-xs truncate">
|
||||
{col === 'imageUrl' && product[col] ? (
|
||||
<img src={product[col]} alt="thumb" className="h-10 w-10 object-contain border rounded" />
|
||||
) : (
|
||||
product[col] ?? ''
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Pagination Controls */}
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
<button
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
>Prev</button>
|
||||
<span>Page {page} of {Math.ceil(total / limit) || 1}</span>
|
||||
<button
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
onClick={() => setPage(p => p + 1)}
|
||||
disabled={page * limit >= total}
|
||||
>Next</button>
|
||||
<select
|
||||
className="ml-4 border rounded px-2 py-1"
|
||||
value={limit}
|
||||
onChange={e => { setLimit(Number(e.target.value)); setPage(1); }}
|
||||
>
|
||||
{[25, 50, 100, 200].map(opt => (
|
||||
<option key={opt} value={opt}>{opt} / page</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="ml-2 text-zinc-500">Total: {total}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user