import { useMemo, useState, useRef, useEffect } from 'react'; import { useRawSQL } from '@/features/database/hooks/useRawSQL'; import { useSQLEditorContext } from '@/features/database/contexts/SQLEditorContext'; import { Badge, Button, CodeEditor, DataGrid, type DataGridColumn, type DataGridRow, } from '@/components'; import { X, Plus } from 'lucide-react'; import { cn } from '@/lib/utils/utils'; interface ResultsViewerProps { data: unknown; } // Helper to detect if data is an array of row objects function isRowData(data: unknown): data is Record[] { return ( Array.isArray(data) && data.length > 0 && typeof data[0] === 'object' && data[0] !== null && !Array.isArray(data[0]) ); } // Convert SQL result rows to DataGrid format function convertRowsToDataGridFormat(rows: Record[]) { // Add synthetic id field if rows don't have one - ensure id is always a string const dataWithIds: DataGridRow[] = rows.map((row, index) => ({ ...row, id: String(row.id || `row-${index}`), })); // Get all column keys from first row const columnKeys = Object.keys(rows[0]); // Create simple columns that render values as plain strings const columns: DataGridColumn[] = columnKeys.map((key) => ({ key, name: key, width: 'minmax(200px, 1fr)', resizable: true, sortable: true, editable: false, })); return { columns, data: dataWithIds }; } function ResultsViewer({ data }: ResultsViewerProps) { // Check if data is row data (array of objects) const isTable = isRowData(data); const gridData = useMemo(() => { if (isTable && data.length > 0) { return convertRowsToDataGridFormat(data); } return null; }, [isTable, data]); if (isTable && gridData) { // Render as table return (
); } // Render as JSON for non-table data const jsonString = JSON.stringify(data, null, 2); const lines = jsonString.split('\n'); return (
        {lines.map((line, index) => (
          
{line ||  }
))}
); } interface ErrorViewerProps { error: Error; } function ErrorViewer({ error }: ErrorViewerProps) { return (
        {error.message}
      
); } export default function SQLEditorPage() { const { tabs, activeTab, activeTabId, addTab, removeTab, setActiveTab, updateTabQuery, updateTabName, } = useSQLEditorContext(); const [editingTabId, setEditingTabId] = useState(null); const [editingTabName, setEditingTabName] = useState(''); const inputRef = useRef(null); const { executeSQL, isPending, data, isSuccess, error, isError } = useRawSQL({ showSuccessToast: true, showErrorToast: false, // Don't show toast, we'll display in results }); useEffect(() => { if (editingTabId && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [editingTabId]); const handleExecuteQuery = () => { if (!activeTab?.query.trim() || isPending) { return; } executeSQL({ query: activeTab.query, params: [] }); }; const handleQueryChange = (newQuery: string) => { if (activeTabId) { updateTabQuery(activeTabId, newQuery); } }; const handleTabNameDoubleClick = (tabId: string, currentName: string) => { setEditingTabId(tabId); setEditingTabName(currentName); }; const handleTabNameChange = (e: React.ChangeEvent) => { setEditingTabName(e.target.value); }; const handleTabNameBlur = () => { if (editingTabId && editingTabName.trim()) { updateTabName(editingTabId, editingTabName.trim()); } setEditingTabId(null); setEditingTabName(''); }; const handleTabNameKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleTabNameBlur(); } else if (e.key === 'Escape') { setEditingTabId(null); setEditingTabName(''); } }; return (
{/* Header */}

SQL Editor

{/* Main Content */}
{/* Tabs and Editor Combined Section */}
{/* Tabs Section */}
{tabs.map((tab) => { const isActive = tab.id === activeTabId; return (
setActiveTab(tab.id)} role="button" tabIndex={0} onKeyDown={(e) => { // Don't handle keyboard events if we're editing if (editingTabId === tab.id) { return; } if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActiveTab(tab.id); } }} > {editingTabId === tab.id ? ( { // Stop propagation to prevent parent handlers from interfering e.stopPropagation(); handleTabNameKeyDown(e); }} className={cn( 'pl-2 pr-1 text-sm font-medium bg-transparent', 'border-none outline-none focus:outline-none w-full min-w-0', 'text-black dark:text-white' )} onClick={(e) => e.stopPropagation()} /> ) : ( <> { e.stopPropagation(); handleTabNameDoubleClick(tab.id, tab.name); }} > {tab.name} {tabs.length > 1 && ( )} )}
); })}
{/* Code Editor Section */}
{/* Run Button */}
{/* Divider */}
{/* Results Section */}
{/* Results Heading */}

Results

{isSuccess && ( Success )} {isError && ( Error )}
{/* Results Content */}
{isError && error ? ( ) : isSuccess && data ? ( ) : (
{isPending ? 'Executing query...' : 'Run a query to see results'}
)}
); }