import { Suspense } from "react"; import { notFound } from "next/navigation"; import Link from "next/link"; import { api, HydrateClient } from "~/trpc/server"; 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 { PageHeader } from "~/components/layout/page-header"; import { PDFDownloadButton } from "../_components/pdf-download-button"; import { SendInvoiceButton } from "../_components/send-invoice-button"; import { InvoiceDetailsSkeleton } from "../_components/invoice-details-skeleton"; import { Building, Edit, FileText, Mail, MapPin, Phone, User, AlertTriangle, Check, } from "lucide-react"; interface InvoiceViewPageProps { params: Promise<{ id: string }>; } async function InvoiceViewContent({ invoiceId }: { invoiceId: string }) { const invoice = await api.invoices.getById({ id: invoiceId }); 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 isOverdue = new Date(invoice.dueDate) < new Date() && invoice.status !== "paid"; const getStatusType = (): StatusType => { if (invoice.status === "paid") return "paid"; if (invoice.status === "draft") return "draft"; if (invoice.status === "overdue") return "overdue"; if (invoice.status === "sent") { return isOverdue ? "overdue" : "sent"; } return "draft"; }; 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) => (

{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 && ( )} {invoice.status === "draft" && ( )}
); } export default async function InvoiceViewPage({ params }: InvoiceViewPageProps) { const { id } = await params; return ( }> ); }