"use client"; import { useState } from "react"; import { api } from "~/trpc/react"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Separator } from "~/components/ui/separator"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { format } from "date-fns"; import { Calendar, FileText, User, DollarSign, Trash2, Edit, Download, Send, ArrowLeft, Clock, MapPin, Mail, Phone, AlertCircle, } from "lucide-react"; import Link from "next/link"; import { generateInvoicePDF } from "~/lib/pdf-export"; import { InvoiceViewSkeleton } from "~/components/ui/skeleton"; interface InvoiceViewProps { invoiceId: string; } const statusConfig = { draft: { label: "Draft", color: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300", icon: FileText, }, sent: { label: "Sent", color: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400", icon: Send, }, paid: { label: "Paid", color: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", icon: DollarSign, }, overdue: { label: "Overdue", color: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400", icon: AlertCircle, }, } as const; export function InvoiceView({ invoiceId }: InvoiceViewProps) { const router = useRouter(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isExportingPDF, setIsExportingPDF] = useState(false); // Fetch invoice data const { data: invoice, isLoading, refetch, } = api.invoices.getById.useQuery({ id: invoiceId }); // Delete mutation const deleteInvoice = api.invoices.delete.useMutation({ onSuccess: () => { toast.success("Invoice deleted successfully"); setDeleteDialogOpen(false); router.push("/dashboard/invoices"); }, onError: (error) => { toast.error(error.message ?? "Failed to delete invoice"); }, }); // Update status mutation const updateStatus = api.invoices.updateStatus.useMutation({ onSuccess: () => { toast.success("Status updated successfully"); void refetch(); }, onError: (error) => { toast.error(error.message ?? "Failed to update status"); }, }); const handleDelete = () => { setDeleteDialogOpen(true); }; const confirmDelete = () => { deleteInvoice.mutate({ id: invoiceId }); }; const handleStatusUpdate = ( newStatus: "draft" | "sent" | "paid" | "overdue", ) => { updateStatus.mutate({ id: invoiceId, status: newStatus }); }; const handlePDFExport = async () => { if (!invoice) return; setIsExportingPDF(true); try { await generateInvoicePDF(invoice); toast.success("PDF exported successfully"); } catch (error) { console.error("PDF export error:", error); toast.error("Failed to export PDF. Please try again."); } finally { setIsExportingPDF(false); } }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(amount); }; const formatDate = (date: Date) => { return format(new Date(date), "MMM dd, yyyy"); }; const isOverdue = invoice && new Date(invoice.dueDate) < new Date() && invoice.status !== "paid"; if (isLoading) { return ; } if (!invoice) { return (

Invoice not found

The invoice you're looking for doesn't exist or has been deleted.

); } const StatusIcon = statusConfig[invoice.status as keyof typeof statusConfig].icon; return (
{/* Status Alert */} {isOverdue && (
This invoice is overdue
)}
{/* Main Content */}
{/* Invoice Header Card */}

{invoice.invoiceNumber}

Professional Invoice

Issue Date

{formatDate(invoice.issueDate)}

Due Date

{formatDate(invoice.dueDate)}

{ statusConfig[invoice.status as keyof typeof statusConfig] .label }
{formatCurrency(invoice.totalAmount)}
{/* Client Information */} Bill To

{invoice.client?.name}

{invoice.client?.email && (
{invoice.client.email}
)} {invoice.client?.phone && (
{invoice.client.phone}
)} {(invoice.client?.addressLine1 ?? invoice.client?.city ?? invoice.client?.state) && (
{invoice.client?.addressLine1 && (
{invoice.client.addressLine1}
)} {invoice.client?.addressLine2 && (
{invoice.client.addressLine2}
)} {(invoice.client?.city ?? invoice.client?.state ?? invoice.client?.postalCode) && (
{[ invoice.client?.city, invoice.client?.state, invoice.client?.postalCode, ] .filter(Boolean) .join(", ")}
)} {invoice.client?.country && (
{invoice.client.country}
)}
)}
{/* Invoice Items */} Invoice Items
{invoice.items?.map((item, index) => ( ))}
Date Description Hours Rate Amount
{formatDate(item.date)} {item.description} {item.hours} {formatCurrency(item.rate)} {formatCurrency(item.amount)}
{/* Notes */} {invoice.notes && ( Notes

{invoice.notes}

)}
{/* Sidebar */}
{/* Status Actions */} Status Actions {invoice.status === "draft" && ( )} {invoice.status === "sent" && ( )} {invoice.status === "overdue" && ( )} {invoice.status === "paid" && (

Invoice Paid

)}
{/* Invoice Summary */} Summary
Subtotal {formatCurrency(invoice.totalAmount)}
Tax $0.00
Total {formatCurrency(invoice.totalAmount)}

{invoice.items?.length ?? 0} item {invoice.items?.length !== 1 ? "s" : ""}

{/* Danger Zone */} Danger Zone
{/* Delete Confirmation Dialog */} Delete Invoice Are you sure you want to delete this invoice? This action cannot be undone and will permanently remove the invoice and all its data.
); }