summaryrefslogtreecommitdiff
path: root/components/data-table/data-table-view-options.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/data-table/data-table-view-options.tsx')
-rw-r--r--components/data-table/data-table-view-options.tsx191
1 files changed, 191 insertions, 0 deletions
diff --git a/components/data-table/data-table-view-options.tsx b/components/data-table/data-table-view-options.tsx
new file mode 100644
index 00000000..c55617ec
--- /dev/null
+++ b/components/data-table/data-table-view-options.tsx
@@ -0,0 +1,191 @@
+"use client"
+
+import * as React from "react"
+import { RowData, type Table } from "@tanstack/react-table"
+import {
+ Check,
+ ChevronsUpDown,
+ GripVertical,
+ Settings2,
+} from "lucide-react"
+
+import { cn, toSentenceCase } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+
+// Sortable
+import {
+ Sortable,
+ SortableItem,
+ SortableDragHandle,
+} from "@/components/ui/sortable"
+
+
+/**
+ * ViewOptionsProps:
+ * - table: TanStack Table instance
+ */
+interface DataTableViewOptionsProps<TData> {
+ table: Table<TData>
+}
+
+declare module "@tanstack/table-core" {
+ interface ColumnMeta<TData extends RowData, TValue> {
+ excelHeader?: string
+ group?: string
+ type?: string
+ // ...anything else you want
+ }
+}
+/**
+ * DataTableViewOptions:
+ * - Renders a Popover with hideable columns
+ * - Lets user reorder columns (drag & drop) + toggle visibility
+ */
+export function DataTableViewOptions<TData>({
+ table,
+}: DataTableViewOptionsProps<TData>) {
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
+
+ // 1) Identify columns that can be hidden
+ const hideableCols = React.useMemo(() => {
+
+
+ return table
+ .getAllLeafColumns()
+ .filter((col) => col.getCanHide())
+ }, [table])
+
+
+ // 2) local state for "columnOrder" (just the ID of hideable columns)
+ // We'll reorder these with drag & drop
+ const [columnOrder, setColumnOrder] = React.useState<string[]>(() =>
+ hideableCols.map((c) => c.id)
+ )
+
+ // 3) onMove: when user finishes drag
+ // - update local `columnOrder` only (no table.setColumnOrder yet)
+ const handleMove = React.useCallback(
+ ({ activeIndex, overIndex }: { activeIndex: number; overIndex: number }) => {
+ setColumnOrder((prev) => {
+ const newOrder = [...prev]
+ const [removed] = newOrder.splice(activeIndex, 1)
+ newOrder.splice(overIndex, 0, removed)
+ return newOrder
+ })
+ },
+ []
+ )
+
+ // 4) After local state changes, reflect in tanstack table
+ // - We do this in useEffect to avoid "update a different component" error
+ React.useEffect(() => {
+ // Also consider "non-hideable" columns, if any, to keep them in original positions
+ const nonHideable = table
+ .getAllColumns()
+ .filter((col) => !hideableCols.some((hc) => hc.id === col.id))
+ .map((c) => c.id)
+
+ // e.g. place nonHideable at the front, then our local hideable order
+ const finalOrder = [...nonHideable, ...columnOrder]
+
+ // Now we set the table's official column order
+ table.setColumnOrder(finalOrder)
+ }, [columnOrder, hideableCols, table])
+
+
+ return (
+ <Popover modal>
+ <PopoverTrigger asChild>
+ <Button
+ ref={triggerRef}
+ aria-label="Toggle columns"
+ variant="outline"
+ role="combobox"
+ size="sm"
+ className="gap-2"
+ >
+ <Settings2 className="size-4" />
+ <span className="hidden sm:inline">View</span>
+ <ChevronsUpDown className="ml-auto size-4 shrink-0 opacity-50 hidden sm:inline" />
+ </Button>
+ </PopoverTrigger>
+
+ <PopoverContent
+ align="end"
+ className="w-44 p-0"
+ onCloseAutoFocus={() => triggerRef.current?.focus()}
+ >
+ <Command>
+ <CommandInput placeholder="Search columns..." />
+ <CommandList>
+ <CommandEmpty>No columns found.</CommandEmpty>
+
+ <CommandGroup>
+ {/**
+ * 5) Sortable: we pass an array of { id: string } from `columnOrder`,
+ * so we can reorder them with drag & drop
+ */}
+ <Sortable
+ value={columnOrder.map((id) => ({ id }))}
+ onMove={handleMove}
+ >
+ {columnOrder.map((colId) => {
+ // find column instance
+ const column = hideableCols.find((c) => c.id === colId)
+
+
+ if (!column) return null
+
+ return (
+ <SortableItem key={colId} value={colId} asChild>
+ <CommandItem
+ onSelect={() =>
+ column.toggleVisibility(!column.getIsVisible())
+ }
+ >
+ {/* Drag handle on the left */}
+ <SortableDragHandle
+ variant="outline"
+ size="icon"
+ className="mr-2 size-5 shrink-0 rounded cursor-grab active:cursor-grabbing"
+ >
+ <GripVertical className="size-3.5" aria-hidden="true" />
+ </SortableDragHandle>
+
+ {/* label */}
+ <span className="truncate">
+ {column?.columnDef?.meta?.excelHeader}
+ </span>
+
+ {/* check if visible */}
+ <Check
+ className={cn(
+ "ml-auto size-4 shrink-0",
+ column.getIsVisible() ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </CommandItem>
+ </SortableItem>
+ )
+ })}
+ </Sortable>
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ )
+} \ No newline at end of file