diff --git a/src/app/_components/tasks-table-columns.tsx b/src/app/_components/tasks-table-columns.tsx index 321b04dd..a2a27a6a 100644 --- a/src/app/_components/tasks-table-columns.tsx +++ b/src/app/_components/tasks-table-columns.tsx @@ -232,6 +232,7 @@ export function getColumns(): ColumnDef[] { ) }, + size: 40, }, ] } diff --git a/src/app/_components/tasks-table-provider.tsx b/src/app/_components/tasks-table-provider.tsx index b552a14d..8a7aa6a1 100644 --- a/src/app/_components/tasks-table-provider.tsx +++ b/src/app/_components/tasks-table-provider.tsx @@ -40,7 +40,7 @@ export function TasksTableProvider({ children }: React.PropsWithChildren) { setFeatureFlags, }} > - {/*
+
))} -
*/} +
{children} ) diff --git a/src/app/_components/tasks-table.tsx b/src/app/_components/tasks-table.tsx index 84cc318f..e68ca99d 100644 --- a/src/app/_components/tasks-table.tsx +++ b/src/app/_components/tasks-table.tsx @@ -73,11 +73,15 @@ export function TasksTable({ tasksPromise }: TasksTableProps) { data, columns, pageCount, - // optional props + /* optional props */ filterFields, enableAdvancedFilter: featureFlags.includes("advancedFilter"), - defaultPerPage: 10, - defaultSort: "createdAt.desc", + state: { + sorting: [{ id: "createdAt", desc: true }], + pagination: { pageIndex: 0, pageSize: 10 }, + columnPinning: { right: ["actions"] }, + }, + /* */ }) return ( diff --git a/src/components/data-table/advanced/data-table-multi-filter.tsx b/src/components/data-table/advanced/data-table-multi-filter.tsx index 4450121e..5321e915 100644 --- a/src/components/data-table/advanced/data-table-multi-filter.tsx +++ b/src/components/data-table/advanced/data-table-multi-filter.tsx @@ -33,8 +33,7 @@ import { SelectValue, } from "@/components/ui/select" import { Separator } from "@/components/ui/separator" - -import { DataTableFacetedFilter } from "../data-table-faceted-filter" +import { DataTableFacetedFilter } from "@/components/data-table/data-table-faceted-filter" interface DataTableMultiFilterProps { table: Table diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx index 5558024c..3ae02846 100644 --- a/src/components/data-table/data-table.tsx +++ b/src/components/data-table/data-table.tsx @@ -1,6 +1,7 @@ import * as React from "react" import { flexRender, type Table as TanstackTable } from "@tanstack/react-table" +import { getCommonPinningStyles } from "@/lib/data-table" import { cn } from "@/lib/utils" import { Table, @@ -48,7 +49,13 @@ export function DataTable({ {headerGroup.headers.map((header) => { return ( - + {header.isPlaceholder ? null : flexRender( @@ -69,7 +76,12 @@ export function DataTable({ data-state={row.getIsSelected() && "selected"} > {row.getVisibleCells().map((cell) => ( - + {flexRender( cell.column.columnDef.cell, cell.getContext() diff --git a/src/hooks/use-data-table.ts b/src/hooks/use-data-table.ts index a99288e1..323fde8a 100644 --- a/src/hooks/use-data-table.ts +++ b/src/hooks/use-data-table.ts @@ -15,6 +15,7 @@ import { type ColumnFiltersState, type PaginationState, type SortingState, + type TableState, type VisibilityState, } from "@tanstack/react-table" import { z } from "zod" @@ -42,22 +43,6 @@ interface UseDataTableProps { */ pageCount: number - /** - * The default number of rows per page. - * @default 10 - * @type number | undefined - * @example 20 - */ - defaultPerPage?: number - - /** - * The default sort order. - * @default undefined - * @type `${Extract}.${"asc" | "desc"}` | undefined - * @example "createdAt.desc" - */ - defaultSort?: `${Extract}.${"asc" | "desc"}` - /** * Defines filter fields for the table. Supports both dynamic faceted filters and search filters. * - Faceted filters are rendered when `options` are provided for a filter field. @@ -96,9 +81,21 @@ interface UseDataTableProps { * @type boolean */ enableAdvancedFilter?: boolean + + /** + * The initial state of the table. + * Can be used to set the initial pagination, sorting, column visibility, row selection, column grouping, column pinning, and column filters. + * @default {} + */ + state?: Omit, "sorting"> & { + sorting?: { + id: Extract + desc: boolean + }[] + } } -const schema = z.object({ +const searchParamsSchema = z.object({ page: z.coerce.number().default(1), per_page: z.coerce.number().optional(), sort: z.string().optional(), @@ -108,20 +105,18 @@ export function useDataTable({ data, columns, pageCount, - defaultPerPage = 10, - defaultSort, filterFields = [], enableAdvancedFilter = false, + state, }: UseDataTableProps) { const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() // Search params - const search = schema.parse(Object.fromEntries(searchParams)) - const page = search.page - const perPage = search.per_page ?? defaultPerPage - const sort = search.sort ?? defaultSort + const { page, per_page, sort } = searchParamsSchema.parse( + Object.fromEntries(searchParams) + ) const [column, order] = sort?.split(".") ?? [] // Memoize computation of searchableColumns and filterableColumns @@ -188,10 +183,12 @@ export function useDataTable({ // Handle server-side pagination const [{ pageIndex, pageSize }, setPagination] = - React.useState({ - pageIndex: page - 1, - pageSize: perPage, - }) + React.useState( + state?.pagination ?? { + pageIndex: page - 1, + pageSize: per_page ?? 10, + } + ) const pagination = React.useMemo( () => ({ @@ -202,12 +199,14 @@ export function useDataTable({ ) // Handle server-side sorting - const [sorting, setSorting] = React.useState([ - { - id: column ?? "", - desc: order === "desc", - }, - ]) + const [sorting, setSorting] = React.useState( + state?.sorting ?? [ + { + id: column ?? "", + desc: order === "desc", + }, + ] + ) React.useEffect(() => { router.push( @@ -307,6 +306,7 @@ export function useDataTable({ columns, pageCount: pageCount ?? -1, state: { + ...state, pagination, sorting, columnVisibility, diff --git a/src/lib/data-table.ts b/src/lib/data-table.ts new file mode 100644 index 00000000..adf5783f --- /dev/null +++ b/src/lib/data-table.ts @@ -0,0 +1,28 @@ +import { type Column } from "@tanstack/react-table" + +export function getCommonPinningStyles({ + column, +}: { + column: Column +}): React.CSSProperties { + const isPinned = column.getIsPinned() + const isLastLeftPinnedColumn = + isPinned === "left" && column.getIsLastColumn("left") + const isFirstRightPinnedColumn = + isPinned === "right" && column.getIsFirstColumn("right") + + return { + boxShadow: isLastLeftPinnedColumn + ? "-5px 0 5px -5px hsl(var(--border)) inset" + : isFirstRightPinnedColumn + ? "5px 0 5px -5px hsl(var(--border)) inset" + : undefined, + left: isPinned === "left" ? `${column.getStart("left")}px` : undefined, + right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined, + opacity: isPinned ? 0.95 : 1, + position: isPinned ? "sticky" : "relative", + background: isPinned ? "hsl(var(--background))" : undefined, + width: column.getSize(), + zIndex: isPinned ? 1 : 0, + } +}