add pagination
This commit is contained in:
parent
4ebe6da793
commit
2fcc239808
@ -138,6 +138,12 @@ function MembersPageContent() {
|
|||||||
const [searchTerm, setSearchTerm] = useState("")
|
const [searchTerm, setSearchTerm] = useState("")
|
||||||
const [statusFilter, setStatusFilter] = useState("all")
|
const [statusFilter, setStatusFilter] = useState("all")
|
||||||
|
|
||||||
|
// Pagination states
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
const [totalUsers, setTotalUsers] = useState(0)
|
||||||
|
const [paginationLoading, setPaginationLoading] = useState(false)
|
||||||
|
|
||||||
// Bulk upload states
|
// Bulk upload states
|
||||||
const [showBulkUpload, setShowBulkUpload] = useState(false)
|
const [showBulkUpload, setShowBulkUpload] = useState(false)
|
||||||
const [bulkUsers, setBulkUsers] = useState<BulkUserRequest[]>([])
|
const [bulkUsers, setBulkUsers] = useState<BulkUserRequest[]>([])
|
||||||
@ -153,19 +159,30 @@ function MembersPageContent() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
fetchUsers()
|
fetchUsers(1, pageSize) // Always start from first page
|
||||||
loadStoredJob()
|
loadStoredJob()
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
// Reset to first page when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && (searchTerm || statusFilter !== "all")) {
|
||||||
|
setCurrentPage(1)
|
||||||
|
fetchUsers(1, pageSize)
|
||||||
|
}
|
||||||
|
}, [searchTerm, statusFilter])
|
||||||
|
|
||||||
|
const fetchUsers = async (page: number = currentPage, size: number = pageSize) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const response = await apiClient.get('/api/v1/users')
|
const response = await apiClient.get(`/api/v1/users?page=${page}&limit=${size}`)
|
||||||
const data: ApiResponse = response.data
|
const data: ApiResponse = response.data
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setUsers(data.data.users)
|
setUsers(data.data.users)
|
||||||
|
setTotalUsers(data.data.pagination.total_count)
|
||||||
|
setCurrentPage(page)
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: `Fetched ${data.data.users.length} users`,
|
description: `Fetched ${data.data.users.length} users`,
|
||||||
@ -186,6 +203,7 @@ function MembersPageContent() {
|
|||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
setPaginationLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +370,7 @@ function MembersPageContent() {
|
|||||||
|
|
||||||
// If job is completed, refresh users list
|
// If job is completed, refresh users list
|
||||||
if (jobResult.status === 'completed' || jobResult.status === 'failed') {
|
if (jobResult.status === 'completed' || jobResult.status === 'failed') {
|
||||||
await fetchUsers()
|
await fetchUsers(currentPage, pageSize)
|
||||||
|
|
||||||
// Show completion message
|
// Show completion message
|
||||||
if (jobResult.status === 'completed') {
|
if (jobResult.status === 'completed') {
|
||||||
@ -395,6 +413,24 @@ function MembersPageContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pagination functions
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setPaginationLoading(true)
|
||||||
|
setCurrentPage(page)
|
||||||
|
fetchUsers(page, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageSizeChange = (size: number) => {
|
||||||
|
setPaginationLoading(true)
|
||||||
|
setPageSize(size)
|
||||||
|
setCurrentPage(1) // Reset to first page when changing page size
|
||||||
|
fetchUsers(1, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalUsers / pageSize)
|
||||||
|
const startIndex = (currentPage - 1) * pageSize + 1
|
||||||
|
const endIndex = Math.min(currentPage * pageSize, totalUsers)
|
||||||
|
|
||||||
const filteredUsers = users.filter((user) => {
|
const filteredUsers = users.filter((user) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
@ -513,8 +549,30 @@ function MembersPageContent() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full md:w-32">
|
||||||
|
<Label htmlFor="pageSize">Page Size</Label>
|
||||||
|
<Select value={pageSize.toString()} onValueChange={(value) => handlePageSizeChange(parseInt(value))}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="5">5 per page</SelectItem>
|
||||||
|
<SelectItem value="10">10 per page</SelectItem>
|
||||||
|
<SelectItem value="20">20 per page</SelectItem>
|
||||||
|
<SelectItem value="50">50 per page</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-end gap-2">
|
<div className="flex items-end gap-2">
|
||||||
<Button onClick={fetchUsers} disabled={loading} className="gap-2">
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentPage(1)
|
||||||
|
fetchUsers(1, pageSize)
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
@ -564,7 +622,8 @@ function MembersPageContent() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Users List</CardTitle>
|
<CardTitle>Users List</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Showing {filteredUsers.length} of {users.length} users
|
Showing {startIndex}-{endIndex} of {totalUsers} total users
|
||||||
|
{filteredUsers.length !== users.length && ` (${filteredUsers.length} filtered)`}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -615,6 +674,105 @@ function MembersPageContent() {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pagination Controls */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="relative flex items-center justify-between px-6 py-4 border-t">
|
||||||
|
{paginationLoading && (
|
||||||
|
<div className="absolute inset-0 bg-white/80 flex items-center justify-center">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<span>
|
||||||
|
Page {currentPage} of {totalPages}
|
||||||
|
</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{startIndex}-{endIndex} of {totalUsers} results
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* Previous Page */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Page Numbers */}
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{/* First Page */}
|
||||||
|
{currentPage > 3 && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePageChange(1)}
|
||||||
|
className="w-8 h-8 p-0"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</Button>
|
||||||
|
{currentPage > 4 && (
|
||||||
|
<span className="px-2 text-gray-400">...</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Page Range */}
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
const page = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i
|
||||||
|
if (page > totalPages) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
variant={page === currentPage ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePageChange(page)}
|
||||||
|
className="w-8 h-8 p-0"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Last Page */}
|
||||||
|
{currentPage < totalPages - 2 && (
|
||||||
|
<>
|
||||||
|
{currentPage < totalPages - 3 && (
|
||||||
|
<span className="px-2 text-gray-400">...</span>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePageChange(totalPages)}
|
||||||
|
className="w-8 h-8 p-0"
|
||||||
|
>
|
||||||
|
{totalPages}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Next Page */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ArrowLeft className="h-4 w-4 rotate-180" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user