import { useState, useCallback, useEffect, useRef } from 'react' import { Button, Dialog, Notification, toast } from '@/components/ui' import Container from '@/components/shared/Container' import { getDataSources } from '@/services/data-source.service' import type { DataSourceDto } from '@/proxy/data-source' import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models' import { sqlObjectManagerService } from '@/services/sql-query-manager.service' import { FaDatabase, FaPlay, FaFileAlt } from 'react-icons/fa' import { FaCheckCircle } from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' import SqlObjectExplorer from './SqlObjectExplorer' import SqlEditor, { SqlEditorRef } from './SqlEditor' import SqlResultsGrid from './SqlResultsGrid' import SqlTableDesignerDialog from './SqlTableDesignerDialog' import { Splitter } from '@/components/codeLayout/Splitter' import { Helmet } from 'react-helmet' import { useStoreState } from '@/store/store' import { APP_NAME } from '@/constants/app.constant' interface SqlManagerState { dataSources: DataSourceDto[] selectedDataSource: string | null editorContent: string isExecuting: boolean executionResult: SqlQueryExecutionResultDto | null showProperties: boolean isDirty: boolean tableColumns: any | null refreshTrigger: number } const SqlQueryManager = () => { const { translate } = useLocalization() const editorRef = useRef(null) const tenantName = useStoreState((state) => state.locale.currentTenantName) const [state, setState] = useState({ dataSources: [], selectedDataSource: tenantName ?? null, editorContent: '', isExecuting: false, refreshTrigger: 0, executionResult: null, showProperties: false, isDirty: false, tableColumns: null, }) const [showTemplateConfirmDialog, setShowTemplateConfirmDialog] = useState(false) const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: string } | null>( null, ) const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false) const [designTableData, setDesignTableData] = useState<{ schemaName: string tableName: string } | null>(null) useEffect(() => { loadDataSources() }, []) const loadDataSources = async () => { try { const response = await getDataSources() const items = response.data.items || [] if (items.length > 0) { setState((prev) => ({ ...prev, dataSources: items, selectedDataSource: items[0].code ?? null, })) } } catch (error) { toast.push( {translate('::App.Platform.FailedtoloadDatasources')} , { placement: 'top-center' }, ) } } const handleDataSourceChange = useCallback((dataSource: DataSourceDto) => { setState((prev) => ({ ...prev, selectedDataSource: dataSource.code ?? null, editorContent: '', executionResult: null, isDirty: false, })) }, []) const handleNewTable = useCallback(() => { setDesignTableData(null) setShowTableDesignerDialog(true) }, []) const handleDesignTable = useCallback((schemaName: string, tableName: string) => { setDesignTableData({ schemaName, tableName }) setShowTableDesignerDialog(true) }, []) const handleEditorChange = useCallback((value: string | undefined) => { setState((prev) => ({ ...prev, editorContent: value || '', isDirty: true, })) }, []) const getTemplateContent = (templateType: string): string => { const templates: Record = { select: `-- Basic SELECT query SELECT Column1, Column2, Column3 FROM TableName WHERE -- Add your conditions Column1 = 'value' AND IsActive = 1 ORDER BY Column1 ASC;`, insert: `-- Basic INSERT query INSERT INTO TableName (Column1, Column2, Column3) VALUES ('Value1', 'Value2', 'Value3');`, update: `-- Basic UPDATE query UPDATE TableName SET Column1 = 'NewValue1', Column2 = 'NewValue2' WHERE -- Add your conditions Id = 1;`, delete: `-- Basic DELETE query DELETE FROM TableName WHERE -- Add your conditions Id = 1;`, 'create-procedure': `-- Create Stored Procedure CREATE PROCEDURE [dbo].[ProcedureName] @Parameter1 INT, @Parameter2 NVARCHAR(100) AS BEGIN SET NOCOUNT ON; -- Add your logic here SELECT Column1, Column2 FROM TableName WHERE Column1 = @Parameter1 AND Column2 = @Parameter2; END GO`, 'create-view': `-- Create View CREATE VIEW [dbo].[ViewName] AS SELECT t1.Column1, t1.Column2, t2.Column3 FROM TableName1 t1 INNER JOIN TableName2 t2 ON t1.Id = t2.TableName1Id WHERE t1.IsActive = 1; GO`, 'create-scalar-function': `-- Create Scalar Function CREATE FUNCTION [dbo].[ScalarFunctionName] ( @Parameter1 INT, @Parameter2 NVARCHAR(100) ) RETURNS NVARCHAR(200) AS BEGIN DECLARE @Result NVARCHAR(200); -- Add your logic here SELECT @Result = Column1 + ' ' + @Parameter2 FROM TableName WHERE Id = @Parameter1; RETURN @Result; END GO`, 'create-table-function': `-- Create Table-Valued Function CREATE FUNCTION [dbo].[TableFunctionName] ( @Parameter1 INT, @Parameter2 NVARCHAR(100) ) RETURNS TABLE AS RETURN ( SELECT t.Column1, t.Column2, t.Column3, t.Column4 FROM TableName t WHERE t.Id = @Parameter1 AND t.Column2 LIKE '%' + @Parameter2 + '%' AND t.IsActive = 1 ) GO`, } return templates[templateType] || templates['select'] } const applyTemplate = useCallback((templateContent: string) => { setState((prev) => ({ ...prev, editorContent: templateContent, executionResult: null, isDirty: false, })) }, []) const handleUseTemplateFromDialog = useCallback( (templateContent: string, templateType: string) => { // Check if editor has content const hasUserContent = state.editorContent?.trim() && state.isDirty if (hasUserContent) { // Ask for confirmation setPendingTemplate({ content: templateContent, type: templateType }) setShowTemplateConfirmDialog(true) } else { // Apply template directly applyTemplate(templateContent) } }, [state.editorContent, state.isDirty, applyTemplate], ) const handleTemplateSelect = useCallback( (template: string, templateType: string) => { // If template is already provided (e.g., from table click), use it const templateContent = template || getTemplateContent(templateType) // Check if editor has content and it's not from a previous template const hasUserContent = state.editorContent?.trim() && state.isDirty if (hasUserContent) { // Ask for confirmation setPendingTemplate({ content: templateContent, type: templateType }) setShowTemplateConfirmDialog(true) } else { // Apply template directly applyTemplate(templateContent) } }, [translate, state.editorContent, state.isDirty, applyTemplate], ) const handleConfirmTemplateReplace = useCallback(() => { if (pendingTemplate) { applyTemplate(pendingTemplate.content) } setShowTemplateConfirmDialog(false) setPendingTemplate(null) }, [pendingTemplate, applyTemplate]) const handleCancelTemplateReplace = useCallback(() => { setShowTemplateConfirmDialog(false) setPendingTemplate(null) }, []) const handleNewQuery = useCallback(() => { setState((prev) => ({ ...prev, editorContent: '', executionResult: null, tableColumns: null, isDirty: false, })) }, []) const handleExecute = async () => { if (!state.selectedDataSource) { toast.push( {translate('::App.Platform.PleaseSelectDataSource')} , { placement: 'top-center' }, ) return } // Seçili text varsa onu, yoksa tüm editor içeriğini kullan const selectedText = editorRef.current?.getSelectedText() || '' const queryToExecute = selectedText.trim() || state.editorContent?.trim() || '' if (!queryToExecute) { toast.push( {translate('::App.Platform.PleaseEnterQuery')} , { placement: 'top-center' }, ) return } // Seçili metni koru const savedSelection = editorRef.current?.preserveSelection() setState((prev) => ({ ...prev, isExecuting: true, executionResult: null, tableColumns: null })) try { const result = await sqlObjectManagerService.executeQuery({ queryText: queryToExecute, dataSourceCode: state.selectedDataSource || '', }) setState((prev) => ({ ...prev, executionResult: result.data, isExecuting: false, tableColumns: null, })) // Seçili metni geri yükle setTimeout(() => { if (savedSelection) { editorRef.current?.restoreSelection(savedSelection) } }, 100) } catch (error: any) { setState((prev) => ({ ...prev, isExecuting: false })) // Hata durumunda da seçili metni geri yükle setTimeout(() => { if (savedSelection) { editorRef.current?.restoreSelection(savedSelection) } }, 100) toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToExecuteQuery')} , { placement: 'top-center' }, ) } } const handleViewDefinition = async (schemaName: string, objectName: string) => { if (!state.selectedDataSource) return try { const result = await sqlObjectManagerService.getNativeObjectDefinition( state.selectedDataSource, schemaName, objectName, ) if (result.data) { const definition = result.data.replace(/\bCREATE\b/i, 'ALTER') setState((prev) => ({ ...prev, editorContent: definition, executionResult: null, tableColumns: null, isDirty: false, })) } } catch (error: any) { toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToLoadDefinition')} , { placement: 'top-center' }, ) } } return (
{/* Toolbar */}
{/* Main Content Area */}
{/* Left Panel - Object Explorer */}
{translate('::App.Platform.ObjectExplorer')}
{/* Center Panel - Editor and Results */}
{state.executionResult || state.tableColumns ? (
{translate('::App.Platform.QueryEditor')}
{state.executionResult?.message || state.tableColumns?.message || translate('::App.Platform.QueryExecutedSuccessfully')}
{translate('::App.Platform.Rows')}:{' '} {state.executionResult?.rowsAffected || state.executionResult?.data?.length || state.tableColumns?.rowsAffected || 0} {state.executionResult && ( {translate('::App.Platform.Time')}:{' '} {state.executionResult.executionTimeMs}ms )}
) : (
{translate('::App.Platform.QueryEditor')}
)}
{/* Template Confirmation Dialog */}
{translate('::App.Platform.ConfirmTemplateReplace')}

{translate('::App.Platform.TemplateReplaceWarning')}

{/* Table Designer Dialog */} { setShowTableDesignerDialog(false) setDesignTableData(null) }} dataSource={state.selectedDataSource ?? ''} initialTableData={designTableData} onDeployed={() => { setState((prev) => ({ ...prev, refreshTrigger: prev.refreshTrigger + 1 })) }} />
) } export default SqlQueryManager