"use client"; import { DollarSign, Edit, Loader2, Trash2 } from "lucide-react"; import Link from "next/link"; import { notFound, useParams, useRouter } from "next/navigation"; import { useState, useEffect } from "react"; import { toast } from "sonner"; import { StatusBadge, type StatusType } from "~/components/data/status-badge"; import { PageHeader } from "~/components/layout/page-header"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { Separator } from "~/components/ui/separator"; import { getEffectiveInvoiceStatus, isInvoiceOverdue, } from "~/lib/invoice-status"; import { api } from "~/trpc/react"; import type { StoredInvoiceStatus } from "~/types/invoice"; import { InvoiceDetailsSkeleton } from "./_components/invoice-details-skeleton"; import { PDFDownloadButton } from "./_components/pdf-download-button"; import { EnhancedSendInvoiceButton } from "~/components/forms/enhanced-send-invoice-button"; import { AlertTriangle, Building, Check, FileText, Mail, MapPin, Phone, User, } from "lucide-react"; function InvoiceViewContent({ invoiceId }: { invoiceId: string }) { const router = useRouter(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const { data: invoice, isLoading } = api.invoices.getById.useQuery({ id: invoiceId, }); const utils = api.useUtils(); 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"); }, }); const updateStatus = api.invoices.updateStatus.useMutation({ onSuccess: (data) => { toast.success(data.message); void utils.invoices.getById.invalidate({ id: invoiceId }); }, onError: (error) => { toast.error(error.message ?? "Failed to update invoice status"); }, }); const handleDelete = () => { setDeleteDialogOpen(true); }; const handleMarkAsPaid = () => { updateStatus.mutate({ id: invoiceId, status: "paid" as StoredInvoiceStatus, }); }; const confirmDelete = () => { deleteInvoice.mutate({ id: invoiceId }); }; if (isLoading) { return ; } if (!invoice) { notFound(); } const formatDate = (date: Date) => { return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "short", day: "numeric", }).format(new Date(date)); }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(amount); }; const subtotal = invoice.items.reduce((sum, item) => sum + item.amount, 0); const taxAmount = (subtotal * invoice.taxRate) / 100; const total = subtotal + taxAmount; const effectiveStatus = getEffectiveInvoiceStatus( invoice.status as StoredInvoiceStatus, invoice.dueDate, ); const isOverdue = isInvoiceOverdue( invoice.status as StoredInvoiceStatus, invoice.dueDate, ); const getStatusType = (): StatusType => { return effectiveStatus as StatusType; }; return (
{/* Content */}
{/* Left Column */}
{/* Invoice Header */}

{invoice.invoiceNumber}

Issued {formatDate(invoice.issueDate)}
Due {formatDate(invoice.dueDate)}

Total Amount

{formatCurrency(total)}

{/* Overdue Alert */} {isOverdue && (

Invoice Overdue

{Math.ceil( (new Date().getTime() - new Date(invoice.dueDate).getTime()) / (1000 * 60 * 60 * 24), )}{" "} days past due date

)} {/* Client & Business Info */}
{/* 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.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}
)}
)}
{/* Business Information */} {invoice.business && ( From

{invoice.business.name}

{invoice.business.email && (
{invoice.business.email}
)} {invoice.business.phone && (
{invoice.business.phone}
)}
)}
{/* Invoice Items */} Invoice Items {invoice.items.map((item, _index) => (

{item.description}

{formatDate(item.date).replace(/ /g, "\u00A0")} {item.hours.toString().replace(/ /g, "\u00A0")}  hours @ ${item.rate}/hr

{formatCurrency(item.amount)}

))} {/* Totals */}
Subtotal: {formatCurrency(subtotal)}
{invoice.taxRate > 0 && (
Tax ({invoice.taxRate}%): {formatCurrency(taxAmount)}
)}
Total: {formatCurrency(total)}
{/* Notes */} {invoice.notes && ( Notes

{invoice.notes}

)}
{/* Right Column - Actions */}
Actions {invoice.items && invoice.client && ( )} {/* Send Invoice Button - Show for draft, sent, and overdue */} {effectiveStatus === "draft" && ( )} {(effectiveStatus === "sent" || effectiveStatus === "overdue") && ( )} {/* Manual Status Updates */} {(effectiveStatus === "sent" || effectiveStatus === "overdue") && ( )}
{/* Delete Confirmation Dialog */} Delete Invoice Are you sure you want to delete invoice{" "} {invoice.invoiceNumber}? This action cannot be undone and will permanently remove the invoice and all its data.
); } export default function InvoiceViewPage() { const params = useParams(); const router = useRouter(); const id = params.id as string; // Handle /invoices/new route - redirect to dedicated new page useEffect(() => { if (id === "new") { router.replace("/dashboard/invoices/new"); } }, [id, router]); // Don't render anything if we're redirecting if (id === "new") { return (
); } return ; }