diff --git a/frontend/bun.lockb b/frontend/bun.lockb index a7b9bef..7b36f43 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/package.json b/frontend/package.json index 6a371b4..0451b01 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,6 +38,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", + "usehooks-ts": "^3.1.0", "zod": "^3.23.8", "zustand": "^4.5.4" }, diff --git a/frontend/src/hooks/use-filters.ts b/frontend/src/hooks/use-filters.ts index 06acbcc..178e987 100644 --- a/frontend/src/hooks/use-filters.ts +++ b/frontend/src/hooks/use-filters.ts @@ -13,7 +13,7 @@ export function useFilters>( const navigate = useNavigate(); const filters = routeApi.useSearch(); - const setFilters = (partialFilters: Partial) => + const setFilters = (partialFilters: Partial = {}) => navigate({ search: (prev) => cleanEmptyParams({ ...prev, ...partialFilters }), }); diff --git a/frontend/src/lib/tableSortMapper.ts b/frontend/src/lib/tableSortMapper.ts new file mode 100644 index 0000000..6bde362 --- /dev/null +++ b/frontend/src/lib/tableSortMapper.ts @@ -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 | undefined, +): SortingState => { + if (!sortBy) return []; + if (!sortBy.sortField) return []; + + return [{ id: sortBy.sortField, desc: sortBy.sortDesc ?? false }]; +}; diff --git a/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx b/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx index a46f1e7..9f67931 100644 --- a/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx +++ b/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx @@ -1,45 +1,154 @@ -import { DataTable } from "@/components/db-table-view/data-table"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useColumns } from "@/components/db-table-view/columns"; +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"; const tableSearchSchema = z.object({ pageSize: z.number().catch(10), pageIndex: z.number().catch(0), + sortField: z.string().optional(), + sortDesc: z.boolean().optional(), }); export const Route = createFileRoute("/db/$dbName/tables/$tableName/data")({ - component: TableView, + component: Component, validateSearch: (search) => tableSearchSchema.parse(search), }); -function TableView() { +function Component() { const { tableName, dbName } = Route.useParams(); - const { pageSize, pageIndex } = Route.useSearch(); - const navigate = useNavigate({ from: Route.fullPath }); + const { filters, setFilters } = useFilters(Route.fullPath); + const [whereQuery, setWhereQuery] = useState(""); + const [columnSizing, setColumnSizing] = useLocalStorage( + `columnSizing-${dbName}-${tableName}`, + {}, + ); + const [columnVisibility, setColumnVisibility] = + useLocalStorage( + `columnVisibility-${dbName}-${tableName}`, + {}, + ); - const updatePageSize = (value: number) => { - console.log(value); - return void navigate({ - search: (prev) => ({ ...prev, pageSize: value, pageIndex: 0 }), - }); - }; - const updatePageIndex = (pageIndex: number) => { - console.log(pageIndex); + const paginationState = useMemo( + () => ({ + pageIndex: filters.pageIndex ?? DEFAULT_PAGE_INDEX, + pageSize: filters.pageSize ?? DEFAULT_PAGE_SIZE, + }), + [filters], + ); - 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 = useCallback( + (updaterOrValue) => { + const newSortingState = + typeof updaterOrValue === "function" + ? updaterOrValue(sortingState) + : updaterOrValue; + return setFilters(stateToSortBy(newSortingState)); + }, + [sortingState, setFilters], + ); + + const handlePaginationChange: OnChangeFn = 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 (
- +
+
+

+ {tableName} + +

+ +

+ Rows: {data?.count} +

+
+ +
+ + + +
+ +
); }