subman-nextjs/src/app/ui/tables/data-table.tsx

275 lines
8.5 KiB
TypeScript

"use client"
import { Button } from "@/components/ui/button"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuRadioItem,
DropdownMenuRadioGroup
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Component, ComponentProps, use, useState } from "react"
import {
ColumnDef,
flexRender,
SortingState,
getSortedRowModel,
ColumnFiltersState,
VisibilityState,
getFilteredRowModel,
getCoreRowModel,
getPaginationRowModel,
useReactTable
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { EyeIcon, Trash2 } from "lucide-react"
import { usePathname } from "next/navigation"
import FormContextMenu from "./contextMenu"
import { deleteRecords } from "app/lib/del"
import { Pathname } from "app/types"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger } from "@/components/ui/dialog"
import pluralize from "app/lib/pluralize"
import { updateTextField } from "app/lib/update"
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function DataTable<TData, TValue>({
columns,
data,
children
}: DataTableProps<TData, TValue> & ComponentProps<"div">) {
//STATE
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
useState<VisibilityState>({})
//
const table = useReactTable({
data,
columns,
enableRowSelection: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
state: {
sorting,
columnFilters,
columnVisibility,
},
//this is where you put arbitrary functions etc to make them accessible via the table api
meta: {
updateTextField,
//TODO - move select row action here so it can be accessed from within a cell
selectRow: () => { }
}
})
const pathname: Pathname = usePathname()
const [filterBy, setFilterBy] = useState(table.getAllColumns()[0])
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
return (<>
<div className="flex justify-between items-center py-4">
<div className="flex gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Filter by
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuRadioGroup value={filterBy} onValueChange={setFilterBy} >
{table
.getAllColumns()
.filter((column) => column.getCanFilter())
.map((column) => {
return (
<DropdownMenuRadioItem value={column} className="capitalize" key={column.id}>
{column.id}
</DropdownMenuRadioItem>
)
})}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<Input
placeholder={`${filterBy.id}`}
value={(table.getColumn(filterBy.id)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(filterBy.id)?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
{children}
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive" disabled={!(table.getIsSomeRowsSelected() || table.getIsAllRowsSelected())}>
<Trash2 />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
{`Delete ${Object.keys(table.getState().rowSelection).length} ${pluralize(pathname.slice(1))}?`}
</DialogHeader>
<DialogDescription>
{`Deleting ${pluralize(pathname.slice(1))} cannot be undone!`}
</DialogDescription>
<DialogFooter>
<DialogClose asChild>
<Button variant="destructive"
onClick={() => {
const selectedRows = table.getState().rowSelection
const rowIds = Object.keys(selectedRows)
const recordIds = rowIds.map(id => Number(table.getRow(id).original.id))
console.table(recordIds)
deleteRecords(recordIds, pathname)
}}>
Yes, delete them!</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="justify-self-end">
<EyeIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter(
(column) => column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</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) => (
<ContextMenu onOpenChange={open => setIsContextMenuOpen(open)}>
<ContextMenuTrigger asChild>
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
tabIndex={0}
//check if context menu is open, so as not to select the row on clicking a menu item
onClick={() => { if (!isContextMenuOpen) { row.toggleSelected() } }}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
<FormContextMenu
pathname={pathname}
row={row}
selectedRows={table.getSelectedRowModel().flatRows}
deselect={table.resetRowSelection}
/>
</TableRow>
</ContextMenuTrigger>
</ContextMenu>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
onBlur={true}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</>
)
}