diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index aa77342..4609c9b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -15836,6 +15836,12 @@ "en": "Used to generate ListForm Code and Menu Code", "tr": "ListForm Kodu ve Menü Kodu oluşturmak için kullanılır" }, + { + "resourceName": "Platform", + "key": "ListForms.Wizard.Step1.Optional", + "en": "Optional", + "tr": "İsteğe Bağlı" + }, { "resourceName": "Platform", "key": "ListForms.Wizard.Step1.WizardName", diff --git a/ui/src/views/admin/listForm/WizardStep1.tsx b/ui/src/views/admin/listForm/WizardStep1.tsx index 496bd67..e0bfca7 100644 --- a/ui/src/views/admin/listForm/WizardStep1.tsx +++ b/ui/src/views/admin/listForm/WizardStep1.tsx @@ -1,4 +1,4 @@ -import { Button, Dialog, FormItem, Input, Notification, Select, toast } from '@/components/ui' +import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui' import { ListFormWizardDto } from '@/proxy/admin/list-form/models' import { MenuDto } from '@/proxy/menus/models' import { SelectBoxOption } from '@/types/shared' @@ -6,7 +6,7 @@ import navigationIcon from '@/proxy/menus/navigation-icon.config' import { MenuItem } from '@/proxy/menus/menu' import { MenuService } from '@/services/menu.service' import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useState } from 'react' import CreatableSelect from 'react-select/creatable' import { FaArrowRight, @@ -19,6 +19,7 @@ import { FaTrash, } from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' +import { IconPickerField, MenuAddDialog } from '@/views/shared/MenuAddDialog' // ─── Types (exported for Wizard.tsx) ───────────────────────────────────────── @@ -371,332 +372,6 @@ function MenuTreeInline({ ) } -// ─── IconPickerField ────────────────────────────────────────────────────────── - -interface IconPickerFieldProps { - value: string - onChange: (iconKey: string) => void - invalid?: boolean -} - -const ALL_ICON_ENTRIES = Object.entries(navigationIcon) -const ICON_PAGE_SIZE = 100 - -function IconPickerField({ value, onChange, invalid }: IconPickerFieldProps) { - const [open, setOpen] = useState(false) - const [search, setSearch] = useState('') - const [limit, setLimit] = useState(ICON_PAGE_SIZE) - const wrapperRef = useRef(null) - - const SelectedIcon = value ? navigationIcon[value] : null - const filtered = search.trim() - ? ALL_ICON_ENTRIES.filter(([key]) => key.toLowerCase().includes(search.toLowerCase())) - : ALL_ICON_ENTRIES - const displayed = filtered.slice(0, limit) - const hasMore = displayed.length < filtered.length - const { translate } = useLocalization() - - useEffect(() => { - setLimit(ICON_PAGE_SIZE) - }, [search]) - - useEffect(() => { - function handleClickOutside(e: MouseEvent) { - if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) setOpen(false) - } - if (open) document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, [open]) - - return ( -
- - - {open && ( -
-
- setSearch(e.target.value)} - placeholder="Search icons… (FaHome, FcSettings)" - className="flex-1 px-2 py-1.5 text-sm rounded border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 outline-none focus:border-indigo-400" - /> - {filtered.length} icons -
-
- {displayed.map(([key, Icon]) => ( - - ))} -
- {hasMore && ( -
- - {displayed.length} / {filtered.length} - - -
- )} -
- )} -
- ) -} - -// ─── MenuAddDialog ──────────────────────────────────────────────────────────── - -interface MenuAddDialogProps { - isOpen: boolean - onClose: () => void - initialParentCode: string - initialOrder: number - rawItems: (MenuItem & { id?: string })[] - onSaved: () => void -} - -function MenuAddDialog({ - isOpen, - onClose, - initialParentCode, - initialOrder, - rawItems, - onSaved, -}: MenuAddDialogProps) { - const [form, setForm] = useState({ - name: '', - code: '', - menuTextEn: '', - menuTextTr: '', - parentCode: initialParentCode, - icon: '', - shortName: '', - order: initialOrder, - }) - const [saving, setSaving] = useState(false) - const { translate } = useLocalization() - - useEffect(() => { - if (isOpen) - setForm({ - name: '', - code: '', - menuTextEn: '', - menuTextTr: '', - parentCode: initialParentCode, - icon: '', - shortName: '', - order: initialOrder, - }) - }, [isOpen, initialParentCode, initialOrder]) - - const handleSave = async () => { - if (!form.code.trim() || !form.menuTextEn.trim()) return - setSaving(true) - try { - // Menü oluşturuluyor - await menuService.createWithLanguageKeyText({ - code: form.code.trim(), - displayName: form.code.trim(), - parentCode: form.parentCode.trim() || undefined, - icon: form.icon || undefined, - shortName: form.shortName.trim() || undefined, - order: form.order, - isDisabled: false, - menuTextTr: form.menuTextTr.trim(), - menuTextEn: form.menuTextEn.trim(), - } as MenuDto) - - onSaved() - onClose() - } catch (e: any) { - toast.push(, { placement: 'top-end' }) - } finally { - setSaving(false) - } - } - - // suppress unused warning — rawItems kept for future use - void rawItems - - const fieldCls = - 'h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400 w-full' - const disabledCls = - 'h-11 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700 text-sm text-gray-400 dark:text-gray-500 cursor-not-allowed w-full' - const labelCls = 'text-xs font-medium text-gray-500 dark:text-gray-400 mb-1' - - return ( - -
- {/* Header */} -
- -
{translate('::ListForms.Wizard.Step1.AddNewMenu')}
-
- - {/* Row 1 — Name | Code */} -
-
- - - setForm((p) => ({ - ...p, - name: e.target.value.replace(/\s+/g, ''), - code: `App.Wizard.${e.target.value.replace(/\s+/g, '')}`, - })) - } - placeholder="MyMenu" - className={fieldCls} - /> -
-
- - -
-
- - {/* Row 3 — Icon (full width) */} -
- - setForm((p) => ({ ...p, icon: key }))} - /> -
- - {/* Row 2 — Display Name EN | Display Name TR */} -
-
- - setForm((p) => ({ ...p, menuTextEn: e.target.value }))} - placeholder="My Menu" - className={fieldCls} - /> -
-
- - setForm((p) => ({ ...p, menuTextTr: e.target.value }))} - placeholder="Menüm" - className={fieldCls} - /> -
-
- - {/* Row 4 — Menu Parent | Order */} -
-
- - -
-
- - setForm((p) => ({ ...p, order: Number(e.target.value) }))} - className={fieldCls} - /> -
-
- - {/* Row 5 — Short Name (full width) */} -
- - setForm((p) => ({ ...p, shortName: e.target.value }))} - placeholder="My Menu (short)" - className={fieldCls} - /> -
- - {/* Footer */} -
- - -
-
-
- ) -} - // ─── WizardStep1 ────────────────────────────────────────────────────────────── export interface WizardStep1Props { diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index ec66b44..6d9bb5f 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -13,11 +13,21 @@ import { FaEdit, FaTimes, FaArrowRight, + FaChevronDown, + FaChevronRight, } from 'react-icons/fa' import { sqlObjectManagerService } from '@/services/sql-query-manager.service' -import { MenuService } from '@/services/menu.service' +import { getMenus } from '@/services/menu.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { CascadeBehavior, SqlTableRelation, RelationshipType } from '@/proxy/developerKit/models' +import navigationIcon from '@/proxy/menus/navigation-icon.config' +import { MenuItem } from '@/proxy/menus/menu' +import { + MenuTreeNode, + buildMenuTree, + filterNonLinkNodes, +} from '@/views/admin/listForm/WizardStep1' +import { MenuAddDialog } from '../shared/MenuAddDialog' // ─── Types ──────────────────────────────────────────────────────────────────── @@ -44,11 +54,6 @@ interface ColumnDefinition { description: string } -interface MenuOption { - value: string - label: string -} - interface TableSettings { menuValue: string menuPrefix: string @@ -234,13 +239,27 @@ function generateCreateTableSql( for (const rel of relationships) { if (!rel.fkColumnName.trim() || !rel.referencedTable.trim()) continue const constraintName = `FK_${tableName}_${rel.fkColumnName}` - const cascadeDelete = rel.cascadeDelete === 'NoAction' ? 'NO ACTION' : rel.cascadeDelete.replace(/([A-Z])/g, ' $1').trim().toUpperCase() - const cascadeUpdate = rel.cascadeUpdate === 'NoAction' ? 'NO ACTION' : rel.cascadeUpdate.replace(/([A-Z])/g, ' $1').trim().toUpperCase() + const cascadeDelete = + rel.cascadeDelete === 'NoAction' + ? 'NO ACTION' + : rel.cascadeDelete + .replace(/([A-Z])/g, ' $1') + .trim() + .toUpperCase() + const cascadeUpdate = + rel.cascadeUpdate === 'NoAction' + ? 'NO ACTION' + : rel.cascadeUpdate + .replace(/([A-Z])/g, ' $1') + .trim() + .toUpperCase() fkLines.push('') fkLines.push(`ALTER TABLE ${fullTableName}`) fkLines.push(` ADD CONSTRAINT [${constraintName}]`) fkLines.push(` FOREIGN KEY ([${rel.fkColumnName}])`) - fkLines.push(` REFERENCES [dbo].[${rel.referencedTable}] ([${rel.referencedColumn || 'Id'}])`) + fkLines.push( + ` REFERENCES [dbo].[${rel.referencedTable}] ([${rel.referencedColumn || 'Id'}])`, + ) fkLines.push(` ON DELETE ${cascadeDelete}`) fkLines.push(` ON UPDATE ${cascadeUpdate};`) } @@ -359,7 +378,8 @@ function generateAlterTableSql( const orig = origById.get(col.id) if (!orig) return // new column, already handled above - const nameChanged = orig.columnName.trim().toLowerCase() !== col.columnName.trim().toLowerCase() + const nameChanged = + orig.columnName.trim().toLowerCase() !== col.columnName.trim().toLowerCase() const typeChanged = orig.dataType !== col.dataType || orig.maxLength !== col.maxLength const nullChanged = orig.isNullable !== col.isNullable @@ -390,7 +410,12 @@ function generateAlterTableSql( // 🔗 FK Diff: drop removed / drop+re-add modified / add new const fkCascadeSql = (b: CascadeBehavior) => - b === 'NoAction' ? 'NO ACTION' : b.replace(/([A-Z])/g, ' $1').trim().toUpperCase() + b === 'NoAction' + ? 'NO ACTION' + : b + .replace(/([A-Z])/g, ' $1') + .trim() + .toUpperCase() const addFkSql = (rel: SqlTableRelation) => { const cname = rel.constraintName ?? `FK_${tableName}_${rel.fkColumnName}` @@ -461,6 +486,138 @@ function generateAlterTableSql( const STEPS = ['Sütun Tasarımı', 'Entity Ayarları', 'İlişkiler', 'T-SQL Önizleme'] as const type Step = 0 | 1 | 2 | 3 +// ─── Simple Menu Tree (read-only selection) ─────────────────────────────────── + +interface SimpleMenuTreeNodeProps { + node: MenuTreeNode & { shortName?: string } + depth: number + selectedCode: string + onSelect: (code: string) => void + expanded: Set + onToggle: (code: string) => void +} + +function SimpleMenuTreeNode({ + node, + depth, + selectedCode, + onSelect, + expanded, + onToggle, +}: SimpleMenuTreeNodeProps) { + const hasChildren = node.children.length > 0 + const isExpanded = expanded.has(node.code) + const isSelected = node.code === selectedCode + const NodeIcon = node.icon ? navigationIcon[node.icon] : null + const { translate } = useLocalization() + + return ( +
+
onSelect(node.code)} + > + + {hasChildren ? ( + isExpanded ? ( + + ) : ( + + ) + ) : null} + + {NodeIcon && ( + + )} + {translate('::' + node.code)} + {node.shortName && ( + + {node.shortName} + + )} +
+ {isExpanded && + node.children.map((child: MenuTreeNode) => ( + + ))} +
+ ) +} + +interface SimpleMenuTreeSelectProps { + selectedCode: string + onSelect: (code: string) => void + nodes: MenuTreeNode[] + isLoading: boolean + invalid?: boolean +} + +function SimpleMenuTreeSelect({ + selectedCode, + onSelect, + nodes, + isLoading, + invalid, +}: SimpleMenuTreeSelectProps) { + const [expanded, setExpanded] = useState>(new Set()) + + const toggle = (code: string) => + setExpanded((prev) => { + const n = new Set(prev) + n.has(code) ? n.delete(code) : n.add(code) + return n + }) + + return ( +
+
+ {isLoading ? ( +
Loading…
+ ) : nodes.length === 0 ? ( +
No menus available
+ ) : ( + nodes.map((node) => ( + + )) + )} +
+
+ ) +} + const createEmptyColumn = (): ColumnDefinition => ({ id: crypto.randomUUID(), columnName: '', @@ -497,7 +654,10 @@ const SqlTableDesignerDialog = ({ const [originalColumns, setOriginalColumns] = useState([]) const [colsLoading, setColsLoading] = useState(false) const [settings, setSettings] = useState(DEFAULT_SETTINGS) - const [menuOptions, setMenuOptions] = useState([]) + const [rawMenuItems, setRawMenuItems] = useState([]) + const [menuTree, setMenuTree] = useState([]) + const [selectedMenuCode, setSelectedMenuCode] = useState('') + const [menuAddDialogOpen, setMenuAddDialogOpen] = useState(false) const [menuLoading, setMenuLoading] = useState(false) const [relationships, setRelationships] = useState([]) const [originalRelationships, setOriginalRelationships] = useState([]) @@ -506,24 +666,35 @@ const SqlTableDesignerDialog = ({ const [editingFkId, setEditingFkId] = useState(null) const [fkForm, setFkForm] = useState>(EMPTY_FK) const [dbTables, setDbTables] = useState<{ schemaName: string; tableName: string }[]>([]) - const [targetTableColumns, setTargetTableColumns] = useState([]) + const [targetTableColumns, setTargetTableColumns] = useState([]) const [targetColsLoading, setTargetColsLoading] = useState(false) - useEffect(() => { - if (!isOpen) return + const reloadMenus = (onLoaded?: (items: MenuItem[]) => void) => { setMenuLoading(true) - const svc = new MenuService() - svc - .getListMainMenu() + getMenus(0, 1000) .then((res) => { - const opts: MenuOption[] = (res.data || []).map((m: any) => ({ - value: m.shortName, - label: m.displayName, - })) - setMenuOptions(opts) + const items = (res.data?.items ?? []) as MenuItem[] + const filtered = items.filter((m) => !!m.shortName?.trim()) + setRawMenuItems(filtered) + const tree = filterNonLinkNodes(buildMenuTree(filtered)) + setMenuTree(tree) + onLoaded?.(filtered) }) .catch(() => {}) .finally(() => setMenuLoading(false)) + } + + useEffect(() => { + if (!isOpen) return + reloadMenus((items) => { + // In edit mode, auto-select the matching menu code by shortName + if (initialTableData) { + const parts = initialTableData.tableName.split('_') + const derivedShortName = parts[0] ?? '' + const match = items.find((m) => m.shortName === derivedShortName) + if (match?.code) setSelectedMenuCode(match.code) + } + }) if (dataSource) { sqlObjectManagerService @@ -618,7 +789,15 @@ const SqlTableDesignerDialog = ({ originalRelationships, ) : generateCreateTableSql(columns, settings, relationships), - [isEditMode, originalColumns, columns, settings, initialTableData, relationships, originalRelationships], + [ + isEditMode, + originalColumns, + columns, + settings, + initialTableData, + relationships, + originalRelationships, + ], ) // ── Column operations ────────────────────────────────────────────────────── @@ -629,9 +808,7 @@ const SqlTableDesignerDialog = ({ const addFullAuditedColumns = () => { const existingNames = new Set(columns.map((c) => c.columnName.trim().toLowerCase())) - const toAdd = FULL_AUDIT_COLUMNS.filter( - (c) => !existingNames.has(c.columnName.toLowerCase()), - ) + const toAdd = FULL_AUDIT_COLUMNS.filter((c) => !existingNames.has(c.columnName.toLowerCase())) setColumns((prev) => { const nonEmpty = prev.filter((c) => c.columnName.trim() !== '') return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()] @@ -674,12 +851,14 @@ const SqlTableDesignerDialog = ({ const buildTableName = (prefix: string, entity: string) => prefix && entity ? `${prefix}_D_${entity}` : '' - const onMenuChange = (value: string) => { - const opt = menuOptions.find((o) => o.value === value) - const prefix = opt?.value ?? '' + const onMenuCodeSelect = (code: string) => { + if (isEditMode) return + const item = rawMenuItems.find((m) => m.code === code) + const prefix = item?.shortName ?? '' + setSelectedMenuCode(code) setSettings((s) => ({ ...s, - menuValue: value, + menuValue: prefix, menuPrefix: prefix, tableName: buildTableName(prefix, s.entityName), })) @@ -813,7 +992,8 @@ const SqlTableDesignerDialog = ({ } catch (error: any) { toast.push( - {error.response?.data?.error?.message || translate('::App.SqlQueryManager.TableDeployFailed')} + {error.response?.data?.error?.message || + translate('::App.SqlQueryManager.TableDeployFailed')} , { placement: 'top-center' }, ) @@ -831,6 +1011,8 @@ const SqlTableDesignerDialog = ({ setOriginalRelationships([]) setDbTables([]) setTargetTableColumns([]) + setSelectedMenuCode('') + setMenuAddDialogOpen(false) onClose() } @@ -878,192 +1060,207 @@ const SqlTableDesignerDialog = ({
{colsLoading && (
- {translate('::App.SqlQueryManager.LoadingColumns')} + {' '} + {translate('::App.SqlQueryManager.LoadingColumns')}
)} {!colsLoading && ( - <> -
-
- - -
-
- - -
-
- - {/* Header row */} -
-
{translate('::App.SqlQueryManager.ColumnName')}
-
{translate('::App.SqlQueryManager.DataType')}
-
{translate('::App.SqlQueryManager.Max')}
-
{translate('::App.SqlQueryManager.Nullable')}
-
{translate('::App.SqlQueryManager.DefaultValue')}
-
{translate('::App.SqlQueryManager.Note')}
-
{translate('::App.SqlQueryManager.Actions')}
-
- - {/* Editable column rows */} - {duplicateColumnNames.size > 0 && ( -
- ⚠️ Aynı isimde sütun tanımlanamaz:{' '} - {[...duplicateColumnNames].map((n) => ( - - {n} - - ))} -
- )} - -
- {columns.map((col, idx) => { - const isDuplicate = - col.columnName.trim() !== '' && - duplicateColumnNames.has(col.columnName.trim().toLowerCase()) - const isNewRow = - isEditMode && - !originalColumns.some((o) => o.id === col.id) - - const origRow = isEditMode ? originalColumns.find((o) => o.id === col.id) : undefined - const isRenamedRow = - isEditMode && - !!origRow && - origRow.columnName.trim().toLowerCase() !== col.columnName.trim().toLowerCase() - const isModifiedRow = - isEditMode && - !!origRow && - !isNewRow && - (origRow.dataType !== col.dataType || - origRow.maxLength !== col.maxLength || - origRow.isNullable !== col.isNullable) - - const rowBg = isDuplicate - ? 'outline outline-1 outline-red-400' - : isNewRow - ? 'bg-green-50 dark:bg-green-900/20 outline outline-1 outline-green-400' - : isRenamedRow && isModifiedRow - ? 'bg-purple-50 dark:bg-purple-900/20 outline outline-1 outline-purple-400' - : isRenamedRow - ? 'bg-blue-50 dark:bg-blue-900/20 outline outline-1 outline-blue-400' - : isModifiedRow - ? 'bg-yellow-50 dark:bg-yellow-900/20 outline outline-1 outline-yellow-400' - : '' - - return ( -
-
- updateColumn(col.id, 'columnName', e.target.value)} - /> + <> +
+
+ +
-
- -
-
- updateColumn(col.id, 'maxLength', e.target.value)} - /> -
-
- updateColumn(col.id, 'isNullable', checked as boolean)} - /> -
-
- updateColumn(col.id, 'defaultValue', e.target.value)} - /> -
-
- updateColumn(col.id, 'description', e.target.value)} - /> -
-
- + - - + {translate('::App.SqlQueryManager.AddColumn')} +
- ) - })} -
- {/* Id warning */} - {!isEditMode && - !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && ( -
- {translate('::App.SqlQueryManager.NoIdColumnWarning')} + {/* Header row */} +
+
{translate('::App.SqlQueryManager.ColumnName')}
+
{translate('::App.SqlQueryManager.DataType')}
+
{translate('::App.SqlQueryManager.Max')}
+
+ {translate('::App.SqlQueryManager.Nullable')} +
+
{translate('::App.SqlQueryManager.DefaultValue')}
+
{translate('::App.SqlQueryManager.Note')}
+
+ {translate('::App.SqlQueryManager.Actions')} +
- )} - + + {/* Editable column rows */} + {duplicateColumnNames.size > 0 && ( +
+ ⚠️ Aynı isimde sütun tanımlanamaz:{' '} + {[...duplicateColumnNames].map((n) => ( + + {n} + + ))} +
+ )} + +
+ {columns.map((col, idx) => { + const isDuplicate = + col.columnName.trim() !== '' && + duplicateColumnNames.has(col.columnName.trim().toLowerCase()) + const isNewRow = isEditMode && !originalColumns.some((o) => o.id === col.id) + + const origRow = isEditMode ? originalColumns.find((o) => o.id === col.id) : undefined + const isRenamedRow = + isEditMode && + !!origRow && + origRow.columnName.trim().toLowerCase() !== col.columnName.trim().toLowerCase() + const isModifiedRow = + isEditMode && + !!origRow && + !isNewRow && + (origRow.dataType !== col.dataType || + origRow.maxLength !== col.maxLength || + origRow.isNullable !== col.isNullable) + + const rowBg = isDuplicate + ? 'outline outline-1 outline-red-400' + : isNewRow + ? 'bg-green-50 dark:bg-green-900/20 outline outline-1 outline-green-400' + : isRenamedRow && isModifiedRow + ? 'bg-purple-50 dark:bg-purple-900/20 outline outline-1 outline-purple-400' + : isRenamedRow + ? 'bg-blue-50 dark:bg-blue-900/20 outline outline-1 outline-blue-400' + : isModifiedRow + ? 'bg-yellow-50 dark:bg-yellow-900/20 outline outline-1 outline-yellow-400' + : '' + + return ( +
+
+ updateColumn(col.id, 'columnName', e.target.value)} + /> +
+
+ +
+
+ updateColumn(col.id, 'maxLength', e.target.value)} + /> +
+
+ updateColumn(col.id, 'isNullable', checked as boolean)} + /> +
+
+ updateColumn(col.id, 'defaultValue', e.target.value)} + /> +
+
+ updateColumn(col.id, 'description', e.target.value)} + /> +
+
+ + + +
+
+ ) + })} +
+ + {/* Id warning */} + {!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && ( +
+ {translate('::App.SqlQueryManager.NoIdColumnWarning')} +
+ )} + )}
) @@ -1075,23 +1272,57 @@ const SqlTableDesignerDialog = ({
{/* Menu Name */}
- - - {menuLoading &&

{translate('::App.SqlQueryManager.Loading')}

} +
+ +
+ + {settings.menuValue && !isEditMode && ( + + )} +
+
+ {} : onMenuCodeSelect} + nodes={menuTree} + isLoading={menuLoading} + invalid={!settings.menuValue && !isEditMode && !menuLoading} + /> + {settings.menuValue && ( +

+ + {settings.menuValue} + +

+ )} + setMenuAddDialogOpen(false)} + initialParentCode={selectedMenuCode} + initialOrder={999} + rawItems={rawMenuItems} + onSaved={() => reloadMenus()} + />
{/* Entity Name */} @@ -1111,7 +1342,9 @@ const SqlTableDesignerDialog = ({ {/* Table Name (readonly, auto-generated) */}
- +
- {/* Warning: no Id column */} - {!isEditMode && - !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && ( -
- {translate('::App.SqlQueryManager.NoIdColumnError')} -
- )} + {!isEditMode && !columns.some((c) => c.columnName.trim().toLowerCase() === 'id') && ( +
+ {translate('::App.SqlQueryManager.NoIdColumnError')} +
+ )}
) @@ -1140,279 +1371,315 @@ const SqlTableDesignerDialog = ({ {/* Loading indicator for edit mode */} {fksLoading && (
- {translate('::App.SqlQueryManager.LoadingFkConstraints')} + {' '} + {translate('::App.SqlQueryManager.LoadingFkConstraints')}
)} - {!fksLoading && <> - {/* Header */} -
- - {relationships.length === 0 - ? translate('::App.SqlQueryManager.NoRelationshipsDefined') - : `${relationships.length} ${translate('::App.SqlQueryManager.Relationship')}`} - - -
- - {/* Empty state */} - {relationships.length === 0 && ( -
- -

{translate('::App.SqlQueryManager.NoRelationshipsYet')}

-

{translate('::App.SqlQueryManager.StepIsOptional')}

-
- )} - - {/* Relationship cards */} -
- {relationships.map((rel) => ( -
-
- -
-
-
- - [{rel.fkColumnName}] - - - - [dbo].[{rel.referencedTable}].[{rel.referencedColumn || 'Id'}] - - - {REL_TYPES.find((t) => t.value === rel.relationshipType)?.label} - -
-
- ON DELETE: {rel.cascadeDelete} - ON UPDATE: {rel.cascadeUpdate} - {rel.isRequired && ( - {translate('::App.SqlQueryManager.Required')} - )} - {rel.description && ( - {rel.description} - )} -
-
-
- - -
+ {!fksLoading && ( + <> + {/* Header */} +
+ + {relationships.length === 0 + ? translate('::App.SqlQueryManager.NoRelationshipsDefined') + : `${relationships.length} ${translate('::App.SqlQueryManager.Relationship')}`} + +
- ))} -
- } + + {/* Empty state */} + {relationships.length === 0 && ( +
+ +

+ {translate('::App.SqlQueryManager.NoRelationshipsYet')} +

+

+ {translate('::App.SqlQueryManager.StepIsOptional')} +

+
+ )} + + {/* Relationship cards */} +
+ {relationships.map((rel) => ( +
+
+ +
+
+
+ + [{rel.fkColumnName}] + + + + [dbo].[{rel.referencedTable}].[{rel.referencedColumn || 'Id'}] + + + {REL_TYPES.find((t) => t.value === rel.relationshipType)?.label} + +
+
+ + ON DELETE: {rel.cascadeDelete} + + + ON UPDATE: {rel.cascadeUpdate} + + {rel.isRequired && ( + + {translate('::App.SqlQueryManager.Required')} + + )} + {rel.description && ( + {rel.description} + )} +
+
+
+ + +
+
+ ))} +
+ + )} {/* FK Add/Edit Modal */} - {fkModalOpen && createPortal( -
-
- {/* Modal Header */} -
-

- {editingFkId ? translate('::App.SqlQueryManager.EditRelationship') : translate('::App.SqlQueryManager.AddNewRelationship')} -

- -
- - {/* Modal Body */} -
- {/* Relationship type */} -
- -
- {REL_TYPES.map((t) => ( - - ))} -
+ {fkModalOpen && + createPortal( +
+
+ {/* Modal Header */} +
+

+ {editingFkId + ? translate('::App.SqlQueryManager.EditRelationship') + : translate('::App.SqlQueryManager.AddNewRelationship')} +

+
- {/* FK column / Referenced table & column */} -
+ {/* Modal Body */} +
+ {/* Relationship type */}
-
+
+ + + +
+
-
-
- - -
+ {/* Cascade */} +
+
+ + +
+
+ + +
+
- {/* Cascade */} -
+ {/* Options */} +
+ +
+ + {/* Description */}
- -
-
- - -
-
- - {/* Options */} -
-