Add CtePlanViewer, CteResultViewer, and PhaseSummaryViewer to render the structured v2 payloads (schema_version: 2) the harness now emits for artifact-gate widgets, per contracts.md. Extract PreviewGrid from ResultsPanel as a reusable AG Grid component shared by CteResultViewer. ArtifactView dispatches to the new viewers on a schema_version/shape guard, falling back to the existing legacy renderers unchanged for older sessions and replay fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { useState } from "react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { sqlPreview, sqlExport } from "../api/sql";
|
||
import { PreviewGrid } from "./PreviewGrid";
|
||
|
||
interface Props {
|
||
sessionId: string;
|
||
}
|
||
|
||
export function ResultsPanel({ sessionId }: Props) {
|
||
const [limit, setLimit] = useState<number>(10);
|
||
|
||
const { data, isLoading, error } = useQuery({
|
||
queryKey: ["preview", sessionId, limit],
|
||
queryFn: () => sqlPreview(sessionId, { limit, offset: 0 }),
|
||
});
|
||
|
||
async function handleExport() {
|
||
await sqlExport(sessionId);
|
||
}
|
||
|
||
const isScalar =
|
||
data != null && data.columns.length === 1 && data.rows.length === 1;
|
||
|
||
return (
|
||
<div>
|
||
{/* Limit selector */}
|
||
<div className="mb-2 flex items-center gap-2">
|
||
<select
|
||
className="rounded-lg border border-input bg-card px-2 py-1 text-sm shadow-xs outline-none transition-[border-color,box-shadow] focus:border-primary/50 focus:ring-2 focus:ring-ring/20"
|
||
value={limit}
|
||
onChange={(e) =>
|
||
setLimit(e.target.value === "all" ? 1_000_000 : Number(e.target.value))
|
||
}
|
||
aria-label="Rows to display"
|
||
>
|
||
<option value={10}>10</option>
|
||
<option value="all">all</option>
|
||
</select>
|
||
|
||
<button
|
||
className="rounded-md border border-border/70 px-3 py-1 text-sm shadow-xs transition-colors hover:bg-accent"
|
||
onClick={handleExport}
|
||
>
|
||
Export CSV
|
||
</button>
|
||
</div>
|
||
|
||
{/* Loading / error states */}
|
||
{isLoading && <div>Loading…</div>}
|
||
{error && <div>Error: {String(error)}</div>}
|
||
|
||
{/* Scalar result: 1 col × 1 row → bold value */}
|
||
{data && isScalar && (
|
||
<strong data-testid="scalar-value">{String(data.rows[0][0])}</strong>
|
||
)}
|
||
|
||
{/* Grid for everything else */}
|
||
{data && !isScalar && (
|
||
<>
|
||
<PreviewGrid columns={data.columns} rows={data.rows} />
|
||
{data.truncated && <div>(results truncated)</div>}
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|