feat: add JSON view mode toggle for query results

Add a Table/JSON segmented toggle to both the query workspace and
table data viewer, allowing users to switch between tabular and
pretty-printed JSON display of results without exporting to a file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 13:05:52 +03:00
parent e02225a3b9
commit a2371f00df
4 changed files with 173 additions and 34 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useCallback, useMemo } from "react";
import { useTableData } from "@/hooks/use-table-data";
import { ResultsTable } from "@/components/results/ResultsTable";
import { ResultsJsonView } from "@/components/results/ResultsJsonView";
import { PaginationControls } from "./PaginationControls";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -9,7 +10,7 @@ import { getTableColumns } from "@/lib/tauri";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useAppStore } from "@/stores/app-store";
import { toast } from "sonner";
import { Save, RotateCcw, Filter, Loader2, Lock, Download, Plus } from "lucide-react";
import { Save, RotateCcw, Filter, Loader2, Lock, Download, Plus, Table2, Braces } from "lucide-react";
import { InsertRowDialog } from "./InsertRowDialog";
import {
DropdownMenu,
@@ -41,6 +42,7 @@ export function TableDataView({ connectionId, schema, table }: Props) {
>(new Map());
const [isSaving, setIsSaving] = useState(false);
const [insertDialogOpen, setInsertDialogOpen] = useState(false);
const [viewMode, setViewMode] = useState<"table" | "json">("table");
const queryClient = useQueryClient();
const { data, isLoading, error } = useTableData({
@@ -197,26 +199,54 @@ export function TableDataView({ connectionId, schema, table }: Props) {
Apply
</Button>
{data && data.columns.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-6 gap-1 text-xs"
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-6 gap-1 text-xs"
>
<Download className="h-3 w-3" />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => handleExport("csv")}>
Export CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleExport("json")}>
Export JSON
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex items-center rounded-md border text-xs">
<button
className={`flex items-center gap-1 px-2 py-0.5 font-medium ${
viewMode === "table"
? "bg-muted text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
onClick={() => setViewMode("table")}
title="Table view"
>
<Download className="h-3 w-3" />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => handleExport("csv")}>
Export CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleExport("json")}>
Export JSON
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Table2 className="h-3 w-3" />
Table
</button>
<button
className={`flex items-center gap-1 px-2 py-0.5 font-medium ${
viewMode === "json"
? "bg-muted text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
onClick={() => setViewMode("json")}
title="JSON view"
>
<Braces className="h-3 w-3" />
JSON
</button>
</div>
</>
)}
{!isReadOnly && (
<Button
@@ -269,18 +299,25 @@ export function TableDataView({ connectionId, schema, table }: Props) {
{String(error)}
</div>
) : data ? (
<ResultsTable
columns={data.columns}
types={data.types}
rows={data.rows}
onCellDoubleClick={handleCellDoubleClick}
highlightedCells={highlightedCells}
externalSort={{
column: sortColumn,
direction: sortDirection,
onSort: handleSort,
}}
/>
viewMode === "json" ? (
<ResultsJsonView
columns={data.columns}
rows={data.rows}
/>
) : (
<ResultsTable
columns={data.columns}
types={data.types}
rows={data.rows}
onCellDoubleClick={handleCellDoubleClick}
highlightedCells={highlightedCells}
externalSort={{
column: sortColumn,
direction: sortDirection,
onSort: handleSort,
}}
/>
)
) : null}
</div>