2026-02-24 20:44:16 +00:00
import { useState , useCallback , useEffect , useRef } from 'react'
2026-03-02 18:31:49 +00:00
import { Button , Dialog , Notification , toast } from '@/components/ui'
2026-02-24 20:44:16 +00:00
import Container from '@/components/shared/Container'
2026-04-30 10:09:05 +00:00
import ConfirmDialog from '@/components/shared/ConfirmDialog'
2026-02-24 20:44:16 +00:00
import { getDataSources } from '@/services/data-source.service'
import type { DataSourceDto } from '@/proxy/data-source'
2026-05-25 14:31:54 +00:00
import { DataSourceTypeEnum } from '@/proxy/form/models'
2026-03-02 18:31:49 +00:00
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
2026-02-24 20:44:16 +00:00
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
2026-05-03 09:52:22 +00:00
import { FaDatabase , FaPlay , FaFileAlt , FaCopy , FaExclamationTriangle } from 'react-icons/fa'
2026-02-24 20:44:16 +00:00
import { FaCheckCircle } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization'
2026-03-18 09:00:11 +00:00
import SqlObjectExplorer , { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
2026-03-01 20:43:25 +00:00
import SqlEditor , { SqlEditorRef } from './SqlEditor'
import SqlResultsGrid from './SqlResultsGrid'
import SqlTableDesignerDialog from './SqlTableDesignerDialog'
2026-02-24 20:44:16 +00:00
import { Splitter } from '@/components/codeLayout/Splitter'
import { Helmet } from 'react-helmet'
import { useStoreState } from '@/store/store'
import { APP_NAME } from '@/constants/app.constant'
2026-04-30 10:09:05 +00:00
import { UiEvalService } from '@/services/UiEvalService'
import { FcAcceptDatabase } from 'react-icons/fc'
2026-05-03 21:35:04 +00:00
import { FaFolderOpen } from "react-icons/fa"
2026-02-24 20:44:16 +00:00
interface SqlManagerState {
dataSources : DataSourceDto [ ]
selectedDataSource : string | null
editorContent : string
isExecuting : boolean
executionResult : SqlQueryExecutionResultDto | null
showProperties : boolean
isDirty : boolean
tableColumns : any | null
refreshTrigger : number
}
2026-03-18 09:00:11 +00:00
interface SqlCopyResultItem {
targetDataSource : string
objectFullName : string
objectType : SqlExplorerSelectedObject [ 'objectType' ]
status : 'success' | 'error' | 'skipped'
message : string
}
2026-02-24 20:44:16 +00:00
const SqlQueryManager = ( ) = > {
const { translate } = useLocalization ( )
const editorRef = useRef < SqlEditorRef > ( null )
const tenantName = useStoreState ( ( state ) = > state . locale . currentTenantName )
const [ state , setState ] = useState < SqlManagerState > ( {
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 ,
)
2026-03-01 20:43:25 +00:00
const [ showTableDesignerDialog , setShowTableDesignerDialog ] = useState ( false )
2026-03-02 18:31:49 +00:00
const [ designTableData , setDesignTableData ] = useState < {
schemaName : string
tableName : string
} | null > ( null )
2026-03-18 09:00:11 +00:00
const [ selectedExplorerObjects , setSelectedExplorerObjects ] = useState <
SqlExplorerSelectedObject [ ]
> ( [ ] )
2026-04-30 10:09:05 +00:00
const [ showDbMigrateConfirmDialog , setShowDbMigrateConfirmDialog ] = useState ( false )
2026-03-18 09:00:11 +00:00
const [ showCopyDialog , setShowCopyDialog ] = useState ( false )
const [ copyTargetDataSources , setCopyTargetDataSources ] = useState < string [ ] > ( [ ] )
const [ overwriteIfExists , setOverwriteIfExists ] = useState ( false )
const [ isCopyingObjects , setIsCopyingObjects ] = useState ( false )
const [ copyResults , setCopyResults ] = useState < SqlCopyResultItem [ ] > ( [ ] )
const [ showCopyResultDialog , setShowCopyResultDialog ] = useState ( false )
2026-03-18 18:53:51 +00:00
const [ copyDialogMode , setCopyDialogMode ] = useState < 'objects' | 'sql' > ( 'objects' )
const [ sqlScriptForCopy , setSqlScriptForCopy ] = useState ( '' )
2026-05-03 21:35:04 +00:00
const [ showSqlDataFilesDialog , setShowSqlDataFilesDialog ] = useState ( false )
const [ isLoadingSqlDataFiles , setIsLoadingSqlDataFiles ] = useState ( false )
const [ sqlDataFiles , setSqlDataFiles ] = useState < { fileName : string ; createdAt : string } [ ] > ( [ ] )
2026-03-01 17:40:25 +00:00
2026-02-24 20:44:16 +00:00
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 (
< Notification type = "danger" title = { translate ( '::App.Platform.Error' ) } >
{ translate ( '::App.Platform.FailedtoloadDatasources' ) }
< / Notification > ,
{ placement : 'top-center' } ,
)
}
}
const handleDataSourceChange = useCallback ( ( dataSource : DataSourceDto ) = > {
setState ( ( prev ) = > ( {
. . . prev ,
selectedDataSource : dataSource.code ? ? null ,
editorContent : '' ,
executionResult : null ,
isDirty : false ,
} ) )
} , [ ] )
2026-03-01 20:43:25 +00:00
const handleNewTable = useCallback ( ( ) = > {
setDesignTableData ( null )
setShowTableDesignerDialog ( true )
} , [ ] )
const handleDesignTable = useCallback ( ( schemaName : string , tableName : string ) = > {
setDesignTableData ( { schemaName , tableName } )
setShowTableDesignerDialog ( true )
} , [ ] )
2026-02-24 20:44:16 +00:00
const handleEditorChange = useCallback ( ( value : string | undefined ) = > {
setState ( ( prev ) = > ( {
. . . prev ,
editorContent : value || '' ,
isDirty : true ,
} ) )
} , [ ] )
2026-03-18 09:00:11 +00:00
const escapeSqlLiteral = ( value : string ) = > value . replace ( /'/g , "''" )
const escapeSqlIdentifier = ( value : string ) = > value . replace ( /]/g , ']]' )
2026-05-25 14:31:54 +00:00
const escapePgIdentifier = ( value : string ) = > value . replace ( /"/g , '""' )
2026-03-18 09:00:11 +00:00
const getSafeFullName = ( schemaName : string , objectName : string ) = >
` [ ${ escapeSqlIdentifier ( schemaName ) } ].[ ${ escapeSqlIdentifier ( objectName ) } ] `
2026-05-25 14:31:54 +00:00
const getSafePgFullName = ( schemaName : string , objectName : string ) = >
` " ${ escapePgIdentifier ( schemaName ) } "." ${ escapePgIdentifier ( objectName ) } " `
const selectedDataSourceType = state . dataSources . find (
( item ) = > item . code === state . selectedDataSource ,
) ? . dataSourceType
const isPostgreSql = selectedDataSourceType === DataSourceTypeEnum . Postgresql
2026-03-18 09:00:11 +00:00
const buildTableScriptQuery = ( schemaName : string , tableName : string ) = > {
const fullName = getSafeFullName ( schemaName , tableName )
const escapedFullName = escapeSqlLiteral ( fullName )
return ` DECLARE @ObjectId INT = OBJECT_ID(N' ${ escapedFullName } ');
IF @ObjectId IS NULL
BEGIN
SELECT CAST ( '' AS NVARCHAR ( MAX ) ) AS Script ;
RETURN ;
END ;
; WITH cols AS
(
SELECT
c . column_id ,
2026-05-01 10:20:40 +00:00
' ' + QUOTENAME ( c . name ) + ' ' +
2026-03-18 09:00:11 +00:00
CASE
WHEN t . name IN ( 'varchar' , 'char' , 'varbinary' , 'binary' ) THEN
t . name + '(' + CASE WHEN c . max_length = - 1 THEN 'MAX' ELSE CAST ( c . max_length AS VARCHAR ( 10 ) ) END + ')'
WHEN t . name IN ( 'nvarchar' , 'nchar' ) THEN
t . name + '(' + CASE WHEN c . max_length = - 1 THEN 'MAX' ELSE CAST ( c . max_length / 2 AS VARCHAR ( 10 ) ) END + ')'
WHEN t . name IN ( 'decimal' , 'numeric' ) THEN
t . name + '(' + CAST ( c . precision AS VARCHAR ( 10 ) ) + ',' + CAST ( c . scale AS VARCHAR ( 10 ) ) + ')'
WHEN t . name IN ( 'datetime2' , 'datetimeoffset' , 'time' ) THEN
t . name + '(' + CAST ( c . scale AS VARCHAR ( 10 ) ) + ')'
ELSE t . name
END +
CASE
WHEN ic . object_id IS NOT NULL
THEN ' IDENTITY(' + CAST ( ic . seed_value AS VARCHAR ( 30 ) ) + ',' + CAST ( ic . increment_value AS VARCHAR ( 30 ) ) + ')'
ELSE ''
END +
CASE WHEN c . is_nullable = 1 THEN ' NULL' ELSE ' NOT NULL' END +
ISNULL ( ' DEFAULT ' + dc . definition , '' ) AS line
FROM sys . columns c
INNER JOIN sys . types t ON c . user_type_id = t . user_type_id
LEFT JOIN sys . identity_columns ic ON c . object_id = ic . object_id AND c . column_id = ic . column_id
LEFT JOIN sys . default_constraints dc ON c . default_object_id = dc . object_id
WHERE c . object_id = @ObjectId
) ,
pk AS
(
SELECT
2026-05-01 10:20:40 +00:00
' CONSTRAINT ' + QUOTENAME ( k . name ) + ' PRIMARY KEY ' +
2026-03-18 09:00:11 +00:00
CASE WHEN i . type = 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END +
2026-05-01 10:20:40 +00:00
CHAR ( 13 ) + CHAR ( 10 ) + ' (' + CHAR ( 13 ) + CHAR ( 10 ) +
(
SELECT ' ' + QUOTENAME ( c . name ) + CASE WHEN ic . is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END + CHAR ( 13 ) + CHAR ( 10 )
FROM sys . index_columns ic
INNER JOIN sys . columns c ON ic . object_id = c . object_id AND ic . column_id = c . column_id
WHERE ic . object_id = i . object_id
AND ic . index_id = i . index_id
AND ic . is_included_column = 0
ORDER BY ic . key_ordinal
FOR XML PATH ( '' ) , TYPE
) . value ( '.' , 'NVARCHAR(MAX)' ) +
' )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]' AS line
2026-03-18 09:00:11 +00:00
FROM sys . key_constraints k
INNER JOIN sys . indexes i ON k . parent_object_id = i . object_id AND k . unique_index_id = i . index_id
WHERE k . parent_object_id = @ObjectId
AND k . type = 'PK'
)
SELECT
2026-05-01 10:20:40 +00:00
'IF OBJECT_ID(N' '${escapedFullName}' ', ' 'U' ') IS NULL' + CHAR ( 13 ) + CHAR ( 10 ) +
'BEGIN' + CHAR ( 13 ) + CHAR ( 10 ) +
' CREATE TABLE ${fullName}' + CHAR ( 13 ) + CHAR ( 10 ) +
' (' + CHAR ( 13 ) + CHAR ( 10 ) +
2026-03-18 09:00:11 +00:00
STUFF (
(
SELECT ',' + CHAR ( 13 ) + CHAR ( 10 ) + line
FROM cols
ORDER BY column_id
FOR XML PATH ( '' ) , TYPE
) . value ( '.' , 'NVARCHAR(MAX)' )
, 1 , 3 , '' ) +
ISNULL (
(
SELECT ',' + CHAR ( 13 ) + CHAR ( 10 ) + line
FROM pk
) ,
''
2026-05-01 10:20:40 +00:00
) + CHAR ( 13 ) + CHAR ( 10 ) + ' ) ON [PRIMARY]' + CHAR ( 13 ) + CHAR ( 10 ) + 'END' + CHAR ( 13 ) + CHAR ( 10 ) + 'GO' AS Script ; `
2026-03-18 09:00:11 +00:00
}
const getTableCreateScript = async ( schemaName : string , tableName : string ) : Promise < string > = > {
if ( ! state . selectedDataSource ) return ''
2026-05-25 14:31:54 +00:00
const result = await sqlObjectManagerService . getTableCreateScript (
state . selectedDataSource ,
schemaName ,
tableName ,
)
2026-03-18 09:00:11 +00:00
2026-05-25 14:31:54 +00:00
return result . data || ''
2026-03-18 09:00:11 +00:00
}
const normalizeNativeDefinitionToCreate = ( definition : string ) = > {
if ( ! definition ? . trim ( ) ) return ''
2026-05-25 14:31:54 +00:00
if ( isPostgreSql ) return definition
2026-05-03 21:17:11 +00:00
return definition . replace ( /^\s*(?:CREATE|ALTER)\s+(?:OR\s+ALTER\s+)?/i , 'CREATE OR ALTER ' )
2026-03-18 09:00:11 +00:00
}
const buildDropIfExistsScript = ( obj : SqlExplorerSelectedObject ) = > {
2026-05-25 14:31:54 +00:00
if ( isPostgreSql ) {
const fullName = getSafePgFullName ( obj . schemaName , obj . objectName )
if ( obj . objectType === 'table' ) {
return ` DROP TABLE IF EXISTS ${ fullName } ; `
}
if ( obj . objectType === 'view' ) {
return ` DROP VIEW IF EXISTS ${ fullName } ; `
}
if ( obj . objectType === 'procedure' ) {
return ` DROP PROCEDURE IF EXISTS ${ fullName } ; `
}
return ` DROP FUNCTION IF EXISTS ${ fullName } ; `
}
2026-03-18 09:00:11 +00:00
const fullName = getSafeFullName ( obj . schemaName , obj . objectName )
if ( obj . objectType === 'table' ) {
return ` IF OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'U') IS NOT NULL DROP TABLE ${ fullName } ; `
}
if ( obj . objectType === 'view' ) {
return ` IF OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'V') IS NOT NULL DROP VIEW ${ fullName } ; `
}
if ( obj . objectType === 'procedure' ) {
return ` IF OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'P') IS NOT NULL DROP PROCEDURE ${ fullName } ; `
}
return ` IF OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'FN') IS NOT NULL OR OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'IF') IS NOT NULL OR OBJECT_ID(N' ${ escapeSqlLiteral ( fullName ) } ', N'TF') IS NOT NULL DROP FUNCTION ${ fullName } ; `
}
const buildObjectExistsCheckQuery = ( obj : SqlExplorerSelectedObject ) = > {
2026-05-25 14:31:54 +00:00
if ( isPostgreSql ) {
const schema = escapeSqlLiteral ( obj . schemaName )
const name = escapeSqlLiteral ( obj . objectName )
if ( obj . objectType === 'table' ) {
return ` SELECT CASE WHEN EXISTS (
SELECT 1 FROM information_schema . tables
WHERE table_schema = '${schema}' AND table_name = '${name}' AND table_type = 'BASE TABLE'
) THEN 1 ELSE 0 END AS "ExistsFlag" ; `
}
if ( obj . objectType === 'view' ) {
return ` SELECT CASE WHEN EXISTS (
SELECT 1 FROM information_schema . views
WHERE table_schema = '${schema}' AND table_name = '${name}'
) THEN 1 ELSE 0 END AS "ExistsFlag" ; `
}
const proKind = obj . objectType === 'procedure' ? 'p' : 'f'
return ` SELECT CASE WHEN EXISTS (
SELECT 1
FROM pg_proc p
INNER JOIN pg_namespace n ON n . oid = p . pronamespace
WHERE n . nspname = '${schema}' AND p . proname = '${name}' AND p . prokind = '${proKind}'
) THEN 1 ELSE 0 END AS "ExistsFlag" ; `
}
2026-03-18 09:00:11 +00:00
const fullName = getSafeFullName ( obj . schemaName , obj . objectName )
const escapedFullName = escapeSqlLiteral ( fullName )
if ( obj . objectType === 'table' ) {
return ` SELECT CASE WHEN OBJECT_ID(N' ${ escapedFullName } ', N'U') IS NOT NULL THEN 1 ELSE 0 END AS ExistsFlag; `
}
if ( obj . objectType === 'view' ) {
return ` SELECT CASE WHEN OBJECT_ID(N' ${ escapedFullName } ', N'V') IS NOT NULL THEN 1 ELSE 0 END AS ExistsFlag; `
}
if ( obj . objectType === 'procedure' ) {
return ` SELECT CASE WHEN OBJECT_ID(N' ${ escapedFullName } ', N'P') IS NOT NULL THEN 1 ELSE 0 END AS ExistsFlag; `
}
return ` SELECT CASE WHEN OBJECT_ID(N' ${ escapedFullName } ', N'FN') IS NOT NULL OR OBJECT_ID(N' ${ escapedFullName } ', N'IF') IS NOT NULL OR OBJECT_ID(N' ${ escapedFullName } ', N'TF') IS NOT NULL THEN 1 ELSE 0 END AS ExistsFlag; `
}
const checkObjectExistsInTarget = async (
targetDataSource : string ,
obj : SqlExplorerSelectedObject ,
) = > {
const result = await sqlObjectManagerService . executeQuery ( {
queryText : buildObjectExistsCheckQuery ( obj ) ,
dataSourceCode : targetDataSource ,
} )
const firstRow = result . data ? . data ? . [ 0 ]
const flag = firstRow ? . ExistsFlag ? ? firstRow ? . existsFlag ? ? 0
return Number ( flag ) === 1
}
2026-02-24 20:44:16 +00:00
const getTemplateContent = ( templateType : string ) : string = > {
2026-05-25 14:31:54 +00:00
if ( isPostgreSql ) {
const pgTemplates : Record < string , string > = {
select : ` -- Basic SELECT query
SELECT
"Column1" ,
"Column2" ,
"Column3"
FROM
"public" . "TableName"
WHERE
"Column1" = 'value'
ORDER BY
"Column1" ASC
LIMIT 100 ; ` ,
insert : ` -- Basic INSERT query
INSERT INTO "public" . "TableName" ( "Column1" , "Column2" , "Column3" )
VALUES
( 'Value1' , 'Value2' , 'Value3' ) ; ` ,
update : ` -- Basic UPDATE query
UPDATE "public" . "TableName"
SET
"Column1" = 'NewValue1' ,
"Column2" = 'NewValue2'
WHERE
"Id" = '00000000-0000-0000-0000-000000000000' ; ` ,
delete : ` -- Basic DELETE query
DELETE FROM "public" . "TableName"
WHERE
"Id" = '00000000-0000-0000-0000-000000000000' ; ` ,
'create-procedure' : ` -- Create Stored Procedure
CREATE OR REPLACE PROCEDURE "public" . "ProcedureName" (
"Parameter1" integer ,
"Parameter2" varchar
)
LANGUAGE plpgsql
AS $ $
BEGIN
-- Add your logic here
END ;
$ $ ; ` ,
'create-view' : ` -- Create View
CREATE OR REPLACE VIEW "public" . "ViewName" AS
SELECT
t1 . "Column1" ,
t1 . "Column2"
FROM "public" . "TableName1" t1
WHERE t1 . "IsActive" = TRUE ; ` ,
'create-scalar-function' : ` -- Create Scalar Function
CREATE OR REPLACE FUNCTION "public" . "ScalarFunctionName" (
"Parameter1" integer ,
"Parameter2" varchar
)
RETURNS varchar
LANGUAGE plpgsql
AS $ $
BEGIN
RETURN "Parameter2" ;
END ;
$ $ ; ` ,
'create-table-function' : ` -- Create Table-Valued Function
CREATE OR REPLACE FUNCTION "public" . "TableFunctionName" (
"Parameter1" integer
)
RETURNS TABLE ( "Column1" integer , "Column2" varchar )
LANGUAGE sql
AS $ $
SELECT t . "Column1" , t . "Column2"
FROM "public" . "TableName" t
WHERE t . "Id" = "Parameter1" ;
$ $ ; ` ,
}
return pgTemplates [ templateType ] || pgTemplates . select
}
2026-02-24 20:44:16 +00:00
const templates : Record < string , string > = {
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' ]
}
2026-03-02 18:31:49 +00:00
const applyTemplate = useCallback ( ( templateContent : string ) = > {
setState ( ( prev ) = > ( {
. . . prev ,
editorContent : templateContent ,
executionResult : null ,
isDirty : false ,
} ) )
} , [ ] )
2026-02-24 20:44:16 +00:00
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 )
} , [ ] )
2026-03-02 18:31:49 +00:00
const handleNewQuery = useCallback ( ( ) = > {
setState ( ( prev ) = > ( {
. . . prev ,
editorContent : '' ,
executionResult : null ,
tableColumns : null ,
isDirty : false ,
} ) )
} , [ ] )
2026-02-24 20:44:16 +00:00
const handleExecute = async ( ) = > {
if ( ! state . selectedDataSource ) {
toast . push (
< Notification type = "warning" title = { translate ( '::App.Platform.Warning' ) } >
{ translate ( '::App.Platform.PleaseSelectDataSource' ) }
< / Notification > ,
{ 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 (
< Notification type = "warning" title = { translate ( '::App.Platform.Warning' ) } >
{ translate ( '::App.Platform.PleaseEnterQuery' ) }
< / Notification > ,
{ 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 (
< Notification type = "danger" title = { translate ( '::App.Platform.Error' ) } >
{ error . response ? . data ? . error ? . message || translate ( '::App.Platform.FailedToExecuteQuery' ) }
< / Notification > ,
{ placement : 'top-center' } ,
)
}
}
2026-03-02 18:31:49 +00:00
const handleViewDefinition = async ( schemaName : string , objectName : string ) = > {
2026-02-24 20:44:16 +00:00
if ( ! state . selectedDataSource ) return
try {
2026-03-02 18:31:49 +00:00
const result = await sqlObjectManagerService . getNativeObjectDefinition (
state . selectedDataSource ,
2026-02-24 20:44:16 +00:00
schemaName ,
2026-03-02 18:31:49 +00:00
objectName ,
2026-02-24 20:44:16 +00:00
)
2026-03-02 18:31:49 +00:00
if ( result . data ) {
2026-05-03 21:17:11 +00:00
const definition = normalizeNativeDefinitionToCreate ( result . data )
2026-03-02 18:31:49 +00:00
setState ( ( prev ) = > ( {
. . . prev ,
editorContent : definition ,
executionResult : null ,
tableColumns : null ,
isDirty : false ,
} ) )
2026-02-24 20:44:16 +00:00
}
} catch ( error : any ) {
toast . push (
< Notification type = "danger" title = { translate ( '::App.Platform.Error' ) } >
2026-03-02 18:31:49 +00:00
{ error . response ? . data ? . error ? . message ||
translate ( '::App.Platform.FailedToLoadDefinition' ) }
2026-02-24 20:44:16 +00:00
< / Notification > ,
{ placement : 'top-center' } ,
)
}
}
2026-03-18 09:00:11 +00:00
const handleGenerateTableScript = async ( schemaName : string , tableName : string ) = > {
if ( ! state . selectedDataSource ) return
try {
const script = await getTableCreateScript ( schemaName , tableName )
if ( ! script ? . trim ( ) ) {
toast . push (
< Notification type = "warning" title = { translate ( '::App.Platform.Warning' ) } >
{ translate ( '::App.Platform.ScriptNotGenerated' ) || 'Tablo scripti olusturulamadi.' }
< / Notification > ,
{ placement : 'top-center' } ,
)
return
}
setState ( ( prev ) = > ( {
. . . prev ,
editorContent : script ,
executionResult : null ,
tableColumns : null ,
isDirty : false ,
} ) )
toast . push (
< Notification type = "success" title = { translate ( '::App.Platform.Success' ) || 'Basarili' } >
{ 'Script Query Editor alanina yuklendi.' }
< / Notification > ,
{ placement : 'top-center' } ,
)
} catch ( error : any ) {
toast . push (
< Notification type = "danger" title = { translate ( '::App.Platform.Error' ) } >
{ error . response ? . data ? . error ? . message || 'Tablo scripti olusturulurken hata olustu.' }
< / Notification > ,
{ placement : 'top-center' } ,
)
}
}
const handleOpenCopyDialog = ( ) = > {
if ( ! state . selectedDataSource ) return
2026-03-18 18:53:51 +00:00
setCopyDialogMode ( 'objects' )
2026-03-18 09:00:11 +00:00
setCopyTargetDataSources ( [ ] )
setOverwriteIfExists ( false )
2026-03-18 18:53:51 +00:00
setSqlScriptForCopy ( '' )
2026-03-18 09:00:11 +00:00
setShowCopyDialog ( true )
2026-03-18 18:53:51 +00:00
// Eğer seçili obje yoksa uyarı göster
if ( selectedExplorerObjects . length === 0 ) {
// SQL mode'da obje seçimi zorunlu değil, object mode'da zorunlu
// Bu uyarı sadece object mode'da gerekirse
return
}
2026-03-18 09:00:11 +00:00
}
const handleCopyObjects = async ( ) = > {
if (
! state . selectedDataSource ||
selectedExplorerObjects . length === 0 ||
copyTargetDataSources . length === 0
) {
return
}
setIsCopyingObjects ( true )
const results : SqlCopyResultItem [ ] = [ ]
try {
const scriptsByObjectId = new Map < string , string > ( )
for ( const obj of selectedExplorerObjects ) {
let createScript = ''
if ( obj . objectType === 'table' ) {
createScript = await getTableCreateScript ( obj . schemaName , obj . objectName )
} else {
const response = await sqlObjectManagerService . getNativeObjectDefinition (
state . selectedDataSource ,
obj . schemaName ,
obj . objectName ,
)
createScript = normalizeNativeDefinitionToCreate ( response . data || '' )
}
if ( ! createScript ? . trim ( ) ) {
results . push ( {
targetDataSource : state.selectedDataSource ,
objectFullName : obj.fullName ,
objectType : obj.objectType ,
status : 'error' ,
2026-03-18 18:53:51 +00:00
message : translate ( '::App.Platform.CreateScriptFailed' ) ,
2026-03-18 09:00:11 +00:00
} )
continue
}
scriptsByObjectId . set ( obj . id , createScript )
}
for ( const targetDataSource of copyTargetDataSources ) {
for ( const obj of selectedExplorerObjects ) {
const createScript = scriptsByObjectId . get ( obj . id )
if ( ! createScript ) continue
if ( ! overwriteIfExists ) {
const exists = await checkObjectExistsInTarget ( targetDataSource , obj )
if ( exists ) {
results . push ( {
targetDataSource ,
objectFullName : obj.fullName ,
objectType : obj.objectType ,
status : 'skipped' ,
2026-03-18 18:53:51 +00:00
message : translate ( '::App.SqlQueryManager.SkippedDescription' ) ,
2026-03-18 09:00:11 +00:00
} )
continue
}
}
const command = overwriteIfExists
? ` ${ buildDropIfExistsScript ( obj ) } \ n ${ createScript } `
: createScript
try {
await sqlObjectManagerService . executeQuery ( {
queryText : command ,
dataSourceCode : targetDataSource ,
} )
results . push ( {
targetDataSource ,
objectFullName : obj.fullName ,
objectType : obj.objectType ,
status : 'success' ,
2026-03-18 18:53:51 +00:00
message : translate ( '::App.Platform.CopyCompleted' ) ,
2026-03-18 09:00:11 +00:00
} )
} catch ( error : any ) {
results . push ( {
targetDataSource ,
objectFullName : obj.fullName ,
objectType : obj.objectType ,
status : 'error' ,
message : error.response?.data?.error?.message || 'Kopyalama basarisiz.' ,
} )
}
}
}
const successCount = results . filter ( ( x ) = > x . status === 'success' ) . length
const errorCount = results . filter ( ( x ) = > x . status === 'error' ) . length
const skippedCount = results . filter ( ( x ) = > x . status === 'skipped' ) . length
const notificationType = errorCount > 0 ? 'warning' : 'success'
toast . push (
< Notification
type = { notificationType }
title = {
errorCount > 0
? translate ( '::App.Platform.Warning' )
: translate ( '::App.Platform.Success' )
}
>
2026-03-18 18:53:51 +00:00
{ translate ( '::App.Platform.CopyCompleted' ) ||
` translate('::App.Platform.Successful'): ${ successCount } , ${ translate ( '::App.Platform.Error' ) } : ${ errorCount } , ${ translate ( '::App.Platform.Skipped' ) } : ${ skippedCount } ` }
2026-03-18 09:00:11 +00:00
< / Notification > ,
{ placement : 'top-center' } ,
)
setCopyResults ( results )
setShowCopyResultDialog ( true )
setShowCopyDialog ( false )
} finally {
setIsCopyingObjects ( false )
}
}
2026-03-18 18:53:51 +00:00
const handleExecuteDirectSql = async ( ) = > {
if ( ! sqlScriptForCopy ? . trim ( ) ) {
toast . push (
< Notification type = "warning" title = { translate ( '::App.Platform.Warning' ) } >
{ translate ( '::App.Platform.PleaseEnterQuery' ) }
< / Notification > ,
{ placement : 'top-center' } ,
)
return
}
if ( copyTargetDataSources . length === 0 ) {
toast . push (
< Notification type = "warning" title = { translate ( '::App.Platform.Warning' ) } >
{ translate ( '::App.Platform.PleaseSelectAtLeastOneTarget' ) }
< / Notification > ,
{ placement : 'top-center' } ,
)
return
}
setIsCopyingObjects ( true )
const results : SqlCopyResultItem [ ] = [ ]
try {
for ( const targetDataSource of copyTargetDataSources ) {
try {
await sqlObjectManagerService . executeQuery ( {
queryText : sqlScriptForCopy ,
dataSourceCode : targetDataSource ,
} )
results . push ( {
targetDataSource ,
objectFullName : 'SQL Script' ,
objectType : 'script' as any ,
status : 'success' ,
message : 'Basariyla calistirildi.' ,
} )
} catch ( error : any ) {
results . push ( {
targetDataSource ,
objectFullName : 'SQL Script' ,
objectType : 'script' as any ,
status : 'error' ,
message : error.response?.data?.error?.message || 'Calistirma basarisiz.' ,
} )
}
}
const successCount = results . filter ( ( x ) = > x . status === 'success' ) . length
const errorCount = results . filter ( ( x ) = > x . status === 'error' ) . length
const notificationType = errorCount > 0 ? 'warning' : 'success'
toast . push (
< Notification
type = { notificationType }
title = {
errorCount > 0
? translate ( '::App.Platform.Warning' )
: translate ( '::App.Platform.Success' )
}
>
{ translate ( '::App.Platform.ExecutionCompleted' ) ||
` translate('::App.Platform.Successful'): ${ successCount } , ${ translate ( '::App.Platform.Error' ) } : ${ errorCount } ` }
< / Notification > ,
{ placement : 'top-center' } ,
)
setCopyResults ( results )
setShowCopyResultDialog ( true )
setShowCopyDialog ( false )
} finally {
setIsCopyingObjects ( false )
}
}
2026-03-18 09:00:11 +00:00
const availableTargetDataSourceCodes = state . dataSources
. filter ( ( d ) = > d . code && d . code !== state . selectedDataSource )
. map ( ( d ) = > d . code || '' )
const allTargetsSelected =
availableTargetDataSourceCodes . length > 0 &&
availableTargetDataSourceCodes . every ( ( code ) = > copyTargetDataSources . includes ( code ) )
const handleToggleSelectAllTargets = ( checked : boolean ) = > {
if ( checked ) {
setCopyTargetDataSources ( availableTargetDataSourceCodes )
return
}
setCopyTargetDataSources ( [ ] )
}
const copySuccessCount = copyResults . filter ( ( x ) = > x . status === 'success' ) . length
const copyErrorCount = copyResults . filter ( ( x ) = > x . status === 'error' ) . length
const copySkippedCount = copyResults . filter ( ( x ) = > x . status === 'skipped' ) . length
2026-05-03 21:35:04 +00:00
const handleOpenSqlDataFilesDialog = async ( ) = > {
setShowSqlDataFilesDialog ( true )
setIsLoadingSqlDataFiles ( true )
try {
const response = await sqlObjectManagerService . getSqlDataFiles ( )
setSqlDataFiles ( response . data || [ ] )
} catch ( error : any ) {
setSqlDataFiles ( [ ] )
toast . push (
< Notification type = "danger" title = { translate ( '::App.Platform.Error' ) } >
{ error . response ? . data ? . error ? . message ||
translate ( '::App.Platform.FailedToLoadFiles' ) ||
'SQL dosya listesi yuklenemedi.' }
< / Notification > ,
{ placement : 'top-center' } ,
)
} finally {
setIsLoadingSqlDataFiles ( false )
}
}
2026-02-24 20:44:16 +00:00
return (
2026-03-02 18:31:49 +00:00
< Container className = "flex flex-col overflow-hidden" style = { { height : 'calc(100vh - 130px)' } } >
2026-02-24 20:44:16 +00:00
< Helmet
titleTemplate = { ` %s | ${ APP_NAME } ` }
title = { translate ( '::' + 'App.SqlQueryManager' ) }
defaultTitle = { APP_NAME }
> < / Helmet >
2026-03-02 18:31:49 +00:00
< div className = "flex flex-col flex-1 min-h-0 p-1" >
2026-02-24 20:44:16 +00:00
{ /* Toolbar */ }
< div className = "flex-shrink-0 shadow-sm mb-4" >
2026-03-18 05:36:36 +00:00
< div className = "flex flex-col gap-2 px-1 py-1 lg:flex-row lg:items-center lg:justify-between" >
< div className = "flex flex-wrap items-center gap-2 sm:gap-3" >
2026-02-24 20:44:16 +00:00
< FaDatabase className = "text-lg text-blue-500" / >
< select
2026-05-19 20:22:25 +00:00
className = "border border-gray-300 rounded px-2 py-1 max-w-full dark:bg-gray-700 dark:border-gray-600"
2026-02-24 20:44:16 +00:00
disabled = { state . selectedDataSource ? . length === 0 }
value = { state . selectedDataSource || '' }
onChange = { ( e ) = > {
const ds = state . dataSources . find ( ( d ) = > d . code === e . target . value )
if ( ds ) handleDataSourceChange ( ds )
} }
>
{ state . dataSources . map ( ( ds ) = > (
< option key = { ds . code } value = { ds . code } >
{ ds . code }
< / option >
) ) }
< / select >
2026-04-30 10:09:05 +00:00
< Button
2026-05-05 17:59:30 +00:00
size = "sm"
2026-04-30 10:09:05 +00:00
variant = "default"
icon = { < FcAcceptDatabase / > }
onClick = { ( ) = > setShowDbMigrateConfirmDialog ( true ) }
title = { translate ( '::App.DbMigrate.StartMessage' ) || 'Run DB Migration' }
>
{ translate ( '::ListForms.ListForm.DbMigrate' ) || 'DB Migrate' }
< / Button >
2026-05-03 21:35:04 +00:00
< Button
2026-05-05 17:59:30 +00:00
size = "sm"
2026-05-03 21:35:04 +00:00
variant = "default"
icon = { < FaFolderOpen / > }
onClick = { handleOpenSqlDataFilesDialog }
className = "shadow-sm px-2 py-1"
title = { translate ( '::App.SqlQueryManager.SqlDataFiles' ) || 'Show SqlData files' }
>
{ translate ( '::App.SqlQueryManager.SqlDataFiles' ) || 'SqlData Files' }
< / Button >
2026-02-24 20:44:16 +00:00
< / div >
2026-03-18 05:36:36 +00:00
< div className = "flex flex-wrap items-center gap-2 sm:gap-3" >
2026-03-18 09:00:11 +00:00
< Button
2026-05-05 17:59:30 +00:00
size = "sm"
2026-03-18 09:00:11 +00:00
variant = "default"
2026-04-30 10:09:05 +00:00
icon = { < FaCopy / > }
2026-03-18 09:00:11 +00:00
onClick = { handleOpenCopyDialog }
2026-03-18 18:53:51 +00:00
disabled = { ! state . selectedDataSource }
2026-05-03 12:24:46 +00:00
className = "shadow-sm px-2 py-1"
2026-03-18 18:53:51 +00:00
title = {
translate ( '::App.Platform.CopyOrExecuteSql' ) ||
'Seçili nesneleri kopyala veya SQL script calistir'
}
2026-03-18 09:00:11 +00:00
>
{ translate ( '::App.Platform.CopySelectedObjects' ) || 'Copy Selected Objects' }
< / Button >
2026-03-02 18:31:49 +00:00
< Button
2026-05-05 17:59:30 +00:00
size = "sm"
2026-03-02 18:31:49 +00:00
variant = "default"
icon = { < FaFileAlt / > }
onClick = { handleNewQuery }
2026-05-03 12:24:46 +00:00
className = "shadow-sm px-2 py-1"
2026-03-02 18:31:49 +00:00
>
{ translate ( '::App.Platform.NewQuery' ) || 'New Query' }
< / Button >
2026-02-24 20:44:16 +00:00
< Button
2026-05-05 17:59:30 +00:00
size = "sm"
2026-02-24 20:44:16 +00:00
variant = "solid"
color = "blue-600"
icon = { < FaPlay / > }
onClick = { handleExecute }
loading = { state . isExecuting }
disabled = { ! state . selectedDataSource }
2026-05-03 12:24:46 +00:00
className = "shadow-sm px-2 py-1"
2026-02-24 20:44:16 +00:00
>
{ translate ( '::App.Platform.Execute' ) }
< span className = "ml-1 text-xs opacity-75" > ( F5 ) < / span >
< / Button >
< / div >
< / div >
< / div >
{ /* Main Content Area */ }
2026-03-18 05:36:36 +00:00
< div className = "flex-1 flex min-h-0 flex-col gap-3 lg:flex-row lg:gap-4" >
2026-02-24 20:44:16 +00:00
{ /* Left Panel - Object Explorer */ }
2026-03-18 05:36:36 +00:00
< div className = "w-full lg:w-1/3 flex-shrink-0 flex flex-col h-[35vh] min-h-[260px] max-h-[420px] lg:h-auto lg:min-h-0 lg:max-h-none bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 shadow" >
2026-03-02 18:31:49 +00:00
< div className = "border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0 rounded-t-lg" >
< h6 className = "font-semibold text-sm" >
{ translate ( '::App.Platform.ObjectExplorer' ) }
< / h6 >
< / div >
< div className = "flex-1 min-h-0 flex flex-col overflow-hidden" >
< SqlObjectExplorer
dataSource = { state . selectedDataSource }
2026-05-25 14:31:54 +00:00
dataSourceType = { selectedDataSourceType }
2026-03-02 18:31:49 +00:00
onTemplateSelect = { handleTemplateSelect }
onViewDefinition = { handleViewDefinition }
2026-03-18 09:00:11 +00:00
onGenerateTableScript = { handleGenerateTableScript }
2026-03-02 18:31:49 +00:00
refreshTrigger = { state . refreshTrigger }
onNewTable = { handleNewTable }
onDesignTable = { handleDesignTable }
2026-03-18 09:00:11 +00:00
onSelectedObjectsChange = { setSelectedExplorerObjects }
2026-03-02 18:31:49 +00:00
/ >
< / div >
2026-02-24 20:44:16 +00:00
< / div >
{ /* Center Panel - Editor and Results */ }
2026-03-18 05:36:36 +00:00
< div className = "flex-1 flex flex-col min-h-0" >
2026-02-24 20:44:16 +00:00
{ state . executionResult || state . tableColumns ? (
< Splitter direction = "vertical" initialSize = { 250 } minSize = { 150 } maxSize = { 1200 } >
< div className = "border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col h-full" >
< div className = "border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0" >
< h6 className = "font-semibold text-sm" >
{ translate ( '::App.Platform.QueryEditor' ) }
< / h6 >
< / div >
< div className = "flex-1 min-h-0 overflow-hidden" >
< SqlEditor
ref = { editorRef }
value = { state . editorContent }
onChange = { handleEditorChange }
onExecute = { handleExecute }
readOnly = { state . isExecuting }
/ >
< / div >
< / div >
< div className = "border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col h-full" >
< div className = "border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0 flex items-center justify-between" >
< div className = "flex items-center gap-2" >
< FaCheckCircle className = "text-green-500" / >
< span className = "text-sm text-green-700 dark:text-green-400" >
{ state . executionResult ? . message ||
state . tableColumns ? . message ||
translate ( '::App.Platform.QueryExecutedSuccessfully' ) }
< / span >
< / div >
< div className = "flex items-center gap-4" >
< div className = "flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400" >
< span >
{ translate ( '::App.Platform.Rows' ) } : { ' ' }
< strong >
{ state . executionResult ? . rowsAffected ||
state . executionResult ? . data ? . length ||
state . tableColumns ? . rowsAffected ||
0 }
< / strong >
< / span >
{ state . executionResult && (
< span >
{ translate ( '::App.Platform.Time' ) } : { ' ' }
< strong > { state . executionResult . executionTimeMs } ms < / strong >
< / span >
) }
< / div >
< / div >
< / div >
< div className = "flex-1 overflow-hidden p-2" >
< SqlResultsGrid result = { ( state . executionResult || state . tableColumns ) ! } / >
< / div >
< / div >
< / Splitter >
) : (
< div className = "flex-1 border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col overflow-hidden" >
< div className = "border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0" >
< h6 className = "font-semibold text-sm" >
{ translate ( '::App.Platform.QueryEditor' ) }
< / h6 >
< / div >
< div className = "flex-1 min-h-0" >
< SqlEditor
ref = { editorRef }
value = { state . editorContent }
onChange = { handleEditorChange }
onExecute = { handleExecute }
readOnly = { state . isExecuting }
/ >
< / div >
< / div >
) }
< / div >
< / div >
< / div >
2026-04-30 10:09:05 +00:00
{ /* DB Migrate Confirmation Dialog */ }
< ConfirmDialog
isOpen = { showDbMigrateConfirmDialog }
type = "info"
title = { translate ( '::ListForms.ListForm.DbMigrate' ) || 'DB Migrate' }
cancelText = { translate ( '::Cancel' ) }
confirmText = { translate ( '::App.Platform.Execute' ) || 'Çalı ştı r' }
onCancel = { ( ) = > setShowDbMigrateConfirmDialog ( false ) }
onClose = { ( ) = > setShowDbMigrateConfirmDialog ( false ) }
onConfirm = { ( ) = > {
setShowDbMigrateConfirmDialog ( false )
UiEvalService . ApiDbMigrate ( )
} }
>
< p className = "text-gray-600 dark:text-gray-400" >
2026-05-03 19:50:51 +00:00
{ translate ( '::App.DbMigrate.ConfirmMessage' ) || 'Are you sure you want to start the database migration process?' }
2026-04-30 10:09:05 +00:00
< / p >
< / ConfirmDialog >
2026-05-03 21:35:04 +00:00
< Dialog
isOpen = { showSqlDataFilesDialog }
onClose = { ( ) = > setShowSqlDataFilesDialog ( false ) }
onRequestClose = { ( ) = > setShowSqlDataFilesDialog ( false ) }
contentClassName = "max-h-[90vh] overflow-hidden"
>
< div className = "flex max-h-[72vh] min-h-[320px] flex-col" >
< h5 className = "mb-4 shrink-0" >
{ translate ( '::App.SqlQueryManager.SqlDataFiles' ) || 'SqlData Files' }
< / h5 >
{ isLoadingSqlDataFiles ? (
< p className = "mb-4 text-gray-600 dark:text-gray-400" >
{ translate ( '::App.Platform.Loading' ) || 'Loading...' }
< / p >
) : sqlDataFiles . length === 0 ? (
< p className = "mb-4 text-gray-600 dark:text-gray-400" >
{ translate ( '::App.SqlQueryManager.NoSqlDataFiles' ) || 'SqlData klasorunde .sql dosyasi bulunamadi.' }
< / p >
) : (
< div className = "mb-4 h-[70vh] min-h-[220px] overflow-y-auto rounded border border-gray-200 dark:border-gray-700" >
< ul className = "divide-y divide-gray-200 dark:divide-gray-700" >
{ sqlDataFiles . map ( ( file ) = > (
< li key = { file . fileName } className = "flex items-center justify-between px-3 py-2 text-sm text-gray-700 dark:text-gray-200" >
< span > { file . fileName } < / span >
< span className = "ml-4 shrink-0 text-xs text-gray-400 dark:text-gray-500" >
{ new Date ( file . createdAt ) . toLocaleString ( ) }
< / span >
< / li >
) ) }
< / ul >
< / div >
) }
< / div >
< / Dialog >
2026-02-24 20:44:16 +00:00
{ /* Template Confirmation Dialog */ }
< Dialog
isOpen = { showTemplateConfirmDialog }
onClose = { handleCancelTemplateReplace }
onRequestClose = { handleCancelTemplateReplace }
>
< h5 className = "mb-4" > { translate ( '::App.Platform.ConfirmTemplateReplace' ) } < / h5 >
< p className = "mb-6 text-gray-600 dark:text-gray-400" >
{ translate ( '::App.Platform.TemplateReplaceWarning' ) }
< / p >
< div className = "flex justify-end gap-2" >
< Button variant = "plain" onClick = { handleCancelTemplateReplace } >
{ translate ( '::Cancel' ) }
< / Button >
2026-05-03 09:52:22 +00:00
< Button variant = "solid" onClick = { handleConfirmTemplateReplace } icon = { < FaExclamationTriangle / > } color = "red-600" >
2026-02-24 20:44:16 +00:00
{ translate ( '::App.Platform.Replace' ) }
< / Button >
< / div >
< / Dialog >
2026-03-01 20:43:25 +00:00
{ /* Table Designer Dialog */ }
< SqlTableDesignerDialog
isOpen = { showTableDesignerDialog }
onClose = { ( ) = > {
setShowTableDesignerDialog ( false )
setDesignTableData ( null )
} }
dataSource = { state . selectedDataSource ? ? '' }
initialTableData = { designTableData }
onDeployed = { ( ) = > {
setState ( ( prev ) = > ( { . . . prev , refreshTrigger : prev.refreshTrigger + 1 } ) )
} }
/ >
2026-03-18 09:00:11 +00:00
< Dialog
isOpen = { showCopyDialog }
onClose = { ( ) = > ! isCopyingObjects && setShowCopyDialog ( false ) }
onRequestClose = { ( ) = > ! isCopyingObjects && setShowCopyDialog ( false ) }
2026-05-15 10:48:07 +00:00
width = { 1050 }
2026-03-18 09:00:11 +00:00
>
2026-05-15 10:48:07 +00:00
< Dialog.Body className = "flex flex-col gap-2" >
< h5 className = "mb-1 flex-shrink-0" > { translate ( '::App.Platform.CopySelectedObjects' ) } < / h5 >
2026-03-18 18:53:51 +00:00
{ /* Mode Tabs */ }
2026-05-15 10:48:07 +00:00
< div className = "flex gap-2 mb-2 border-b flex-shrink-0" >
2026-03-18 18:53:51 +00:00
< button
onClick = { ( ) = > setCopyDialogMode ( 'objects' ) }
className = { ` px-4 py-2 font-medium text-sm border-b-2 transition ${
copyDialogMode === 'objects'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-600 hover:text-gray-900'
} ` }
disabled = { isCopyingObjects }
>
{ translate ( '::App.Platform.CopyObjects' ) || 'Nesneleri Kopyala' }
< / button >
< button
onClick = { ( ) = > setCopyDialogMode ( 'sql' ) }
className = { ` px-4 py-2 font-medium text-sm border-b-2 transition ${
copyDialogMode === 'sql'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-600 hover:text-gray-900'
} ` }
disabled = { isCopyingObjects }
>
{ translate ( '::App.Platform.DirectSqlScript' ) || 'Direkt SQL Script' }
< / button >
< / div >
2026-05-15 10:48:07 +00:00
< div className = "flex-1 min-h-0 overflow-y-auto pr-1" >
2026-03-18 18:53:51 +00:00
{ copyDialogMode === 'objects' ? (
< >
{ /* Objects Mode */ }
< div className = "mb-2 flex items-center justify-between gap-4" >
< p className = "text-sm text-gray-600 dark:text-gray-400 mb-0" >
{ translate ( '::App.Platform.SourceDataSource' ) } : { ' ' }
< strong > { state . selectedDataSource } < / strong >
< / p >
< label className = "flex items-center gap-2 text-sm cursor-pointer shrink-0" >
< input
type = "checkbox"
checked = { overwriteIfExists }
onChange = { ( e ) = > setOverwriteIfExists ( e . target . checked ) }
disabled = { isCopyingObjects }
/ >
< span > { translate ( '::App.Platform.OverwriteIfExists' ) } < / span >
< / label >
< / div >
2026-03-18 09:00:11 +00:00
2026-03-18 18:53:51 +00:00
< div className = "mb-4 max-h-36 overflow-auto border rounded p-2" >
{ selectedExplorerObjects . length === 0 ? (
< p className = "text-sm text-gray-500" >
{ translate ( '::App.Platform.NoObjectSelected' ) ||
'Secili nesne yok. Lutfen Explorer alanı ndan bir nesne secin.' }
< / p >
) : (
selectedExplorerObjects . map ( ( obj ) = > (
< div key = { obj . id } className = "text-sm py-0.5" >
{ obj . objectType . toUpperCase ( ) } - { obj . fullName }
< / div >
) )
) }
2026-03-18 09:00:11 +00:00
< / div >
2026-03-18 18:53:51 +00:00
< div className = "mb-4" >
< div className = "text-sm font-medium mb-2" >
{ translate ( '::App.Platform.TargetDataSources' ) }
< / div >
< label className = "flex items-center gap-2 text-sm cursor-pointer mb-2" >
< input
type = "checkbox"
checked = { allTargetsSelected }
onChange = { ( e ) = > handleToggleSelectAllTargets ( e . target . checked ) }
/ >
< span > { translate ( '::App.Platform.SelectAllTargets' ) || 'Tumunu sec' } < / span >
< / label >
< div className = "max-h-44 overflow-auto border-t pt-2" >
{ availableTargetDataSourceCodes . map ( ( code ) = > {
const checked = copyTargetDataSources . includes ( code )
return (
< label
key = { code }
className = "flex items-center gap-2 text-sm cursor-pointer"
>
< input
type = "checkbox"
checked = { checked }
onChange = { ( e ) = > {
setCopyTargetDataSources ( ( prev ) = > {
if ( e . target . checked ) return [ . . . prev , code ]
return prev . filter ( ( x ) = > x !== code )
} )
} }
/ >
< span > { code } < / span >
< / label >
)
} ) }
< / div >
< / div >
< / >
) : (
< >
{ /* SQL Mode */ }
< div className = "mb-2" >
< p className = "text-sm text-gray-600 dark:text-gray-400 mb-2" >
{ translate ( '::App.Platform.SourceDataSource' ) } : { ' ' }
< strong > { state . selectedDataSource } < / strong >
< / p >
< / div >
< div className = "mb-4 border rounded bg-white dark:bg-gray-800 h-64 overflow-hidden" >
< SqlEditor
value = { sqlScriptForCopy }
onChange = { ( value ) = > setSqlScriptForCopy ( value || '' ) }
readOnly = { isCopyingObjects }
/ >
< / div >
< div className = "mb-4" >
< div className = "text-sm font-medium mb-2" >
{ translate ( '::App.Platform.TargetDataSources' ) }
< / div >
< label className = "flex items-center gap-2 text-sm cursor-pointer mb-2" >
< input
type = "checkbox"
checked = { allTargetsSelected }
onChange = { ( e ) = > handleToggleSelectAllTargets ( e . target . checked ) }
/ >
< span > { translate ( '::App.Platform.SelectAllTargets' ) || 'Tumunu sec' } < / span >
< / label >
< div className = "max-h-44 overflow-auto border-t pt-2" >
{ availableTargetDataSourceCodes . map ( ( code ) = > {
const checked = copyTargetDataSources . includes ( code )
return (
< label
key = { code }
className = "flex items-center gap-2 text-sm cursor-pointer"
>
< input
type = "checkbox"
checked = { checked }
onChange = { ( e ) = > {
setCopyTargetDataSources ( ( prev ) = > {
if ( e . target . checked ) return [ . . . prev , code ]
return prev . filter ( ( x ) = > x !== code )
} )
} }
/ >
< span > { code } < / span >
< / label >
)
} ) }
< / div >
< / div >
< / >
) }
2026-03-18 09:00:11 +00:00
< / div >
2026-05-15 10:48:07 +00:00
< / Dialog.Body >
< Dialog.Footer className = "flex justify-end gap-2 border-t pt-3 mt-1" >
< Button
variant = "plain"
onClick = { ( ) = > setShowCopyDialog ( false ) }
disabled = { isCopyingObjects }
>
{ translate ( '::Cancel' ) }
< / Button >
< Button
variant = "solid"
onClick = { copyDialogMode === 'objects' ? handleCopyObjects : handleExecuteDirectSql }
loading = { isCopyingObjects }
disabled = {
copyTargetDataSources . length === 0 ||
( copyDialogMode === 'objects'
? selectedExplorerObjects . length === 0
: ! sqlScriptForCopy ? . trim ( ) )
}
>
{ copyDialogMode === 'objects'
? translate ( '::Copy' )
: translate ( '::App.Platform.Execute' ) || 'Calistir' }
< / Button >
< / Dialog.Footer >
2026-03-18 09:00:11 +00:00
< / Dialog >
< Dialog
isOpen = { showCopyResultDialog }
onClose = { ( ) = > setShowCopyResultDialog ( false ) }
onRequestClose = { ( ) = > setShowCopyResultDialog ( false ) }
width = { 1050 }
>
2026-05-15 10:48:07 +00:00
< Dialog.Body className = "flex flex-col gap-2" >
< h5 className = "mb-1 flex-shrink-0" >
2026-03-18 09:00:11 +00:00
{ translate ( '::App.Platform.Results' ) || 'Kopyalama Sonuc Detaylari' }
< / h5 >
2026-05-15 10:48:07 +00:00
< div className = "grid grid-cols-1 sm:grid-cols-3 gap-2 mb-2 text-xs sm:text-sm flex-shrink-0" >
2026-03-18 09:00:11 +00:00
< div className = "rounded border border-green-200 bg-green-50 px-3 py-2 text-green-700" >
{ translate ( '::App.Platform.Success' ) } : < strong > { copySuccessCount } < / strong >
< / div >
< div className = "rounded border border-amber-200 bg-amber-50 px-3 py-2 text-amber-700" >
{ translate ( '::App.Platform.Skipped' ) } : < strong > { copySkippedCount } < / strong >
< / div >
< div className = "rounded border border-red-200 bg-red-50 px-3 py-2 text-red-700" >
{ translate ( '::App.Platform.Error' ) } : < strong > { copyErrorCount } < / strong >
< / div >
< / div >
2026-05-15 10:48:07 +00:00
< div className = "flex-1 min-h-0 overflow-auto border rounded" >
2026-03-18 09:00:11 +00:00
< div className = "md:hidden p-2 space-y-2" >
{ copyResults . map ( ( row , idx ) = > {
const isError = row . status === 'error'
const isSkipped = row . status === 'skipped'
const cardClass = isError
? 'border-red-200 bg-red-50/60 dark:bg-red-900/20'
: isSkipped
? 'border-amber-200 bg-amber-50/60 dark:bg-amber-900/20'
: 'border-gray-200 bg-white dark:bg-gray-800'
return (
< div
key = { ` ${ row . targetDataSource } - ${ row . objectFullName } - ${ idx } ` }
className = { ` rounded border p-3 ${ cardClass } ` }
>
< div className = "flex items-center justify-between gap-2 mb-2" >
< span className = "text-xs font-semibold uppercase text-gray-500" >
{ row . objectType }
< / span >
{ row . status === 'success' && (
< span className = "inline-flex rounded-full bg-green-100 text-green-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Success' ) }
< / span >
) }
{ row . status === 'error' && (
< span className = "inline-flex rounded-full bg-red-100 text-red-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Error' ) }
< / span >
) }
{ row . status === 'skipped' && (
< span className = "inline-flex rounded-full bg-amber-100 text-amber-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Skipped' ) }
< / span >
) }
< / div >
< div className = "text-sm font-medium break-words mb-1" > { row . objectFullName } < / div >
< div className = "text-xs text-gray-500 mb-2" > Hedef : { row . targetDataSource } < / div >
< div
className = { ` text-sm whitespace-normal break-words ${ isError ? 'text-red-700 dark:text-red-300 font-medium' : 'text-gray-700 dark:text-gray-200' } ` }
>
{ row . message }
< / div >
< / div >
)
} ) }
{ copyResults . length === 0 && (
< div className = "px-3 py-8 text-center text-gray-500" >
{ translate ( '::App.Platform.NoResults' ) }
< / div >
) }
< / div >
< table className = "hidden md:table w-full table-fixed text-sm" >
< thead className = "bg-gray-50 dark:bg-gray-700 sticky top-0 z-10" >
< tr >
2026-03-18 18:53:51 +00:00
< th className = "w-[110px] text-left px-3 py-2 border-b" >
{ translate ( '::App.Platform.Status' ) }
< / th >
< th className = "w-[270px] text-left px-3 py-2 border-b" >
{ translate ( '::App.Platform.Object' ) }
< / th >
< th className = "w-[90px] text-left px-3 py-2 border-b" >
{ translate ( '::App.Platform.Type' ) }
< / th >
< th className = "w-[140px] text-left px-3 py-2 border-b" >
{ translate ( '::App.Platform.Target' ) }
< / th >
< th className = "text-left px-3 py-2 border-b" >
{ translate ( '::App.Platform.Message' ) }
< / th >
2026-03-18 09:00:11 +00:00
< / tr >
< / thead >
< tbody >
{ copyResults . map ( ( row , idx ) = > {
const isError = row . status === 'error'
const isSkipped = row . status === 'skipped'
const rowClass = isError
? 'bg-red-50/60 dark:bg-red-900/20'
: isSkipped
? 'bg-amber-50/60 dark:bg-amber-900/20'
: idx % 2 === 0
? 'bg-white dark:bg-gray-800'
: 'bg-gray-50/40 dark:bg-gray-800/70'
return (
< tr
key = { ` ${ row . targetDataSource } - ${ row . objectFullName } - ${ idx } ` }
className = { rowClass }
>
< td className = "px-3 py-2 border-b align-top" >
{ row . status === 'success' && (
< span className = "inline-flex rounded-full bg-green-100 text-green-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Success' ) }
< / span >
) }
{ row . status === 'error' && (
< span className = "inline-flex rounded-full bg-red-100 text-red-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Error' ) }
< / span >
) }
{ row . status === 'skipped' && (
< span className = "inline-flex rounded-full bg-amber-100 text-amber-700 px-2 py-0.5 text-xs font-semibold" >
{ translate ( '::App.Platform.Skipped' ) }
< / span >
) }
< / td >
< td
className = "px-3 py-2 border-b align-top truncate"
title = { row . objectFullName }
>
{ row . objectFullName }
< / td >
< td className = "px-3 py-2 border-b align-top uppercase" > { row . objectType } < / td >
< td className = "px-3 py-2 border-b align-top" > { row . targetDataSource } < / td >
< td
className = { ` px-3 py-2 border-b align-top whitespace-normal break-words leading-5 ${ isError ? 'text-red-700 dark:text-red-300 font-medium' : 'text-gray-700 dark:text-gray-200' } ` }
title = { row . message }
>
{ row . message }
< / td >
< / tr >
)
} ) }
{ copyResults . length === 0 && (
< tr >
< td colSpan = { 5 } className = "px-3 py-8 text-center text-gray-500" >
{ translate ( '::App.Platform.NoResults' ) }
< / td >
< / tr >
) }
< / tbody >
< / table >
< / div >
2026-05-15 10:48:07 +00:00
< / Dialog.Body >
2026-03-18 09:00:11 +00:00
2026-05-15 10:48:07 +00:00
< Dialog.Footer className = "flex justify-end gap-2 border-t pt-3 mt-1" >
2026-03-18 09:00:11 +00:00
< Button variant = "solid" onClick = { ( ) = > setShowCopyResultDialog ( false ) } >
2026-03-18 18:53:51 +00:00
{ translate ( '::App.Platform.Close' ) }
2026-03-18 09:00:11 +00:00
< / Button >
2026-05-15 10:48:07 +00:00
< / Dialog.Footer >
2026-03-18 09:00:11 +00:00
< / Dialog >
2026-02-24 20:44:16 +00:00
< / Container >
)
}
export default SqlQueryManager