Simplify invoices table.tsx component (#166)

* Rename file and add data fetches for overview page

* Rename calculations.ts to utils.ts

* Update code to match course

* Add temporary calculation

* Fix error

* Move types to data fetching file

* Add error handling

* Parallelize data fetches

* Add skeletons

* Add delayed data request

* Fix ts errors

* Code for chapter 8

* Fix error

* Clean up

* Move formatDateToLocal to utils file

* Extract add invoice button

* Extract edit invoice button

* Extract InvoiceStatus

* Use formatCurrency from utils

* Misc

* Use outline icon

* outline!

* Rename table-search to search
This commit is contained in:
Delba de Oliveira
2023-09-19 19:24:38 +01:00
committed by GitHub
parent 24bcb816e5
commit 2897b290d3
6 changed files with 78 additions and 59 deletions

View File

@@ -1,4 +1,4 @@
import { Invoice, Revenue, Customer, LatestInvoice } from './definitions';
import { Invoice, Revenue, Customer } from './definitions';
export const formatCurrency = (amount: number) => {
return (amount / 100).toLocaleString('en-US', {
@@ -7,6 +7,20 @@ export const formatCurrency = (amount: number) => {
});
};
export const formatDateToLocal = (
dateStr: string,
locale: string = 'en-US',
) => {
const date = new Date(dateStr);
const options: Intl.DateTimeFormatOptions = {
day: 'numeric',
month: 'short',
year: 'numeric',
};
const formatter = new Intl.DateTimeFormat(locale, options);
return formatter.format(date);
};
export function findLatestInvoices(invoices: Invoice[], customers: Customer[]) {
// Sort the invoices by date in descending order and take the top 5
const latestInvoices = [...invoices]

View File

@@ -0,0 +1,11 @@
import Link from 'next/link';
export default function AddInvoice() {
return (
<Link
href="/dashboard/invoices/create"
className="block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Add Invoice
</Link>
);
}

View File

@@ -0,0 +1,12 @@
import { PencilSquareIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
export default function EditInvoice({ id }: { id: number }) {
return (
<Link
href={`/dashboard/invoices/${id}/edit`}
className="rounded-md border p-1"
>
<PencilSquareIcon className="w-4" />
</Link>
);
}

View File

@@ -3,7 +3,7 @@
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { usePathname, useRouter } from 'next/navigation';
export default function TableSearch({
export default function Search({
searchParams,
}: {
searchParams: { query: string; page: string };

View File

@@ -0,0 +1,27 @@
import { CheckCircleIcon, ClockIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
export default function InvoiceStatus({ status }: { status: string }) {
return (
<span
className={clsx(
'inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset',
{
'bg-red-50 text-red-700 ring-red-600/20': status === 'pending',
'bg-green-50 text-green-700 ring-green-600/20': status === 'paid',
},
)}
>
{status === 'pending' ? (
<>
<ClockIcon className="mr-1 w-4 text-red-700" /> Pending
</>
) : null}
{status === 'paid' ? (
<>
<CheckCircleIcon className="mr-1 w-4 text-green-700" /> Paid
</>
) : null}
</span>
);
}

View File

@@ -1,48 +1,15 @@
import Link from 'next/link';
import Image from 'next/image';
import {
PencilSquareIcon,
ClockIcon,
CheckCircleIcon,
} from '@heroicons/react/24/outline';
import DeleteInvoice from '@/app/ui/invoices/delete-button';
import TableSearch from './table-search';
import PaginationButtons from './pagination';
import EditInvoice from '@/app/ui/invoices/edit-button';
import AddInvoice from '@/app/ui/invoices/add-button';
import Search from '@/app/ui/invoices/search';
import PaginationButtons from '@/app/ui/invoices/pagination';
import InvoiceStatus from '@/app/ui/invoices/status';
import {
fetchFilteredInvoices,
fetchInvoiceCountBySearchTerm,
} from '@/app/lib/data';
const ITEMS_PER_PAGE = 10;
function renderInvoiceStatus(status: string) {
if (status === 'pending') {
return (
<span className="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20">
<ClockIcon className="mr-1 w-4 text-red-700" />
Pending
</span>
);
} else {
return (
<span className="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
<CheckCircleIcon className="mr-1 w-4 text-green-700" />
Paid
</span>
);
}
}
function formatDateToLocal(dateStr: string, locale: string = 'en-US') {
const date = new Date(dateStr);
const options: Intl.DateTimeFormatOptions = {
day: 'numeric',
month: 'short',
year: 'numeric',
};
const formatter = new Intl.DateTimeFormat(locale, options);
return formatter.format(date);
}
import { formatDateToLocal, formatCurrency } from '@/app/lib/utils';
export default async function InvoicesTable({
searchParams,
@@ -52,6 +19,7 @@ export default async function InvoicesTable({
page: string;
};
}) {
const ITEMS_PER_PAGE = 10;
const searchTerm = searchParams.query ?? '';
let currentPage = 1;
if (!searchTerm) {
@@ -71,15 +39,10 @@ export default async function InvoicesTable({
<div className="w-full">
<div className="flex w-full items-center justify-between">
<h1 className="text-base font-semibold">Invoices</h1>
<Link
href="/dashboard/invoices/create"
className="block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Add Invoice
</Link>
<AddInvoice />
</div>
<div className="mt-8 flex items-center justify-between gap-2">
<TableSearch searchParams={searchParams} />
<Search searchParams={searchParams} />
<PaginationButtons
searchParams={searchParams}
totalPages={totalPages}
@@ -141,24 +104,16 @@ export default async function InvoicesTable({
{invoice.customer_email}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{(invoice.amount / 100).toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
})}
{formatCurrency(invoice.amount)}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{formatDateToLocal(invoice.date)}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{renderInvoiceStatus(invoice.status)}
<InvoiceStatus status={invoice.status} />
</td>
<td className="flex justify-end gap-2 whitespace-nowrap py-4 pl-3 pr-6 text-sm">
<Link
href={`/dashboard/invoices/${invoice.id}/edit`}
className="rounded-md border p-1"
>
<PencilSquareIcon className="w-4" />
</Link>
<EditInvoice id={invoice.id} />
<DeleteInvoice id={invoice.id} />
</td>
</tr>