feat: add SQL editor, query results, and workspace panels
Add CodeMirror 6 SQL editor with Ctrl+Enter execution, ResultsTable with virtual scrolling and resizable columns, ResultsPanel, EditableCell, WorkspacePanel (editor + results split), and TabContent router. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
src/components/workspace/WorkspacePanel.tsx
Normal file
102
src/components/workspace/WorkspacePanel.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { SqlEditor } from "@/components/editor/SqlEditor";
|
||||
import { ResultsPanel } from "@/components/results/ResultsPanel";
|
||||
import { useQueryExecution } from "@/hooks/use-query-execution";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Play, Loader2 } from "lucide-react";
|
||||
import type { QueryResult } from "@/types";
|
||||
|
||||
interface Props {
|
||||
connectionId: string;
|
||||
initialSql?: string;
|
||||
onSqlChange?: (sql: string) => void;
|
||||
onResult?: (result: QueryResult | null, error: string | null) => void;
|
||||
}
|
||||
|
||||
export function WorkspacePanel({
|
||||
connectionId,
|
||||
initialSql = "",
|
||||
onSqlChange,
|
||||
onResult,
|
||||
}: Props) {
|
||||
const [sqlValue, setSqlValue] = useState(initialSql);
|
||||
const [result, setResult] = useState<QueryResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const queryMutation = useQueryExecution();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(val: string) => {
|
||||
setSqlValue(val);
|
||||
onSqlChange?.(val);
|
||||
},
|
||||
[onSqlChange]
|
||||
);
|
||||
|
||||
const handleExecute = useCallback(() => {
|
||||
if (!sqlValue.trim() || !connectionId) return;
|
||||
setError(null);
|
||||
queryMutation.mutate(
|
||||
{ connectionId, sql: sqlValue },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setResult(data);
|
||||
setError(null);
|
||||
onResult?.(data, null);
|
||||
},
|
||||
onError: (err) => {
|
||||
setResult(null);
|
||||
setError(String(err));
|
||||
onResult?.(null, String(err));
|
||||
},
|
||||
}
|
||||
);
|
||||
}, [connectionId, sqlValue, queryMutation, onResult]);
|
||||
|
||||
return (
|
||||
<ResizablePanelGroup orientation="vertical">
|
||||
<ResizablePanel id="editor" defaultSize="40%" minSize="15%">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center gap-2 border-b px-2 py-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 gap-1 text-xs"
|
||||
onClick={handleExecute}
|
||||
disabled={queryMutation.isPending || !sqlValue.trim()}
|
||||
>
|
||||
{queryMutation.isPending ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Play className="h-3 w-3" />
|
||||
)}
|
||||
Run
|
||||
</Button>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
Ctrl+Enter to execute
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<SqlEditor
|
||||
value={sqlValue}
|
||||
onChange={handleChange}
|
||||
onExecute={handleExecute}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel id="results" defaultSize="60%" minSize="15%">
|
||||
<ResultsPanel
|
||||
result={result}
|
||||
error={error}
|
||||
isLoading={queryMutation.isPending}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user