import { useState, useCallback, useMemo } from "react"; import { useTableData } from "@/hooks/use-table-data"; import { ResultsTable } from "@/components/results/ResultsTable"; import { PaginationControls } from "./PaginationControls"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { updateRow as updateRowApi } from "@/lib/tauri"; 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"; interface Props { connectionId: string; schema: string; table: string; } export function TableDataView({ connectionId, schema, table }: Props) { const readOnlyMap = useAppStore((s) => s.readOnlyMap); const isReadOnly = readOnlyMap[connectionId] ?? true; const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [sortColumn, _setSortColumn] = useState(); const [sortDirection, _setSortDirection] = useState(); const [filter, setFilter] = useState(""); const [appliedFilter, setAppliedFilter] = useState(); const [pendingChanges, setPendingChanges] = useState< Map >(new Map()); const [isSaving, setIsSaving] = useState(false); const queryClient = useQueryClient(); const { data, isLoading, error } = useTableData({ connectionId, schema, table, page, pageSize, sortColumn, sortDirection, filter: appliedFilter, }); const { data: columnsInfo } = useQuery({ queryKey: ["table-columns", connectionId, schema, table], queryFn: () => getTableColumns(connectionId, schema, table), }); const pkColumns = useMemo( () => columnsInfo?.filter((c) => c.is_primary_key).map((c) => c.name) ?? [], [columnsInfo] ); const highlightedCells = useMemo( () => new Set(pendingChanges.keys()), [pendingChanges] ); const handleCellDoubleClick = useCallback( (rowIndex: number, colIndex: number, value: unknown) => { if (isReadOnly) { toast.warning("Read-only mode is active", { description: "Switch to Read-Write mode to edit data.", }); return; } const key = `${rowIndex}:${colIndex}`; const currentValue = pendingChanges.get(key)?.value ?? value; const newVal = prompt("Edit value (leave empty and click NULL for null):", currentValue === null ? "" : String(currentValue)); if (newVal !== null) { setPendingChanges((prev) => { const next = new Map(prev); next.set(key, { rowIndex, colIndex, value: newVal === "" ? null : newVal }); return next; }); } }, [pendingChanges, isReadOnly] ); const handleCommit = async () => { if (!data || pkColumns.length === 0) { toast.error("Cannot save: no primary key detected"); return; } setIsSaving(true); try { for (const [_key, change] of pendingChanges) { const row = data.rows[change.rowIndex]; const pkValues = pkColumns.map((pkCol) => { const idx = data.columns.indexOf(pkCol); return row[idx]; }); const colName = data.columns[change.colIndex]; await updateRowApi({ connectionId, schema, table, pkColumns, pkValues: pkValues as unknown[], column: colName, value: change.value, }); } setPendingChanges(new Map()); queryClient.invalidateQueries({ queryKey: ["table-data", connectionId], }); toast.success(`${pendingChanges.size} change(s) saved`); } catch (err) { toast.error("Save failed", { description: String(err) }); } finally { setIsSaving(false); } }; const handleRollback = () => { setPendingChanges(new Map()); }; const handleApplyFilter = () => { setAppliedFilter(filter || undefined); setPage(1); }; return (
{isReadOnly && ( Read-Only )} setFilter(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleApplyFilter()} /> {pendingChanges.size > 0 && ( <> )}
{isLoading && !data ? (
Loading...
) : error ? (
{String(error)}
) : data ? ( ) : null}
{data && ( { setPageSize(size); setPage(1); }} /> )}
); }