feat(admin): allow selecting items per page (10, 20, 50, 100, 500) on /products
This commit is contained in:
1 parent
a27743678c
commit
b585277214
1 file changed
+94
-27
@@ -42,7 +42,9 @@ export default function ProductsPage() {
|
|||||||
|
|
||||||
// Pagination State
|
// Pagination State
|
||||||
const page = searchParams.get('page') ? Number(searchParams.get('page')) : 1
|
const page = searchParams.get('page') ? Number(searchParams.get('page')) : 1
|
||||||
const [limit] = useState(10) // Small limit for testing pagination UI easily
|
const validLimits = [10, 20, 50, 100, 500]
|
||||||
|
const limitParam = Number(searchParams.get('limit'))
|
||||||
|
const limit = validLimits.includes(limitParam) ? limitParam : 10
|
||||||
const [meta, setMeta] = useState<PaginationMeta | null>(null)
|
const [meta, setMeta] = useState<PaginationMeta | null>(null)
|
||||||
|
|
||||||
const fixUrl = (url: string) => {
|
const fixUrl = (url: string) => {
|
||||||
@@ -265,7 +267,19 @@ export default function ProductsPage() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch when page, category, or search changes
|
const handleLimitChange = (newLimit: number) => {
|
||||||
|
setSearchParams(prev => {
|
||||||
|
if (newLimit === 10) {
|
||||||
|
prev.delete('limit')
|
||||||
|
} else {
|
||||||
|
prev.set('limit', newLimit.toString())
|
||||||
|
}
|
||||||
|
prev.delete('page')
|
||||||
|
return prev
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch when page, category, search, or limit changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only debounce if we have a search value to avoid rapid fetches while typing
|
// Only debounce if we have a search value to avoid rapid fetches while typing
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -273,7 +287,7 @@ export default function ProductsPage() {
|
|||||||
}, search ? 300 : 0)
|
}, search ? 300 : 0)
|
||||||
|
|
||||||
return () => clearTimeout(timeout)
|
return () => clearTimeout(timeout)
|
||||||
}, [page, activeCategory, search])
|
}, [page, activeCategory, search, limit])
|
||||||
|
|
||||||
// Reset to page 1 when filter changes
|
// Reset to page 1 when filter changes
|
||||||
const handleCategoryChange = (catId: number | null) => {
|
const handleCategoryChange = (catId: number | null) => {
|
||||||
@@ -493,48 +507,101 @@ export default function ProductsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination Controls */}
|
{/* Pagination Controls */}
|
||||||
<div className="px-6 py-4 bg-white/[0.01] border-t border-white/5 flex items-center justify-between">
|
<div className="px-6 py-4 bg-white/[0.01] border-t border-white/5 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
<div className="hidden sm:block">
|
<div className="flex flex-wrap items-center gap-4 text-xs text-slate-500">
|
||||||
<p className="text-xs text-slate-500">
|
<p>
|
||||||
Showing page <span className="text-white font-bold">{page}</span> of {meta?.lastPage || 1}
|
Showing page <span className="text-white font-bold">{page}</span> of {meta?.lastPage || 1}
|
||||||
|
{meta?.total !== undefined ? ` (${meta.total} ${t('products.totalItems', 'รายการทั้งหมด')})` : ''}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-slate-400 font-medium">{t('products.perPage', 'แสดงต่อหน้า:')}</span>
|
||||||
|
<select
|
||||||
|
value={limit}
|
||||||
|
onChange={(e) => handleLimitChange(Number(e.target.value))}
|
||||||
|
className="bg-night-950 border border-white/10 rounded-lg px-2.5 py-1 text-xs font-semibold text-cyan-glow hover:border-cyan-glow/40 focus:outline-none focus:border-cyan-glow cursor-pointer transition-all shadow-inner"
|
||||||
|
>
|
||||||
|
{validLimits.map((val) => (
|
||||||
|
<option key={val} value={val} className="bg-night-900 text-white">
|
||||||
|
{val}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||||
disabled={page <= 1 || loading}
|
disabled={page <= 1 || loading}
|
||||||
className="p-2 rounded-lg bg-night-950 border border-white/5 disabled:opacity-30 disabled:cursor-not-allowed hover:border-white/10 transition-all text-white"
|
className="p-2 rounded-lg bg-night-950 border border-white/5 disabled:opacity-30 disabled:cursor-not-allowed hover:border-white/10 transition-all text-white cursor-pointer"
|
||||||
|
title="Previous page"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-4 h-4" />
|
<ChevronLeft className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Page numbers (limited) */}
|
{/* Page numbers with sliding window */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{Array.from({ length: Math.min(5, meta?.lastPage || 0) }).map((_, i) => {
|
{(() => {
|
||||||
const pageNum = i + 1;
|
const lastPage = meta?.lastPage || 1
|
||||||
// Logic for showing a realistic range could go here, but 5 is enough for now
|
const maxButtons = 5
|
||||||
return (
|
let start = Math.max(1, page - 2)
|
||||||
<button
|
let end = Math.min(lastPage, start + maxButtons - 1)
|
||||||
key={pageNum}
|
if (end - start < maxButtons - 1) {
|
||||||
onClick={() => setPage(pageNum)}
|
start = Math.max(1, end - maxButtons + 1)
|
||||||
className={`w-8 h-8 rounded-lg text-xs font-bold transition-all ${
|
}
|
||||||
page === pageNum
|
const pages = []
|
||||||
? 'bg-cyan-glow text-night-900 border border-cyan-glow shadow-[0_0_10px_rgba(102,252,241,0.2)]'
|
for (let i = start; i <= end; i++) {
|
||||||
: 'hover:bg-white/5 text-slate-400'
|
pages.push(i)
|
||||||
}`}
|
}
|
||||||
>
|
|
||||||
{pageNum}
|
return (
|
||||||
</button>
|
<>
|
||||||
)
|
{start > 1 && (
|
||||||
})}
|
<>
|
||||||
{(meta?.lastPage || 0) > 5 && <span className="text-slate-600 px-1">...</span>}
|
<button
|
||||||
|
onClick={() => setPage(1)}
|
||||||
|
className="w-8 h-8 rounded-lg text-xs font-bold hover:bg-white/5 text-slate-400 transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
{start > 2 && <span className="text-slate-600 px-1 text-xs">...</span>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{pages.map((pageNum) => (
|
||||||
|
<button
|
||||||
|
key={pageNum}
|
||||||
|
onClick={() => setPage(pageNum)}
|
||||||
|
className={`w-8 h-8 rounded-lg text-xs font-bold transition-all cursor-pointer ${
|
||||||
|
page === pageNum
|
||||||
|
? 'bg-cyan-glow text-night-900 border border-cyan-glow shadow-[0_0_10px_rgba(102,252,241,0.2)]'
|
||||||
|
: 'hover:bg-white/5 text-slate-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{end < lastPage && (
|
||||||
|
<>
|
||||||
|
{end < lastPage - 1 && <span className="text-slate-600 px-1 text-xs">...</span>}
|
||||||
|
<button
|
||||||
|
onClick={() => setPage(lastPage)}
|
||||||
|
className="w-8 h-8 rounded-lg text-xs font-bold hover:bg-white/5 text-slate-400 transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
{lastPage}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage(p => Math.min(meta?.lastPage || p, p + 1))}
|
onClick={() => setPage(p => Math.min(meta?.lastPage || p, p + 1))}
|
||||||
disabled={page >= (meta?.lastPage || 1) || loading}
|
disabled={page >= (meta?.lastPage || 1) || loading}
|
||||||
className="p-2 rounded-lg bg-night-950 border border-white/5 disabled:opacity-30 disabled:cursor-not-allowed hover:border-white/10 transition-all text-white"
|
className="p-2 rounded-lg bg-night-950 border border-white/5 disabled:opacity-30 disabled:cursor-not-allowed hover:border-white/10 transition-all text-white cursor-pointer"
|
||||||
|
title="Next page"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-4 h-4" />
|
<ChevronRight className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in new issue
Block a user