beta/components/task-table-legacy/data-table.tsx
Christopher Arraya bcf3f113ad i'm done
2023-11-05 07:34:37 -05:00

301 lines
8.2 KiB
TypeScript

import * as React from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import type {
DataTableFilterableColumn,
DataTableSearchableColumn,
} from "@/types";
import {
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type ColumnDef,
type ColumnFiltersState,
type PaginationState,
type SortingState,
type VisibilityState,
} from "@tanstack/react-table";
import { useDebounce } from "@/hooks/use-debounce";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { DataTablePagination } from "@/components/task-table-legacy/data-table-pagination";
import { DataTableToolbar } from "@/components/task-table-legacy/data-table-toolbar";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
pageCount: number;
filterableColumns?: DataTableFilterableColumn<TData>[];
searchableColumns?: DataTableSearchableColumn<TData>[];
}
export function DataTable<TData, TValue>({
columns,
data,
pageCount,
filterableColumns = [],
searchableColumns = [],
}: DataTableProps<TData, TValue>) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
// Search params
const page = searchParams?.get("page") ?? "1";
const per_page = searchParams?.get("per_page") ?? "10";
const sort = searchParams?.get("sort");
const [column, order] = sort?.split(".") ?? [];
// Create query string
const createQueryString = React.useCallback(
(params: Record<string, string | number | null>) => {
const newSearchParams = new URLSearchParams(searchParams?.toString());
for (const [key, value] of Object.entries(params)) {
if (value === null) {
newSearchParams.delete(key);
} else {
newSearchParams.set(key, String(value));
}
}
return newSearchParams.toString();
},
[searchParams]
);
// Table states
const [rowSelection, setRowSelection] = React.useState({});
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
// Handle server-side pagination
const [{ pageIndex, pageSize }, setPagination] =
React.useState<PaginationState>({
pageIndex: Number(page) - 1,
pageSize: Number(per_page),
});
const pagination = React.useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize]
);
React.useEffect(() => {
setPagination({
pageIndex: Number(page) - 1,
pageSize: Number(per_page),
});
}, [page, per_page]);
React.useEffect(() => {
router.push(
`${pathname}?${createQueryString({
page: pageIndex + 1,
per_page: pageSize,
})}`
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex, pageSize]);
// Handle server-side sorting
const [sorting, setSorting] = React.useState<SortingState>([
{
id: column ?? "",
desc: order === "desc",
},
]);
React.useEffect(() => {
router.push(
`${pathname}?${createQueryString({
page,
sort: sorting[0]?.id
? `${sorting[0]?.id}.${sorting[0]?.desc ? "desc" : "asc"}`
: null,
})}`
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sorting]);
// Handle server-side filtering
const debouncedSearchableColumnFilters = JSON.parse(
useDebounce(
JSON.stringify(
columnFilters.filter((filter) => {
return searchableColumns.find((column) => column.id === filter.id);
})
),
500
)
) as ColumnFiltersState;
const filterableColumnFilters = columnFilters.filter((filter) => {
return filterableColumns.find((column) => column.id === filter.id);
});
React.useEffect(() => {
for (const column of debouncedSearchableColumnFilters) {
if (typeof column.value === "string") {
router.push(
`${pathname}?${createQueryString({
page: 1,
[column.id]: typeof column.value === "string" ? column.value : null,
})}`
);
}
}
for (const key of searchParams.keys()) {
if (
searchableColumns.find((column) => column.id === key) &&
!debouncedSearchableColumnFilters.find((column) => column.id === key)
) {
router.push(
`${pathname}?${createQueryString({
page: 1,
[key]: null,
})}`
);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(debouncedSearchableColumnFilters)]);
React.useEffect(() => {
for (const column of filterableColumnFilters) {
if (typeof column.value === "object" && Array.isArray(column.value)) {
router.push(
`${pathname}?${createQueryString({
page: 1,
[column.id]: column.value.join("."),
})}`
);
}
}
for (const key of searchParams.keys()) {
if (
filterableColumns.find((column) => column.id === key) &&
!filterableColumnFilters.find((column) => column.id === key)
) {
router.push(
`${pathname}?${createQueryString({
page: 1,
[key]: null,
})}`
);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(filterableColumnFilters)]);
const table = useReactTable({
data,
columns,
pageCount: pageCount ?? -1,
state: {
pagination,
sorting,
columnVisibility,
rowSelection,
columnFilters,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
manualPagination: true,
manualSorting: true,
manualFiltering: true,
});
return (
<div className="w-full space-y-4 overflow-auto">
<DataTableToolbar
table={table}
filterableColumns={filterableColumns}
searchableColumns={searchableColumns}
/>
<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) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} />
</div>
);
}