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>
169 lines
6.0 KiB
TypeScript
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>
|
|
);
|
|
}
|