mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-15 10:34:43 -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";
|
"use client";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { notFound, useRouter, useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
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 { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { StatusBadge, type StatusType } from "~/components/data/status-badge";
|
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 { PDFDownloadButton } from "../_components/pdf-download-button";
|
||||||
import { SendInvoiceButton } from "../_components/send-invoice-button";
|
import { SendInvoiceButton } from "../_components/send-invoice-button";
|
||||||
import { InvoiceDetailsSkeleton } from "../_components/invoice-details-skeleton";
|
import { InvoiceDetailsSkeleton } from "../_components/invoice-details-skeleton";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "~/components/ui/dialog";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Building,
|
Building,
|
||||||
@@ -21,14 +32,39 @@ import {
|
|||||||
User,
|
User,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Check,
|
Check,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
interface InvoiceViewPageProps {
|
function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
||||||
params: Promise<{ id: string }>;
|
const router = useRouter();
|
||||||
}
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
async function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
const { data: invoice, isLoading } = api.invoices.getById.useQuery({
|
||||||
const invoice = await api.invoices.getById({ id: invoiceId });
|
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) {
|
if (!invoice) {
|
||||||
notFound();
|
notFound();
|
||||||
@@ -368,22 +404,57 @@ async function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
|||||||
{invoice.status === "draft" && (
|
{invoice.status === "draft" && (
|
||||||
<SendInvoiceButton invoiceId={invoice.id} className="w-full" />
|
<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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function InvoiceViewPage({ params }: InvoiceViewPageProps) {
|
export default function InvoiceViewPage() {
|
||||||
const { id } = await params;
|
const params = useParams();
|
||||||
|
const id = params.id as string;
|
||||||
|
|
||||||
return (
|
return <InvoiceViewContent invoiceId={id} />;
|
||||||
<HydrateClient>
|
|
||||||
<Suspense fallback={<InvoiceDetailsSkeleton />}>
|
|
||||||
<InvoiceViewContent invoiceId={id} />
|
|
||||||
</Suspense>
|
|
||||||
</HydrateClient>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleRowClick = (invoice: Invoice) => {
|
const handleRowClick = (invoice: Invoice) => {
|
||||||
router.push(`/dashboard/invoices/${invoice.id}`);
|
router.push(`/dashboard/invoices/${invoice.id}/view`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (invoice: Invoice) => {
|
const handleDelete = (invoice: Invoice) => {
|
||||||
@@ -208,7 +208,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
const invoice = row.original;
|
const invoice = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="flex items-center justify-end gap-1">
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
<Link href={`/dashboard/invoices/${invoice.id}/view`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user