store column sizes and column visibility in localstorage

This commit is contained in:
2024-07-14 16:02:28 +02:00
parent 1aee971c7c
commit 7ff64879c1
5 changed files with 168 additions and 26 deletions

Binary file not shown.

View File

@@ -38,6 +38,7 @@
"sonner": "^1.5.0", "sonner": "^1.5.0",
"tailwind-merge": "^2.4.0", "tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"usehooks-ts": "^3.1.0",
"zod": "^3.23.8", "zod": "^3.23.8",
"zustand": "^4.5.4" "zustand": "^4.5.4"
}, },

View File

@@ -13,7 +13,7 @@ export function useFilters<T extends RouteIds<RegisteredRouter["routeTree"]>>(
const navigate = useNavigate(); const navigate = useNavigate();
const filters = routeApi.useSearch(); const filters = routeApi.useSearch();
const setFilters = (partialFilters: Partial<typeof filters>) => const setFilters = (partialFilters: Partial<typeof filters> = {}) =>
navigate({ navigate({
search: (prev) => cleanEmptyParams({ ...prev, ...partialFilters }), search: (prev) => cleanEmptyParams({ ...prev, ...partialFilters }),
}); });

View File

@@ -0,0 +1,32 @@
import type { SortingState } from "@tanstack/react-table";
type SortBy = {
sortField?: string;
sortDesc?: boolean;
};
export const stateToSortBy = (
sorting: SortingState | undefined,
): SortBy | undefined => {
if (!sorting || sorting.length === 0)
return {
sortField: undefined,
sortDesc: undefined,
};
const sort = sorting[0];
return {
sortField: sort.id,
sortDesc: sort.desc,
};
};
export const sortByToState = (
sortBy: Partial<SortBy> | undefined,
): SortingState => {
if (!sortBy) return [];
if (!sortBy.sortField) return [];
return [{ id: sortBy.sortField, desc: sortBy.sortDesc ?? false }];
};

View File

@@ -1,45 +1,154 @@
import { DataTable } from "@/components/db-table-view/data-table"; import { useColumns } from "@/components/db-table-view/columns";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { ColumnsDropdown } from "@/components/db-table-view/columns-dropdown";
import {
TableScrollContainer,
TableView,
} from "@/components/db-table-view/table";
import {
WhereClauseForm,
type WhereClauseFormValues,
} from "@/components/db-table-view/where-clause-form";
import { DataTablePagination } from "@/components/ui";
import { useFilters } from "@/hooks/use-filters";
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from "@/lib/constants";
import { sortByToState, stateToSortBy } from "@/lib/tableSortMapper";
import { useTableDataQuery } from "@/services/db";
import { createFileRoute } from "@tanstack/react-router";
import {
type ColumnSizingState,
type OnChangeFn,
type PaginationState,
type SortingState,
type VisibilityState,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Rows3 } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { useLocalStorage } from "usehooks-ts";
import { z } from "zod"; import { z } from "zod";
const tableSearchSchema = z.object({ const tableSearchSchema = z.object({
pageSize: z.number().catch(10), pageSize: z.number().catch(10),
pageIndex: z.number().catch(0), pageIndex: z.number().catch(0),
sortField: z.string().optional(),
sortDesc: z.boolean().optional(),
}); });
export const Route = createFileRoute("/db/$dbName/tables/$tableName/data")({ export const Route = createFileRoute("/db/$dbName/tables/$tableName/data")({
component: TableView, component: Component,
validateSearch: (search) => tableSearchSchema.parse(search), validateSearch: (search) => tableSearchSchema.parse(search),
}); });
function TableView() { function Component() {
const { tableName, dbName } = Route.useParams(); const { tableName, dbName } = Route.useParams();
const { pageSize, pageIndex } = Route.useSearch(); const { filters, setFilters } = useFilters(Route.fullPath);
const navigate = useNavigate({ from: Route.fullPath }); const [whereQuery, setWhereQuery] = useState("");
const [columnSizing, setColumnSizing] = useLocalStorage<ColumnSizingState>(
`columnSizing-${dbName}-${tableName}`,
{},
);
const [columnVisibility, setColumnVisibility] =
useLocalStorage<VisibilityState>(
`columnVisibility-${dbName}-${tableName}`,
{},
);
const updatePageSize = (value: number) => { const paginationState = useMemo(
console.log(value); () => ({
return void navigate({ pageIndex: filters.pageIndex ?? DEFAULT_PAGE_INDEX,
search: (prev) => ({ ...prev, pageSize: value, pageIndex: 0 }), pageSize: filters.pageSize ?? DEFAULT_PAGE_SIZE,
}); }),
}; [filters],
const updatePageIndex = (pageIndex: number) => { );
console.log(pageIndex);
return void navigate({ search: (prev) => ({ ...prev, pageIndex }) }); const sortingState = useMemo(() => sortByToState(filters), [filters]);
}; const columns = useColumns({ dbName, tableName });
const { data, refetch } = useTableDataQuery({
whereQuery,
tableName,
dbName,
perPage: filters.pageSize,
page: filters.pageIndex,
sortField: filters.sortField,
sortDesc: filters.sortDesc,
});
const handleWhereClauseFormSubmit = useCallback(
({ whereClause }: WhereClauseFormValues) => {
if (whereClause === whereQuery) {
void refetch();
return;
}
setWhereQuery(whereClause);
},
[whereQuery, refetch],
);
const handleSortingChange: OnChangeFn<SortingState> = useCallback(
(updaterOrValue) => {
const newSortingState =
typeof updaterOrValue === "function"
? updaterOrValue(sortingState)
: updaterOrValue;
return setFilters(stateToSortBy(newSortingState));
},
[sortingState, setFilters],
);
const handlePaginationChange: OnChangeFn<PaginationState> = useCallback(
(pagination) => {
setFilters(
typeof pagination === "function"
? pagination(paginationState)
: pagination,
);
},
[paginationState, setFilters],
);
const table = useReactTable({
columnResizeMode: "onChange",
columns,
data: data?.data ?? [],
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
manualSorting: true,
onColumnSizingChange: setColumnSizing,
onColumnVisibilityChange: setColumnVisibility,
onPaginationChange: handlePaginationChange,
onSortingChange: handleSortingChange,
rowCount: data?.count ?? 0,
state: {
sorting: sortingState,
columnSizing: columnSizing,
columnVisibility,
pagination: paginationState,
},
});
return ( return (
<div className="p-3 h-layout w-layout"> <div className="p-3 h-layout w-layout">
<DataTable <div className={"flex flex-col gap-4 flex-1 max-h-full pb-3"}>
key={tableName} <div className={"flex gap-4 items-center justify-between"}>
dbName={dbName} <h1 className="text-2xl font-bold flex items-center gap-2">
tableName={tableName} <Rows3 /> {tableName}
pageSize={pageSize} <WhereClauseForm onSubmit={handleWhereClauseFormSubmit} />
pageIndex={pageIndex} </h1>
onPageIndexChange={updatePageIndex} <ColumnsDropdown table={table} />
onPageSizeChange={updatePageSize} <p>
/> Rows: <strong>{data?.count}</strong>
</p>
</div>
<div className="rounded-md border min-h-0 h-full w-full min-w-0 flex flex-col">
<TableScrollContainer>
<TableView table={table} columnLength={columns.length} />
</TableScrollContainer>
</div>
<DataTablePagination table={table} />
</div>
</div> </div>
); );
} }