clean up stuff

This commit is contained in:
2025-07-02 04:16:53 -04:00
parent f43f68709a
commit bdb5e0fe55
22 changed files with 740 additions and 680 deletions
+160
View File
@@ -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>
);
}