2026-02-24 20:44:16 +00:00
|
|
|
import { useMemo } from 'react'
|
|
|
|
|
import { DataGrid } from 'devextreme-react'
|
|
|
|
|
import { Column, Paging, Scrolling, SearchPanel, Export, Selection } from 'devextreme-react/data-grid'
|
|
|
|
|
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
|
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
import { FaTimesCircle } from 'react-icons/fa'
|
|
|
|
|
|
|
|
|
|
interface SqlResultsGridProps {
|
|
|
|
|
result: SqlQueryExecutionResultDto
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
|
|
|
|
const columns = useMemo(() => {
|
|
|
|
|
// Get columns from metadata if available
|
|
|
|
|
if (result.metadata?.columns && Array.isArray(result.metadata.columns)) {
|
|
|
|
|
return result.metadata.columns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise infer from data
|
|
|
|
|
if (result.data && result.data.length > 0) {
|
|
|
|
|
const firstRow = result.data[0]
|
|
|
|
|
return Object.keys(firstRow).map((key) => ({
|
|
|
|
|
name: key,
|
|
|
|
|
dataType: typeof firstRow[key],
|
|
|
|
|
isNullable: true,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return []
|
|
|
|
|
}, [result])
|
|
|
|
|
|
|
|
|
|
const dataSource = useMemo(() => {
|
|
|
|
|
return result.data || []
|
|
|
|
|
}, [result])
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="h-full flex flex-col">
|
|
|
|
|
<div className="flex items-center gap-2 mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
|
2026-05-19 20:22:25 +00:00
|
|
|
<FaTimesCircle className="text-red-500 dark:text-red-400 text-xl" />
|
2026-02-24 20:44:16 +00:00
|
|
|
<div>
|
|
|
|
|
<div className="font-semibold text-red-700 dark:text-red-400">
|
|
|
|
|
{translate('::App.Platform.Error')}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-sm text-red-600 dark:text-red-500">
|
|
|
|
|
{result.error || result.message}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="h-full flex flex-col">
|
|
|
|
|
{dataSource.length > 0 ? (
|
|
|
|
|
<div className="flex-1 overflow-hidden relative">
|
|
|
|
|
<div className="absolute inset-0">
|
|
|
|
|
<DataGrid
|
|
|
|
|
dataSource={dataSource}
|
|
|
|
|
showBorders={true}
|
|
|
|
|
showRowLines={true}
|
|
|
|
|
showColumnLines={true}
|
|
|
|
|
rowAlternationEnabled={true}
|
|
|
|
|
columnAutoWidth={true}
|
|
|
|
|
wordWrapEnabled={false}
|
|
|
|
|
allowColumnReordering={true}
|
|
|
|
|
allowColumnResizing={true}
|
|
|
|
|
columnResizingMode="widget"
|
|
|
|
|
height="100%"
|
|
|
|
|
>
|
|
|
|
|
<Scrolling mode="virtual" rowRenderingMode="virtual" />
|
|
|
|
|
<Paging enabled={true} pageSize={50} />
|
|
|
|
|
<SearchPanel visible={true} width={240} placeholder={translate('::App.Platform.Search')} />
|
|
|
|
|
<Export enabled={true} allowExportSelectedData={true} />
|
|
|
|
|
<Selection mode="multiple" showCheckBoxesMode="always" />
|
|
|
|
|
|
|
|
|
|
{columns.map((col, index) => (
|
|
|
|
|
<Column
|
|
|
|
|
key={col.name || index}
|
|
|
|
|
dataField={col.name}
|
|
|
|
|
caption={col.name}
|
|
|
|
|
dataType={
|
|
|
|
|
col.dataType?.toLowerCase().includes('int') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('decimal') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('numeric') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('float') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('money')
|
|
|
|
|
? 'number'
|
|
|
|
|
: col.dataType?.toLowerCase().includes('date') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('time')
|
|
|
|
|
? 'date'
|
|
|
|
|
: col.dataType?.toLowerCase().includes('bit') ||
|
|
|
|
|
col.dataType?.toLowerCase().includes('boolean')
|
|
|
|
|
? 'boolean'
|
|
|
|
|
: 'string'
|
|
|
|
|
}
|
|
|
|
|
allowSorting={true}
|
|
|
|
|
allowFiltering={true}
|
|
|
|
|
allowHeaderFiltering={true}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</DataGrid>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<div className="text-lg mb-2">{translate('::App.Platform.NoResults')}</div>
|
|
|
|
|
<div className="text-sm">
|
|
|
|
|
{result.rowsAffected > 0
|
|
|
|
|
? translate('::App.Platform.RowCount', { count: result.rowsAffected })
|
|
|
|
|
: translate('::App.Platform.NoResultsReturned')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default SqlResultsGrid
|