From b8935abc32c711a482ad21deb2ad3753275770b4 Mon Sep 17 00:00:00 2001 From: Stephanie Dietz <49788645+StephDietz@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:37:23 -0500 Subject: [PATCH] add pagination and search bar to the invoice table (#146) * add pagination and search bar to the invoice table * make table component server * fix eslint * eslint * eslint * Updates pagination url to not use state or router and moves pagination component up above the table * fix eslint typing error * resolve github convos * update all 'q' to be 'query' --------- Co-authored-by: Michael Novotny --- .../15-final/app/dashboard/invoices/page.tsx | 11 ++- dashboard/15-final/app/lib/dummy-data.tsx | 49 +++++++++++++ .../15-final/app/ui/invoices/pagination.tsx | 71 +++++++++++++++++++ .../15-final/app/ui/invoices/table-search.tsx | 70 ++++++++++++++++++ dashboard/15-final/app/ui/invoices/table.tsx | 49 ++++++++++++- 5 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 dashboard/15-final/app/ui/invoices/pagination.tsx create mode 100644 dashboard/15-final/app/ui/invoices/table-search.tsx diff --git a/dashboard/15-final/app/dashboard/invoices/page.tsx b/dashboard/15-final/app/dashboard/invoices/page.tsx index 30cb6f7..d46e3cd 100644 --- a/dashboard/15-final/app/dashboard/invoices/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/page.tsx @@ -1,9 +1,16 @@ import InvoicesTable from '@/app/ui/invoices/table'; -export default function Page() { +export default function Page({ + searchParams, +}: { + searchParams: { + query: string; + page: string; + }; +}) { return (
- +
); } diff --git a/dashboard/15-final/app/lib/dummy-data.tsx b/dashboard/15-final/app/lib/dummy-data.tsx index 0b0f2ed..2ab62b2 100644 --- a/dashboard/15-final/app/lib/dummy-data.tsx +++ b/dashboard/15-final/app/lib/dummy-data.tsx @@ -94,6 +94,55 @@ export const invoices: Invoice[] = [ status: 'paid', date: '2023-06-01', }, + { + id: 9, + customerId: 3, + amount: 1250, + status: 'paid', + date: '2023-06-02', + }, + { + id: 10, + customerId: 1, + amount: 8945, + status: 'paid', + date: '2023-06-01', + }, + { + id: 11, + customerId: 2, + amount: 500, + status: 'paid', + date: '2023-08-01', + }, + { + id: 12, + customerId: 3, + amount: 8945, + status: 'paid', + date: '2023-06-01', + }, + { + id: 13, + customerId: 3, + amount: 8945, + status: 'paid', + date: '2023-06-01', + }, + { + id: 14, + customerId: 4, + amount: 8945, + status: 'paid', + date: '2023-10-01', + }, + { + id: 15, + customerId: 3, + amount: 1000, + status: 'paid', + date: '2022-06-12', + }, ]; export const revenue: Revenue[] = [ diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx new file mode 100644 index 0000000..2cd3416 --- /dev/null +++ b/dashboard/15-final/app/ui/invoices/pagination.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; +import { usePathname, useSearchParams } from 'next/navigation'; +import clsx from 'clsx'; +import Link from 'next/link'; + +export default function PaginationButtons({ + totalPages, + currentPage, +}: { + totalPages: number; + currentPage: number; +}) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1); + + const createPageUrl = (pageNumber: number) => { + const newSearchParams = new URLSearchParams(searchParams.toString()); + newSearchParams.set('page', pageNumber.toString()); + return `${pathname}?${newSearchParams.toString()}`; + }; + + const PreviousPageTag = currentPage === 1 ? 'p' : Link; + const NextPageTag = currentPage === totalPages ? 'p' : Link; + + return ( +
+ + + + {pageNumbers.map((page) => { + const PageTag = page === currentPage ? 'p' : Link; + return ( + + {page} + + ); + })} + + + +
+ ); +} diff --git a/dashboard/15-final/app/ui/invoices/table-search.tsx b/dashboard/15-final/app/ui/invoices/table-search.tsx new file mode 100644 index 0000000..3d2efd9 --- /dev/null +++ b/dashboard/15-final/app/ui/invoices/table-search.tsx @@ -0,0 +1,70 @@ +'use client'; + +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { useTransition } from 'react'; + +export default function TableSearch() { + const { replace } = useRouter(); + const searchParams = useSearchParams()!; + const pathname = usePathname(); + const [isPending, startTransition] = useTransition(); + + function handleSearch(term: string) { + const params = new URLSearchParams(searchParams); + if (term) { + params.set('query', term); + } else { + params.delete('query'); + } + + startTransition(() => { + replace(`${pathname}?${params.toString()}`); + }); + } + + return ( +
+ +
+ + handleSearch(e.target.value)} + className="absolute inset-0 w-full rounded-md border border-gray-300 bg-transparent p-2 pl-8 text-sm" + /> +
+ {isPending && } +
+ ); +} + +function LoadingIcon() { + return ( +
+ + + + +
+ ); +} diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx index ad8a472..0048b9f 100644 --- a/dashboard/15-final/app/ui/invoices/table.tsx +++ b/dashboard/15-final/app/ui/invoices/table.tsx @@ -8,6 +8,10 @@ import { CheckCircleIcon, } from '@heroicons/react/24/outline'; import DeleteInvoice from '@/app/ui/invoices/delete-invoice-button'; +import TableSearch from './table-search'; +import PaginationButtons from './pagination'; + +const ITEMS_PER_PAGE = 10; function renderInvoiceStatus(status: string) { if (status === 'pending') { @@ -27,12 +31,47 @@ function renderInvoiceStatus(status: string) { } } -export default function InvoicesTable() { +export default function InvoicesTable({ + searchParams, +}: { + searchParams: { + query: string; + page: string; + }; +}) { + const searchTerm = searchParams.query ?? ''; + const currentPage = parseInt(searchParams.page ?? '1'); + + const filteredInvoices = invoices.filter((invoice) => { + const customer = getCustomerById(invoice.customerId); + + const invoiceMatches = Object.values(invoice).some( + (value) => + value?.toString().toLowerCase().includes(searchTerm.toLowerCase()), + ); + + const customerMatches = + customer && + Object.values(customer).some( + (value) => + value?.toString().toLowerCase().includes(searchTerm.toLowerCase()), + ); + + return invoiceMatches || customerMatches; + }); + + const paginatedInvoices = filteredInvoices.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE, + ); + function getCustomerById(customerId: number): Customer | null { const customer = customers.find((customer) => customer.id === customerId); return customer ? customer : null; } + const totalPages = Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE); + return (
@@ -44,7 +83,11 @@ export default function InvoicesTable() { Add Invoice
-
+
+ + +
+
@@ -75,7 +118,7 @@ export default function InvoicesTable() { - {invoices.map((invoice) => ( + {paginatedInvoices.map((invoice) => (
{invoice.id}