mirror of
https://github.com/ershisan99/db-studio.git
synced 2025-12-16 05:09:26 +00:00
remove prime ng and fix table layout. Fix select
This commit is contained in:
Binary file not shown.
@@ -5,7 +5,6 @@ import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
ScrollArea,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -167,7 +166,7 @@ export const DataTable = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={"flex flex-col gap-4 flex-1 max-h-full h-full pb-3"}>
|
||||
<div className={"flex flex-col gap-4 flex-1 max-h-full pb-3"}>
|
||||
<div className={"flex gap-4 items-center justify-between"}>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2">
|
||||
<Rows3 /> {tableName}
|
||||
@@ -201,103 +200,105 @@ export const DataTable = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="rounded-md border min-h-0 h-full w-full min-w-0">
|
||||
<Table
|
||||
className={"table-fixed min-w-full"}
|
||||
{...{
|
||||
style: {
|
||||
width: table.getCenterTotalSize(),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const sorted = header.column.getIsSorted();
|
||||
<div className="rounded-md border min-h-0 h-full w-full min-w-0 flex flex-col">
|
||||
<div className={"flex flex-col flex-1 overflow-auto relative"}>
|
||||
<Table
|
||||
className={"table-fixed min-w-full"}
|
||||
{...{
|
||||
style: {
|
||||
width: table.getCenterTotalSize(),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className={"sticky top-0"}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const sorted = header.column.getIsSorted();
|
||||
|
||||
return (
|
||||
<TableHead
|
||||
className={"p-0 relative"}
|
||||
key={header.id}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
}}
|
||||
>
|
||||
<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(),
|
||||
onMouseDown: header.getResizeHandler(),
|
||||
onTouchStart: header.getResizeHandler(),
|
||||
className: `resizer ${header.column.getIsResizing() ? "isResizing" : ""}`,
|
||||
return (
|
||||
<TableHead
|
||||
className={"p-0 relative"}
|
||||
key={header.id}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
}}
|
||||
/>
|
||||
</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}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
>
|
||||
<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(),
|
||||
onMouseDown: header.getResizeHandler(),
|
||||
onTouchStart: header.getResizeHandler(),
|
||||
className: `resizer ${header.column.getIsResizing() ? "isResizing" : ""}`,
|
||||
}}
|
||||
/>
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
))}
|
||||
</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}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
<DataTablePagination table={table} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -49,8 +49,10 @@ function RawSessionSelector() {
|
||||
value={currentSessionId ? currentSessionId.toString() : ""}
|
||||
onValueChange={handleSessionSelected}
|
||||
>
|
||||
<SelectTrigger className="max-w-full">
|
||||
<SelectValue placeholder="Select a Database" />
|
||||
<SelectTrigger className="w-full">
|
||||
<span className={"truncate max-w-[calc(100%-1.5rem)]"}>
|
||||
<SelectValue placeholder="Select a Database" />
|
||||
</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent>{mappedSessions}</SelectContent>
|
||||
</Select>
|
||||
119
frontend/src/components/sidebar/sidebar.tsx
Normal file
119
frontend/src/components/sidebar/sidebar.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { SessionSelector } from "@/components/sidebar/session-selector";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
buttonVariants,
|
||||
} from "@/components/ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Route } from "@/routes/__root";
|
||||
import { useDatabasesListQuery, useTablesListQuery } from "@/services/db";
|
||||
import { useUiStore } from "@/state";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { Database, Rows3 } from "lucide-react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
function SidebarContent() {
|
||||
const { data } = useDatabasesListQuery();
|
||||
|
||||
const showSidebar = useUiStore.use.showSidebar();
|
||||
const params = useParams({ strict: false });
|
||||
const dbName = params.dbName ?? "";
|
||||
const { data: tables } = useTablesListQuery({ dbName });
|
||||
const navigate = useNavigate({ from: Route.fullPath });
|
||||
|
||||
const handleSelectedDb = (dbName: string) => {
|
||||
void navigate({ to: "/db/$dbName/tables", params: { dbName } });
|
||||
};
|
||||
if (!showSidebar) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SessionSelector />
|
||||
<Select value={dbName} onValueChange={handleSelectedDb}>
|
||||
<SelectTrigger className="w-full mt-4">
|
||||
<SelectValue placeholder="Select a Database" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{data?.map((db) => {
|
||||
return (
|
||||
<SelectItem value={db} key={db}>
|
||||
{db}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<nav className="flex flex-col gap-1 mt-4">
|
||||
{dbName && (
|
||||
<Link
|
||||
to={"/db/$dbName/tables"}
|
||||
params={{ dbName }}
|
||||
activeOptions={{ exact: true }}
|
||||
title={dbName}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded py-1.5 pl-1.5",
|
||||
"hover:bg-muted",
|
||||
"[&.active]:bg-muted [&.active]:font-semibold",
|
||||
)}
|
||||
>
|
||||
<Database className={"size-4"} />
|
||||
<span className={"max-w-full inline-block truncate"}>{dbName}</span>
|
||||
</Link>
|
||||
)}
|
||||
{tables?.map((table) => {
|
||||
return (
|
||||
<div
|
||||
key={table.table_name}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-2.5 rounded py-1.5 justify-between w-full",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
className={cn(
|
||||
"max-w-full inline-block truncate",
|
||||
"hover:underline",
|
||||
"[&.active]:font-medium",
|
||||
)}
|
||||
title={table.table_name}
|
||||
to={"/db/$dbName/tables/$tableName"}
|
||||
params={{ tableName: table.table_name, dbName: dbName }}
|
||||
>
|
||||
{table.table_name}
|
||||
</Link>
|
||||
<Link
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
"hover:underline",
|
||||
buttonVariants({ variant: "ghost", size: "iconSm" }),
|
||||
"[&.active]:bg-muted",
|
||||
)}
|
||||
title={"Explore Data"}
|
||||
aria-label={"Explore Data"}
|
||||
to={"/db/$dbName/tables/$tableName/data"}
|
||||
params={{ tableName: table.table_name, dbName: dbName }}
|
||||
search={{ pageIndex: 0, pageSize: 10 }}
|
||||
>
|
||||
<Rows3 className={"size-4 shrink-0"} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarContainer({ children }: PropsWithChildren) {
|
||||
return <aside className={"p-3"}>{children}</aside>;
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
return (
|
||||
<SidebarContainer>
|
||||
<SidebarContent />
|
||||
</SidebarContainer>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ const SelectTrigger = forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
|
||||
const Table = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<div className="relative w-full overflow-auto rounded-md">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
@@ -23,7 +23,11 @@ const TableHeader = forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn("[&_tr]:border-b [&_tr]:bg-background", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
|
||||
@@ -74,11 +74,11 @@
|
||||
text-underline-position: under;
|
||||
--sidebar-width: 264px;
|
||||
}
|
||||
|
||||
|
||||
.sidebar-closed {
|
||||
--sidebar-width: 0;
|
||||
}
|
||||
|
||||
|
||||
.grid-rows-layout {
|
||||
grid-template-rows: 60px 1fr;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
import { SessionSelector } from "@/components/session-selector";
|
||||
import { SettingsDialog } from "@/components/settings-dialog";
|
||||
import {
|
||||
Button,
|
||||
ModeToggle,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
buttonVariants,
|
||||
} from "@/components/ui";
|
||||
import { Sidebar } from "@/components/sidebar/sidebar";
|
||||
import { Button, ModeToggle } from "@/components/ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useDatabasesListQuery, useTablesListQuery } from "@/services/db";
|
||||
import { useUiStore } from "@/state";
|
||||
import {
|
||||
Link,
|
||||
Outlet,
|
||||
createRootRoute,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "@tanstack/react-router";
|
||||
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
|
||||
import { Database, PanelLeft, PanelLeftClose, Rows3 } from "lucide-react";
|
||||
import { PanelLeft, PanelLeftClose } from "lucide-react";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: Root,
|
||||
@@ -31,16 +15,6 @@ function Root() {
|
||||
const showSidebar = useUiStore.use.showSidebar();
|
||||
const toggleSidebar = useUiStore.use.toggleSidebar();
|
||||
|
||||
const { data } = useDatabasesListQuery();
|
||||
const params = useParams({ strict: false });
|
||||
const dbName = params.dbName ?? "";
|
||||
const navigate = useNavigate({ from: Route.fullPath });
|
||||
|
||||
const handleSelectedDb = (dbName: string) => {
|
||||
void navigate({ to: "/db/$dbName/tables", params: { dbName } });
|
||||
};
|
||||
|
||||
const { data: tables } = useTablesListQuery({ dbName });
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -70,86 +44,7 @@ function Root() {
|
||||
<SettingsDialog />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside className={"p-3"}>
|
||||
{showSidebar && (
|
||||
<>
|
||||
<SessionSelector />
|
||||
<Select value={dbName} onValueChange={handleSelectedDb}>
|
||||
<SelectTrigger className="w-full mt-4">
|
||||
<SelectValue placeholder="Select a Database" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{data?.map((db) => {
|
||||
return (
|
||||
<SelectItem value={db} key={db}>
|
||||
{db}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<nav className="flex flex-col gap-1 mt-4">
|
||||
{dbName && (
|
||||
<Link
|
||||
to={"/db/$dbName/tables"}
|
||||
params={{ dbName }}
|
||||
activeOptions={{ exact: true }}
|
||||
title={dbName}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded py-1.5 pl-1.5",
|
||||
"hover:bg-muted",
|
||||
"[&.active]:bg-muted [&.active]:font-semibold",
|
||||
)}
|
||||
>
|
||||
<Database className={"size-4"} />
|
||||
<span className={"max-w-full inline-block truncate"}>
|
||||
{dbName}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{tables?.map((table) => {
|
||||
return (
|
||||
<div
|
||||
key={table.table_name}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-2.5 rounded py-1.5 justify-between w-full",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
className={cn(
|
||||
"max-w-full inline-block truncate",
|
||||
"hover:underline",
|
||||
"[&.active]:font-medium",
|
||||
)}
|
||||
title={table.table_name}
|
||||
to={"/db/$dbName/tables/$tableName"}
|
||||
params={{ tableName: table.table_name, dbName: dbName }}
|
||||
>
|
||||
{table.table_name}
|
||||
</Link>
|
||||
<Link
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
"hover:underline",
|
||||
buttonVariants({ variant: "ghost", size: "iconSm" }),
|
||||
"[&.active]:bg-muted",
|
||||
)}
|
||||
title={"Explore Data"}
|
||||
aria-label={"Explore Data"}
|
||||
to={"/db/$dbName/tables/$tableName/data"}
|
||||
params={{ tableName: table.table_name, dbName: dbName }}
|
||||
search={{ pageIndex: 0, pageSize: 10 }}
|
||||
>
|
||||
<Rows3 className={"size-4 shrink-0"} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
<Sidebar />
|
||||
<Outlet />
|
||||
</div>
|
||||
<TanStackRouterDevtools />
|
||||
|
||||
Reference in New Issue
Block a user