From ab6a33c34075d3fbdb4a164532eccf26da6a91d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Fri, 17 Jul 2026 23:23:43 +0300 Subject: [PATCH] Wizard Problemleri Giderildi --- .../ListForms/Wizard/ListFormWizardDto.cs | 1 + .../ListForms/ListFormWizardAppService.cs | 19 +- .../Menu/MenuAppService.cs | 25 ++ .../Seeds/LanguagesData.json | 18 +- .../Seeds/SqlData/Pro_T_Approval.sql | 18 ++ .../Seeds/SqlData/Pro_T_Todo.sql | 22 ++ .../WizardData/202607171806_Approval.json | 281 ++++++++++++++++++ .../Seeds/WizardData/202607172226_Todo.json | 281 ++++++++++++++++++ .../Seeds/WizardDataSeeder.cs | 33 +- .../WizardConsts.cs | 12 +- .../Queries/QueryManager.cs | 176 ++++++----- ui/public/version.json | 13 +- ui/src/proxy/admin/wizard/models.ts | 5 +- ui/src/views/admin/listForm/wizard/Wizard.tsx | 70 ++++- .../admin/listForm/wizard/WizardStep1.tsx | 2 + .../admin/listForm/wizard/WizardStep3.tsx | 94 ++++-- .../admin/listForm/wizard/WizardStep6.tsx | 101 +++++-- .../admin/listForm/wizard/WizardStep7.tsx | 166 ++++++----- .../admin/listForm/wizard/WizardStep8.tsx | 77 +++-- .../developerKit/SqlTableDesignerDialog.tsx | 164 +++++----- ui/src/views/list/CardView.tsx | 11 +- ui/src/views/list/TodoBoard.tsx | 70 ++++- ui/src/views/list/useListFormColumns.ts | 4 + 23 files changed, 1320 insertions(+), 343 deletions(-) create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Approval.sql create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Todo.sql create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607171806_Approval.json create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607172226_Todo.json diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs index bfe84ab..f53e96f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs @@ -41,6 +41,7 @@ public class ListFormWizardDto public string PermissionGroupName { get; set; } public string MenuParentCode { get; set; } + public string MenuParentShortName { get; set; } public string MenuParentIcon { get; set; } public string MenuIcon { get; set; } public string DataSourceCode { get; set; } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 97b125b..0584998 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -180,11 +180,18 @@ public class ListFormWizardAppService( Code = input.MenuParentCode, DisplayName = input.MenuParentCode, IsDisabled = false, + ShortName = input.MenuParentShortName?.Trim(), Icon = menuParentIcon, Order = maxRootOrder + 1, }, autoSave: false); inserted.MenuCodes.Add(input.MenuParentCode); } + else if (string.IsNullOrWhiteSpace(menuParent.ShortName) && + !string.IsNullOrWhiteSpace(input.MenuParentShortName)) + { + menuParent.ShortName = input.MenuParentShortName.Trim(); + await repoMenu.UpdateAsync(menuParent, autoSave: false); + } //Menu var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0; @@ -301,8 +308,8 @@ public class ListFormWizardAppService( ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(), PermissionJson = WizardConsts.DefaultPermissionJson(code), DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, - DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), - InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), + DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultDeleteFieldsJsonOnlyId(input.KeyFieldDbSourceType), + InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultInsertFieldsJsonOnlyId(input.KeyFieldDbSourceType), PagerOptionJson = WizardConsts.DefaultPagerOptionJson, EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail), EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, @@ -382,9 +389,11 @@ public class ListFormWizardAppService( Width = 0, ListOrderNo = fieldOrder, EditGroupOrderNo = item.IncludeInEditingForm ? editGroupOrder : null, - EditOrderNo = item.IncludeInEditingForm && item.FieldName != input.KeyFieldName ? editOrder : null, - EditorType2 = item.IncludeInEditingForm ? item.EditorType : null, - ColSpan = item.IncludeInEditingForm ? item.ColSpan : null, + EditOrderNo = item.IncludeInEditingForm && item.FieldName != input.KeyFieldName + ? editOrder + : null, + EditorType2 = item.EditorType, + ColSpan = item.ColSpan, EditorOptions = string.IsNullOrWhiteSpace(item.EditorOptions) ? null : item.EditorOptions, EditorScript = string.IsNullOrWhiteSpace(item.EditorScript) ? null : item.EditorScript, SourceDbType = item.DbSourceType, diff --git a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs index 133d189..5071e87 100644 --- a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs @@ -15,6 +15,7 @@ using Volo.Abp.PermissionManagement; using Volo.Abp.TenantManagement; using static Sozsoft.Platform.Data.Seeds.SeedConsts; using Sozsoft.Languages; +using Sozsoft.Languages.Languages; using Sozsoft.Platform.ListForms; namespace Sozsoft.Platform.Menus; @@ -131,6 +132,30 @@ public class MenuAppService : CrudAppService< .ToList(); entityDtos = await base.MapToGetListOutputDtosAsync(entities); + + var languageKeys = entityDtos + .Select(item => string.IsNullOrWhiteSpace(item.DisplayName) ? item.Code : item.DisplayName) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .Distinct() + .ToList(); + var languageTexts = await _repositoryText.GetListAsync(text => + text.ResourceName == PlatformConsts.AppName && + languageKeys.Contains(text.Key) && + (text.CultureName == PlatformConsts.DefaultLanguage || + text.CultureName == LanguageCodes.Tr)); + + foreach (var menu in entityDtos) + { + var languageKey = string.IsNullOrWhiteSpace(menu.DisplayName) + ? menu.Code + : menu.DisplayName; + menu.MenuTextEn = languageTexts.FirstOrDefault(text => + text.Key == languageKey && + text.CultureName == PlatformConsts.DefaultLanguage)?.Value; + menu.MenuTextTr = languageTexts.FirstOrDefault(text => + text.Key == languageKey && + text.CultureName == LanguageCodes.Tr)?.Value; + } } return new PagedResultDto( diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 262dbb0..fb5a6a8 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -19467,7 +19467,7 @@ { "resourceName": "Platform", "key": "ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder", - "en": "Inbox, Today, This Week, This Month, Later, Done", + "en": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging", "tr": "Gelen Kutusu, Bugün, Bu Hafta, Bu Ay, Daha Sonra, Tamamlandı" }, { @@ -19476,6 +19476,18 @@ "en": "Allow cards to be dragged between columns", "tr": "Kartları kolonlar arasında sürüklemeye izin ver" }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormFieldEdit.TodoRequiredFields", + "en": "Title field and column / status field are required.", + "tr": "Başlık alanı ve kolon / durum alanı zorunludur." + }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormFieldEdit.TodoDisabledInListSettings", + "en": "Todo view is disabled in the list settings. You can skip this step or enable the Todo view in the previous steps.", + "tr": "Todo görünümü liste ayarlarında kapalı. Bu adımı atlayabilir veya önceki adımlardan Todo görünümünü etkinleştirebilirsiniz." + }, { "resourceName": "Platform", "key": "ListForms.TodoBoard.Uncategorized", @@ -19743,13 +19755,13 @@ { "resourceName": "Platform", "key": "ListForms.TodoBoard.NewColumn", - "en": "New Kanban column", + "en": "New Kanban", "tr": "Yeni kanban" }, { "resourceName": "Platform", "key": "ListForms.TodoBoard.AddNewColumn", - "en": "Add a new Kanban column", + "en": "Add a new Kanban", "tr": "Yeni kanban ekle" }, { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Approval.sql b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Approval.sql new file mode 100644 index 0000000..94c4b87 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Approval.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID(N'[dbo].[Pro_T_Approval]', 'U') IS NULL +BEGIN + CREATE TABLE [dbo].[Pro_T_Approval] + ( + [Id] uniqueidentifier NOT NULL DEFAULT NEWID(), + [ApprovalUserName] nvarchar(256) NULL, + [ApprovalStatus] nvarchar(50) NULL, + [ApprovalDate] datetime NULL, + [ApprovalDescription] nvarchar(200) NULL, + [TenantId] uniqueidentifier NULL, + [Title] nvarchar(100) NULL, + CONSTRAINT [PK_Pro_T_Approval] PRIMARY KEY NONCLUSTERED + ( + [Id] ASC + )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] + ) ON [PRIMARY] +END +GO \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Todo.sql b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Todo.sql new file mode 100644 index 0000000..cafaad5 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Pro_T_Todo.sql @@ -0,0 +1,22 @@ +IF OBJECT_ID(N'[dbo].[Pro_T_Todo]', 'U') IS NULL +BEGIN + CREATE TABLE [dbo].[Pro_T_Todo] + ( + [Id] uniqueidentifier NOT NULL DEFAULT NEWID(), + [Title] nvarchar(300) NOT NULL, + [Status] nvarchar(50) NOT NULL DEFAULT N'Inbox', + [Description] nvarchar(MAX) NULL, + [DueDate] datetime2 NULL, + [Tags] nvarchar(500) NULL, + [Assignees] nvarchar(500) NULL, + [Priority] nvarchar(50) NULL, + [Completed] bit NOT NULL DEFAULT 0, + [SortOrder] int NOT NULL DEFAULT 0, + [TenantId] uniqueidentifier NULL, + CONSTRAINT [PK_Table] PRIMARY KEY NONCLUSTERED + ( + [Id] ASC + )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] + ) ON [PRIMARY] +END +GO \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607171806_Approval.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607171806_Approval.json new file mode 100644 index 0000000..2ba2cde --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607171806_Approval.json @@ -0,0 +1,281 @@ +{ + "Wizard": { + "WizardName": "Approval", + "ListFormCode": "App.Wizard.Approval", + "MenuCode": "App.Wizard.Approval", + "IsTenant": true, + "IsBranch": false, + "IsOrganizationUnit": false, + "AllowAdding": true, + "AllowUpdating": true, + "AllowDeleting": true, + "AllowDetail": false, + "ConfirmDelete": true, + "DefaultLayout": "grid", + "Grid": true, + "Card": true, + "Pivot": true, + "Tree": true, + "Chart": true, + "Gantt": true, + "Scheduler": true, + "Todo": false, + "LanguageTextMenuEn": "Approval", + "LanguageTextMenuTr": "Onaylama", + "LanguageTextTitleEn": "Approval", + "LanguageTextTitleTr": "Onaylama", + "LanguageTextDescEn": "Approval", + "LanguageTextDescTr": "Onaylama", + "LanguageTextMenuParentEn": "Project", + "LanguageTextMenuParentTr": "Project", + "PermissionGroupName": "App.Wizard.Project", + "MenuParentCode": "App.Wizard.Project", + "MenuParentShortName": "Pro", + "MenuParentIcon": "FcVideoProjector", + "MenuIcon": "FcApproval", + "DataSourceCode": "Default", + "DataSourceConnectionString": "", + "SelectCommandType": 1, + "SelectCommand": "Pro_T_Approval", + "KeyFieldName": "Id", + "KeyFieldDbSourceType": 9, + "TreeKeyExpr": "", + "TreeParentIdExpr": "", + "TreeAutoExpandAll": false, + "GanttKeyExpr": "", + "GanttParentIdExpr": "", + "GanttAutoExpandAll": false, + "GanttTitleExpr": "", + "GanttStartExpr": "", + "GanttEndExpr": "", + "GanttProgressExpr": "", + "SchedulerTextExpr": "", + "SchedulerStartDateExpr": "", + "SchedulerEndDateExpr": "", + "TodoTitleExpr": "", + "TodoStatusExpr": "", + "TodoDescriptionExpr": "", + "TodoDueDateExpr": "", + "TodoTagExpr": "", + "TodoAssigneeExpr": "", + "TodoPriorityExpr": "", + "TodoCompletedExpr": "", + "TodoOrderExpr": "", + "TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging", + "TodoAllowDragging": true, + "ApprovalUserFieldName": "ApprovalUserName", + "ApprovalDateFieldName": "ApprovalDate", + "ApprovalStatusFieldName": "ApprovalStatus", + "ApprovalDescriptionFieldName": "ApprovalDescription", + "ApprovalIsFilterUserName": false, + "Criteria": [ + { + "ListFormCode": "App.Wizard.Approval", + "Kind": "Start", + "Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat1", + "CompareColumn": "Price", + "CompareOperator": "\u003E", + "CompareValue": 5000, + "Approver": "", + "NextOnStart": "App.Wizard.Approval-N002", + "NextOnTrue": "", + "NextOnFalse": "", + "NextOnApprove": "", + "NextOnReject": "", + "PositionX": 46, + "PositionY": 46, + "CompareOutcomes": [], + "Id": "App.Wizard.Approval-N001" + }, + { + "ListFormCode": "App.Wizard.Approval", + "Kind": "Approval", + "Title": "Onay1", + "CompareColumn": "Price", + "CompareOperator": "\u003E", + "CompareValue": 5000, + "Approver": "system@sozsoft.com", + "NextOnStart": "", + "NextOnTrue": "", + "NextOnFalse": "", + "NextOnApprove": "App.Wizard.Approval-N003", + "NextOnReject": "App.Wizard.Approval-N004", + "PositionX": 337, + "PositionY": 39, + "CompareOutcomes": [], + "Id": "App.Wizard.Approval-N002" + }, + { + "ListFormCode": "App.Wizard.Approval", + "Kind": "Inform", + "Title": "Bilgilendirme1", + "CompareColumn": "Price", + "CompareOperator": "\u003E", + "CompareValue": 5000, + "Approver": "system@sozsoft.com", + "NextOnStart": "App.Wizard.Approval-N004", + "NextOnTrue": "", + "NextOnFalse": "", + "NextOnApprove": "", + "NextOnReject": "", + "PositionX": 538, + "PositionY": 324, + "CompareOutcomes": [], + "Id": "App.Wizard.Approval-N003" + }, + { + "ListFormCode": "App.Wizard.Approval", + "Kind": "End", + "Title": "\u0130\u015F Ak\u0131\u015F\u0131 Bitir1", + "CompareColumn": "Price", + "CompareOperator": "\u003E", + "CompareValue": 5000, + "Approver": "", + "NextOnStart": "", + "NextOnTrue": "", + "NextOnFalse": "", + "NextOnApprove": "", + "NextOnReject": "", + "PositionX": 852, + "PositionY": 26, + "CompareOutcomes": [], + "Id": "App.Wizard.Approval-N004" + } + ], + "Groups": [ + { + "Caption": "", + "ColCount": 1, + "Items": [ + { + "FieldName": "Id", + "CaptionName": "App.Listform.ListformField.Id", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": true, + "DbSourceType": 9, + "TurkishCaption": "Id", + "EnglishCaption": "Id", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "ApprovalUserName", + "CaptionName": "App.Listform.ListformField.ApprovalUserName", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Approval User Name", + "EnglishCaption": "Approval User Name", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "ApprovalStatus", + "CaptionName": "App.Listform.ListformField.ApprovalStatus", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Approval Status", + "EnglishCaption": "Approval Status", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "ApprovalDate", + "CaptionName": "App.Listform.ListformField.ApprovalDate", + "EditorType": "dxDateBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 6, + "TurkishCaption": "Approval Date", + "EnglishCaption": "Approval Date", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "ApprovalDescription", + "CaptionName": "App.Listform.ListformField.ApprovalDescription", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Approval Description", + "EnglishCaption": "Approval Description", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Title", + "CaptionName": "App.Listform.ListformField.Title", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": true, + "DbSourceType": 16, + "TurkishCaption": "Title", + "EnglishCaption": "Title", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + } + ] + } + ], + "SubForms": [], + "Widgets": [] + }, + "IsDeletedField": false, + "IsCreatedField": false, + "InsertedRecords": { + "LanguageKeys": [ + "App.Wizard.Approval", + "App.Wizard.Approval.Title", + "App.Wizard.Approval.Desc" + ], + "PermissionGroupNames": [], + "PermissionNames": [ + "App.Wizard.Approval", + "App.Wizard.Approval.Create", + "App.Wizard.Approval.Update", + "App.Wizard.Approval.Delete", + "App.Wizard.Approval.Export", + "App.Wizard.Approval.Import", + "App.Wizard.Approval.Note" + ], + "MenuCodes": [ + "App.Wizard.Approval" + ], + "DataSourceCodes": [] + } +} \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607172226_Todo.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607172226_Todo.json new file mode 100644 index 0000000..db30f65 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607172226_Todo.json @@ -0,0 +1,281 @@ +{ + "Wizard": { + "WizardName": "Todo", + "ListFormCode": "App.Wizard.Todo", + "MenuCode": "App.Wizard.Todo", + "IsTenant": true, + "IsBranch": false, + "IsOrganizationUnit": false, + "AllowAdding": true, + "AllowUpdating": true, + "AllowDeleting": true, + "AllowDetail": false, + "ConfirmDelete": true, + "DefaultLayout": "todo", + "Grid": true, + "Card": true, + "Pivot": true, + "Tree": true, + "Chart": true, + "Gantt": true, + "Scheduler": true, + "Todo": true, + "LanguageTextMenuEn": "Todo", + "LanguageTextMenuTr": "\u0130\u015Fler", + "LanguageTextTitleEn": "Todo", + "LanguageTextTitleTr": "\u0130\u015Fler", + "LanguageTextDescEn": "Todo", + "LanguageTextDescTr": "\u0130\u015Fler", + "LanguageTextMenuParentEn": "Project", + "LanguageTextMenuParentTr": "Project", + "PermissionGroupName": "App.Wizard.Project", + "MenuParentCode": "App.Wizard.Project", + "MenuParentShortName": "Pro", + "MenuParentIcon": "FcVideoProjector", + "MenuIcon": "FcTodoList", + "DataSourceCode": "Default", + "DataSourceConnectionString": "", + "SelectCommandType": 1, + "SelectCommand": "Pro_T_Todo", + "KeyFieldName": "Id", + "KeyFieldDbSourceType": 9, + "TreeKeyExpr": "", + "TreeParentIdExpr": "", + "TreeAutoExpandAll": false, + "GanttKeyExpr": "", + "GanttParentIdExpr": "", + "GanttAutoExpandAll": false, + "GanttTitleExpr": "", + "GanttStartExpr": "", + "GanttEndExpr": "", + "GanttProgressExpr": "", + "SchedulerTextExpr": "", + "SchedulerStartDateExpr": "", + "SchedulerEndDateExpr": "", + "TodoTitleExpr": "Title", + "TodoStatusExpr": "Status", + "TodoDescriptionExpr": "Description", + "TodoDueDateExpr": "DueDate", + "TodoTagExpr": "Tags", + "TodoAssigneeExpr": "Assignees", + "TodoPriorityExpr": "Priority", + "TodoCompletedExpr": "Completed", + "TodoOrderExpr": "SortOrder", + "TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging", + "TodoAllowDragging": true, + "ApprovalUserFieldName": "", + "ApprovalDateFieldName": "", + "ApprovalStatusFieldName": "", + "ApprovalDescriptionFieldName": "", + "ApprovalIsFilterUserName": false, + "Criteria": [], + "Groups": [ + { + "Caption": "", + "ColCount": 1, + "Items": [ + { + "FieldName": "Id", + "CaptionName": "App.Listform.ListformField.Id", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": true, + "DbSourceType": 9, + "TurkishCaption": "Id", + "EnglishCaption": "Id", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Title", + "CaptionName": "App.Listform.ListformField.Title", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": true, + "DbSourceType": 16, + "TurkishCaption": "Title", + "EnglishCaption": "Title", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Status", + "CaptionName": "App.Listform.ListformField.Status", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Status", + "EnglishCaption": "Status", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Description", + "CaptionName": "App.Listform.ListformField.Description", + "EditorType": "dxHtmlEditor", + "EditorOptions": "{\u0022mediaResizing\u0022:{\u0022enabled\u0022:true},\u0022imageUpload\u0022:{\u0022tabs\u0022:[\u0022file\u0022,\u0022url\u0022],\u0022fileUploadMode\u0022:\u0022base64\u0022},\u0022toolbar\u0022:{\u0022multiline\u0022:true,\u0022items\u0022:[{\u0022name\u0022:\u0022undo\u0022},{\u0022name\u0022:\u0022redo\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022size\u0022,\u0022acceptedValues\u0022:[\u00228pt\u0022,\u002210pt\u0022,\u002212pt\u0022,\u002214pt\u0022,\u002218pt\u0022,\u002224pt\u0022,\u002236pt\u0022],\u0022options\u0022:{\u0022inputAttr\u0022:{\u0022aria-label\u0022:\u0022Font size\u0022}}},{\u0022name\u0022:\u0022font\u0022,\u0022acceptedValues\u0022:[\u0022Arial\u0022,\u0022Courier New\u0022,\u0022Georgia\u0022,\u0022Impact\u0022,\u0022Lucida Console\u0022,\u0022Tahoma\u0022,\u0022Times New Roman\u0022,\u0022Verdana\u0022],\u0022options\u0022:{\u0022inputAttr\u0022:{\u0022aria-label\u0022:\u0022Font family\u0022}}},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022bold\u0022},{\u0022name\u0022:\u0022italic\u0022},{\u0022name\u0022:\u0022strike\u0022},{\u0022name\u0022:\u0022underline\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022alignLeft\u0022},{\u0022name\u0022:\u0022alignCenter\u0022},{\u0022name\u0022:\u0022alignRight\u0022},{\u0022name\u0022:\u0022alignJustify\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022orderedList\u0022},{\u0022name\u0022:\u0022bulletList\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022header\u0022,\u0022acceptedValues\u0022:[false,1,2,3,4,5]},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022color\u0022},{\u0022name\u0022:\u0022background\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022link\u0022},{\u0022name\u0022:\u0022image\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022clear\u0022},{\u0022name\u0022:\u0022codeBlock\u0022},{\u0022name\u0022:\u0022blockquote\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022insertTable\u0022},{\u0022name\u0022:\u0022deleteTable\u0022},{\u0022name\u0022:\u0022insertRowAbove\u0022},{\u0022name\u0022:\u0022insertRowBelow\u0022},{\u0022name\u0022:\u0022deleteRow\u0022},{\u0022name\u0022:\u0022insertColumnLeft\u0022},{\u0022name\u0022:\u0022insertColumnRight\u0022},{\u0022name\u0022:\u0022deleteColumn\u0022},{\u0022name\u0022:\u0022cellProperties\u0022},{\u0022name\u0022:\u0022tableProperties\u0022}]}}", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Description", + "EnglishCaption": "Description", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "DueDate", + "CaptionName": "App.Listform.ListformField.DueDate", + "EditorType": "dxDateBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 6, + "TurkishCaption": "Due Date", + "EnglishCaption": "Due Date", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Tags", + "CaptionName": "App.Listform.ListformField.Tags", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Tags", + "EnglishCaption": "Tags", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Assignees", + "CaptionName": "App.Listform.ListformField.Assignees", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Assignees", + "EnglishCaption": "Assignees", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Priority", + "CaptionName": "App.Listform.ListformField.Priority", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Priority", + "EnglishCaption": "Priority", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "Completed", + "CaptionName": "App.Listform.ListformField.Completed", + "EditorType": "dxCheckBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": false, + "DbSourceType": 3, + "TurkishCaption": "Completed", + "EnglishCaption": "Completed", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "FieldName": "SortOrder", + "CaptionName": "App.Listform.ListformField.SortOrder", + "EditorType": "dxNumberBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": true, + "IncludeInEditingForm": false, + "DbSourceType": 11, + "TurkishCaption": "Sort Order", + "EnglishCaption": "Sort Order", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + } + ] + } + ], + "SubForms": [], + "Widgets": [] + }, + "IsDeletedField": false, + "IsCreatedField": false, + "InsertedRecords": { + "LanguageKeys": [ + "App.Wizard.Todo", + "App.Wizard.Todo.Title", + "App.Wizard.Todo.Desc", + "App.Listform.ListformField.Assignees", + "App.Listform.ListformField.Completed", + "App.Listform.ListformField.SortOrder" + ], + "PermissionGroupNames": [ + "App.Wizard.Project" + ], + "PermissionNames": [ + "App.Wizard.Todo", + "App.Wizard.Todo.Create", + "App.Wizard.Todo.Update", + "App.Wizard.Todo.Delete", + "App.Wizard.Todo.Export", + "App.Wizard.Todo.Import", + "App.Wizard.Todo.Note" + ], + "MenuCodes": [ + "App.Wizard.Todo" + ], + "DataSourceCodes": [] + } +} \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs index 5d61b8a..96955ff 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs @@ -107,6 +107,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency var fileName = Path.GetFileName(filePath); + await BackfillMenuParentShortNameAsync(seedFile.Wizard); + // Zaten seeded mi kontrol et (ListForm var mı?) if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode)) { @@ -253,6 +255,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency Code = input.MenuParentCode, DisplayName = input.MenuParentCode, IsDisabled = false, + ShortName = input.MenuParentShortName, Icon = menuParentIcon, Order = maxRootOrder + 1, }, autoSave: true); @@ -410,10 +413,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) - : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), + : WizardConsts.DefaultDeleteFieldsJsonOnlyId(input.KeyFieldDbSourceType), InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) - : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), + : WizardConsts.DefaultInsertFieldsJsonOnlyId(input.KeyFieldDbSourceType), PagerOptionJson = WizardConsts.DefaultPagerOptionJson, EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, formWidth, formHeight, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail), EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, @@ -465,9 +468,11 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency AllowSearch = true, ListOrderNo = fieldOrder, EditGroupOrderNo = item.IncludeInEditingForm ? editGroupOrder : null, - EditOrderNo = item.IncludeInEditingForm && item.FieldName != input.KeyFieldName ? editOrder : null, - EditorType2 = item.IncludeInEditingForm ? item.EditorType : null, - ColSpan = item.IncludeInEditingForm ? item.ColSpan : null, + EditOrderNo = item.IncludeInEditingForm && item.FieldName != input.KeyFieldName + ? editOrder + : null, + EditorType2 = item.EditorType, + ColSpan = item.ColSpan, EditorOptions = string.IsNullOrWhiteSpace(item.EditorOptions) ? null : item.EditorOptions, EditorScript = string.IsNullOrWhiteSpace(item.EditorScript) ? null : item.EditorScript, SourceDbType = item.DbSourceType, @@ -517,6 +522,24 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency } } + private async Task BackfillMenuParentShortNameAsync(ListFormWizardDto input) + { + if (string.IsNullOrWhiteSpace(input.MenuParentCode) || + string.IsNullOrWhiteSpace(input.MenuParentShortName)) + return; + + var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode); + if (menuParent == null || !string.IsNullOrWhiteSpace(menuParent.ShortName)) + return; + + menuParent.ShortName = input.MenuParentShortName.Trim(); + await _repoMenu.UpdateAsync(menuParent, autoSave: true); + _logger.LogInformation( + "Menu parent '{MenuParentCode}' ShortName backfilled as '{ShortName}'.", + input.MenuParentCode, + menuParent.ShortName); + } + private static bool HasWorkflow(WorkflowDto workflow, List criteria) { return workflow != null && ( diff --git a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs index 3c7f4df..bdb393c 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs @@ -198,6 +198,14 @@ public static class WizardConsts }); } + public static string DefaultInsertFieldsJsonOnlyId(DbType dbType = DbType.Guid) + { + return JsonSerializer.Serialize(new[] + { + new { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey } + }); + } + public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) { return JsonSerializer.Serialize(new[] @@ -207,11 +215,11 @@ public static class WizardConsts }); } - public static string DefaultFieldsJsonOnlyId(DbType dbType = DbType.Guid) + public static string DefaultDeleteFieldsJsonOnlyId(DbType dbType = DbType.Guid) { return JsonSerializer.Serialize(new[] { - new { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey } + new { FieldName = "Id", FieldDbType = dbType, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey } }); } diff --git a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs index 69b1a72..fb91a1f 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs @@ -170,7 +170,8 @@ public class QueryManager : PlatformDomainService, IQueryManager // oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir if (!string.IsNullOrEmpty(command)) { - sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType); + // sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType); + sql = command; } else { @@ -188,12 +189,6 @@ public class QueryManager : PlatformDomainService, IQueryManager } else if (op == OperationEnum.Update) { - var where = dataSourceType switch - { - DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN @{listForm.KeyFieldName}", - DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})", - _ => string.Empty, - }; var updateFields = parameters.Select(a => $"\"{a.Key}\" = @{a.Key}").ToList(); var val = QueryHelper.GetFormattedValue(listForm.KeyFieldDbSourceType, keys); if (!parameters.ContainsKey(listForm.KeyFieldName)) @@ -202,6 +197,18 @@ public class QueryManager : PlatformDomainService, IQueryManager listForm.KeyFieldName, val.GetType().IsArray ? val : new object[] { val }); } + + var where = BuildWhereCondition( + listForm.KeyFieldName, + parameters[listForm.KeyFieldName], + dataSourceType); + + if (parameters.TryGetValue("TenantId", out var tenantId) + && !string.Equals(listForm.KeyFieldName, "TenantId", StringComparison.OrdinalIgnoreCase)) + { + where += $" AND {BuildWhereCondition("TenantId", tenantId, dataSourceType)}"; + } + sql = $"UPDATE \"{listForm.SelectCommand}\" SET {string.Join(',', updateFields)} WHERE {where}"; } else if (op == OperationEnum.Delete) @@ -212,12 +219,7 @@ public class QueryManager : PlatformDomainService, IQueryManager { where = string.Join( " AND ", - parameters.Select(a => dataSourceType switch - { - DataSourceTypeEnum.Mssql => $"\"{a.Key}\" IN @{a.Key}", - DataSourceTypeEnum.Postgresql => $"\"{a.Key}\" = ANY(@{a.Key})", - _ => "1 = 0", - }).ToList()); + parameters.Select(a => BuildWhereCondition(a.Key, a.Value, dataSourceType)).ToList()); } else { @@ -240,77 +242,101 @@ public class QueryManager : PlatformDomainService, IQueryManager return sql; } - private static string NormalizeCollectionParameterCommand( - string command, - Dictionary parameters, - DataSourceTypeEnum dataSourceType) - { - if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0) - { - return command; - } - - var sql = command; - foreach (var parameter in parameters) - { - if (!IsCollectionParameter(parameter.Value)) - { - continue; - } - - var escapedParameterName = Regex.Escape(parameter.Key); - sql = dataSourceType switch - { - DataSourceTypeEnum.Mssql => NormalizeMssqlCollectionParameter(sql, escapedParameterName, parameter.Key), - DataSourceTypeEnum.Postgresql => NormalizePostgresqlCollectionParameter(sql, escapedParameterName, parameter.Key), - _ => sql - }; - } - - return sql; - } - - private static bool IsCollectionParameter(object value) + private static bool IsCollectionValue(object value) { return value is System.Collections.IEnumerable && value is not string && value is not byte[]; } - private static string NormalizeMssqlCollectionParameter( - string sql, - string escapedParameterName, - string parameterName) + private static string BuildWhereCondition( + string fieldName, + object value, + DataSourceTypeEnum dataSourceType) { - sql = Regex.Replace( - sql, - $@"IN\s*\(\s*@{escapedParameterName}\s*\)", - $"IN @{parameterName}", - RegexOptions.IgnoreCase); - - return Regex.Replace( - sql, - $@"(?(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", - match => $"{match.Groups["column"].Value} IN @{parameterName}", - RegexOptions.IgnoreCase); + return dataSourceType switch + { + DataSourceTypeEnum.Mssql when value is null or DBNull => $"\"{fieldName}\" IS NULL", + DataSourceTypeEnum.Postgresql when value is null or DBNull => $"\"{fieldName}\" IS NULL", + DataSourceTypeEnum.Mssql when IsCollectionValue(value) => $"\"{fieldName}\" IN @{fieldName}", + DataSourceTypeEnum.Postgresql when IsCollectionValue(value) => $"\"{fieldName}\" = ANY(@{fieldName})", + DataSourceTypeEnum.Mssql => $"\"{fieldName}\" = @{fieldName}", + DataSourceTypeEnum.Postgresql => $"\"{fieldName}\" = @{fieldName}", + _ => "1 = 0", + }; } - private static string NormalizePostgresqlCollectionParameter( - string sql, - string escapedParameterName, - string parameterName) - { - sql = Regex.Replace( - sql, - $@"IN\s*\(\s*@{escapedParameterName}\s*\)", - $"= ANY(@{parameterName})", - RegexOptions.IgnoreCase); + // private static string NormalizeCollectionParameterCommand( + // string command, + // Dictionary parameters, + // DataSourceTypeEnum dataSourceType) + // { + // if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0) + // { + // return command; + // } - return Regex.Replace( - sql, - $@"(?(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", - match => $"{match.Groups["column"].Value} = ANY(@{parameterName})", - RegexOptions.IgnoreCase); - } + // var sql = command; + // foreach (var parameter in parameters) + // { + // if (!IsCollectionParameter(parameter.Value)) + // { + // continue; + // } + + // var escapedParameterName = Regex.Escape(parameter.Key); + // sql = dataSourceType switch + // { + // DataSourceTypeEnum.Mssql => NormalizeMssqlCollectionParameter(sql, escapedParameterName, parameter.Key), + // DataSourceTypeEnum.Postgresql => NormalizePostgresqlCollectionParameter(sql, escapedParameterName, parameter.Key), + // _ => sql + // }; + // } + + // return sql; + // } + + // private static bool IsCollectionParameter(object value) + // { + // return value is System.Collections.IEnumerable + // && value is not string + // && value is not byte[]; + // } + + // private static string NormalizeMssqlCollectionParameter( + // string sql, + // string escapedParameterName, + // string parameterName) + // { + // sql = Regex.Replace( + // sql, + // $@"IN\s*\(\s*@{escapedParameterName}\s*\)", + // $"IN @{parameterName}", + // RegexOptions.IgnoreCase); + + // return Regex.Replace( + // sql, + // $@"(?(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", + // match => $"{match.Groups["column"].Value} IN @{parameterName}", + // RegexOptions.IgnoreCase); + // } + + // private static string NormalizePostgresqlCollectionParameter( + // string sql, + // string escapedParameterName, + // string parameterName) + // { + // sql = Regex.Replace( + // sql, + // $@"IN\s*\(\s*@{escapedParameterName}\s*\)", + // $"= ANY(@{parameterName})", + // RegexOptions.IgnoreCase); + + // return Regex.Replace( + // sql, + // $@"(?(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", + // match => $"{match.Groups["column"].Value} = ANY(@{parameterName})", + // RegexOptions.IgnoreCase); + // } } diff --git a/ui/public/version.json b/ui/public/version.json index 2d7a0c8..6c11781 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -1,6 +1,17 @@ { - "commit": "1d05d24", + "commit": "8146adf", "releases": [ + { + "version": "1.1.08", + "buildDate": "2026-07-16", + "commit": "24e92fcb82eef0ee26ca5d65e08c5404f132a09e", + "changeLog": [ + "- CardView komponenti geliştirildi.", + "- Todo / Kanban komponenti geliştirildi.", + "- Licence information", + "- Check New Versiyon komponenti." + ] + }, { "version": "1.1.07", "buildDate": "2026-07-13", diff --git a/ui/src/proxy/admin/wizard/models.ts b/ui/src/proxy/admin/wizard/models.ts index d81170f..f6851dd 100644 --- a/ui/src/proxy/admin/wizard/models.ts +++ b/ui/src/proxy/admin/wizard/models.ts @@ -7,7 +7,7 @@ import { import { ListViewLayoutType } from '@/views/admin/listForm/edit/types' export interface ListFormWizardColumnItemDto { - dataField: string + fieldName: string editorType: string editorOptions: string editorScript: string @@ -56,6 +56,7 @@ export interface ListFormWizardDto { languageTextMenuParentTr: string permissionGroupName: string menuParentCode: string + menuParentShortName?: string menuParentIcon?: string menuIcon: string dataSourceCode: string @@ -129,7 +130,7 @@ export interface WizardInsertedRecordsDto { } export interface WizardSeedFileItemDto { - dataField: string + fieldName: string editorType: string editorOptions: string editorScript: string diff --git a/ui/src/views/admin/listForm/wizard/Wizard.tsx b/ui/src/views/admin/listForm/wizard/Wizard.tsx index ef756fc..259836f 100644 --- a/ui/src/views/admin/listForm/wizard/Wizard.tsx +++ b/ui/src/views/admin/listForm/wizard/Wizard.tsx @@ -64,7 +64,7 @@ const initialValues: ListFormWizardDto = { chart: true, gantt: true, scheduler: true, - todo: true, + todo: false, todoTitleExpr: '', todoStatusExpr: '', todoDescriptionExpr: '', @@ -74,7 +74,7 @@ const initialValues: ListFormWizardDto = { todoPriorityExpr: '', todoCompletedExpr: '', todoOrderExpr: '', - todoStatusOrder: 'Inbox, Today, This Week, This Month, Later, Done', + todoStatusOrder: 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging', todoAllowDragging: true, languageTextMenuEn: '', languageTextMenuTr: '', @@ -86,6 +86,7 @@ const initialValues: ListFormWizardDto = { languageTextMenuParentTr: '', permissionGroupName: '', menuParentCode: '', + menuParentShortName: '', menuParentIcon: '', menuIcon: '', dataSourceCode: '', @@ -146,7 +147,26 @@ const step2ValidationSchema = Yup.object().shape({ confirmDelete: Yup.boolean(), }) -const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSchema) +const todoValidationSchema = Yup.object().shape({ + todoTitleExpr: Yup.string() + .trim() + .when('todo', { + is: true, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired(), + }), + todoStatusExpr: Yup.string() + .trim() + .when('todo', { + is: true, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired(), + }), +}) + +const listFormValidationSchema = step1ValidationSchema + .concat(step2ValidationSchema) + .concat(todoValidationSchema) // ─── Wizard ─────────────────────────────────────────────────────────────────── @@ -266,7 +286,7 @@ const Wizard = () => { const removeTenantGroupItems = (groups: WizardGroup[]) => groups.map((group) => ({ ...group, - items: group.items.filter((item) => !isTenantColumn(item.dataField)), + items: group.items.filter((item) => !isTenantColumn(item.fieldName)), })) const loadColumns = async (dsCode: string, schema: string, name: string) => { @@ -433,7 +453,9 @@ const Wizard = () => { todoPriorityExpr: w.todoPriorityExpr ?? '', todoCompletedExpr: w.todoCompletedExpr ?? '', todoOrderExpr: w.todoOrderExpr ?? '', - todoStatusOrder: w.todoStatusOrder ?? 'Inbox, Today, This Week, This Month, Later, Done', + todoStatusOrder: + w.todoStatusOrder ?? + 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging', todoAllowDragging: w.todoAllowDragging ?? true, languageTextMenuEn: w.languageTextMenuEn ?? '', languageTextMenuTr: w.languageTextMenuTr ?? '', @@ -445,6 +467,7 @@ const Wizard = () => { languageTextMenuParentTr: w.languageTextMenuParentTr ?? '', permissionGroupName: w.permissionGroupName ?? '', menuParentCode: w.menuParentCode ?? '', + menuParentShortName: w.menuParentShortName ?? '', menuParentIcon: w.menuParentIcon ?? '', menuIcon: w.menuIcon ?? '', dataSourceCode: w.dataSourceCode ?? '', @@ -497,19 +520,19 @@ const Wizard = () => { caption: g.caption ?? '', colCount: g.colCount ?? 2, items: (g.items ?? []).map( - (it: any, ii: number) => + (it: WizardGroupItem, ii: number) => ({ - id: `${it.dataField}_edit_${ii}_${Date.now()}`, - dataField: it.dataField ?? '', + id: `${it.fieldName}_edit_${ii}_${Date.now()}`, + fieldName: it.fieldName ?? '', editorType: it.editorType ?? 'dxTextBox', editorOptions: it.editorOptions ?? '', editorScript: it.editorScript ?? '', colSpan: it.colSpan ?? 1, isRequired: it.isRequired ?? false, includeInEditingForm: it.includeInEditingForm ?? true, - turkishCaption: it.turkishCaption ?? it.dataField, - englishCaption: it.englishCaption ?? it.dataField, - captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`, + turkishCaption: it.turkishCaption ?? it.fieldName, + englishCaption: it.englishCaption ?? it.fieldName, + captionName: it.captionName ?? `App.Listform.ListformField.${it.fieldName}`, lookupDataSourceType: (it.lookupDataSourceType as UiLookupDataSourceTypeEnum) ?? UiLookupDataSourceTypeEnum.StaticData, @@ -522,7 +545,7 @@ const Wizard = () => { setEditingGroups(restoredGroups) // Restore selectedColumns from all group items - const allFields = new Set(restoredGroups.flatMap((g) => g.items.map((i) => i.dataField))) + const allFields = new Set(restoredGroups.flatMap((g) => g.items.map((i) => i.fieldName))) setSelectedColumns(allFields) } @@ -638,8 +661,16 @@ const Wizard = () => { formikRef.current?.setFieldValue('menuParentCode', code) const selectedMenu = rawMenuItems.find((item) => item.code === code) formikRef.current?.setFieldValue('menuParentIcon', selectedMenu?.icon ?? '') - if (!code) return - applyPermissionGroupFromRoot(findRootCode(rawMenuItems, code)) + formikRef.current?.setFieldValue('languageTextMenuParentEn', selectedMenu?.menuTextEn ?? '') + formikRef.current?.setFieldValue('languageTextMenuParentTr', selectedMenu?.menuTextTr ?? '') + if (!code) { + formikRef.current?.setFieldValue('menuParentShortName', '') + return + } + const rootCode = findRootCode(rawMenuItems, code) + const rootMenu = rawMenuItems.find((item) => item.code === rootCode) + formikRef.current?.setFieldValue('menuParentShortName', rootMenu?.shortName ?? '') + applyPermissionGroupFromRoot(rootCode) } const handleMenuCreated = async (menu: { @@ -648,6 +679,7 @@ const Wizard = () => { menuTextEn: string menuTextTr: string icon?: string + shortName?: string }) => { formikRef.current?.setFieldValue('menuParentCode', menu.code) formikRef.current?.setFieldValue('menuParentIcon', menu.icon ?? '') @@ -655,6 +687,11 @@ const Wizard = () => { formikRef.current?.setFieldValue('languageTextMenuParentTr', menu.menuTextTr) const rootCode = menu.parentCode ? findRootCode(rawMenuItems, menu.parentCode) : menu.code + const existingRoot = rawMenuItems.find((item) => item.code === rootCode) + formikRef.current?.setFieldValue( + 'menuParentShortName', + menu.parentCode ? (existingRoot?.shortName ?? '') : (menu.shortName ?? ''), + ) applyPermissionGroupFromRoot(rootCode) await getMenuList() @@ -726,10 +763,10 @@ const Wizard = () => { caption: g.caption, colCount: g.colCount, items: g.items.map((item) => { - const col = selectCommandColumns.find((c) => c.columnName === item.dataField) + const col = selectCommandColumns.find((c) => c.columnName === item.fieldName) return { - dataField: item.dataField, + fieldName: item.fieldName, editorType: item.editorType, editorOptions: item.editorOptions ?? '', editorScript: item.editorScript ?? '', @@ -836,6 +873,7 @@ const Wizard = () => { onMenuParentChange={handleMenuParentChange} onClearMenuParent={() => { formikRef.current?.setFieldValue('menuParentCode', '') + formikRef.current?.setFieldValue('menuParentShortName', '') formikRef.current?.setFieldValue('menuParentIcon', '') }} onMenuCreated={handleMenuCreated} diff --git a/ui/src/views/admin/listForm/wizard/WizardStep1.tsx b/ui/src/views/admin/listForm/wizard/WizardStep1.tsx index 0a20d2f..b1d5fbb 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep1.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep1.tsx @@ -26,6 +26,7 @@ import { ListFormWizardDto } from '@/proxy/admin/wizard/models' export interface MenuTreeNode { code: string displayName: string + shortName?: string icon?: string url?: string order?: number @@ -41,6 +42,7 @@ export function buildMenuTree(items: MenuItem[]): MenuTreeNode[] { map.set(item.code!, { code: item.code!, displayName: item.displayName ?? item.code!, + shortName: item.shortName ?? undefined, icon: item.icon ?? undefined, url: item.url ?? undefined, order: item.order ?? undefined, diff --git a/ui/src/views/admin/listForm/wizard/WizardStep3.tsx b/ui/src/views/admin/listForm/wizard/WizardStep3.tsx index 617ec74..e62204a 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep3.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep3.tsx @@ -1,4 +1,5 @@ import { Button, Dialog } from '@/components/ui' +import type { SelectBoxOption } from '@/types/shared' import { useLocalization } from '@/utils/hooks/useLocalization' import { columnEditorTypeListOptions, @@ -28,14 +29,17 @@ import { FaTrash, FaArrowRight, FaCode, + FaSlidersH, } from 'react-icons/fa' import { UiLookupDataSourceTypeEnum } from '@/proxy/form/models' +import EditorOptionsBuilderDialog from '../edit/json-row-operations/EditorOptionsBuilderDialog' +import EditorScriptBuilderDialog from '../edit/json-row-operations/EditorScriptBuilderDialog' import { buildLookupQuery } from '../lookupQuery' // ─── Types ──────────────────────────────────────────────────────────────────── export interface WizardGroupItem { id: string - dataField: string + fieldName: string editorType: string editorOptions: string editorScript: string @@ -109,7 +113,7 @@ function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupIte const sqlType = meta?.dataType ?? '' return { id: `${colName}_${Date.now()}`, - dataField: colName, + fieldName: colName, editorType: inferEditorType(sqlType), editorOptions: '', editorScript: '', @@ -169,6 +173,7 @@ interface SortableItemProps { groupColCount: number dbObjects: SqlObjectExplorerDto | null dsCode: string + editorScriptFields: SelectBoxOption[] onTurkishCaptionChange: (val: string) => void onEnglishCaptionChange: (val: string) => void onEditorTypeChange: (val: string) => void @@ -190,6 +195,7 @@ function SortableItem({ groupColCount, dbObjects, dsCode, + editorScriptFields, onTurkishCaptionChange, onEnglishCaptionChange, onEditorTypeChange, @@ -207,6 +213,8 @@ function SortableItem({ }: SortableItemProps) { const { translate } = useLocalization() const [isTablePickerOpen, setIsTablePickerOpen] = useState(false) + const [isEditorOptionsDialogOpen, setIsEditorOptionsDialogOpen] = useState(false) + const [isEditorScriptDialogOpen, setIsEditorScriptDialogOpen] = useState(false) const [tableSearch, setTableSearch] = useState('') const [pickerStep, setPickerStep] = useState<'table' | 'columns'>('table') const [pickerTable, setPickerTable] = useState<{ schemaName: string; tableName: string } | null>( @@ -248,7 +256,7 @@ function SortableItem({ tabIndex={-1} /> - {item.dataField} + {item.fieldName} diff --git a/ui/src/views/admin/listForm/wizard/WizardStep7.tsx b/ui/src/views/admin/listForm/wizard/WizardStep7.tsx index 4144201..56604ff 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep7.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep7.tsx @@ -4,7 +4,7 @@ import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models' import type { SelectBoxOption } from '@/types/shared' import { Field, type FieldProps, useFormikContext } from 'formik' import { useMemo, useState } from 'react' -import { FaArrowLeft, FaArrowRight, FaColumns } from 'react-icons/fa' +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa' interface WizardStep7Props { selectedColumns: Set @@ -25,18 +25,41 @@ const mappings: Array<{ | 'todoPriorityExpr' | 'todoCompletedExpr' | 'todoOrderExpr' - label: string + translationKey: string required?: boolean }> = [ - { name: 'todoTitleExpr', label: 'Başlık alanı', required: true }, - { name: 'todoStatusExpr', label: 'Kanban / durum alanı', required: true }, - { name: 'todoDescriptionExpr', label: 'Açıklama alanı' }, - { name: 'todoDueDateExpr', label: 'Termin tarihi alanı' }, - { name: 'todoTagExpr', label: 'Etiket alanı' }, - { name: 'todoAssigneeExpr', label: 'Atanan kişi alanı' }, - { name: 'todoPriorityExpr', label: 'Öncelik alanı' }, - { name: 'todoCompletedExpr', label: 'Tamamlandı alanı' }, - { name: 'todoOrderExpr', label: 'Kart sıra alanı' }, + { + name: 'todoTitleExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoTitleExpr', + required: true, + }, + { + name: 'todoStatusExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoStatusExpr', + required: true, + }, + { + name: 'todoDescriptionExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoDescriptionExpr', + }, + { + name: 'todoDueDateExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoDueDateExpr', + }, + { name: 'todoTagExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoTagExpr' }, + { + name: 'todoAssigneeExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoAssigneeExpr', + }, + { + name: 'todoPriorityExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoPriorityExpr', + }, + { + name: 'todoCompletedExpr', + translationKey: '::ListForms.ListFormFieldEdit.TodoCompletedExpr', + }, + { name: 'todoOrderExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoOrderExpr' }, ] const WizardStep7 = ({ @@ -46,7 +69,8 @@ const WizardStep7 = ({ onBack, onNext, }: WizardStep7Props) => { - const { values, setFieldValue } = useFormikContext() + const { errors, touched, values, setFieldTouched, setFieldValue, validateForm } = + useFormikContext() const [validationError, setValidationError] = useState('') const fieldOptions = useMemo( () => @@ -56,9 +80,15 @@ const WizardStep7 = ({ [selectCommandColumns, selectedColumns], ) - const continueToDeploy = () => { - if (values.todo && (!values.todoTitleExpr || !values.todoStatusExpr)) { - setValidationError('Başlık alanı ve Kanban / durum alanı zorunludur.') + const continueToDeploy = async () => { + await Promise.all([ + setFieldTouched('todoTitleExpr', true, false), + setFieldTouched('todoStatusExpr', true, false), + ]) + const validationErrors = await validateForm() + + if (validationErrors.todoTitleExpr || validationErrors.todoStatusExpr) { + setValidationError(translate('::ListForms.ListFormFieldEdit.TodoRequiredFields')) return } setValidationError('') @@ -67,75 +97,63 @@ const WizardStep7 = ({ return (
-
-
- Todo / Kanban ayrıntıları -
-

- Liste alanlarını Todo kartının başlık, durum ve diğer özellikleriyle eşleştirin. -

-
+
+ {mappings.map((mapping) => { + const isRequired = Boolean(mapping.required && values.todo) - {!values.todo ? ( -
- Todo görünümü liste ayarlarında kapalı. Bu adımı atlayabilir veya önceki adımlardan Todo - görünümünü etkinleştirebilirsiniz. -
- ) : ( - <> -
- {mappings.map((mapping) => ( - - - {({ field, form }: FieldProps) => ( - option.value === values[mapping.name])} + onChange={(option) => { + form.setFieldValue(field.name, option?.value ?? '') + setValidationError('') + }} /> )} -
- - )} + ) + })} +
+ +
+ + + + + + {({ field }: FieldProps) => ( + setFieldValue(field.name, checked)} + /> + )} + + +
{validationError &&

{validationError}

}
diff --git a/ui/src/views/admin/listForm/wizard/WizardStep8.tsx b/ui/src/views/admin/listForm/wizard/WizardStep8.tsx index 1f4e224..ed1c10e 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep8.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep8.tsx @@ -218,7 +218,9 @@ const WizardStep8 = ({ ...prev, { id: 999, - label: `Hata: ${err?.message ?? 'Bilinmeyen hata'}`, + label: `${translate('::ListForms.Wizard.Step4.Error')}: ${ + err?.message ?? translate('::App.Platform.Unknown') + }`, status: 'error', }, ]) @@ -231,10 +233,10 @@ const WizardStep8 = ({ const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0) const editingFormFields = groups.flatMap((g) => g.items - .filter((item) => item.includeInEditingForm && item.dataField !== values.keyFieldName) + .filter((item) => item.includeInEditingForm && item.fieldName !== values.keyFieldName) .map((item) => ({ ...item, groupCaption: g.caption })), ) - const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.dataField))) + const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.fieldName))) const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col)) const hasWorkflowFields = Boolean( workflow.approvalUserFieldName || @@ -387,8 +389,8 @@ const WizardStep8 = ({ ) : ( g.items.map((item) => { - const meta = selectCommandColumns.find((c) => c.columnName === item.dataField) - const isKeyField = item.dataField === values.keyFieldName + const meta = selectCommandColumns.find((c) => c.columnName === item.fieldName) + const isKeyField = item.fieldName === values.keyFieldName const isPopupField = item.includeInEditingForm && !isKeyField return (
- {item.dataField} + {item.fieldName} {meta?.dataType && ( @@ -574,17 +576,52 @@ const WizardStep8 = ({ {values.todo && (
- - - - - - - - - - - + + + + + + + + + + +
)} @@ -643,12 +680,12 @@ const WizardStep8 = ({
{isDone && ( - {translate('::ListForms.Wizard.Step4.Success') || 'Başarılı'} + {translate('::ListForms.Wizard.Step4.Success')} )} {hasError && ( - {translate('::ListForms.Wizard.Step4.Error') || 'Hata'} + {translate('::ListForms.Wizard.Step4.Error')} )}
@@ -658,7 +695,7 @@ const WizardStep8 = ({
- {translate('::ListForms.Wizard.Step4.AllInfoReady') || 'Tüm bilgiler hazır.'} + {translate('::ListForms.Wizard.Step4.AllInfoReady')}
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index 832258e..7bbd40b 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -90,6 +90,47 @@ interface TableIndex { // ─── Constants ──────────────────────────────────────────────────────────────── +const REL_TYPES: { value: RelationshipType; label: string; desc: string }[] = [ + { value: 'OneToMany', label: '1 → N', desc: 'Bire-çok' }, + { value: 'OneToOne', label: '1 → 1', desc: 'Bire-bir' }, +] + +const CASCADE_OPTIONS: { value: CascadeBehavior; label: string }[] = [ + { value: 'NoAction', label: 'No Action' }, + { value: 'Cascade', label: 'Cascade' }, + { value: 'SetNull', label: 'Set Null' }, + { value: 'Restrict', label: 'Restrict' }, +] + +const EMPTY_FK: Omit = { + relationshipType: 'OneToMany', + fkColumnName: '', + referencedTable: '', + referencedColumn: 'Id', + cascadeDelete: 'NoAction', + cascadeUpdate: 'Cascade', + isRequired: false, + description: '', +} + +const EMPTY_INDEX: Omit = { + indexName: '', + indexType: 'Index', + isClustered: false, + columns: [], + description: '', +} + +const INDEX_TYPES: { value: IndexType; label: string; desc: string }[] = [ + { + value: 'PrimaryKey', + label: 'Primary Key', + desc: 'App.SqlQueryManager.IndexType_PrimaryKey_Desc', + }, + { value: 'UniqueKey', label: 'Unique Key', desc: 'App.SqlQueryManager.IndexType_UniqueKey_Desc' }, + { value: 'Index', label: 'Index', desc: 'App.SqlQueryManager.IndexType_Index_Desc' }, +] + const DATA_TYPES: { value: SqlDataType; label: string }[] = [ { value: 'nvarchar', label: 'String (nvarchar)' }, { value: 'nvarchar(MAX)', label: 'Text (nvarchar MAX)' }, @@ -180,47 +221,6 @@ const FULL_AUDIT_COLUMNS: ColumnDefinition[] = [ }, ] -const REL_TYPES: { value: RelationshipType; label: string; desc: string }[] = [ - { value: 'OneToMany', label: '1 → N', desc: 'Bire-çok' }, - { value: 'OneToOne', label: '1 → 1', desc: 'Bire-bir' }, -] - -const CASCADE_OPTIONS: { value: CascadeBehavior; label: string }[] = [ - { value: 'NoAction', label: 'No Action' }, - { value: 'Cascade', label: 'Cascade' }, - { value: 'SetNull', label: 'Set Null' }, - { value: 'Restrict', label: 'Restrict' }, -] - -const EMPTY_FK: Omit = { - relationshipType: 'OneToMany', - fkColumnName: '', - referencedTable: '', - referencedColumn: 'Id', - cascadeDelete: 'NoAction', - cascadeUpdate: 'Cascade', - isRequired: false, - description: '', -} - -const EMPTY_INDEX: Omit = { - indexName: '', - indexType: 'Index', - isClustered: false, - columns: [], - description: '', -} - -const INDEX_TYPES: { value: IndexType; label: string; desc: string }[] = [ - { - value: 'PrimaryKey', - label: 'Primary Key', - desc: 'App.SqlQueryManager.IndexType_PrimaryKey_Desc', - }, - { value: 'UniqueKey', label: 'Unique Key', desc: 'App.SqlQueryManager.IndexType_UniqueKey_Desc' }, - { value: 'Index', label: 'Index', desc: 'App.SqlQueryManager.IndexType_Index_Desc' }, -] - const TENANT_COLUMN: ColumnDefinition = { id: '__TenantId', columnName: 'TenantId', @@ -232,6 +232,15 @@ const TENANT_COLUMN: ColumnDefinition = { } const WORKFLOW_COLUMNS: ColumnDefinition[] = [ + { + id: '__WorkflowId', + columnName: 'Id', + dataType: 'uniqueidentifier', + maxLength: '', + isNullable: false, + defaultValue: 'NEWID()', + description: 'Workflow primary key', + }, { id: '__ApprovalUserId', columnName: 'ApprovalUserName', @@ -1324,6 +1333,26 @@ const SqlTableDesignerDialog = ({ const clearAllColumns = () => setColumns([createEmptyColumn()]) + const addAutomaticPrimaryKey = (preset: string, description: string) => { + const tableName = settings.tableName || initialTableData?.tableName || 'Table' + + setIndexes((prev) => { + if (prev.some((index) => index.indexType === 'PrimaryKey')) return prev + + return [ + ...prev, + { + id: `auto-${preset}-${crypto.randomUUID()}`, + indexName: `PK_${tableName}`, + indexType: 'PrimaryKey', + isClustered: false, + columns: [{ columnName: 'Id', order: 'ASC' }], + description, + }, + ] + }) + } + 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())) @@ -1332,24 +1361,7 @@ const SqlTableDesignerDialog = ({ return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()] }) - // FullAudited ile Id eklendiğinde PK tanımı Index/Key listesine otomatik düşsün. - setIndexes((prev) => { - const hasPk = prev.some((ix) => ix.indexType === 'PrimaryKey') - if (hasPk) return prev - - const tableName = settings.tableName || initialTableData?.tableName || 'Table' - return [ - ...prev, - { - id: `auto-pk-${crypto.randomUUID()}`, - indexName: `PK_${tableName}`, - indexType: 'PrimaryKey', - isClustered: false, - columns: [{ columnName: 'Id', order: 'ASC' }], - description: 'Primary key (auto)', - }, - ] - }) + addAutomaticPrimaryKey('full-audit', 'Full audited primary key (auto)') } const addMultiTenantColumns = () => { @@ -1372,6 +1384,8 @@ const SqlTableDesignerDialog = ({ return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()] }) } + + addAutomaticPrimaryKey('workflow', 'Workflow primary key (auto)') } const addTodoColumns = () => { @@ -1385,22 +1399,7 @@ const SqlTableDesignerDialog = ({ }) } - setIndexes((prev) => { - if (prev.some((index) => index.indexType === 'PrimaryKey')) return prev - - const tableName = settings.tableName || initialTableData?.tableName || 'Table' - return [ - ...prev, - { - id: `auto-todo-pk-${crypto.randomUUID()}`, - indexName: `PK_${tableName}`, - indexType: 'PrimaryKey', - isClustered: false, - columns: [{ columnName: 'Id', order: 'ASC' }], - description: 'Todo primary key (auto)', - }, - ] - }) + addAutomaticPrimaryKey('todo', 'Todo primary key (auto)') } const importColumnsFromRememberedCreateTable = async () => { @@ -1511,11 +1510,10 @@ const SqlTableDesignerDialog = ({ const syncAutoPkName = (newTableName: string) => { if (!newTableName) return setIndexes((prev) => - prev.map((ix) => - ix.indexType === 'PrimaryKey' && ix.id.startsWith('auto-pk') - ? { ...ix, indexName: `PK_${newTableName}` } - : ix, - ), + prev.map((ix) => { + if (!ix.id.startsWith('auto-') || ix.indexType !== 'PrimaryKey') return ix + return { ...ix, indexName: `PK_${newTableName}` } + }), ) } @@ -1862,12 +1860,12 @@ const SqlTableDesignerDialog = ({ <>
- + diff --git a/ui/src/views/list/CardView.tsx b/ui/src/views/list/CardView.tsx index 31d4c78..7351341 100644 --- a/ui/src/views/list/CardView.tsx +++ b/ui/src/views/list/CardView.tsx @@ -99,6 +99,7 @@ const toCardColumn = ( const isImageColumn = typeof column.cellTemplate === 'function' && Object.prototype.hasOwnProperty.call(column.extras ?? {}, 'imageUploadOptions') + const isHtmlColumn = column.colData?.editorType2 === 'dxHtmlEditor' return Object.fromEntries( Object.entries({ @@ -144,7 +145,14 @@ const toCardColumn = ( }) return container } - : undefined, + : isHtmlColumn + ? (templateOptions: any) => { + const templateData = templateOptions?.model ?? templateOptions + const container = document.createElement('div') + container.innerHTML = String(templateData.field?.value ?? '') + return container + } + : undefined, format: column.format, headerFilter: column.headerFilter, lookup: column.lookup, @@ -1239,7 +1247,6 @@ const CardView = (props: CardViewProps) => { /> (field ? row[field] : undefined) +const htmlToPlainText = (value: unknown) => { + const html = String(value ?? '') + if (!html) return '' + if (typeof document === 'undefined') return html.replace(/<[^>]*>/g, ' ') + + const container = document.createElement('div') + container.innerHTML = html + return (container.textContent ?? '').replace(/\s+/g, ' ').trim() +} + const textList = (value: unknown) => Array.isArray(value) ? value.map(String) @@ -682,18 +701,51 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => { -