Import source: src/components/common/data-table
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
type ColumnDef,
|
||||||
|
type SortingState,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { ArrowUpDown, ChevronLeft, ChevronRight, Search } from "lucide-react";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export function DataTable<TData>({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
searchPlaceholder = "Search",
|
||||||
|
compact = false,
|
||||||
|
}: {
|
||||||
|
data: TData[];
|
||||||
|
columns: ColumnDef<TData>[];
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
compact?: boolean;
|
||||||
|
}) {
|
||||||
|
const [globalFilter, setGlobalFilter] = useState("");
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const memoColumns = useMemo(() => columns, [columns]);
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns: memoColumns,
|
||||||
|
state: { globalFilter, sorting },
|
||||||
|
onGlobalFilterChange: setGlobalFilter,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
initialState: { pagination: { pageSize: compact ? 8 : 10 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<div className="relative w-full max-w-sm">
|
||||||
|
<Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
value={globalFilter}
|
||||||
|
onChange={(event) => setGlobalFilter(event.target.value)}
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{table.getFilteredRowModel().rows.length} results
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="glass-panel overflow-hidden rounded-lg">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full border-collapse text-sm">
|
||||||
|
<thead className="bg-muted/50">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
className="whitespace-nowrap border-b border-border/60 px-3 py-2.5 text-left text-[11px] font-semibold uppercase tracking-wider text-muted-foreground"
|
||||||
|
>
|
||||||
|
{header.isPlaceholder ? null : (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1 transition-colors",
|
||||||
|
header.column.getCanSort() && "hover:text-primary",
|
||||||
|
)}
|
||||||
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
{header.column.getCanSort() ? (
|
||||||
|
<ArrowUpDown className="h-3 w-3" />
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{table.getRowModel().rows.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<tr key={row.id} className="transition-colors hover:bg-primary/[0.05]">
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<td
|
||||||
|
key={cell.id}
|
||||||
|
className={cn(
|
||||||
|
"border-b border-border/50 px-3",
|
||||||
|
compact ? "py-1.5" : "py-2.5",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="px-3 py-10 text-center text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
No matching records.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{Math.max(1, table.getPageCount())}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user