feat(frontend): results panel (AGGrid + preview/export)
Add ResultsPanel component with AGGrid v36 for tabular preview results, bold scalar display for 1×1 results, limit selector (10 / tutti), and Esporta CSV button delegating export to the backend. MSW-tested with 3 passing scenarios (grid cells, scalar, export endpoint call). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
||||
import { sqlPreview, sqlExport } from "../api/sql";
|
||||
|
||||
// Register AG Grid modules once at module load time
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
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;
|
||||
|
||||
const columnDefs = data
|
||||
? data.columns.map((col) => ({ field: col, headerName: col }))
|
||||
: [];
|
||||
|
||||
const rowData = data
|
||||
? data.rows.map((row) => {
|
||||
const obj: Record<string, unknown> = {};
|
||||
data.columns.forEach((col, i) => {
|
||||
obj[col] = row[i];
|
||||
});
|
||||
return obj;
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Limit selector */}
|
||||
<div style={{ marginBottom: 8, display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<select
|
||||
value={limit}
|
||||
onChange={(e) =>
|
||||
setLimit(e.target.value === "tutti" ? 1_000_000 : Number(e.target.value))
|
||||
}
|
||||
aria-label="Righe da visualizzare"
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value="tutti">tutti</option>
|
||||
</select>
|
||||
|
||||
<button onClick={handleExport}>Esporta CSV</button>
|
||||
</div>
|
||||
|
||||
{/* Loading / error states */}
|
||||
{isLoading && <div>Caricamento...</div>}
|
||||
{error && <div>Errore: {String(error)}</div>}
|
||||
|
||||
{/* Scalar result: 1 col × 1 row → bold value */}
|
||||
{data && isScalar && (
|
||||
<strong role="strong">{String(data.rows[0][0])}</strong>
|
||||
)}
|
||||
|
||||
{/* Grid for everything else */}
|
||||
{data && !isScalar && (
|
||||
<div className="ag-theme-alpine" style={{ height: 400, width: "100%" }}>
|
||||
<AgGridReact columnDefs={columnDefs} rowData={rowData} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user