Skip to content

Commit 6672a3a

Browse files
committed
added pagination in dashboard
1 parent 9838a24 commit 6672a3a

1 file changed

Lines changed: 106 additions & 39 deletions

File tree

Client/src/pages/DashBoard.tsx

Lines changed: 106 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ const Dashboard: React.FC = () => {
3838
const [roleFilter, setRoleFilter] = useState<WorkspaceRole | "all">("all");
3939
const [filterOpen, setFilterOpen] = useState(false);
4040

41+
// pagination state
42+
const ITEMS_PER_PAGE = 6; // 2 rows * 3 columns on desktop
43+
const [currentPage, setCurrentPage] = useState(1);
44+
4145
useEffect(() => {
4246
if (userId && !initialLoadDone) {
4347
fetchWorkspaces();
@@ -66,6 +70,21 @@ const Dashboard: React.FC = () => {
6670
? workspaces
6771
: workspaces.filter((w) => w.currentUserRole === roleFilter);
6872

73+
// reset page when filter changes or list length changes
74+
useEffect(() => {
75+
setCurrentPage(1);
76+
}, [roleFilter, workspaces.length]);
77+
78+
const totalPages = Math.max(
79+
1,
80+
Math.ceil(filteredWorkspaces.length / ITEMS_PER_PAGE)
81+
);
82+
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
83+
const currentItems = filteredWorkspaces.slice(
84+
startIndex,
85+
startIndex + ITEMS_PER_PAGE
86+
);
87+
6988
return (
7089
<div className="min-h-screen bg-linear-to-br from-slate-50 via-white to-slate-100">
7190
<div className="mx-auto max-w-6xl px-4 py-8 sm:px-6 lg:px-8 space-y-8">
@@ -110,8 +129,7 @@ const Dashboard: React.FC = () => {
110129
</div>
111130
)}
112131

113-
{/* Stats row – all three boxes same size */}
114-
{/* Stats row – compact content, same card size */}
132+
{/* Stats row – compact */}
115133
<section className="grid grid-cols-3 gap-3 sm:gap-4">
116134
{/* Workspaces count */}
117135
<div className="rounded-xl border border-slate-200/50 bg-white/80 backdrop-blur-sm px-4 py-3 shadow-sm flex items-center justify-between min-h-[72px]">
@@ -123,7 +141,7 @@ const Dashboard: React.FC = () => {
123141
</div>
124142
</div>
125143

126-
{/* Filter card with dropdown (unchanged alignment) */}
144+
{/* Filter card with dropdown */}
127145
<div className="relative">
128146
<div
129147
className="rounded-xl border border-slate-200/50 bg-white/80 backdrop-blur-sm px-4 py-3 shadow-sm flex items-center justify-between min-h-[72px] cursor-pointer hover:shadow-md transition-shadow"
@@ -188,7 +206,7 @@ const Dashboard: React.FC = () => {
188206
</div>
189207
</section>
190208

191-
{/* Workspaces */}
209+
{/* Workspaces with pagination */}
192210
<section className="space-y-4">
193211
{isLoading && !workspaces.length && (
194212
<div className="flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed border-slate-200/50 bg-white/70 backdrop-blur-sm px-8 py-12 text-center shadow-sm">
@@ -210,56 +228,105 @@ const Dashboard: React.FC = () => {
210228
</div>
211229
)}
212230

213-
{workspaces.length > 0 && filteredWorkspaces.length === 0 && (
231+
{filteredWorkspaces.length > 0 && currentItems.length === 0 && (
214232
<div className="rounded-2xl border-2 border-dashed border-slate-200/50 bg-white/70 backdrop-blur-sm px-6 py-8 text-center text-sm text-slate-500 shadow-sm">
215233
No workspaces match this filter.
216234
</div>
217235
)}
218236

219-
{filteredWorkspaces.length > 0 && (
220-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
221-
{filteredWorkspaces.map((ws) => {
222-
const role = ws.currentUserRole;
223-
const color = ROLE_COLORS[role] || "bg-slate-400";
237+
{currentItems.length > 0 && (
238+
<>
239+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
240+
{currentItems.map((ws) => {
241+
const role = ws.currentUserRole;
242+
const color = ROLE_COLORS[role] || "bg-slate-400";
224243

225-
return (
226-
<div
227-
key={ws.id}
228-
className="group flex flex-col rounded-2xl border border-slate-200/50 bg-white/80 backdrop-blur-sm p-6 shadow-sm hover:shadow-xl hover:shadow-slate-300/40 hover:-translate-y-1 transition-all duration-300 hover:border-slate-300/70"
229-
>
230-
<div className="flex items-start justify-between gap-4">
231-
<div className="min-w-0">
232-
<h3 className="truncate text-lg font-semibold text-slate-900 group-hover:text-indigo-700 transition-colors">
233-
{ws.name || "Untitled workspace"}
234-
</h3>
235-
<p className="mt-1 text-xs text-slate-500">
236-
{new Date(ws.createdAt).toLocaleDateString()}
237-
</p>
244+
return (
245+
<div
246+
key={ws.id}
247+
className="group flex flex-col rounded-2xl border border-slate-200/50 bg-white/80 backdrop-blur-sm p-6 shadow-sm hover:shadow-xl hover:shadow-slate-300/40 hover:-translate-y-1 transition-all duration-300 hover:border-slate-300/70"
248+
>
249+
<div className="flex items-start justify-between gap-4">
250+
<div className="min-w-0">
251+
<h3 className="truncate text-lg font-semibold text-slate-900 group-hover:text-indigo-700 transition-colors">
252+
{ws.name || "Untitled workspace"}
253+
</h3>
254+
<p className="mt-1 text-xs text-slate-500">
255+
{new Date(ws.createdAt).toLocaleDateString()}
256+
</p>
257+
</div>
258+
259+
<span
260+
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold text-white shadow-sm ${color}`}
261+
>
262+
{role}
263+
</span>
238264
</div>
239265

240-
<span
241-
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold text-white shadow-sm ${color}`}
242-
>
243-
{role}
244-
</span>
266+
{(role === "Owner" || role === "Admin") && (
267+
<div className="mt-6 pt-4 border-t border-slate-100/50 flex items-center justify-between text-xs text-slate-500">
268+
<button
269+
onClick={() => handleInvite(ws.id)}
270+
className="font-semibold text-slate-700 hover:text-slate-900 transition-colors"
271+
aria-label={`Invite member to ${ws.name}`}
272+
>
273+
Invite
274+
</button>
275+
<span className="text-slate-400 text-xs">
276+
Members
277+
</span>
278+
</div>
279+
)}
245280
</div>
281+
);
282+
})}
283+
</div>
246284

247-
{(role === "Owner" || role === "Admin") && (
248-
<div className="mt-6 pt-4 border-t border-slate-100/50 flex items-center justify-between text-xs text-slate-500">
285+
{/* Pagination controls */}
286+
{totalPages > 1 && (
287+
<div className="flex items-center justify-between gap-3 pt-2">
288+
<div className="text-xs text-slate-500">
289+
Page {currentPage} of {totalPages}
290+
</div>
291+
<div className="flex items-center gap-1">
292+
<button
293+
type="button"
294+
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
295+
disabled={currentPage === 1}
296+
className="px-2 py-1 text-xs rounded-md border border-slate-200 bg-white text-slate-700 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-slate-50"
297+
>
298+
Prev
299+
</button>
300+
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
301+
(page) => (
249302
<button
250-
onClick={() => handleInvite(ws.id)}
251-
className="font-semibold text-slate-700 hover:text-slate-900 transition-colors"
252-
aria-label={`Invite member to ${ws.name}`}
303+
key={page}
304+
type="button"
305+
onClick={() => setCurrentPage(page)}
306+
className={`px-2.5 py-1 text-xs rounded-md border ${
307+
currentPage === page
308+
? "border-slate-900 bg-slate-900 text-white"
309+
: "border-slate-200 bg-white text-slate-700 hover:bg-slate-50"
310+
}`}
253311
>
254-
Invite
312+
{page}
255313
</button>
256-
<span className="text-slate-400 text-xs">Members</span>
257-
</div>
314+
)
258315
)}
316+
<button
317+
type="button"
318+
onClick={() =>
319+
setCurrentPage((p) => Math.min(totalPages, p + 1))
320+
}
321+
disabled={currentPage === totalPages}
322+
className="px-2 py-1 text-xs rounded-md border border-slate-200 bg-white text-slate-700 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-slate-50"
323+
>
324+
Next
325+
</button>
259326
</div>
260-
);
261-
})}
262-
</div>
327+
</div>
328+
)}
329+
</>
263330
)}
264331
</section>
265332
</div>

0 commit comments

Comments
 (0)