diff --git a/resources/js/components/data-table-column-header.tsx b/resources/js/components/data-table-column-header.tsx new file mode 100644 index 0000000..ce7a614 --- /dev/null +++ b/resources/js/components/data-table-column-header.tsx @@ -0,0 +1,43 @@ +import { type Column } from "@tanstack/react-table" +import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +interface DataTableColumnHeaderProps + extends React.HTMLAttributes { + column: Column + title: string +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
+ } + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/resources/js/components/data-table.tsx b/resources/js/components/data-table.tsx new file mode 100644 index 0000000..690ba27 --- /dev/null +++ b/resources/js/components/data-table.tsx @@ -0,0 +1,275 @@ +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + getPaginationRowModel, + SortingState, + getSortedRowModel, + ColumnFiltersState, + getFilteredRowModel, + VisibilityState, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "./ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import React from "react"; +import { Input } from "./ui/input"; +import { Settings2, ChevronDown, ListFilter, Trash2 } from "lucide-react"; + +interface DataTableFilterOption { + columnId: string; + title: string; + options: { label: string; value: string }[]; +} + +export interface DataTableBulkAction { + label: string; + onClick: (rows: TData[]) => void; + icon?: React.ElementType; + variant?: "default" | "destructive"; +} + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + searchKey?: string; + filters?: DataTableFilterOption[]; + bulkActions?: DataTableBulkAction[]; +} + +export function DataTable({ + columns, + data, + filters, + bulkActions, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState([]); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [globalFilter, setGlobalFilter] = React.useState(""); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + onGlobalFilterChange: setGlobalFilter, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + globalFilter, + }, + }); + + const selectedRows = table.getFilteredSelectedRowModel().rows; + + return ( +
+
+
+ setGlobalFilter(event.target.value)} + className="max-w-sm" + /> + + {filters && filters.length > 0 && ( + + + + + + Filter Berdasarkan + + {filters.map((filter) => { + const column = table.getColumn(filter.columnId); + if (!column) return null; + + return ( + + + {filter.title} + + + column.setFilterValue(undefined)} + > + Semua + + + {filter.options.map((option) => ( + { + if (option.value === "true") column.setFilterValue(true); + else if (option.value === "false") column.setFilterValue(false); + else column.setFilterValue(option.value); + }} + > + {option.label} + + ))} + + + ); + })} + + + )} + + {selectedRows.length > 0 && bulkActions && ( + + + + + + {bulkActions.map((action, i) => ( + action.onClick(selectedRows.map((r) => r.original))} + className={action.variant === "destructive" ? "text-red-600 focus:text-red-600" : ""} + > + {action.icon && } + {action.label} + + ))} + + + )} +
+ + + + + + + Tampilan Kolom + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + onSelect={(e) => e.preventDefault()} + > + {(column.columnDef.meta as any)?.title || + (typeof column.columnDef.header === "string" + ? column.columnDef.header + : String(column.id).replace(/_/g, " "))} + + ); + })} + + +
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + Tidak ada data yang ditemukan. + + + )} + +
+
+ +
+
+ {table.getFilteredSelectedRowModel().rows.length} dari{" "} + {table.getFilteredRowModel().rows.length} baris dipilih. +
+
+ + +
+
+
+ ); +} diff --git a/resources/js/components/ui/table.tsx b/resources/js/components/ui/table.tsx new file mode 100644 index 0000000..fcfa0a2 --- /dev/null +++ b/resources/js/components/ui/table.tsx @@ -0,0 +1,114 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}