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:
Delba de Oliveira
2023-09-06 14:18:40 +01:00
committed by GitHub
parent 482f4bf042
commit 83293ab924
6 changed files with 88 additions and 16 deletions

View File

@@ -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>
);
}

View File

@@ -1,3 +1,3 @@
export default function Page() {
return <div>Individual Invoice Page</div>
return <div>Individual Invoice Page</div>;
}

View File

@@ -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" />;
}

View File

@@ -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");
}

View File

@@ -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"

View File

@@ -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">