feat: add column sort, SQL formatter, table stats, insert dialog, saved queries & sessions monitor

- Column sort by header click in table view (ASC/DESC/none cycle, server-side)
- SQL formatter with Format button and Shift+Alt+F keybinding (sql-formatter)
- Table size and row count display in schema tree via pg_class
- Insert row dialog with column type hints and auto-skip for identity columns
- Saved queries (bookmarks) with CRUD backend, sidebar panel, and save dialog
- Active sessions monitor (pg_stat_activity) with auto-refresh, cancel & terminate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 11:52:05 +03:00
parent ab72eeee80
commit 9d54167023
29 changed files with 1223 additions and 18 deletions

View File

@@ -9,7 +9,8 @@ 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 } from "lucide-react";
import { Save, RotateCcw, Filter, Loader2, Lock, Download, Plus } from "lucide-react";
import { InsertRowDialog } from "./InsertRowDialog";
import {
DropdownMenu,
DropdownMenuContent,
@@ -31,14 +32,15 @@ export function TableDataView({ connectionId, schema, table }: Props) {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [sortColumn, _setSortColumn] = useState<string | undefined>();
const [sortDirection, _setSortDirection] = useState<string | undefined>();
const [sortColumn, setSortColumn] = useState<string | undefined>();
const [sortDirection, setSortDirection] = useState<string | undefined>();
const [filter, setFilter] = useState("");
const [appliedFilter, setAppliedFilter] = useState<string | undefined>();
const [pendingChanges, setPendingChanges] = useState<
Map<string, { rowIndex: number; colIndex: number; value: string | null }>
>(new Map());
const [isSaving, setIsSaving] = useState(false);
const [insertDialogOpen, setInsertDialogOpen] = useState(false);
const queryClient = useQueryClient();
const { data, isLoading, error } = useTableData({
@@ -155,6 +157,15 @@ export function TableDataView({ connectionId, schema, table }: Props) {
[data, table]
);
const handleSort = useCallback(
(column: string | undefined, direction: string | undefined) => {
setSortColumn(column);
setSortDirection(direction);
setPage(1);
},
[]
);
const handleApplyFilter = () => {
setAppliedFilter(filter || undefined);
setPage(1);
@@ -207,6 +218,17 @@ export function TableDataView({ connectionId, schema, table }: Props) {
</DropdownMenuContent>
</DropdownMenu>
)}
{!isReadOnly && (
<Button
size="sm"
variant="ghost"
className="h-6 gap-1 text-xs"
onClick={() => setInsertDialogOpen(true)}
>
<Plus className="h-3 w-3" />
Insert Row
</Button>
)}
{pendingChanges.size > 0 && (
<>
<Button
@@ -253,6 +275,11 @@ export function TableDataView({ connectionId, schema, table }: Props) {
rows={data.rows}
onCellDoubleClick={handleCellDoubleClick}
highlightedCells={highlightedCells}
externalSort={{
column: sortColumn,
direction: sortDirection,
onSort: handleSort,
}}
/>
) : null}
</div>
@@ -269,6 +296,19 @@ export function TableDataView({ connectionId, schema, table }: Props) {
}}
/>
)}
<InsertRowDialog
open={insertDialogOpen}
onOpenChange={setInsertDialogOpen}
connectionId={connectionId}
schema={schema}
table={table}
onSuccess={() => {
queryClient.invalidateQueries({
queryKey: ["table-data", connectionId],
});
}}
/>
</div>
);
}