From c1a31640a3f9bfdf99bc656450d49dcbef896e9a Mon Sep 17 00:00:00 2001 From: andres Date: Sun, 7 Jul 2024 13:10:31 +0200 Subject: [PATCH] add sorting to table data --- api/src/index.ts | 16 +++- .../components/db-table-view/data-table.tsx | 78 +++++++++++++++++-- frontend/src/main.tsx | 5 +- frontend/src/routes/__root.tsx | 20 ++--- .../src/routes/db/$dbName/tables/index.tsx | 2 +- frontend/src/routes/index.tsx | 28 +++---- frontend/src/services/db/db.hooks.ts | 4 +- frontend/src/services/db/db.service.ts | 11 ++- frontend/src/services/db/db.types.ts | 2 + 9 files changed, 117 insertions(+), 49 deletions(-) diff --git a/api/src/index.ts b/api/src/index.ts index 76bef49..996efe1 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -32,12 +32,14 @@ const app = new Elysia({ prefix: "/api" }) "databases/:dbName/tables/:tableName/data", async ({ params, query }) => { const { tableName, dbName } = params; - const { perPage = "50", page = "0" } = query; + const { perPage = "50", page = "0", sortField, sortDesc } = query; return getTableData({ tableName, dbName, perPage: Number.parseInt(perPage, 10), page: Number.parseInt(page, 10), + sortField, + sortDesc: sortDesc === "true", }); }, ) @@ -294,11 +296,15 @@ async function getTableData({ dbName, perPage, page, + sortDesc, + sortField, }: { tableName: string; dbName: string; perPage: number; page: number; + sortField?: string; + sortDesc?: boolean; }) { const offset = (perPage * page).toString(); const rows = sql` @@ -308,7 +314,13 @@ async function getTableData({ const tables = sql` SELECT * FROM ${sql(dbName)}.${sql(tableName)} - LIMIT ${perPage} OFFSET ${offset}`; + ${ + sortField + ? sql`ORDER BY ${sql(sortField)} ${sortDesc ? sql`DESC` : sql`ASC`}` + : sql`` + } + LIMIT ${perPage} OFFSET ${offset} + `; const [[count], data] = await Promise.all([rows, tables]); diff --git a/frontend/src/components/db-table-view/data-table.tsx b/frontend/src/components/db-table-view/data-table.tsx index 9ebd621..973e11a 100644 --- a/frontend/src/components/db-table-view/data-table.tsx +++ b/frontend/src/components/db-table-view/data-table.tsx @@ -1,5 +1,10 @@ import { + Button, DataTablePagination, + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, Table, TableBody, TableCell, @@ -13,12 +18,14 @@ import { type ColumnDef, type OnChangeFn, type PaginationState, + type SortingState, + type VisibilityState, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; -import { Rows3 } from "lucide-react"; -import { useMemo } from "react"; +import { ArrowUp, Rows3 } from "lucide-react"; +import { useMemo, useState } from "react"; import { z } from "zod"; function isUrl(value: string) { @@ -48,12 +55,17 @@ export const DataTable = ({ onPageIndexChange: (pageIndex: number) => void; onPageSizeChange: (pageSize: number) => void; }) => { + const [sorting, setSorting] = useState([]); + const [columnVisibility, setColumnVisibility] = useState({}); + const { data: details } = useTableColumnsQuery({ dbName, tableName }); const { data } = useTableDataQuery({ tableName, dbName, perPage: pageSize, page: pageIndex, + sortDesc: sorting[0]?.desc, + sortField: sorting[0]?.id, }); const paginationUpdater: OnChangeFn = (args) => { @@ -137,8 +149,13 @@ export const DataTable = ({ columns, columnResizeMode: "onChange", getCoreRowModel: getCoreRowModel(), + onColumnVisibilityChange: setColumnVisibility, rowCount: data?.count ?? 0, + manualSorting: true, + onSortingChange: setSorting, state: { + sorting, + columnVisibility, pagination: { pageIndex, pageSize, @@ -154,6 +171,30 @@ export const DataTable = ({

{tableName}

+ + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + e.preventDefault()} + checked={column.getIsVisible()} + onCheckedChange={column.toggleVisibility} + > + {column.id} + + ); + })} + +

Rows: {data?.count}

@@ -172,20 +213,41 @@ export const DataTable = ({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { + const sorted = header.column.getIsSorted(); + return ( - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), +
header.column.resetSize(), diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ed8319f..1fbb748 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -27,7 +27,10 @@ if (rootElement && !rootElement.innerHTML) { - + diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 88ea91f..fc4e4fd 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -18,15 +18,9 @@ import { } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/router-devtools"; import { Database, Rows3, Table2 } from "lucide-react"; -import { z } from "zod"; - -const searchSchema = z.object({ - dbSchema: z.string().optional().catch(""), -}); export const Route = createRootRoute({ component: Root, - validateSearch: (search) => searchSchema.parse(search), }); function Root() { @@ -36,8 +30,8 @@ function Root() { const dbName = params.dbName ?? ""; const navigate = useNavigate({ from: Route.fullPath }); - const handleSelectedSchema = (schema: string) => { - void navigate({ to: "/db/$dbName/tables", params: { dbName: schema } }); + const handleSelectedDb = (dbName: string) => { + void navigate({ to: "/db/$dbName/tables", params: { dbName } }); }; const { data: tables } = useTablesListQuery({ dbName }); @@ -52,15 +46,15 @@ function Root() {
- ) - } - return -} - -function TableView({ dbSchema }: { dbSchema: string }) { return ( -
-

Table View

- {dbSchema} +
+
+

Welcome!

+

Select a database to continue.

+
- ) + ); } diff --git a/frontend/src/services/db/db.hooks.ts b/frontend/src/services/db/db.hooks.ts index 312da2c..90aa1b0 100644 --- a/frontend/src/services/db/db.hooks.ts +++ b/frontend/src/services/db/db.hooks.ts @@ -1,4 +1,4 @@ -import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { DB_QUERY_KEYS } from "./db.query-keys"; import { dbService } from "./db.service"; import type { @@ -13,7 +13,6 @@ export const useDatabasesListQuery = () => { return useQuery({ queryKey: [DB_QUERY_KEYS.DATABASES.ALL], queryFn: () => dbService.getDatabasesList(), - placeholderData: keepPreviousData, }); }; @@ -22,7 +21,6 @@ export const useTablesListQuery = (args: GetTablesListArgs) => { queryKey: [DB_QUERY_KEYS.TABLES.ALL, args], queryFn: () => dbService.getTablesList(args), enabled: !!args.dbName, - placeholderData: keepPreviousData, }); }; diff --git a/frontend/src/services/db/db.service.ts b/frontend/src/services/db/db.service.ts index bb45104..f2a3fc8 100644 --- a/frontend/src/services/db/db.service.ts +++ b/frontend/src/services/db/db.service.ts @@ -27,10 +27,17 @@ class DbService { .json(); } - getTableData({ dbName, tableName, page, perPage }: GetTableDataArgs) { + getTableData({ + dbName, + tableName, + page, + perPage, + sortField, + sortDesc, + }: GetTableDataArgs) { return dbInstance .get(`api/databases/${dbName}/tables/${tableName}/data`, { - searchParams: getValuable({ perPage, page }), + searchParams: getValuable({ perPage, page, sortField, sortDesc }), }) .json(); } diff --git a/frontend/src/services/db/db.types.ts b/frontend/src/services/db/db.types.ts index 79f9a1c..3371b54 100644 --- a/frontend/src/services/db/db.types.ts +++ b/frontend/src/services/db/db.types.ts @@ -26,6 +26,8 @@ export type GetTableDataArgs = { dbName: string; perPage?: number; page?: number; + sortField?: string; + sortDesc?: boolean; }; export type GetTableDataResponse = {