'use client'; import React, { useState, useEffect, useCallback } from 'react'; import MonacoEditor from '@monaco-editor/react'; import { Play, Loader2, AlertCircle, CheckCircle2, History, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useTheme } from 'next-themes'; import { cn } from '@/lib/utils'; interface SqlEditorProps { deploymentId?: string; queryEndpoint?: string; workspaceId?: string; } interface QueryResult { success: boolean; columns?: string[]; rows?: unknown[][]; rowsAffected?: number; error?: string; executionTime?: number; } const HISTORY_KEY = 'osw-sql-history'; const MAX_HISTORY = 20; export function SqlEditor({ deploymentId, queryEndpoint, workspaceId }: SqlEditorProps) { const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api'; const [sql, setSql] = useState('SELECT * FROM '); const [executing, setExecuting] = useState(false); const [result, setResult] = useState(null); const [history, setHistory] = useState([]); const [showHistory, setShowHistory] = useState(false); const { resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); // Load history from localStorage const savedHistory = localStorage.getItem(HISTORY_KEY); if (savedHistory) { try { setHistory(JSON.parse(savedHistory)); } catch { // Ignore invalid JSON } } }, []); const saveToHistory = useCallback((query: string) => { setHistory(prev => { const newHistory = [query, ...prev.filter(q => q !== query)].slice(0, MAX_HISTORY); localStorage.setItem(HISTORY_KEY, JSON.stringify(newHistory)); return newHistory; }); }, []); const executeQuery = useCallback(async () => { if (!sql.trim()) return; setExecuting(true); setResult(null); const startTime = Date.now(); try { const endpoint = queryEndpoint || `${apiBase}/admin/deployments/${deploymentId}/database/query`; const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sql: sql.trim() }), }); const data = await res.json(); const executionTime = Date.now() - startTime; if (!res.ok) { setResult({ success: false, error: data.error || 'Query failed', executionTime, }); } else { setResult({ success: true, columns: data.columns, rows: data.rows, rowsAffected: data.rowsAffected, executionTime, }); saveToHistory(sql.trim()); } } catch (err) { setResult({ success: false, error: err instanceof Error ? err.message : 'Query failed', executionTime: Date.now() - startTime, }); } finally { setExecuting(false); } }, [sql, deploymentId, saveToHistory]); // Handle keyboard shortcut const handleEditorMount = useCallback((editor: unknown) => { const monacoEditor = editor as { addCommand: (keybinding: number, handler: () => void) => void }; // Cmd/Ctrl + Enter to execute monacoEditor.addCommand(2048 | 3, () => { // KeyMod.CtrlCmd | KeyCode.Enter executeQuery(); }); }, [executeQuery]); if (!mounted) { return
; } return (
{/* Editor Section */}
Ctrl/Cmd + Enter
{/* History dropdown */} {showHistory && history.length > 0 && (
{history.map((query, i) => ( ))}
)}
setSql(value || '')} onMount={handleEditorMount} options={{ minimap: { enabled: false }, fontSize: 13, lineNumbers: 'off', folding: false, scrollBeyondLastLine: false, wordWrap: 'on', automaticLayout: true, }} />
{/* Results Section */}
{result === null ? (
Execute a query to see results
) : result.success ? (
{/* Status bar */}
{result.rows && result.rows.length > 0 ? ( {result.rows.length} row{result.rows.length !== 1 ? 's' : ''} ) : result.rowsAffected !== undefined && result.rowsAffected > 0 ? ( {result.rowsAffected} row{result.rowsAffected !== 1 ? 's' : ''} affected ) : ( Query executed successfully )} ({result.executionTime}ms)
{/* Results table */} {result.columns && result.columns.length > 0 && result.rows ? (
{result.columns.map((col, i) => ( ))} {result.rows.map((row, i) => ( {row.map((cell, j) => ( ))} ))}
{col}
{cell === null ? ( NULL ) : typeof cell === 'object' ? ( JSON.stringify(cell) ) : ( String(cell) )}
) : null}
) : (

Query Error

{result.error}

)}
); }