Replace Radix ScrollArea with plain overflow-auto div to allow nested horizontal scrolling in lookup result tables. Add overflow-auto to table containers. Increase per-database search timeout from 30s to 120s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
ChevronDown,
|
|
ChevronRight,
|
|
AlertCircle,
|
|
Database,
|
|
} from "lucide-react";
|
|
import { ResultsTable } from "@/components/results/ResultsTable";
|
|
import type { LookupDatabaseResult } from "@/types";
|
|
|
|
interface Props {
|
|
dbResult: LookupDatabaseResult;
|
|
}
|
|
|
|
export function LookupResultGroup({ dbResult }: Props) {
|
|
const [expanded, setExpanded] = useState(dbResult.tables.length > 0);
|
|
const [expandedTables, setExpandedTables] = useState<Set<string>>(
|
|
() => new Set(dbResult.tables.map((t) => `${t.schema}.${t.table}`))
|
|
);
|
|
|
|
const totalRows = dbResult.tables.reduce((s, t) => s + t.row_count, 0);
|
|
const hasError = !!dbResult.error;
|
|
const hasMatches = dbResult.tables.length > 0;
|
|
|
|
const toggleTable = (key: string) => {
|
|
setExpandedTables((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(key)) next.delete(key);
|
|
else next.add(key);
|
|
return next;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="border rounded-md">
|
|
<button
|
|
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-accent/50"
|
|
onClick={() => setExpanded(!expanded)}
|
|
>
|
|
{expanded ? (
|
|
<ChevronDown className="h-4 w-4 shrink-0" />
|
|
) : (
|
|
<ChevronRight className="h-4 w-4 shrink-0" />
|
|
)}
|
|
<Database className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
<span className="font-medium">{dbResult.database}</span>
|
|
|
|
{hasMatches && (
|
|
<span className="text-xs text-muted-foreground">
|
|
{dbResult.tables.length} table{dbResult.tables.length !== 1 && "s"},{" "}
|
|
{totalRows} row{totalRows !== 1 && "s"}
|
|
</span>
|
|
)}
|
|
{hasError && (
|
|
<span className="flex items-center gap-1 text-xs text-destructive">
|
|
<AlertCircle className="h-3 w-3" />
|
|
{dbResult.error}
|
|
</span>
|
|
)}
|
|
{!hasError && !hasMatches && (
|
|
<span className="text-xs text-muted-foreground">no matches</span>
|
|
)}
|
|
|
|
<span className="ml-auto text-xs text-muted-foreground">
|
|
{dbResult.search_time_ms}ms
|
|
</span>
|
|
</button>
|
|
|
|
{expanded && hasMatches && (
|
|
<div className="border-t">
|
|
{dbResult.tables.map((table) => {
|
|
const key = `${table.schema}.${table.table}`;
|
|
const isOpen = expandedTables.has(key);
|
|
|
|
return (
|
|
<div key={key} className="border-b last:border-b-0">
|
|
<button
|
|
className="flex w-full items-center gap-2 px-5 py-1.5 text-left text-xs hover:bg-accent/50"
|
|
onClick={() => toggleTable(key)}
|
|
>
|
|
{isOpen ? (
|
|
<ChevronDown className="h-3 w-3 shrink-0" />
|
|
) : (
|
|
<ChevronRight className="h-3 w-3 shrink-0" />
|
|
)}
|
|
<span className="font-medium">
|
|
{table.schema}.{table.table}
|
|
</span>
|
|
<span className="text-muted-foreground">
|
|
({table.row_count} row{table.row_count !== 1 && "s"}
|
|
{table.total_count > table.row_count &&
|
|
`, ${table.total_count} total`}
|
|
)
|
|
</span>
|
|
<span className="text-muted-foreground">
|
|
[{table.column_type}]
|
|
</span>
|
|
</button>
|
|
|
|
{isOpen && table.columns.length > 0 && (
|
|
<div className="h-[200px] overflow-auto border-t">
|
|
<ResultsTable
|
|
columns={table.columns}
|
|
types={table.types}
|
|
rows={table.rows as unknown[][]}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|