Skip to content

Commit 574ccb2

Browse files
committed
Implement pagination and enhanced search functionality for user data
1 parent f8abd86 commit 574ccb2

File tree

3 files changed

+168
-31
lines changed

3 files changed

+168
-31
lines changed

client/src/app/components/Navbar.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ const Navbar: React.FC<NavbarProps> = ({
3535

3636
{/* Search */}
3737
{searchBarPresent && (
38-
<form className="">
39-
<div className="relative flex ">
38+
<form className="" onSubmit={(e) => e.preventDefault()}>
39+
<div className="relative flex items-center">
4040
<input
4141
value={search}
4242
onChange={(e) => setSearch && setSearch(e.target.value)}
4343
type="text"
44-
placeholder="Search by username"
45-
className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded px-4 py-3 focus:outline-none pl-10"
44+
placeholder="Search by name"
45+
className="bg-[#1f1f1f] sm:w-[260px] w-[100px] font-sourcecodepro text-white rounded-l px-4 py-3 focus:outline-none pl-10"
4646
/>
4747
<Image
4848
src="/assets/icons/search.svg"
4949
alt="Search Icon"
5050
width={20}
5151
height={20}
52-
className="absolute left-2 -translate-y-1/2 top-1/2 "
52+
className="absolute left-2 -translate-y-1/2 top-1/2"
5353
/>
5454
</div>
5555
</form>

client/src/app/page.tsx

Lines changed: 119 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Navbar from "./components/Navbar";
55
import Card from "./components/Card";
66
import Footer from "./components/Footer";
77
import GenerateStats from "./components/GenerateStats";
8-
import { useContext, useEffect, useState } from "react";
8+
import { useContext, useEffect, useState, useMemo } from "react";
99
import PromotionCard from "./components/PromotionCard";
1010
import { DataContext } from "./context/DataContext";
1111
import { ToastContainer } from "react-toastify";
@@ -18,21 +18,62 @@ export default function Home() {
1818
const [loading, setLoading] = useState(true);
1919
const [showStats, setShowStats] = useState(false);
2020
const [sortBy, setSortBy] = useState("default");
21+
const [pagination, setPagination] = useState({
22+
currentPage: 1,
23+
totalPages: 1,
24+
totalItems: 0,
25+
limit: 11,
26+
});
27+
const [searchTerm, setSearchTerm] = useState("");
28+
const [debouncedSearch, setDebouncedSearch] = useState("");
2129

22-
const fetchData = async () => {
30+
// Debounce search input to avoid too many requests
31+
useEffect(() => {
32+
const timer = setTimeout(() => {
33+
setDebouncedSearch(search);
34+
}, 500);
35+
36+
return () => clearTimeout(timer);
37+
}, [search]);
38+
39+
// Reset pagination when search changes
40+
useEffect(() => {
41+
setPagination((prev) => ({ ...prev, currentPage: 1 }));
42+
}, [debouncedSearch]);
43+
44+
const fetchData = async (page = 1) => {
2345
try {
24-
const res = await fetch("/api/fetchdata");
25-
const data = await res.json();
26-
setDatas(data.data);
46+
setLoading(true);
47+
const queryParams = new URLSearchParams();
48+
49+
if (debouncedSearch) {
50+
queryParams.append("search", debouncedSearch);
51+
}
52+
53+
queryParams.append("page", page.toString());
54+
queryParams.append("limit", pagination.limit.toString());
55+
queryParams.append("sortBy", sortBy);
56+
57+
const res = await fetch(`/api/fetchdata?${queryParams.toString()}`);
58+
const response = await res.json();
59+
60+
setDatas(response.data);
61+
setPagination({
62+
currentPage: response.pagination.page,
63+
totalPages: response.pagination.totalPages,
64+
totalItems: response.pagination.total,
65+
limit: response.pagination.limit,
66+
});
2767
setLoading(false);
2868
} catch (error) {
2969
console.error("Error fetching messages:", error);
70+
setLoading(false);
3071
}
3172
};
3273

3374
useEffect(() => {
34-
fetchData();
35-
}, [datas && datas.length]);
75+
fetchData(pagination.currentPage);
76+
}, [debouncedSearch, pagination.currentPage, pagination.limit, sortBy]);
3677

3778
useEffect(() => {
3879
setTimeout(() => {
@@ -42,22 +83,13 @@ export default function Home() {
4283

4384
const handleSortChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
4485
setSortBy(e.target.value);
86+
// Reset to first page when sorting changes
87+
setPagination((prev) => ({ ...prev, currentPage: 1 }));
4588
};
4689

47-
const searchedData = datas?.filter((data: any) =>
48-
data.profileData.fullName.toLowerCase().includes(search.toLowerCase())
49-
);
50-
const sorting = (data: any[]) => {
51-
switch (sortBy) {
52-
case "question-solved":
53-
return data.slice().sort((a, b) => b.totalSolved - a.totalSolved);
54-
case "default":
55-
default:
56-
return data.slice().sort((a, b) => {
57-
return (
58-
new Date(b.timeStamp).getTime() - new Date(a.timeStamp).getTime()
59-
);
60-
});
90+
const handlePageChange = (newPage: number) => {
91+
if (newPage > 0 && newPage <= pagination.totalPages) {
92+
setPagination((prev) => ({ ...prev, currentPage: newPage }));
6193
}
6294
};
6395

@@ -106,17 +138,80 @@ export default function Home() {
106138
<PromotionCard />
107139
{loading ? (
108140
<>
109-
{Array.from({ length: 6 }, (_, i) => (
141+
{Array.from({ length: 8 }, (_, i) => (
110142
<Skeleton key={i} />
111143
))}
112144
</>
113145
) : (
114-
sorting(searchedData).map((userData: any, index: number) => (
115-
<Card userData={userData} index={index} key={index} />
146+
datas?.map((userData: any, index: number) => (
147+
<Card
148+
userData={userData}
149+
index={index}
150+
key={userData._id || index}
151+
/>
116152
))
117153
)}
118154
</div>
119155

156+
{/* Pagination Controls */}
157+
{!loading && pagination.totalPages > 1 && (
158+
<div className="flex justify-center mt-12 mb-16 max-w-7xl mx-auto">
159+
<div className="flex space-x-2 items-center font-sourcecodepro">
160+
<button
161+
onClick={() => handlePageChange(pagination.currentPage - 1)}
162+
disabled={pagination.currentPage === 1}
163+
className="px-4 py-2 rounded-md border border-gray-600 bg-[#0e0e0e] text-white disabled:opacity-50 disabled:cursor-not-allowed"
164+
>
165+
Previous
166+
</button>
167+
168+
<div className="flex space-x-2">
169+
{Array.from(
170+
{ length: Math.min(5, pagination.totalPages) },
171+
(_, i) => {
172+
// Show 5 pages around current page
173+
let pageNum = 1;
174+
if (pagination.totalPages <= 5) {
175+
pageNum = i + 1;
176+
} else if (pagination.currentPage <= 3) {
177+
pageNum = i + 1;
178+
} else if (
179+
pagination.currentPage >=
180+
pagination.totalPages - 2
181+
) {
182+
pageNum = pagination.totalPages - 4 + i;
183+
} else {
184+
pageNum = pagination.currentPage - 2 + i;
185+
}
186+
187+
return (
188+
<button
189+
key={pageNum}
190+
onClick={() => handlePageChange(pageNum)}
191+
className={`w-10 h-10 rounded-md ${
192+
pagination.currentPage === pageNum
193+
? "bg-gradient-to-r from-[#cb42b2] to-[#ecf576] text-black font-bold"
194+
: "border border-gray-600 bg-[#0e0e0e] text-white"
195+
}`}
196+
>
197+
{pageNum}
198+
</button>
199+
);
200+
}
201+
)}
202+
</div>
203+
204+
<button
205+
onClick={() => handlePageChange(pagination.currentPage + 1)}
206+
disabled={pagination.currentPage === pagination.totalPages}
207+
className="px-4 py-2 rounded-md border border-gray-600 bg-[#0e0e0e] text-white disabled:opacity-50 disabled:cursor-not-allowed"
208+
>
209+
Next
210+
</button>
211+
</div>
212+
</div>
213+
)}
214+
120215
<Footer />
121216

122217
<ToastContainer

client/src/pages/api/fetchdata.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,56 @@ import UserData from "../../models/userdata";
44

55
export default async function handler(req: any, res: any) {
66
const { method } = req;
7+
const { search, page = 1, limit = 11, sortBy = "default" } = req.query;
78

89
await connectToDatabase();
910

1011
switch (method) {
1112
case "GET":
1213
try {
13-
const messages = await UserData.find({});
14-
res.status(200).json({ success: true, data: messages });
14+
const pageNumber = parseInt(page as string);
15+
const limitNumber = parseInt(limit as string);
16+
const skip = (pageNumber - 1) * limitNumber;
17+
18+
// Build the query based on search parameter
19+
let query = {};
20+
if (search) {
21+
query = {
22+
"profileData.fullName": { $regex: search, $options: "i" },
23+
};
24+
}
25+
26+
// Determine sort options based on sortBy parameter
27+
let sortOptions = {};
28+
switch (sortBy) {
29+
case "question-solved":
30+
sortOptions = { totalSolved: -1 }; // -1 for descending order
31+
break;
32+
case "default":
33+
default:
34+
sortOptions = { timeStamp: -1 }; // Sort by timestamp descending (newest first)
35+
break;
36+
}
37+
38+
// Get total count for pagination
39+
const totalItems = await UserData.countDocuments(query);
40+
41+
// Fetch data with pagination and sorting
42+
const messages = await UserData.find(query)
43+
.sort(sortOptions)
44+
.skip(skip)
45+
.limit(limitNumber);
46+
47+
res.status(200).json({
48+
success: true,
49+
data: messages,
50+
pagination: {
51+
total: totalItems,
52+
page: pageNumber,
53+
limit: limitNumber,
54+
totalPages: Math.ceil(totalItems / limitNumber),
55+
},
56+
});
1557
} catch (error) {
1658
res.status(400).json({ success: false, error });
1759
}

0 commit comments

Comments
 (0)