diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index c1fdc05..cec03df 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -224,6 +224,8 @@ const TENANT_COLUMN: ColumnDefinition = { description: 'Tenant ID for multi-tenancy', } +const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript' + // ─── T-SQL Generator ────────────────────────────────────────────────────────── function colToSqlLine(col: ColumnDefinition, addComma = true): string { @@ -377,6 +379,136 @@ function dbColToColumnDef(col: { } } +function splitByTopLevelComma(input: string): string[] { + const parts: string[] = [] + let current = '' + let depth = 0 + + for (let i = 0; i < input.length; i++) { + const ch = input[i] + if (ch === '(') depth++ + if (ch === ')') depth = Math.max(0, depth - 1) + + if (ch === ',' && depth === 0) { + if (current.trim()) parts.push(current.trim()) + current = '' + continue + } + + current += ch + } + + if (current.trim()) parts.push(current.trim()) + return parts +} + +function unwrapDefaultExpression(value: string): string { + let result = value.trim() + while (result.startsWith('(') && result.endsWith(')')) { + result = result.slice(1, -1).trim() + } + return result +} + +function mapSqlTypeToDesigner(dataTypeRaw: string, lengthRaw: string): { + dataType: SqlDataType + maxLength: string +} { + const dt = dataTypeRaw.toLowerCase() + const len = lengthRaw.toLowerCase() + + if (dt === 'nvarchar' || dt === 'varchar' || dt === 'char' || dt === 'nchar') { + if (len === 'max') { + return { dataType: 'nvarchar(MAX)', maxLength: '' } + } + return { dataType: 'nvarchar', maxLength: len || '100' } + } + + if (dt === 'int') return { dataType: 'int', maxLength: '' } + if (dt === 'bigint') return { dataType: 'bigint', maxLength: '' } + if (dt === 'decimal' || dt === 'numeric') return { dataType: 'decimal', maxLength: '' } + if (dt === 'float' || dt === 'real') return { dataType: 'float', maxLength: '' } + if (dt === 'bit') return { dataType: 'bit', maxLength: '' } + if (dt.startsWith('datetime') || dt === 'smalldatetime' || dt === 'time') { + return { dataType: 'datetime2', maxLength: '' } + } + if (dt === 'date') return { dataType: 'date', maxLength: '' } + if (dt === 'uniqueidentifier') return { dataType: 'uniqueidentifier', maxLength: '' } + if (dt === 'money' || dt === 'smallmoney') return { dataType: 'money', maxLength: '' } + + return { dataType: 'nvarchar', maxLength: '100' } +} + +function parseCreateTableColumns(script: string): ColumnDefinition[] { + if (!script?.trim()) return [] + + const createIdx = script.search(/\bCREATE\s+TABLE\b/i) + if (createIdx < 0) return [] + + const openParenIdx = script.indexOf('(', createIdx) + if (openParenIdx < 0) return [] + + let depth = 0 + let closeParenIdx = -1 + for (let i = openParenIdx; i < script.length; i++) { + const ch = script[i] + if (ch === '(') depth++ + if (ch === ')') { + depth-- + if (depth === 0) { + closeParenIdx = i + break + } + } + } + + if (closeParenIdx < 0) return [] + + const body = script.slice(openParenIdx + 1, closeParenIdx) + const defs = splitByTopLevelComma(body) + const columns: ColumnDefinition[] = [] + + for (const def of defs) { + const trimmed = def.trim() + if (!trimmed) continue + + if ( + /^(CONSTRAINT|PRIMARY\s+KEY|UNIQUE\s+KEY|UNIQUE|FOREIGN\s+KEY|CHECK)\b/i.test(trimmed) + ) { + continue + } + + const colMatch = trimmed.match(/^\[([^\]]+)\]\s+(.+)$/i) + if (!colMatch) continue + + const columnName = colMatch[1].trim() + const rest = colMatch[2].trim() + + const typeMatch = rest.match(/^\[?([A-Za-z0-9_]+)\]?\s*(?:\(([^)]*)\))?/) + if (!typeMatch) continue + + const sqlType = typeMatch[1] ?? 'nvarchar' + const sqlLength = (typeMatch[2] ?? '').trim() + const mapped = mapSqlTypeToDesigner(sqlType, sqlLength) + + const isNullable = !/\bNOT\s+NULL\b/i.test(rest) + const defaultMatch = rest.match(/\bDEFAULT\b\s+(.+)$/i) + const defaultValue = defaultMatch ? unwrapDefaultExpression(defaultMatch[1]) : '' + + columns.push({ + id: crypto.randomUUID(), + columnName, + dataType: mapped.dataType, + maxLength: mapped.maxLength, + isNullable, + defaultValue, + description: '', + }) + } + + return columns +} + /** Generate ALTER TABLE diff SQL (edit mode) */ function generateAlterTableSql( originalCols: ColumnDefinition[], @@ -1025,6 +1157,84 @@ const SqlTableDesignerDialog = ({ } } + const importColumnsFromRememberedCreateTable = async () => { + let script = '' + + try { + const clipboardText = await navigator.clipboard.readText() + if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) { + script = clipboardText + localStorage.setItem(CREATE_TABLE_SCRIPT_STORAGE_KEY, clipboardText) + } + } catch { + // Clipboard read can fail due to browser permissions; fallback to saved script. + } + + if (!script) { + script = localStorage.getItem(CREATE_TABLE_SCRIPT_STORAGE_KEY) || '' + } + + if (!script.trim()) { + toast.push( + + CREATE TABLE script bulunamadı. Scripti panoya kopyalayıp tekrar deneyin. + , + { placement: 'top-center' }, + ) + return + } + + const parsedColumns = parseCreateTableColumns(script) + if (parsedColumns.length === 0) { + toast.push( + + CREATE TABLE scriptinden sütunlar ayrıştırılamadı. + , + { placement: 'top-center' }, + ) + return + } + + let addedCount = 0 + let skippedCount = 0 + + setColumns((prev) => { + const existingNonEmpty = prev.filter((c) => c.columnName.trim() !== '') + const existingNames = new Set( + existingNonEmpty.map((c) => c.columnName.trim().toLowerCase()), + ) + + const toAdd: ColumnDefinition[] = [] + for (const col of parsedColumns) { + const key = col.columnName.trim().toLowerCase() + if (!key) { + skippedCount++ + continue + } + + if (existingNames.has(key)) { + skippedCount++ + continue + } + + existingNames.add(key) + toAdd.push(col) + } + + addedCount = toAdd.length + return [...existingNonEmpty, ...toAdd, createEmptyColumn()] + }) + + toast.push( + + {addedCount > 0 + ? `${addedCount} sütun eklendi${skippedCount > 0 ? `, ${skippedCount} sütun zaten mevcut olduğu için atlandı.` : '.'}` + : 'Yeni sütun eklenmedi. Scriptteki sütunların tamamı zaten mevcut.'} + , + { placement: 'top-center' }, + ) + } + const removeColumn = (id: string) => setColumns((prev) => prev.filter((c) => c.id !== id)) const moveColumn = (id: string, direction: 'up' | 'down') => { @@ -1257,8 +1467,7 @@ const SqlTableDesignerDialog = ({ if (isEditMode) return true // table name is locked in edit mode const baseOk = !!settings.tableName.trim() && !!settings.entityName.trim() && !!settings.menuValue - if (!baseOk) return false - return columns.some((c) => c.columnName.trim().toLowerCase() === 'id') + return baseOk } return true } @@ -1395,6 +1604,15 @@ const SqlTableDesignerDialog = ({ +