Add delete invoice functionality (#140)

* Organize components

* Add delete invoice component and action

* Update customer emails

* Add delete button, pending/paid UI, and placeholder edit button

* Fix build errors

* More errors
This commit is contained in:
Delba de Oliveira
2023-09-05 16:30:42 +01:00
committed by GitHub
parent 340cd8dd05
commit b85b3e217b
18 changed files with 173 additions and 92 deletions

View File

@@ -1,4 +1,4 @@
import AddInvoiceForm from "@/app/ui/invoice-form";
import AddInvoiceForm from "@/app/ui/invoices/add-invoice-form";
export default function Page() {
return <AddInvoiceForm />;

View File

@@ -1,9 +1,9 @@
import Table from "@/app/ui/table";
import Table from "@/app/ui/invoices/table";
export default function Page() {
return (
<div>
<Table />
</div>
)
);
}

View File

@@ -1,5 +1,5 @@
import TopNav from "@/app/ui/dashboard-topnav";
import SideNav from "../ui/dashboard-sidenav";
import TopNav from "@/app/ui/dashboard/topnav";
import SideNav from "@/app/ui/dashboard/sidenav";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
@@ -13,4 +13,4 @@ export default function Layout({ children }: { children: React.ReactNode }) {
</div>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
import DashboardOverview from "@/app/ui/dashboard-overview";
import DashboardOverview from "@/app/ui/dashboard/overview";
export default function Page() {
return (

View File

@@ -0,0 +1,6 @@
"use server";
export async function deleteInvoice(id: number) {
// TO DO: Add delete invoice logic
console.log("Delete invoice", id);
}

View File

@@ -14,25 +14,25 @@ export const customers: Customer[] = [
{
id: 1,
name: "Ada Lovelace",
email: "ada@earlycomputing.com",
email: "ada@lovelace.com",
imageUrl: "/customers/ada-lovelace.png",
},
{
id: 2,
name: "Grace Hopper",
email: "grace@personalcomputers.com",
email: "grace@hopper.com",
imageUrl: "/customers/grace-hopper.png",
},
{
id: 3,
name: "Hedy Lammar",
email: "hedy@wifi.com",
email: "hedy@lammar.com",
imageUrl: "/customers/hedy-lammar.png",
},
{
id: 4,
name: "Margaret Hamilton",
email: "margaret@nasa.com",
email: "margaret@hamilton.com",
imageUrl: "/customers/margaret-hamilton.png",
},
];

View File

@@ -1,8 +1,8 @@
import Card from "@/app/ui/card";
import Card from "@/app/ui/dashboard/card";
import { invoices, customers, revenue } from "@/app/lib/dummy-data";
import { calculateInvoices } from "@/app/lib/calculations";
import RevenueChart from "@/app/ui/revenue-chart";
import LatestInvoices from "@/app/ui/latest-invoices";
import RevenueChart from "@/app/ui/dashboard/revenue-chart";
import LatestInvoices from "@/app/ui/dashboard/latest-invoices";
export default function DashboardOverview() {
const totalPaidInvoices = calculateInvoices(invoices, "paid");

View File

@@ -17,8 +17,8 @@ export default function SideNav() {
const tabs = [
{ name: "Home", href: "/dashboard", icon: HomeIcon },
{ name: "Customers", href: "/dashboard/customers", icon: UserGroupIcon },
{ name: "Invoices", href: "/dashboard/invoices", icon: InboxIcon },
{ name: "Customers", href: "/dashboard/customers", icon: UserGroupIcon },
];
return (

View File

@@ -1,4 +1,4 @@
import Search from "./search";
import Search from "@/app/ui/dashboard/search";
export default function TopNav() {
return (

View File

@@ -0,0 +1,18 @@
"use client";
import { TrashIcon } from "@heroicons/react/24/outline";
import { useTransition } from "react";
import { deleteInvoice } from "@/app/lib/actions";
export default function DeleteInvoice({ id }: { id: number }) {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => startTransition(() => deleteInvoice(id))}
className="rounded-md border p-1"
>
<TrashIcon className="w-4" />
</button>
);
}

View File

@@ -0,0 +1,132 @@
import { invoices, customers } from "@/app/lib/dummy-data";
import { Customer } from "@/app/lib/definitions";
import Link from "next/link";
import {
PencilSquareIcon,
ClockIcon,
CheckCircleIcon,
} from "@heroicons/react/24/outline";
import DeleteInvoice from "@/app/ui/invoices/delete-invoice-button";
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>
);
}
}
export default function Table() {
function getCustomerById(customerId: number): Customer | null {
const customer = customers.find((customer) => customer.id === customerId);
return customer ? customer : null;
}
return (
<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>
</div>
<div className="mt-8">
<div className="overflow-x-auto">
<div className="rounded-md border">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50 text-left text-sm">
<tr>
<th scope="col" className="px-3.5 py-3.5 sm:pl-6">
#
</th>
<th scope="col" className="px-3 py-3.5 font-semibold">
Customer
</th>
<th scope="col" className="px-3 py-3.5 font-semibold">
Email
</th>
<th scope="col" className="px-3 py-3.5 font-semibold">
Amount
</th>
<th scope="col" className="px-3 py-3.5 font-semibold">
Date
</th>
<th scope="col" className="px-3 py-3.5 font-semibold">
Status
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span className="sr-only">Edit</span>
</th>
{/* <th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span className="sr-only">View</span>
</th> */}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white text-gray-500">
{invoices.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}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
<div className="flex items-center gap-3">
<img
src={getCustomerById(invoice.customerId)?.imageUrl}
className="h-7 w-7 rounded-full"
/>
<p>{getCustomerById(invoice.customerId)?.name}</p>
</div>
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{getCustomerById(invoice.customerId)?.email}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{(invoice.amount / 100).toLocaleString("en-US", {
style: "currency",
currency: "USD",
})}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{invoice.date}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm">
{renderInvoiceStatus(invoice.status)}
</td>
<td className="flex justify-end gap-2 whitespace-nowrap py-4 pl-3 pr-6 text-sm">
<button className="rounded-md border p-1">
<PencilSquareIcon className="w-4" />
</button>
<DeleteInvoice id={invoice.id} />
</td>
{/* <td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<Link
href={`/dashboard/invoices/${invoice.id}`}
className="text-blue-600 hover:text-blue-900"
>
View<span className="sr-only">, {invoice.id}</span>
</Link>
</td> */}
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,75 +0,0 @@
import {invoices, customers} from "../lib/dummy-data";
import Link from "next/link";
export default function Example() {
function getNameById(customerId: number) {
const customerName = customers.find(customer => customer.id === customerId);
return customerName ? customerName.name : null;
}
return (
<div className="w-full">
<div className="w-full flex items-center justify-between">
<h1 className="text-base font-semibold text-gray-900">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>
</div>
<div className="mt-8">
<div className="overflow-x-auto">
<div className="border rounded-md">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
ID
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Customer Name
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Amount
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Status
</th>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Date
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span className="sr-only">View</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{invoices.map((invoice) => (
<tr key={invoice.id}>
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
{invoice.id}
</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{getNameById(invoice.customerId)}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{invoice.amount.toLocaleString("en-US", {style: "currency", currency: "USD"})}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{invoice.status}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{invoice.date}</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<Link href={`/dashboard/invoices/${invoice.id}`} className="text-blue-600 hover:text-blue-900">
View<span className="sr-only">, {invoice.id}</span>
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}

View File

@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};