mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-13 01:24:44 -05:00
Convert invoice view to client component
This conversion enables client-side features like delete functionality with confirmation dialog and live data updates through React Query
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { Suspense } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { notFound, useRouter, useParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { api, HydrateClient } from "~/trpc/server";
|
||||
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";
|
||||
@@ -10,6 +12,15 @@ 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 {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
Building,
|
||||
@@ -21,14 +32,39 @@ import {
|
||||
User,
|
||||
AlertTriangle,
|
||||
Check,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
|
||||
interface InvoiceViewPageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
||||
const router = useRouter();
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
|
||||
async function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
||||
const invoice = await api.invoices.getById({ id: invoiceId });
|
||||
const { data: invoice, isLoading } = api.invoices.getById.useQuery({
|
||||
id: invoiceId,
|
||||
});
|
||||
|
||||
// Delete mutation
|
||||
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 handleDelete = () => {
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
deleteInvoice.mutate({ id: invoiceId });
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <InvoiceDetailsSkeleton />;
|
||||
}
|
||||
|
||||
if (!invoice) {
|
||||
notFound();
|
||||
@@ -368,22 +404,57 @@ async function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
||||
{invoice.status === "draft" && (
|
||||
<SendInvoiceButton invoiceId={invoice.id} className="w-full" />
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteInvoice.isPending}
|
||||
className="w-full text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Delete Invoice
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Invoice</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete invoice{" "}
|
||||
<strong>{invoice.invoiceNumber}</strong>? This action cannot be
|
||||
undone and will permanently remove the invoice and all its data.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setDeleteDialogOpen(false)}
|
||||
disabled={deleteInvoice.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={confirmDelete}
|
||||
disabled={deleteInvoice.isPending}
|
||||
>
|
||||
{deleteInvoice.isPending ? "Deleting..." : "Delete Invoice"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function InvoiceViewPage({ params }: InvoiceViewPageProps) {
|
||||
const { id } = await params;
|
||||
export default function InvoiceViewPage() {
|
||||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
|
||||
return (
|
||||
<HydrateClient>
|
||||
<Suspense fallback={<InvoiceDetailsSkeleton />}>
|
||||
<InvoiceViewContent invoiceId={id} />
|
||||
</Suspense>
|
||||
</HydrateClient>
|
||||
);
|
||||
return <InvoiceViewContent invoiceId={id} />;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
||||
});
|
||||
|
||||
const handleRowClick = (invoice: Invoice) => {
|
||||
router.push(`/dashboard/invoices/${invoice.id}`);
|
||||
router.push(`/dashboard/invoices/${invoice.id}/view`);
|
||||
};
|
||||
|
||||
const handleDelete = (invoice: Invoice) => {
|
||||
@@ -208,7 +208,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
||||
const invoice = row.original;
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
||||
<Link href={`/dashboard/invoices/${invoice.id}/view`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user