"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { api } from "~/trpc/react"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { StatusBadge, type StatusType } from "~/components/data/status-badge"; import { Separator } from "~/components/ui/separator"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { toast } from "sonner"; import { format } from "date-fns"; import { FileText, User, DollarSign, Trash2, Download, Send, Clock, MapPin, Mail, Phone, AlertCircle, } from "lucide-react"; import Link from "next/link"; import { generateInvoicePDF } from "~/lib/pdf-export"; import { Skeleton } from "~/components/ui/skeleton"; interface InvoiceViewProps { invoiceId: string; } const statusIconConfig = { draft: FileText, sent: Send, paid: DollarSign, overdue: 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") => { 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 (
The invoice you're looking for doesn't exist or has been deleted.
Professional Invoice
{formatDate(invoice.issueDate)}
{formatDate(invoice.dueDate)}
| Date | Description | Hours | Rate | Amount |
|---|---|---|---|---|
| {formatDate(item.date)} | {item.description} | {item.hours} | {formatCurrency(item.rate)} | {formatCurrency(item.amount)} |
{invoice.notes}
Invoice Paid
{invoice.items?.length ?? 0} item {invoice.items?.length !== 1 ? "s" : ""}