"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.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.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 */}
);
}
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 ;
}