Files
tusk/src/components/table-viewer/TableStructure.tsx
A.Shakhmatov 9b9d2cee94 feat: add table data viewer, structure inspector, and export
Add TableDataView with pagination, filtering, and inline editing,
TableStructure with columns/constraints/indexes tabs,
PaginationControls, and ExportDialog for CSV/JSON export.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:06:54 +03:00

169 lines
6.0 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import {
getTableColumns,
getTableConstraints,
getTableIndexes,
} from "@/lib/tauri";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
interface Props {
connectionId: string;
schema: string;
table: string;
}
export function TableStructure({ connectionId, schema, table }: Props) {
const { data: columns } = useQuery({
queryKey: ["table-columns", connectionId, schema, table],
queryFn: () => getTableColumns(connectionId, schema, table),
});
const { data: constraints } = useQuery({
queryKey: ["table-constraints", connectionId, schema, table],
queryFn: () => getTableConstraints(connectionId, schema, table),
});
const { data: indexes } = useQuery({
queryKey: ["table-indexes", connectionId, schema, table],
queryFn: () => getTableIndexes(connectionId, schema, table),
});
return (
<Tabs defaultValue="columns" className="flex h-full flex-col">
<TabsList className="mx-2 mt-2 w-fit">
<TabsTrigger value="columns" className="text-xs">
Columns{columns ? ` (${columns.length})` : ""}
</TabsTrigger>
<TabsTrigger value="constraints" className="text-xs">
Constraints{constraints ? ` (${constraints.length})` : ""}
</TabsTrigger>
<TabsTrigger value="indexes" className="text-xs">
Indexes{indexes ? ` (${indexes.length})` : ""}
</TabsTrigger>
</TabsList>
<TabsContent value="columns" className="flex-1 overflow-hidden mt-0">
<ScrollArea className="h-full">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-xs">#</TableHead>
<TableHead className="text-xs">Name</TableHead>
<TableHead className="text-xs">Type</TableHead>
<TableHead className="text-xs">Nullable</TableHead>
<TableHead className="text-xs">Default</TableHead>
<TableHead className="text-xs">Key</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{columns?.map((col) => (
<TableRow key={col.name}>
<TableCell className="text-xs text-muted-foreground">
{col.ordinal_position}
</TableCell>
<TableCell className="text-xs font-medium">
{col.name}
</TableCell>
<TableCell className="text-xs">
{col.data_type}
{col.character_maximum_length
? `(${col.character_maximum_length})`
: ""}
</TableCell>
<TableCell className="text-xs">
{col.is_nullable ? "YES" : "NO"}
</TableCell>
<TableCell className="max-w-[200px] truncate text-xs text-muted-foreground">
{col.column_default ?? "—"}
</TableCell>
<TableCell>
{col.is_primary_key && (
<Badge variant="outline" className="text-[10px]">
PK
</Badge>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</TabsContent>
<TabsContent value="constraints" className="flex-1 overflow-hidden mt-0">
<ScrollArea className="h-full">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-xs">Name</TableHead>
<TableHead className="text-xs">Type</TableHead>
<TableHead className="text-xs">Columns</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{constraints?.map((c) => (
<TableRow key={c.name}>
<TableCell className="text-xs font-medium">
{c.name}
</TableCell>
<TableCell>
<Badge variant="secondary" className="text-[10px]">
{c.constraint_type}
</Badge>
</TableCell>
<TableCell className="text-xs">
{c.columns.join(", ")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</TabsContent>
<TabsContent value="indexes" className="flex-1 overflow-hidden mt-0">
<ScrollArea className="h-full">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-xs">Name</TableHead>
<TableHead className="text-xs">Definition</TableHead>
<TableHead className="text-xs">Unique</TableHead>
<TableHead className="text-xs">Primary</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{indexes?.map((idx) => (
<TableRow key={idx.name}>
<TableCell className="text-xs font-medium">
{idx.name}
</TableCell>
<TableCell className="max-w-[400px] truncate text-xs text-muted-foreground">
{idx.definition}
</TableCell>
<TableCell className="text-xs">
{idx.is_unique ? "YES" : "NO"}
</TableCell>
<TableCell className="text-xs">
{idx.is_primary ? "YES" : "NO"}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</TabsContent>
</Tabs>
);
}