Skip to content

Commit

Permalink
Merge branch 'main' into example-4ygf
Browse files Browse the repository at this point in the history
  • Loading branch information
delbaoliveira committed Sep 7, 2023
2 parents b349a7d + b8935ab commit 8df1675
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 11 deletions.
2 changes: 1 addition & 1 deletion basics/assets-metadata-css-starter/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function Home() {
</a>

<a
href="https://github.com/vercel/next.js/tree/master/examples"
href="https://github.com/vercel/next.js/tree/canary/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
Expand Down
6 changes: 4 additions & 2 deletions basics/errors/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
If you see an installation error for the following installation command:

```bash
npx create-next-app nextjs-blog --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
npx create-next-app nextjs-blog --example "https://github.com/vercel/next-learn/tree/main/basics/learn-starter"
```

Try removing everything after `nextjs-blog`:
Expand All @@ -14,4 +14,6 @@ Try removing everything after `nextjs-blog`:
npx create-next-app nextjs-blog
```

If that doesn’t work either, please let us know in a [GitHub Issue](https://github.com/vercel/next-learn/issues) with the error text, your OS, and Node.js version (make sure your Node.js version 18 or higher).
A `Could not locate the repository` error message could be the result of your workplace or school network or proxy configuration. A temporary solution may be changing your network environment by disconnecting from your workplace or school VPN, using a VPN browser extension, or trying a different wifi connection.

If none of the steps above resolve your issue, please let us know in a [GitHub Issue](https://github.com/vercel/next-learn/issues) with the error text, your OS, and Node.js version (make sure your Node.js version 18 or higher).
2 changes: 1 addition & 1 deletion basics/learn-starter/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function Home() {
</a>

<a
href="https://github.com/vercel/next.js/tree/master/examples"
href="https://github.com/vercel/next.js/tree/canary/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
Expand Down
2 changes: 1 addition & 1 deletion basics/navigate-between-pages-starter/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function Home() {
</a>

<a
href="https://github.com/vercel/next.js/tree/master/examples"
href="https://github.com/vercel/next.js/tree/canary/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
Expand Down
11 changes: 9 additions & 2 deletions dashboard/15-final/app/dashboard/invoices/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<InvoicesTable />
<InvoicesTable searchParams={searchParams} />
</div>
);
}
51 changes: 50 additions & 1 deletion dashboard/15-final/app/lib/dummy-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,56 @@ export const invoices: Invoice[] = [
amount: 32545,
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[] = [
Expand Down
71 changes: 71 additions & 0 deletions dashboard/15-final/app/ui/invoices/pagination.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex items-center justify-end">
<PreviousPageTag
href={createPageUrl(currentPage - 1)}
className={clsx(
'flex h-8 w-8 items-center justify-center rounded-l-md border border-gray-300',
{
'text-gray-300': currentPage === 1,
},
)}
>
<ChevronLeftIcon className="w-4" />
</PreviousPageTag>
{pageNumbers.map((page) => {
const PageTag = page === currentPage ? 'p' : Link;
return (
<PageTag
key={page}
href={createPageUrl(page)}
className={clsx(
'flex h-8 w-8 items-center justify-center border-y border-r border-gray-300 text-sm',
{
'border-blue-600 bg-blue-600 text-white': currentPage === page,
},
)}
>
{page}
</PageTag>
);
})}
<NextPageTag
href={createPageUrl(currentPage + 1)}
className={clsx(
'flex h-8 w-8 items-center justify-center rounded-r-md border border-l-0 border-gray-300',
{
'text-gray-300': currentPage === totalPages,
},
)}
>
<ChevronRightIcon className="w-4" />
</NextPageTag>
</div>
);
}
70 changes: 70 additions & 0 deletions dashboard/15-final/app/ui/invoices/table-search.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="relative max-w-md flex-grow">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative flex items-center px-2 py-2">
<MagnifyingGlassIcon className="h-5 text-gray-400" />
<input
type="text"
placeholder="Search..."
onChange={(e) => handleSearch(e.target.value)}
className="absolute inset-0 w-full rounded-md border border-gray-300 bg-transparent p-2 pl-8 text-sm"
/>
</div>
{isPending && <LoadingIcon />}
</div>
);
}

function LoadingIcon() {
return (
<div className="absolute bottom-0 right-0 top-0 flex items-center justify-center">
<svg
className="-ml-1 mr-3 h-5 w-5 animate-spin text-gray-700"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
);
}
49 changes: 46 additions & 3 deletions dashboard/15-final/app/ui/invoices/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -38,12 +42,47 @@ function formatDateToLocal(dateStr: string, locale: string = 'en-US') {
return formatter.format(date);
}

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 (
<div className="w-full">
<div className="flex w-full items-center justify-between">
Expand All @@ -55,7 +94,11 @@ export default function InvoicesTable() {
Add Invoice
</Link>
</div>
<div className="mt-8">
<div className="mt-8 flex items-center justify-between">
<TableSearch />
<PaginationButtons totalPages={totalPages} currentPage={currentPage} />
</div>
<div className="mt-4">
<div className="overflow-x-auto">
<div className="overflow-hidden rounded-md border">
<table className="min-w-full divide-y divide-gray-300">
Expand Down Expand Up @@ -86,7 +129,7 @@ export default function InvoicesTable() {
</tr>
</thead>
<tbody className="divide-y divide-gray-200 text-gray-500">
{invoices.map((invoice) => (
{paginatedInvoices.map((invoice) => (
<tr key={invoice.id}>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-black sm:pl-6">
{invoice.id}
Expand Down

0 comments on commit 8df1675

Please sign in to comment.