mirror of
https://github.com/vercel/next-learn.git
synced 2026-06-11 09:51:47 +00:00
Add "edit" invoice functionality (#144)
* 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 * Fix table corners * Rename add invoice form, and make it generic for edit or add * Wire it up! * Add server action placeholder * Fix TS errors * Misc
This commit is contained in:
committed by
GitHub
parent
482f4bf042
commit
83293ab924
@@ -1,3 +1,18 @@
|
||||
export default function Page() {
|
||||
return <div>Edit Invoice Page</div>
|
||||
import InvoiceForm from "@/app/ui/invoices/form";
|
||||
import { invoices } from "@/app/lib/dummy-data";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default function Page({ params }: { params: { id: string } }) {
|
||||
const id = params.id ? parseInt(params.id) : null;
|
||||
const invoice = invoices.find((invoice) => invoice.id === id);
|
||||
|
||||
if (!invoice) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InvoiceForm type="edit" invoice={invoice} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function Page() {
|
||||
return <div>Individual Invoice Page</div>
|
||||
return <div>Individual Invoice Page</div>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AddInvoiceForm from "@/app/ui/invoices/add-invoice-form";
|
||||
import InvoiceForm from "@/app/ui/invoices/form";
|
||||
|
||||
export default function Page() {
|
||||
return <AddInvoiceForm />;
|
||||
return <InvoiceForm type="new" />;
|
||||
}
|
||||
|
||||
@@ -4,3 +4,8 @@ export async function deleteInvoice(id: number) {
|
||||
// TO DO: Add delete invoice logic
|
||||
console.log("Delete invoice", id);
|
||||
}
|
||||
|
||||
export async function addOrUpdateInvoice(formData: FormData) {
|
||||
// TO DO: Add create/update invoice logic
|
||||
console.log("Edit Invoice");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { Invoice } from "@/app/lib/definitions";
|
||||
import { customers } from "@/app/lib/dummy-data";
|
||||
import { useState, FormEvent } from "react";
|
||||
|
||||
export default function AddInvoiceForm() {
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(0);
|
||||
const [amount, setAmount] = useState("");
|
||||
// import { addOrUpdateInvoice } from "@/app/lib/actions";
|
||||
// export const dynamic = "force-dynamic";
|
||||
|
||||
export default function InvoiceForm({
|
||||
type,
|
||||
invoice,
|
||||
}: {
|
||||
type: "new" | "edit";
|
||||
invoice?: Invoice;
|
||||
}) {
|
||||
// TO DO: Replace state and handleSubmit with a Server Action
|
||||
const customer = customers.find(
|
||||
(customer) => customer.id === invoice?.customerId,
|
||||
);
|
||||
const initialCustomer = customer ? customer.id : 0;
|
||||
const initialAmount = invoice?.amount ? invoice.amount / 100 : 0;
|
||||
const initialStatus = invoice?.status || "pending";
|
||||
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(initialCustomer);
|
||||
const [amount, setAmount] = useState<number>(initialAmount);
|
||||
const [status, setStatus] = useState<"pending" | "paid">(initialStatus);
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -13,11 +32,11 @@ export default function AddInvoiceForm() {
|
||||
if (selectedCustomer && amount) {
|
||||
const newInvoice: Invoice = {
|
||||
customerId: selectedCustomer,
|
||||
amount: parseInt(amount) * 100, // Convert to cents
|
||||
amount: amount * 100, // Convert to cents
|
||||
|
||||
// These would be generated on the server
|
||||
id: 1, // Record ID will be automatically incremented
|
||||
status: "pending", // Default status for a new invoice
|
||||
status: status, // Default status for a new invoice
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
};
|
||||
|
||||
@@ -28,7 +47,9 @@ export default function AddInvoiceForm() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md p-4">
|
||||
<h2 className="mb-6 text-xl font-semibold text-gray-900">New Invoice</h2>
|
||||
<h2 className="mb-6 text-xl font-semibold text-gray-900">
|
||||
{type === "new" ? "New Invoice" : "Edit Invoice"}
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-4">
|
||||
<label
|
||||
@@ -37,10 +58,12 @@ export default function AddInvoiceForm() {
|
||||
>
|
||||
Customer
|
||||
</label>
|
||||
|
||||
<select
|
||||
id="customer"
|
||||
onChange={(e) => setSelectedCustomer(Number(e.target.value))}
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-3 text-sm ring-1 ring-inset ring-gray-200 placeholder:text-gray-200"
|
||||
value={selectedCustomer}
|
||||
>
|
||||
{customers.map((customer) => (
|
||||
<option key={customer.id} value={customer.id}>
|
||||
@@ -60,12 +83,38 @@ export default function AddInvoiceForm() {
|
||||
type="number"
|
||||
value={amount}
|
||||
placeholder="00.00"
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
onChange={(e) => setAmount(Number(e.target.value))}
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-7 text-sm leading-6 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{type === "edit" ? (
|
||||
<div className="mb-4">
|
||||
<label
|
||||
className="mb-2 block text-sm font-semibold"
|
||||
htmlFor="status"
|
||||
>
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
id="status"
|
||||
className="block w-full rounded-md border-0 py-1.5 pl-3 text-sm ring-1 ring-inset ring-gray-200 placeholder:text-gray-200"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
if (value === "paid" || value === "pending") {
|
||||
setStatus(value);
|
||||
}
|
||||
}}
|
||||
value={status}
|
||||
>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="paid">Paid</option>
|
||||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-md bg-blue-500 px-4 py-2 text-center text-sm font-semibold text-white hover:bg-blue-600"
|
||||
@@ -46,7 +46,7 @@ export default function InvoicesTable() {
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="rounded-md border">
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<thead className="bg-gray-50 text-left text-sm">
|
||||
<tr>
|
||||
@@ -77,7 +77,7 @@ export default function InvoicesTable() {
|
||||
</th> */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 bg-white text-gray-500">
|
||||
<tbody className="divide-y divide-gray-200 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">
|
||||
@@ -108,9 +108,12 @@ export default function InvoicesTable() {
|
||||
{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">
|
||||
<Link
|
||||
href={`/dashboard/invoices/${invoice.id}/edit`}
|
||||
className="rounded-md border p-1"
|
||||
>
|
||||
<PencilSquareIcon className="w-4" />
|
||||
</button>
|
||||
</Link>
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user