mirror of
https://github.com/vercel/next-learn.git
synced 2026-06-24 13:15:48 +00:00
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:
committed by
GitHub
parent
340cd8dd05
commit
b85b3e217b
@@ -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 />;
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import DashboardOverview from "@/app/ui/dashboard-overview";
|
||||
import DashboardOverview from "@/app/ui/dashboard/overview";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
|
||||
6
dashboard/15-final/app/lib/actions.tsx
Normal file
6
dashboard/15-final/app/lib/actions.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
"use server";
|
||||
|
||||
export async function deleteInvoice(id: number) {
|
||||
// TO DO: Add delete invoice logic
|
||||
console.log("Delete invoice", id);
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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");
|
||||
@@ -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 (
|
||||
@@ -1,4 +1,4 @@
|
||||
import Search from "./search";
|
||||
import Search from "@/app/ui/dashboard/search";
|
||||
|
||||
export default function TopNav() {
|
||||
return (
|
||||
18
dashboard/15-final/app/ui/invoices/delete-invoice-button.tsx
Normal file
18
dashboard/15-final/app/ui/invoices/delete-invoice-button.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
132
dashboard/15-final/app/ui/invoices/table.tsx
Normal file
132
dashboard/15-final/app/ui/invoices/table.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user