add sorting to table data

This commit is contained in:
2024-07-07 13:10:31 +02:00
parent 5d1658c522
commit c1a31640a3
9 changed files with 117 additions and 49 deletions

View File

@@ -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]);

View File

@@ -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<SortingState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
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<PaginationState> = (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 = ({
<h1 className="text-2xl font-bold flex items-center gap-2">
<Rows3 /> {tableName}
</h1>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
onSelect={(e) => e.preventDefault()}
checked={column.getIsVisible()}
onCheckedChange={column.toggleVisibility}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<p>
Rows: <strong>{data?.count}</strong>
</p>
@@ -172,20 +213,41 @@ export const DataTable = ({
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const sorted = header.column.getIsSorted();
return (
<TableHead
className={"relative"}
className={"p-0 relative"}
key={header.id}
style={{
width: header.getSize(),
}}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
<Button
variant="ghost"
onClick={header.column.getToggleSortingHandler()}
title={
header.column.getNextSortingOrder() === "asc"
? "Sort ascending"
: header.column.getNextSortingOrder() === "desc"
? "Sort descending"
: "Clear sort"
}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
<ArrowUp
className={cn(
"ml-2 size-4 opacity-0 transition-transform",
sorted && "opacity-100",
(sorted as string) === "desc" && "rotate-180",
)}
/>
</Button>
<div
{...{
onDoubleClick: () => header.column.resetSize(),

View File

@@ -27,7 +27,10 @@ if (rootElement && !rootElement.innerHTML) {
<StrictMode>
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<ReactQueryDevtools
initialIsOpen={false}
buttonPosition={"top-right"}
/>
<RouterProvider router={router} />
</QueryClientProvider>
</ThemeProvider>

View File

@@ -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() {
</Link>
</header>
<aside className={"p-3"}>
<Select value={dbName} onValueChange={handleSelectedSchema}>
<Select value={dbName} onValueChange={handleSelectedDb}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Database Schema" />
<SelectValue placeholder="Database" />
</SelectTrigger>
<SelectContent>
{data?.map((schema) => {
{data?.map((db) => {
return (
<SelectItem value={schema} key={schema}>
{schema}
<SelectItem value={db} key={db}>
{db}
</SelectItem>
);
})}

View File

@@ -194,7 +194,7 @@ function Component() {
)}
<ArrowUp
className={cn(
"ml-2 size-4 opacity-0",
"ml-2 size-4 opacity-0 transition-transform",
sorted && "opacity-100",
(sorted as string) === "desc" && "rotate-180",
)}

View File

@@ -1,26 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute('/')({
export const Route = createFileRoute("/")({
component: Index,
})
});
function Index() {
const { dbSchema } = Route.useSearch()
if (!dbSchema) {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
}
return <TableView dbSchema={dbSchema} />
}
function TableView({ dbSchema }: { dbSchema: string }) {
return (
<div className="p-2">
<h3>Table View</h3>
{dbSchema}
<div className="p-2 h-layout w-layout grid place-items-center">
<div>
<h1 className={"text-xl text-center font-semibold"}>Welcome!</h1>
<p>Select a database to continue.</p>
</div>
</div>
)
);
}

View File

@@ -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,
});
};

View File

@@ -27,10 +27,17 @@ class DbService {
.json<GetTablesListResponse>();
}
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<GetTableDataResponse>();
}

View File

@@ -26,6 +26,8 @@ export type GetTableDataArgs = {
dbName: string;
perPage?: number;
page?: number;
sortField?: string;
sortDesc?: boolean;
};
export type GetTableDataResponse = {