import { useState, useCallback, useEffect } 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, SqlObjectType, SqlQueryExecutionResultDto, } from '@/proxy/sql-query-manager/models' import { sqlObjectManagerService, } from '@/services/sql-query-manager.service' import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' import SqlObjectExplorer from './components/SqlObjectExplorer' import SqlEditor from './components/SqlEditor' import SqlResultsGrid from './components/SqlResultsGrid' import SqlObjectProperties from './components/SqlObjectProperties' import { FaCloudUploadAlt } from 'react-icons/fa' 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 } const SqlQueryManager = () => { const { translate } = useLocalization() const [state, setState] = useState({ dataSources: [], selectedDataSource: null, selectedObject: null, selectedObjectType: null, editorContent: '', isExecuting: false, executionResult: null, showProperties: false, isDirty: false, }) const [showSaveDialog, setShowSaveDialog] = useState(false) const [saveDialogData, setSaveDialogData] = useState({ name: '', description: '' }) 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, isDirty: false, })) }, [state.isDirty, translate], ) 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, selectedObject: null, selectedObjectType: null, executionResult: null, isDirty: false, })) toast.push( {translate('::App.Platform.TemplateLoaded')} , { placement: 'top-center' }, ) }, [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 } if (!state.editorContent.trim()) { toast.push( {translate('::App.Platform.PleaseEnterQuery')} , { placement: 'top-center' }, ) return } setState((prev) => ({ ...prev, isExecuting: true, executionResult: null })) try { const result = await sqlObjectManagerService.executeQuery({ queryText: state.editorContent, dataSourceCode: state.selectedDataSource.code || '', }) setState((prev) => ({ ...prev, executionResult: result.data, isExecuting: false })) toast.push( {translate('::App.Platform.QueryExecutedSuccessfully')} ({result.data.executionTimeMs}ms) , { placement: 'top-center' }, ) } catch (error: any) { setState((prev) => ({ ...prev, isExecuting: false })) 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 await handleUpdate() } else { // Create new object - show dialog to choose type setSaveDialogData({ name: '', description: '' }) setShowSaveDialog(true) } } const handleUpdate = async () => { if (!state.selectedObject || !state.selectedObjectType || !state.selectedDataSource) return if (!state.selectedObject.id) return try { const objectId = state.selectedObject.id switch (state.selectedObjectType) { case 1: // Query await sqlObjectManagerService.updateQuery(objectId, { ...(state.selectedObject as SqlQueryDto), queryText: state.editorContent, }) break case 2: // Stored Procedure await sqlObjectManagerService.updateStoredProcedure(objectId, { displayName: (state.selectedObject as SqlStoredProcedureDto).displayName, description: (state.selectedObject as SqlStoredProcedureDto).description, procedureBody: state.editorContent, category: (state.selectedObject as SqlStoredProcedureDto).category, parameters: (state.selectedObject as SqlStoredProcedureDto).parameters, }) break case 3: // View await sqlObjectManagerService.updateView(objectId, { displayName: (state.selectedObject as SqlViewDto).displayName, description: (state.selectedObject as SqlViewDto).description, viewDefinition: state.editorContent, category: (state.selectedObject as SqlViewDto).category, withSchemaBinding: (state.selectedObject as SqlViewDto).withSchemaBinding, }) break case 4: // Function await sqlObjectManagerService.updateFunction(objectId, { displayName: (state.selectedObject as SqlFunctionDto).displayName, description: (state.selectedObject as SqlFunctionDto).description, functionBody: state.editorContent, returnType: (state.selectedObject as SqlFunctionDto).returnType, category: (state.selectedObject as SqlFunctionDto).category, parameters: (state.selectedObject as SqlFunctionDto).parameters, }) break } setState((prev) => ({ ...prev, isDirty: false })) toast.push( {translate('::App.Platform.ObjectUpdatedSuccessfully')} , { placement: 'top-center' }, ) } catch (error: any) { toast.push( {error.response?.data?.error?.message || translate('::App.Platform.FailedToUpdateObject')} , { placement: 'top-center' }, ) } } const handleCreateNewQuery = async () => { if (!state.selectedDataSource || !saveDialogData.name) return try { await sqlObjectManagerService.createQuery({ code: saveDialogData.name.replace(/\s+/g, '_'), name: saveDialogData.name, description: saveDialogData.description, queryText: state.editorContent, dataSourceCode: state.selectedDataSource.code || '', category: '', tags: '', isModifyingData: false, parameters: '', }) setState((prev) => ({ ...prev, isDirty: false })) setShowSaveDialog(false) toast.push( {translate('::App.Platform.QuerySavedSuccessfully')} , { 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 2: // Stored Procedure result = await sqlObjectManagerService.deployStoredProcedure({ id: objectId, dropIfExists: true }) break case 3: // View result = await sqlObjectManagerService.deployView({ id: objectId, dropIfExists: true }) break case 4: // Function result = await sqlObjectManagerService.deployFunction({ id: objectId, dropIfExists: true }) break default: toast.push( {translate('::App.Platform.ThisObjectTypeCannotBeDeployed')} , { placement: 'top-center' }, ) return } 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' }, ) } } return (
{/* Toolbar */}
{translate('::App.Platform.DataSource')}:
{state.selectedObject && state.selectedObjectType && state.selectedObjectType !== 1 && ( )}
{/* Main Content Area */}
{/* Left Panel - Object Explorer */}
{translate('::App.Platform.ObjectExplorer')}
{/* Center Panel - Editor and Results */}
{translate('::App.Platform.QueryEditor')}
{state.executionResult && (
{translate('::App.Platform.Results')}
)}
{/* 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.SaveAsNewQuery')}
setSaveDialogData((prev) => ({ ...prev, name: e.target.value }))} />
setSaveDialogData((prev) => ({ ...prev, description: e.target.value })) } />
) } export default SqlQueryManager