From 8d2e5f83592fea8b989a9c2fea1bfaa79d4f32be Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Thu, 24 Apr 2025 15:30:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20pagination=20to=20events=20pa?= =?UTF-8?q?ge=20(#1689)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../src/app/[locale]/(space)/events/page.tsx | 41 ++++++++-- .../api/get-scheduled-events.ts | 82 +++++++++++-------- 2 files changed, 82 insertions(+), 41 deletions(-) diff --git a/apps/web/src/app/[locale]/(space)/events/page.tsx b/apps/web/src/app/[locale]/(space)/events/page.tsx index 4b09e791f..4d9eb3214 100644 --- a/apps/web/src/app/[locale]/(space)/events/page.tsx +++ b/apps/web/src/app/[locale]/(space)/events/page.tsx @@ -16,6 +16,7 @@ import { EmptyStateIcon, EmptyStateTitle, } from "@/components/empty-state"; +import { Pagination } from "@/components/pagination"; import { StackedList, StackedListItem } from "@/components/stacked-list"; import { Trans } from "@/components/trans"; import { getScheduledEvents } from "@/features/scheduled-event/api/get-scheduled-events"; @@ -30,17 +31,22 @@ import { EventsTabbedView } from "./events-tabbed-view"; async function loadData({ status, search, + page = 1, + pageSize = 10, }: { status: Status; search?: string; + page?: number; + pageSize?: number; }) { const { userId } = await requireUser(); - const scheduledEvents = await getScheduledEvents({ + return getScheduledEvents({ userId, status, search, + page, + pageSize, }); - return scheduledEvents; } async function ScheduledEventEmptyState({ status }: { status: Status }) { @@ -98,11 +104,24 @@ export default async function Page({ searchParams, }: { params: Params; - searchParams?: { status: string; q?: string }; + searchParams?: { status: string; q?: string; page?: string }; }) { const { t } = await getTranslation(params.locale); const status = statusSchema.catch("upcoming").parse(searchParams?.status); - const scheduledEvents = await loadData({ status, search: searchParams?.q }); + const pageParam = searchParams?.page; + const page = pageParam ? Math.max(1, Number(pageParam) || 1) : 1; + const pageSize = 10; + + const { + events: paginatedEvents, + totalCount, + totalPages, + } = await loadData({ + status, + search: searchParams?.q, + page, + pageSize, + }); return ( @@ -127,12 +146,12 @@ export default async function Page({ })} />
- {scheduledEvents.length === 0 && ( + {paginatedEvents.length === 0 && ( )} - {scheduledEvents.length > 0 && ( + {paginatedEvents.length > 0 && ( - {scheduledEvents.map((event) => ( + {paginatedEvents.map((event) => ( )} + {totalPages > 1 && ( + + )}
diff --git a/apps/web/src/features/scheduled-event/api/get-scheduled-events.ts b/apps/web/src/features/scheduled-event/api/get-scheduled-events.ts index 1c77cf2a6..815d8ddf3 100644 --- a/apps/web/src/features/scheduled-event/api/get-scheduled-events.ts +++ b/apps/web/src/features/scheduled-event/api/get-scheduled-events.ts @@ -1,3 +1,4 @@ +import type { Prisma } from "@rallly/database"; import { prisma } from "@rallly/database"; import dayjs from "dayjs"; import timezone from "dayjs/plugin/timezone"; @@ -19,55 +20,65 @@ export async function getScheduledEvents({ userId, status, search, + page = 1, + pageSize = 10, }: { userId: string; status: Status; search?: string; + page?: number; + pageSize?: number; }) { const now = new Date(); - const rawEvents = await prisma.scheduledEvent.findMany({ - where: { - userId, - deletedAt: null, - ...(status != "past" && { start: { gte: now } }), - ...(status === "past" && { start: { lt: now } }), - ...(search && { title: { contains: search, mode: "insensitive" } }), - status: mapStatus[status], - }, - orderBy: { - start: status === "past" ? "desc" : "asc", - }, - select: { - id: true, - title: true, - description: true, - location: true, - start: true, - end: true, - allDay: true, - timeZone: true, - status: true, - invites: { - select: { - id: true, - inviteeName: true, - user: { - select: { - image: true, + const where: Prisma.ScheduledEventWhereInput = { + userId, + deletedAt: null, + ...(status != "past" && { start: { gte: now } }), + ...(status === "past" && { start: { lt: now } }), + ...(search && { title: { contains: search, mode: "insensitive" } }), + status: mapStatus[status], + }; + + const [rawEvents, totalCount] = await Promise.all([ + prisma.scheduledEvent.findMany({ + where, + orderBy: { + start: status === "past" ? "desc" : "asc", + }, + select: { + id: true, + title: true, + description: true, + location: true, + start: true, + end: true, + allDay: true, + timeZone: true, + status: true, + invites: { + select: { + id: true, + inviteeName: true, + user: { + select: { + image: true, + }, }, }, }, }, - }, - }); + skip: (page - 1) * pageSize, + take: pageSize, + }), + prisma.scheduledEvent.count({ where }), + ]); const events = rawEvents.map((event) => ({ ...event, status: event.status === "confirmed" - ? // If the event is confirmed, it's either past or upcoming - ((event.start < now ? "past" : "upcoming") as Status) + ? ((event.start < now ? "past" : "upcoming") as Status) : event.status, invites: event.invites.map((invite) => ({ id: invite.id, @@ -76,5 +87,8 @@ export async function getScheduledEvents({ })), })); - return events; + const totalPages = Math.ceil(totalCount / pageSize); + const hasNextPage = page * pageSize < totalCount; + + return { events, totalCount, totalPages, hasNextPage }; }