From e6e24f12d452fa12d56dfe743dd03c3da511dd9c Mon Sep 17 00:00:00 2001 From: briannelson95 <71141646+briannelson95@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:33:23 -0400 Subject: [PATCH] pagination --- app/(auth)/dashboard/page.tsx | 4 +- app/(auth)/guest-book/page.tsx | 21 ++++++-- app/api/guestbook/paginate/route.ts | 24 +++++++++ components/EventInfoDisplay.tsx | 82 ++++++++++++++++++++++------- components/EventNotesEditor.tsx | 2 +- components/GuestBookPageClient.tsx | 35 +++++++++++- components/events/EventHeader.tsx | 13 +++++ lib/helper/getDaysUntilEvent.ts | 13 +++++ lib/queries.ts | 47 ++++++++++++++--- 9 files changed, 211 insertions(+), 30 deletions(-) create mode 100644 app/api/guestbook/paginate/route.ts create mode 100644 components/events/EventHeader.tsx create mode 100644 lib/helper/getDaysUntilEvent.ts diff --git a/app/(auth)/dashboard/page.tsx b/app/(auth)/dashboard/page.tsx index fc31f49..4844f25 100644 --- a/app/(auth)/dashboard/page.tsx +++ b/app/(auth)/dashboard/page.tsx @@ -8,7 +8,9 @@ import React from 'react' export default async function DashboardPage() { const events = await queries.fetchEvents(); - const guestBookEntries = await queries.fetchGuestBookEntries(5); + const guestBookData = await queries.fetchGuestBookEntries({ takeOnlyRecent: 5 }); + + const guestBookEntries = Array.isArray(guestBookData) ? guestBookData : guestBookData.entries; return (
diff --git a/app/(auth)/guest-book/page.tsx b/app/(auth)/guest-book/page.tsx index 9dc5905..8a2b4b2 100644 --- a/app/(auth)/guest-book/page.tsx +++ b/app/(auth)/guest-book/page.tsx @@ -3,11 +3,26 @@ import { queries } from '@/lib/queries' import { getServerSession } from 'next-auth' import GuestBookPageClient from '@/components/GuestBookPageClient' -export default async function GuestBookPage() { +export default async function GuestBookPage({ searchParams }: { searchParams: { page?: string } }) { const session = await getServerSession(authOptions) if (!session?.user) return

Unauthorized

- const entries = await queries.fetchGuestBookEntries() + const currentPage = Number(searchParams.page) || 1 - return + const guestBookData = await queries.fetchGuestBookEntries({ + page: currentPage, + pageSize: 10, + }) + + const { entries, totalPages, currentPage: verifiedPage } = !Array.isArray(guestBookData) + ? guestBookData + : { entries: guestBookData, totalPages: 1, currentPage: 1 } + + return ( + + ) } diff --git a/app/api/guestbook/paginate/route.ts b/app/api/guestbook/paginate/route.ts new file mode 100644 index 0000000..6afe7f2 --- /dev/null +++ b/app/api/guestbook/paginate/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from 'next/server' +import { prisma } from '@/lib/prisma' + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url) + const cursor = searchParams.get('cursor') ?? undefined + const take = parseInt(searchParams.get('take') || '10', 10) + + try { + const entries = await prisma.guestBookEntry.findMany({ + take, + skip: cursor ? 1 : 0, + ...(cursor && { cursor: { id: cursor } }), + orderBy: [{ lName: 'asc' }, { fName: 'asc' }], + }) + + const nextCursor = entries.length === take ? entries[entries.length - 1].id : null + + return NextResponse.json({ entries, nextCursor }) + } catch (error) { + console.error('[GET GUESTBOOK PAGINATE]', error) + return NextResponse.json({ message: 'Failed to fetch paginated entries' }, { status: 500 }) + } +} diff --git a/components/EventInfoDisplay.tsx b/components/EventInfoDisplay.tsx index 695755c..bb7fa5a 100644 --- a/components/EventInfoDisplay.tsx +++ b/components/EventInfoDisplay.tsx @@ -7,6 +7,8 @@ import AddGuestFromGuestBook from './AddGuestFromGuestBook' import EventNotesEditor from './EventNotesEditor' import ToDoList from './ToDoList' import { fetchEventTodos } from '@/lib/helper/fetchTodos' +import EventHeader from './events/EventHeader' +import { getDaysUntilEvent } from '@/lib/helper/getDaysUntilEvent' interface Creator { id: string @@ -48,6 +50,7 @@ export default function EventInfoDisplay({ event }: Props) { const [todos, setTodos] = useState(event.todos) const eventGuests = event.eventGuests + console.log(eventGuests) const [saving, setSaving] = useState(false) const [error, setError] = useState('') @@ -109,11 +112,26 @@ export default function EventInfoDisplay({ event }: Props) { return `${d.toLocaleDateString()} ${d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` } + const attendingGuests = eventGuests.filter((g) => g.rsvp === 'YES'); + const notAttendingGuests = eventGuests.filter((g) => g.rsvp === 'NO'); + const pendingGuests = eventGuests.filter((g) => g.rsvp === 'PENDING'); + + let daysLeft + + if (event.date !== null) { + daysLeft = getDaysUntilEvent(event.date); + } + return (
+ {/* */}
-

{event.name}

+

{event.name}

@@ -153,17 +171,45 @@ export default function EventInfoDisplay({ event }: Props) {

{event.location || 'N/A'}

)}
- - {/* Creator Email */} -
- + {/*
+

{event.creator.email}

-
- - {/* Created At */} -
- +

{formatDate(event.createdAt)}

+
*/} +
+
+

Guest Summary

+
+
+

Invited

+

{eventGuests.length}

+
+
+

Attending

+

{attendingGuests.length}

+
+
+

Declined

+

{notAttendingGuests.length}

+
+
+

No Response

+

{pendingGuests.length}

+
+
+
+
+

Countdown

+
+ {daysLeft && daysLeft > 0 + ? `${daysLeft} day${daysLeft !== 1 ? 's' : ''} until the event` + : daysLeft === 0 + ? 'Today is the big day!' + : `This event happened ${Math.abs(daysLeft)} day${Math.abs(daysLeft) !== 1 ? 's' : ''} ago` + } +
+
{error &&

{error}

} @@ -178,13 +224,6 @@ export default function EventInfoDisplay({ event }: Props) {
)} -
- -
@@ -245,6 +284,13 @@ export default function EventInfoDisplay({ event }: Props) { onUpdate={refreshTodos} />
+
+ +
) } diff --git a/components/EventNotesEditor.tsx b/components/EventNotesEditor.tsx index 562fe6f..95e2748 100644 --- a/components/EventNotesEditor.tsx +++ b/components/EventNotesEditor.tsx @@ -47,7 +47,7 @@ export default function EventNotesEditor({ eventId, initialNotes, canEdit }: Pro if (!canEdit) { return ( -
+

Event Notes

{notes || '_No notes yet._'} diff --git a/components/GuestBookPageClient.tsx b/components/GuestBookPageClient.tsx index ecd5395..2f5844b 100644 --- a/components/GuestBookPageClient.tsx +++ b/components/GuestBookPageClient.tsx @@ -6,6 +6,7 @@ import GuestBookList from '@/components/GuestBookList' import TableIcon from './icons/TableIcon' import GuestCardIcon from './icons/GuestCardIcon' import BulkUploadGuest from './BulkUploadGuest' +import { useRouter } from 'next/navigation' interface GuestBookEntry { id: string @@ -18,9 +19,22 @@ interface GuestBookEntry { notes?: string | null } -export default function GuestBookPageClient({ entries }: { entries: GuestBookEntry[] }) { +export default function GuestBookPageClient({ + entries, + totalPages, + currentPage, +}: { + entries: GuestBookEntry[] + totalPages: number + currentPage: number +}) { const [isOpen, setIsOpen] = useState(false); const [view, setView] = useState<'CARD' | 'TABLE'>('TABLE') + const router = useRouter() + + const handlePageChange = (page: number) => { + router.push(`/guest-book?page=${page}`) + } async function handleAddGuest(data: { fName: string @@ -71,6 +85,25 @@ export default function GuestBookPageClient({ entries }: { entries: GuestBookEnt + {totalPages > 1 && ( +
+ {[...Array(totalPages)].map((_, idx) => { + const pageNum = idx + 1 + return ( + + ) + })} +
+ )} + setIsOpen(false)} diff --git a/components/events/EventHeader.tsx b/components/events/EventHeader.tsx new file mode 100644 index 0000000..02fb5e7 --- /dev/null +++ b/components/events/EventHeader.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +export default function EventHeader({ name, date, location }: { name: string, date?: Date | null, location?: string | null }) { + return ( +
+

{name}

+
+

{date ? date?.toDateString() : "Upcoming"} | {location}

+ +
+
+ ) +} diff --git a/lib/helper/getDaysUntilEvent.ts b/lib/helper/getDaysUntilEvent.ts new file mode 100644 index 0000000..0212002 --- /dev/null +++ b/lib/helper/getDaysUntilEvent.ts @@ -0,0 +1,13 @@ +export function getDaysUntilEvent(eventDate: Date): number { + const today = new Date(); + const target = new Date(eventDate); + + // Clear time from both dates to ensure accurate full-day difference + today.setHours(0, 0, 0, 0); + target.setHours(0, 0, 0, 0); + + const diffInMs = target.getTime() - today.getTime(); + const diffInDays = Math.ceil(diffInMs / (1000 * 60 * 60 * 24)); + + return diffInDays; +} \ No newline at end of file diff --git a/lib/queries.ts b/lib/queries.ts index e0ee4e2..a3454a8 100644 --- a/lib/queries.ts +++ b/lib/queries.ts @@ -79,12 +79,47 @@ export const queries = { return event }, - async fetchGuestBookEntries(amount?: number) { - return await prisma.guestBookEntry.findMany({ - orderBy: amount - ? { createdAt: 'desc'} - : [{ lName: 'asc' }, { fName: 'asc' }], - ...(amount ? {take: amount} : {}) + async fetchGuestBookEntries({ + page, + pageSize = 10, + newestFirst = false, + takeOnlyRecent, + }: { + page?: number + pageSize?: number + newestFirst?: boolean + takeOnlyRecent?: number // Optional: Just get the latest N + }) { + // ⏱ Quick recent entries (e.g., homepage) + if (takeOnlyRecent) { + const entries = await prisma.guestBookEntry.findMany({ + take: takeOnlyRecent, + orderBy: { createdAt: 'desc' }, }) + return entries + } + + // 📄 Paginated GuestBook view + const skip = ((page ?? 1) - 1) * pageSize + + const [entries, totalCount] = await Promise.all([ + prisma.guestBookEntry.findMany({ + skip, + take: pageSize, + orderBy: newestFirst + ? { createdAt: 'desc' } + : [{ lName: 'asc' }, { fName: 'asc' }], + }), + prisma.guestBookEntry.count(), + ]) + + const totalPages = Math.ceil(totalCount / pageSize) + + return { + entries, + totalPages, + currentPage: page ?? 1, + } }, + } \ No newline at end of file