feat: add CSV/JSON export buttons to query results and table data view
Export dropdown (CSV/JSON) appears in the WorkspacePanel toolbar when query results are available, and in the TableDataView toolbar for table data. Uses the Tauri save dialog for file path selection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,15 @@ 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 } from "lucide-react";
|
||||
import { Save, RotateCcw, Filter, Loader2, Lock, Download } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { exportCsv, exportJson } from "@/lib/tauri";
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
interface Props {
|
||||
connectionId: string;
|
||||
@@ -123,6 +131,30 @@ export function TableDataView({ connectionId, schema, table }: Props) {
|
||||
setPendingChanges(new Map());
|
||||
};
|
||||
|
||||
const handleExport = useCallback(
|
||||
async (format: "csv" | "json") => {
|
||||
if (!data || data.columns.length === 0) return;
|
||||
const ext = format === "csv" ? "csv" : "json";
|
||||
const path = await save({
|
||||
title: `Export as ${ext.toUpperCase()}`,
|
||||
defaultPath: `${table}.${ext}`,
|
||||
filters: [{ name: ext.toUpperCase(), extensions: [ext] }],
|
||||
});
|
||||
if (!path) return;
|
||||
try {
|
||||
if (format === "csv") {
|
||||
await exportCsv(path, data.columns, data.rows as unknown[][]);
|
||||
} else {
|
||||
await exportJson(path, data.columns, data.rows as unknown[][]);
|
||||
}
|
||||
toast.success(`Exported to ${path}`);
|
||||
} catch (err) {
|
||||
toast.error("Export failed", { description: String(err) });
|
||||
}
|
||||
},
|
||||
[data, table]
|
||||
);
|
||||
|
||||
const handleApplyFilter = () => {
|
||||
setAppliedFilter(filter || undefined);
|
||||
setPage(1);
|
||||
@@ -153,6 +185,28 @@ 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"
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{pendingChanges.size > 0 && (
|
||||
<>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user