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}
|