diff --git a/src/app/dashboard/invoices/_components/invoices-data-table.tsx b/src/app/dashboard/invoices/_components/invoices-data-table.tsx index fc86d8a..fe633ae 100644 --- a/src/app/dashboard/invoices/_components/invoices-data-table.tsx +++ b/src/app/dashboard/invoices/_components/invoices-data-table.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import type { ColumnDef } from "@tanstack/react-table"; @@ -7,7 +8,17 @@ import { Button } from "~/components/ui/button"; import { StatusBadge, type StatusType } from "~/components/data/status-badge"; import { PDFDownloadButton } from "~/app/dashboard/invoices/[id]/_components/pdf-download-button"; import { DataTable, DataTableColumnHeader } from "~/components/data/data-table"; -import { Eye, Edit } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog"; +import { Eye, Edit, Trash2 } from "lucide-react"; +import { api } from "~/trpc/react"; +import { toast } from "sonner"; // Type for invoice data interface Invoice { @@ -81,11 +92,37 @@ const formatCurrency = (amount: number) => { export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) { const router = useRouter(); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [invoiceToDelete, setInvoiceToDelete] = useState(null); + + const utils = api.useUtils(); + const deleteInvoice = api.invoices.delete.useMutation({ + onSuccess: () => { + toast.success("Invoice deleted successfully"); + void utils.invoices.getAll.invalidate(); + setDeleteDialogOpen(false); + setInvoiceToDelete(null); + }, + onError: (error) => { + toast.error(error.message ?? "Failed to delete invoice"); + }, + }); const handleRowClick = (invoice: Invoice) => { router.push(`/dashboard/invoices/${invoice.id}`); }; + const handleDelete = (invoice: Invoice) => { + setInvoiceToDelete(invoice); + setDeleteDialogOpen(true); + }; + + const confirmDelete = () => { + if (invoiceToDelete) { + deleteInvoice.mutate({ id: invoiceToDelete.id }); + } + }; + const columns: ColumnDef[] = [ { accessorKey: "client.name", @@ -191,6 +228,18 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) { + {invoice.items && invoice.client && (
@@ -216,13 +265,46 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) { ]; return ( - + <> + + + {/* Delete Confirmation Dialog */} + + + + Delete Invoice + + Are you sure you want to delete invoice{" "} + {invoiceToDelete?.invoiceNumber} for{" "} + {invoiceToDelete?.client?.name}? This action + cannot be undone. + + + + + + + + + ); } diff --git a/src/components/forms/invoice-form.tsx b/src/components/forms/invoice-form.tsx index 92d1fd2..e0a7e8c 100644 --- a/src/components/forms/invoice-form.tsx +++ b/src/components/forms/invoice-form.tsx @@ -24,7 +24,15 @@ import { FloatingActionBar } from "~/components/layout/floating-action-bar"; import { InvoiceLineItems } from "./invoice-line-items"; import { api } from "~/trpc/react"; import { toast } from "sonner"; -import { FileText, DollarSign, Check, Save, Clock } from "lucide-react"; +import { FileText, DollarSign, Check, Save, Clock, Trash2 } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog"; const STATUS_OPTIONS = [ { value: "draft", label: "Draft" }, @@ -108,6 +116,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { const [loading, setLoading] = useState(false); const [initialized, setInitialized] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); // Data queries const { data: clients, isLoading: loadingClients } = @@ -120,6 +129,17 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { { enabled: !!invoiceId && invoiceId !== "new" }, ); + // Delete mutation + const deleteInvoice = api.invoices.delete.useMutation({ + onSuccess: () => { + toast.success("Invoice deleted successfully"); + router.push("/dashboard/invoices"); + }, + onError: (error) => { + toast.error(error.message ?? "Failed to delete invoice"); + }, + }); + // Single initialization effect - only runs once when data is ready useEffect(() => { if (initialized) return; @@ -250,11 +270,13 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { if (idx === 0) return; setFormData((prev) => { const newItems = [...prev.items]; - if (newItems[idx] && newItems[idx - 1]) { - [newItems[idx - 1], newItems[idx]] = [ - newItems[idx]!, - newItems[idx - 1]!, - ]; + if (idx > 0 && idx < newItems.length) { + const currentItem = newItems[idx]; + const previousItem = newItems[idx - 1]; + if (currentItem && previousItem) { + newItems[idx - 1] = currentItem; + newItems[idx] = previousItem; + } } return { ...prev, items: newItems }; }); @@ -264,11 +286,13 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { if (idx === formData.items.length - 1) return; setFormData((prev) => { const newItems = [...prev.items]; - if (newItems[idx] && newItems[idx + 1]) { - [newItems[idx], newItems[idx + 1]] = [ - newItems[idx + 1]!, - newItems[idx]!, - ]; + if (idx >= 0 && idx < newItems.length - 1) { + const currentItem = newItems[idx]; + const nextItem = newItems[idx + 1]; + if (currentItem && nextItem) { + newItems[idx] = nextItem; + newItems[idx + 1] = currentItem; + } } return { ...prev, items: newItems }; }); @@ -392,6 +416,16 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { } }; + const handleDelete = () => { + setDeleteDialogOpen(true); + }; + + const confirmDelete = () => { + if (invoiceId && invoiceId !== "new") { + deleteInvoice.mutate({ id: invoiceId }); + } + }; + // Field update functions const updateField = ( field: K, @@ -419,11 +453,22 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) { } description={ invoiceId && invoiceId !== "new" - ? "Update invoice details" - : "Create a new invoice" + ? "Update invoice details and line items" + : "Create a new invoice for your client" } variant="gradient" > + {invoiceId && invoiceId !== "new" && ( + + )} + )} + + + + ); }