import { useState, useCallback, useEffect, useRef } from 'react' import { Button, Dialog, Input, Notification, toast } from '@/components/ui' import Container from '@/components/shared/Container' import AdaptableCard from '@/components/shared/AdaptableCard' import { getDataSources } from '@/services/data-source.service' import type { DataSourceDto } from '@/proxy/data-source' import type { SqlFunctionDto, SqlQueryDto, SqlStoredProcedureDto, SqlViewDto, SqlQueryExecutionResultDto, } from '@/proxy/sql-query-manager/models' import { SqlObjectType } from '@/proxy/sql-query-manager/models' import { sqlObjectManagerService } from '@/services/sql-query-manager.service' import { FaDatabase, FaPlay, FaSave, FaSyncAlt, FaCloudUploadAlt } from 'react-icons/fa' import { HiOutlineCheckCircle } from 'react-icons/hi' import { useLocalization } from '@/utils/hooks/useLocalization' import SqlObjectExplorer from './components/SqlObjectExplorer' import SqlEditor, { SqlEditorRef } from './components/SqlEditor' import SqlResultsGrid from './components/SqlResultsGrid' import SqlObjectProperties from './components/SqlObjectProperties' import { Splitter } from '@/components/codeLayout/Splitter' import { Helmet } from 'react-helmet' export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto interface SqlManagerState { dataSources: DataSourceDto[] selectedDataSource: DataSourceDto | null selectedObject: SqlObject | null selectedObjectType: SqlObjectType | null editorContent: string isExecuting: boolean executionResult: SqlQueryExecutionResultDto | null showProperties: boolean isDirty: boolean tableColumns: any | null isSaved: boolean refreshTrigger: number } const SqlQueryManager = () => { const { translate } = useLocalization() const editorRef = useRef(null) const [state, setState] = useState({ dataSources: [], selectedDataSource: null, selectedObject: null, selectedObjectType: null, editorContent: '', isExecuting: false, refreshTrigger: 0, executionResult: null, showProperties: false, isDirty: false, tableColumns: null, isSaved: false, }) const [showSaveDialog, setShowSaveDialog] = useState(false) const [saveDialogData, setSaveDialogData] = useState({ name: '', description: '', detectedType: '', detectedName: '', isExistingObject: false, }) const [showTemplateConfirmDialog, setShowTemplateConfirmDialog] = useState(false) const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: 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], })) } } catch (error) { toast.push( {translate('::App.Platform.FailedtoloadDatasources')} , { placement: 'top-center' }, ) } } const handleDataSourceChange = useCallback((dataSource: DataSourceDto) => { setState((prev) => ({ ...prev, selectedDataSource: dataSource, selectedObject: null, editorContent: '', executionResult: null, isDirty: false, })) }, []) const handleObjectSelect = useCallback( (object: SqlObject | null, objectType: SqlObjectType | null) => { if (state.isDirty) { if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) { return } } let content = '' if (object) { if (objectType === 1) { // Query content = (object as SqlQueryDto).queryText } else if (objectType === 2) { // Stored Procedure content = (object as SqlStoredProcedureDto).procedureBody } else if (objectType === 3) { // View content = (object as SqlViewDto).viewDefinition } else if (objectType === 4) { // Function content = (object as SqlFunctionDto).functionBody } } setState((prev) => ({ ...prev, selectedObject: object, selectedObjectType: objectType, editorContent: content, executionResult: null, tableColumns: null, isDirty: false, isSaved: false, })) }, [state.isDirty, translate], ) const handleEditorChange = useCallback((value: string | undefined) => { setState((prev) => ({ ...prev, editorContent: value || '', isDirty: true, isSaved: false, })) }, []) 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'] } // SQL analiz fonksiyonu - SQL metnini analiz edip nesne türünü ve adını tespit eder const detectSqlObject = (sql: string): { type: string; name: string } => { const upperSql = sql.trim().toUpperCase() // VIEW tespiti if (upperSql.includes('CREATE VIEW') || upperSql.includes('ALTER VIEW')) { // Son kelimeyi al (schema varsa sonraki kelime, yoksa ilk kelime) const viewMatch = sql.match( /(?:CREATE|ALTER)\s+VIEW\s+(?:[\[\]]*\w+[\[\]]*\.)?\s*[\[]?(\w+)[\]]?/i, ) return { type: 'View', name: viewMatch ? viewMatch[1] : '', } } // STORED PROCEDURE tespiti if ( upperSql.includes('CREATE PROCEDURE') || upperSql.includes('CREATE PROC') || upperSql.includes('ALTER PROCEDURE') || upperSql.includes('ALTER PROC') ) { const procMatch = sql.match( /(?:CREATE|ALTER)\s+(?:PROCEDURE|PROC)\s+(?:[\[\]]*\w+[\[\]]*\.)?\s*[\[]?(\w+)[\]]?/i, ) return { type: 'StoredProcedure', name: procMatch ? procMatch[1] : '', } } // FUNCTION tespiti if (upperSql.includes('CREATE FUNCTION') || upperSql.includes('ALTER FUNCTION')) { const funcMatch = sql.match( /(?:CREATE|ALTER)\s+FUNCTION\s+(?:[\[\]]*\w+[\[\]]*\.)?\s*[\[]?(\w+)[\]]?/i, ) return { type: 'Function', name: funcMatch ? funcMatch[1] : '', } } // Default: Query return { type: 'Query', name: '', } } const applyTemplate = useCallback( (templateContent: string) => { setState((prev) => ({ ...prev, editorContent: templateContent, selectedObject: null, selectedObjectType: null, executionResult: null, isDirty: false, })) }, [translate], ) 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 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.code || '', }) 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 handleSave = async () => { if (!state.selectedDataSource) { toast.push( {translate('::App.Platform.PleaseSelectDataSource')} , { placement: 'top-center' }, ) return } if (!state.editorContent.trim()) { toast.push( {translate('::App.Platform.PleaseEnterContentToSave')} , { placement: 'top-center' }, ) return } if (state.selectedObject && state.selectedObjectType) { // Update existing object - open dialog with existing data const typeMap: Record = { [SqlObjectType.Query]: 'Query', [SqlObjectType.View]: 'View', [SqlObjectType.StoredProcedure]: 'StoredProcedure', [SqlObjectType.Function]: 'Function', } // Get name based on object type let objectName = '' if ('viewName' in state.selectedObject) { objectName = state.selectedObject.viewName || state.selectedObject.displayName || '' } else if ('procedureName' in state.selectedObject) { objectName = state.selectedObject.procedureName || state.selectedObject.displayName || '' } else if ('functionName' in state.selectedObject) { objectName = state.selectedObject.functionName || state.selectedObject.displayName || '' } else if ('name' in state.selectedObject) { objectName = state.selectedObject.name || '' } setSaveDialogData({ name: objectName, description: state.selectedObject.description || '', detectedType: typeMap[state.selectedObjectType] || '', detectedName: objectName, isExistingObject: true, }) setShowSaveDialog(true) } else { // New object - analyze SQL and show dialog with detection const detection = detectSqlObject(state.editorContent) setSaveDialogData({ name: detection.name, description: '', detectedType: detection.type, detectedName: detection.name, isExistingObject: false, }) setShowSaveDialog(true) } } const handleCreateNewQuery = async () => { if (!state.selectedDataSource || !saveDialogData.name) return try { // Smart save ile kaydet const result = await sqlObjectManagerService.smartSave({ sqlText: state.editorContent, dataSourceCode: state.selectedDataSource.code || '', name: saveDialogData.name, description: saveDialogData.description, }) // Kaydedilen objeyi state'e set et const savedObject: any = { id: result.data.objectId, displayName: saveDialogData.name, description: saveDialogData.description, isDeployed: result.data.deployed, } // ObjectType'a göre ekstra alanlar ekle let objectType: SqlObjectType | null = null if (result.data.objectType === 'View') { objectType = SqlObjectType.View savedObject.viewName = saveDialogData.name savedObject.viewDefinition = state.editorContent } else if (result.data.objectType === 'StoredProcedure') { objectType = SqlObjectType.StoredProcedure savedObject.procedureName = saveDialogData.name savedObject.procedureBody = state.editorContent } else if (result.data.objectType === 'Function') { objectType = SqlObjectType.Function savedObject.functionName = saveDialogData.name savedObject.functionBody = state.editorContent } else if (result.data.objectType === 'Query') { objectType = SqlObjectType.Query savedObject.queryText = state.editorContent } setState((prev) => ({ ...prev, isDirty: false, isSaved: true, selectedObject: savedObject, selectedObjectType: objectType, refreshTrigger: prev.refreshTrigger + 1, })) setShowSaveDialog(false) toast.push( {result.data.message || translate('::App.Platform.SavedSuccessfully')} , { placement: 'top-center' }, ) } catch (error: any) { toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToSaveQuery')} , { placement: 'top-center' }, ) } } const handleDeploy = async () => { if (!state.selectedObject || !state.selectedObjectType) { toast.push( {translate('::App.Platform.PleaseSelectAnObjectToDeploy')} , { placement: 'top-center' }, ) return } if (!state.selectedObject.id) return try { const objectId = state.selectedObject.id let result: any switch (state.selectedObjectType) { case SqlObjectType.StoredProcedure: result = await sqlObjectManagerService.deployStoredProcedure({ id: objectId, dropIfExists: true, }) break case SqlObjectType.View: result = await sqlObjectManagerService.deployView({ id: objectId, dropIfExists: true }) break case SqlObjectType.Function: result = await sqlObjectManagerService.deployFunction({ id: objectId, dropIfExists: true, }) break default: toast.push( {translate('::App.Platform.ThisObjectTypeCannotBeDeployed')} , { placement: 'top-center' }, ) return } // Update selectedObject's isDeployed status setState((prev) => ({ ...prev, selectedObject: prev.selectedObject ? { ...prev.selectedObject, isDeployed: true } : null, refreshTrigger: prev.refreshTrigger + 1, })) toast.push( {translate('::App.Platform.ObjectDeployedSuccessfully')} , { placement: 'top-center' }, ) } catch (error: any) { toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToDeployObject')} , { placement: 'top-center' }, ) } } const handleShowTableColumns = async (schemaName: string, tableName: string) => { if (!state.selectedDataSource) return try { const response = await sqlObjectManagerService.getTableColumns( state.selectedDataSource.code || '', schemaName, tableName, ) // Transform API response to match display format const transformedData = response.data.map((col: any) => ({ ColumnName: col.columnName, DataType: col.dataType, MaxLength: col.maxLength || '-', IsNullable: col.isNullable, IsPrimaryKey: col.isPrimaryKey || false, })) // Create a result object that looks like execution result for display const columnsResult = { success: true, message: `Columns for ${schemaName}.${tableName}`, data: transformedData, rowsAffected: transformedData.length, executionTimeMs: 0, metadata: { columns: [ { name: 'ColumnName', dataType: 'string' }, { name: 'DataType', dataType: 'string' }, { name: 'MaxLength', dataType: 'string' }, { name: 'IsNullable', dataType: 'boolean' }, { name: 'IsPrimaryKey', dataType: 'boolean' }, ], }, } setState((prev) => ({ ...prev, tableColumns: columnsResult, executionResult: null, // Clear query results when showing columns })) } catch (error: any) { toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToLoadColumns')} , { placement: 'top-center' }, ) } } return (
{/* Toolbar */}
{translate('::App.Platform.DataSource')}:
{/* 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')}
)}
{/* Right Panel - Properties (Optional) */} {state.showProperties && state.selectedObject && (
)}
{/* Template Confirmation Dialog */}
{translate('::App.Platform.ConfirmTemplateReplace')}

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

{/* Save Dialog */} setShowSaveDialog(false)} onRequestClose={() => setShowSaveDialog(false)} >
{translate('::App.Platform.SaveQuery')}
{/* Detected Object Type */} {saveDialogData.detectedType && (
{translate('::App.Platform.DetectedObjectType')}
{saveDialogData.detectedType === 'View' && translate('::App.Platform.View')} {saveDialogData.detectedType === 'StoredProcedure' && translate('::App.Platform.StoredProcedure')} {saveDialogData.detectedType === 'Function' && translate('::App.Platform.Function')} {saveDialogData.detectedType === 'Query' && translate('::App.Platform.Query')}
{saveDialogData.detectedName && (
{translate('::App.Platform.DetectedName')}:{' '} {saveDialogData.detectedName}
)}
)}
setSaveDialogData((prev) => ({ ...prev, name: e.target.value }))} placeholder={saveDialogData.detectedName || translate('::App.Platform.Name')} invalid={!saveDialogData.name.trim()} disabled={saveDialogData.isExistingObject} />
setSaveDialogData((prev) => ({ ...prev, description: e.target.value })) } placeholder={translate('::App.Platform.Description')} />
) } export default SqlQueryManager