import type { ReactNode } from "react"; import Link from "next/link"; import { type Row, type Table, flexRender } from "@tanstack/react-table"; import { TableEmptyStateRow, TableLoadingRow, } from "@/components/ui/table-state"; import { Button, buttonVariants } from "@/components/ui/button"; export type DataTableEmptyState = { icon: ReactNode; title: string; description: string; actionHref?: string; actionLabel?: string; }; export type DataTableRowAction = { key: string; label: string; href?: (row: TData) => string | null; onClick?: (row: TData) => void; className?: string; }; export type DataTableRowActions = { header?: ReactNode; actions?: DataTableRowAction[]; getEditHref?: (row: TData) => string | null; onDelete?: (row: TData) => void; cellClassName?: string; }; type DataTableProps = { table: Table; isLoading?: boolean; loadingLabel?: string; emptyMessage?: string; emptyState?: DataTableEmptyState; rowActions?: DataTableRowActions; stickyHeader?: boolean; tableClassName?: string; headerClassName?: string; headerCellClassName?: string; bodyClassName?: string; rowClassName?: string | ((row: Row) => string); cellClassName?: string; }; export function DataTable({ table, isLoading = false, loadingLabel = "Loading…", emptyMessage = "No rows found.", emptyState, rowActions, stickyHeader = false, tableClassName = "w-full text-left text-sm", headerClassName, headerCellClassName = "px-3 py-2 md:px-6 md:py-3", bodyClassName = "divide-y divide-slate-100", rowClassName = "hover:bg-slate-50", cellClassName = "px-3 py-3 md:px-6 md:py-4", }: DataTableProps) { const resolvedRowActions = rowActions ? (rowActions.actions ?? [ rowActions.getEditHref ? ({ key: "edit", label: "Edit", href: rowActions.getEditHref, } as DataTableRowAction) : null, rowActions.onDelete ? ({ key: "delete", label: "Delete", onClick: rowActions.onDelete, } as DataTableRowAction) : null, ].filter((value): value is DataTableRowAction => value !== null)) : []; const hasRowActions = resolvedRowActions.length > 0; const colSpan = (table.getVisibleLeafColumns().length || 1) + (hasRowActions ? 1 : 0); return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} {hasRowActions ? ( ) : null} ))} {isLoading ? ( ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} {hasRowActions ? ( ) : null} )) ) : emptyState ? ( ) : ( )}
{header.isPlaceholder ? null : header.column.getCanSort() ? ( ) : ( flexRender( header.column.columnDef.header, header.getContext(), ) )} {rowActions?.header ?? ""}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
{resolvedRowActions.map((action) => { const href = action.href?.(row.original) ?? null; if (href) { return ( {action.label} ); } if (action.onClick) { return ( ); } return null; })}
{emptyMessage}
); }