Data table
tsxa year ago
"use client";
import React from "react";
import { DataTable } from "@/components/data-table";
import { columns } from "./columns";
import { FinancialCategory } from "@prisma/client";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { bulkDeleteFinancialCategories } from "@/actions/financial-categories";
const ClientTable = ({
data,
error,
}: {
data?: FinancialCategory[];
error?: string;
}) => {
const router = useRouter();
const [loading, setLoading] = React.useState(false);
return (
<div>
<DataTable
columns={columns}
data={error ? [] : data || []}
onDelete={(row) => {
setLoading(true);
const ids = row.map((r) => r.original.id);
bulkDeleteFinancialCategories(ids)
.then((res) => {
if (res) {
if (res.error) {
toast.error(res.error);
} else {
toast.success(res.success);
router.refresh();
}
}
})
.catch(() => {
toast.error("An error occurred");
})
.finally(() => {
toast.dismiss();
setLoading(false);
});
}}
disabled={loading}
/>
</div>
);
};
export default ClientTable;Implementation
tsxa year ago
const [ConfirmDialog, confirm] = useConfirm(
"Are you sure you want to delete this Category?",
"You are about to delete an Category. This action cannot be undone."
);ConfrimDialog
tsxa year ago
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export const useConfirm = (
title: string,
description: string
): [() => JSX.Element, () => Promise<unknown>] => {
const [promise, setPromise] = useState<{
resolve: (value: boolean) => void;
} | null>(null);
const confirm = () =>
new Promise((resolve) => {
setPromise({ resolve });
});
const handleClose = () => {
setPromise(null);
};
const handleConfirm = () => {
promise?.resolve(true);
handleClose();
};
const handleCancel = () => {
promise?.resolve(false);
handleClose();
};
const ConfirmDialog = () => (
<Dialog open={promise !== null} onOpenChange={handleClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter className="pt-2">
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button onClick={handleConfirm} variant={"destructive"}>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
return [ConfirmDialog, confirm];
};Dialog Component
tsxa year ago
"use client";
import * as React from "react";
import {
ColumnDef,
flexRender,
SortingState,
getCoreRowModel,
useReactTable,
getPaginationRowModel,
getSortedRowModel,
Row,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Trash } from "lucide-react";
import { useConfirm } from "@/hooks/use-confirm";
import UploadBtn from "./csv-upload-btn";
import PlaidLinkBankButton from "./plaid-bank-button";
import { Premium, User } from "@prisma/client";
import ScanTransactionReceiptsButton from "./scan-transaction-receipt";
interface ExtendedUser extends User {
Premium: Premium[];
}
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
onDelete: (rows: Row<TData>[]) => void;
disabled?: boolean;
onUpload?: (results: any) => void;
user?: ExtendedUser | null;
onScan?: () => void;
}
export function DataTable<TData, TValue>({
columns,
data,
onDelete,
disabled = false,
onUpload,
user,
onScan,
}: DataTableProps<TData, TValue>) {
const [ConfirmDialog, confirm] = useConfirm(
"Are you sure you want to delete these rows?",
"You are about to delete some rows. This action cannot be undone."
);
const pathname = usePathname();
const { replace } = useRouter();
const searchParams = useSearchParams();
const [sorting, setSorting] = React.useState<SortingState>([]);
const [rowSelection, setRowSelection] = React.useState({});
const [input, setInput] = React.useState<string>("");
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onRowSelectionChange: setRowSelection,
state: {
sorting,
rowSelection,
},
});
React.useEffect(() => {
const debouncedFn = () => {
const params = new URLSearchParams(searchParams);
if (input) {
params.set("search", input);
} else {
params.delete("search");
}
replace(`${pathname}?${params.toString()}`);
};
const debounced = setTimeout(debouncedFn, 150);
return () => clearTimeout(debounced);
}, [input, searchParams, replace, pathname]);
return (
<div>
<ConfirmDialog />
<div className="mb-4 flex justify-between flex-wrap gap-2">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
type="text"
placeholder="Search"
className="max-w-xs"
/>
<div className="flex gap-2 items-center">
{table.getFilteredSelectedRowModel().rows.length > 0 && (
<Button
size={"sm"}
variant="destructive"
disabled={disabled}
onClick={async () => {
const ok = await confirm();
if (ok) {
onDelete(table.getFilteredSelectedRowModel().rows);
table.setRowSelection({});
}
}}
>
<Trash className="h-4 w-4 mr-2" />
Delete ({table.getFilteredSelectedRowModel().rows.length})
</Button>
)}
<div className="flex gap-2 items-center w-full">
{onUpload && <UploadBtn onUpload={onUpload} />}
{onScan && <ScanTransactionReceiptsButton onScan={onScan} />}
{user && <PlaidLinkBankButton user={user} />}
</div>
</div>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
);
}- Total 4 snippets
- 1