Wizard Problemleri Giderildi

This commit is contained in:
Sedat Öztürk 2026-07-17 23:23:43 +03:00
parent 319afbcb13
commit ab6a33c340
23 changed files with 1320 additions and 343 deletions

View file

@ -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; }

View file

@ -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,

View file

@ -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<MenuDto>(

View file

@ -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"
},
{

View file

@ -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

View file

@ -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

View file

@ -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": []
}
}

View file

@ -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": []
}
}

View file

@ -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<ListFormWorkflowCriteriaDto> criteria)
{
return workflow != null && (

View file

@ -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 }
});
}

View file

@ -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<string, object> 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,
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
match => $"{match.Groups["column"].Value} IN @{parameterName}",
RegexOptions.IgnoreCase);
}
private static string NormalizePostgresqlCollectionParameter(
string sql,
string escapedParameterName,
string parameterName)
return dataSourceType switch
{
sql = Regex.Replace(
sql,
$@"IN\s*\(\s*@{escapedParameterName}\s*\)",
$"= ANY(@{parameterName})",
RegexOptions.IgnoreCase);
return Regex.Replace(
sql,
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
match => $"{match.Groups["column"].Value} = ANY(@{parameterName})",
RegexOptions.IgnoreCase);
}
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 NormalizeCollectionParameterCommand(
// string command,
// Dictionary<string, object> 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)
// {
// 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,
// $@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\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,
// $@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
// match => $"{match.Groups["column"].Value} = ANY(@{parameterName})",
// RegexOptions.IgnoreCase);
// }
}

View file

@ -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",

View file

@ -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

View file

@ -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}

View file

@ -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,

View file

@ -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}
/>
<span className="flex-1 text-xs font-semibold text-indigo-600 dark:text-indigo-400 truncate">
{item.dataField}
{item.fieldName}
</span>
<Button
type="button"
@ -564,12 +572,23 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium">
{translate('::ListForms.Wizard.Step3.EditorOptions')}
</span>
<div className="flex items-center gap-1">
<input
value={item.editorOptions}
onChange={(e) => onEditorOptionsChange(e.target.value)}
placeholder='{"readOnly": false}'
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
className="min-w-0 flex-1 text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
/>
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
title="Build editor options"
icon={<FaSlidersH />}
onClick={() => setIsEditorOptionsDialogOpen(true)}
/>
</div>
</div>
{/* Editor Script */}
@ -577,13 +596,40 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium">
{translate('::ListForms.Wizard.Step3.EditorScript')}
</span>
<div className="flex items-center gap-1">
<input
value={item.editorScript}
onChange={(e) => onEditorScriptChange(e.target.value)}
placeholder="(e) => { /* e.component */ }"
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
className="min-w-0 flex-1 text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
/>
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
title="Build editor script"
icon={<FaCode />}
onClick={() => setIsEditorScriptDialogOpen(true)}
/>
</div>
</div>
<EditorOptionsBuilderDialog
isOpen={isEditorOptionsDialogOpen}
value={item.editorOptions}
editorType={item.editorType}
onClose={() => setIsEditorOptionsDialogOpen(false)}
onApply={onEditorOptionsChange}
/>
<EditorScriptBuilderDialog
isOpen={isEditorScriptDialogOpen}
value={item.editorScript}
currentField={item.fieldName}
fields={editorScriptFields}
onClose={() => setIsEditorScriptDialogOpen(false)}
onApply={onEditorScriptChange}
/>
{/* Bottom row: ColSpan + Editing Form + Required */}
<div className="flex items-center gap-2">
@ -639,6 +685,7 @@ interface GroupCardProps {
hasAvailable: boolean
dbObjects: SqlObjectExplorerDto | null
dsCode: string
editorScriptFields: SelectBoxOption[]
onCaptionChange: (val: string) => void
onColCountChange: (val: number) => void
onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void
@ -653,6 +700,7 @@ function GroupCard({
hasAvailable,
dbObjects,
dsCode,
editorScriptFields,
onCaptionChange,
onColCountChange,
onItemChange,
@ -759,6 +807,7 @@ function GroupCard({
groupColCount={group.colCount}
dbObjects={dbObjects}
dsCode={dsCode}
editorScriptFields={editorScriptFields}
onCaptionNameChange={(val) => onItemChange(item.id, { captionName: val })}
onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })}
onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })}
@ -809,7 +858,7 @@ const WizardStep3 = ({
// 1. Remove items that are no longer in selectedColumns
const cleaned = groups.map((g) => ({
...g,
items: g.items.filter((i) => selectedColumns.has(i.dataField)),
items: g.items.filter((i) => selectedColumns.has(i.fieldName)),
}))
// 2. Ensure at least one empty group exists for the user to drag into
@ -835,8 +884,14 @@ const WizardStep3 = ({
}, [selectedColumns])
// ── Available columns = selected but NOT yet placed ───────────────────────
const placedColumns = new Set(groups.flatMap((g) => g.items.map((i) => i.dataField)))
const placedColumns = new Set(groups.flatMap((g) => g.items.map((i) => i.fieldName)))
const availableColumns = [...selectedColumns].filter((c) => !placedColumns.has(c))
const editorScriptFields: SelectBoxOption[] = groups.flatMap((group) =>
group.items.map((item) => ({
value: item.fieldName,
label: item.captionName || item.fieldName,
})),
)
// ── Helpers ───────────────────────────────────────────────────────────────
const colMeta = (name: string) => selectCommandColumns.find((c) => c.columnName === name)
@ -878,7 +933,7 @@ const WizardStep3 = ({
}
const addAllToGroup = (groupId: string) => {
const placed = new Set(groups.flatMap((g) => g.items.map((i) => i.dataField)))
const placed = new Set(groups.flatMap((g) => g.items.map((i) => i.fieldName)))
const toAdd = [...selectedColumns].filter((c) => !placed.has(c))
if (toAdd.length === 0) return
onGroupsChange(
@ -1018,7 +1073,7 @@ const WizardStep3 = ({
<div className="flex items-center gap-2 px-3 py-2 rounded-lg border border-indigo-400 bg-indigo-50 text-sm shadow-lg cursor-grabbing">
<FaGripVertical className="text-indigo-300 text-xs" />
<span className="text-xs font-semibold text-indigo-600 bg-indigo-100 px-2 py-0.5 rounded">
{item.dataField}
{item.fieldName}
</span>
<span className="text-xs text-gray-500">{item.editorType}</span>
</div>
@ -1087,6 +1142,7 @@ const WizardStep3 = ({
hasAvailable={availableColumns.length > 0}
dbObjects={dbObjects}
dsCode={dsCode}
editorScriptFields={editorScriptFields}
onCaptionChange={(val) => updateGroup(group.id, { caption: val })}
onColCountChange={(val) => updateGroup(group.id, { colCount: val })}
onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)}

View file

@ -37,6 +37,8 @@ type PendingLink = {
outcome: string
} | null
type RequiredWorkflowField = 'approvalUserFieldName' | 'approvalStatusFieldName'
const toDesignerCriteria = (items: ListFormWorkflowCriteriaDto[]): WorkflowCriteriaDto[] =>
items.map((item) => ({
...item,
@ -67,6 +69,9 @@ function WizardStep6({
const [dragPreview, setDragPreview] = useState<any>(null)
const [canvasZoom, setCanvasZoom] = useState(1)
const [designerTab, setDesignerTab] = useState('flow')
const [workflowErrors, setWorkflowErrors] = useState<
Partial<Record<RequiredWorkflowField, string>>
>({})
const canvasRef = useRef<HTMLDivElement | null>(null)
const currentCriteria = useMemo(() => toDesignerCriteria(criteria), [criteria])
@ -91,6 +96,10 @@ function WizardStep6({
setCriteriaForm(selected ? toCriteriaForm(selected) : emptyCriteria('Start', listFormCode))
}, [currentCriteria, listFormCode, selectedId])
useEffect(() => {
if (criteria.length === 0) setWorkflowErrors({})
}, [criteria.length])
const updateCriteria = (next: WorkflowCriteriaDto[]) => onCriteriaChange(toWizardCriteria(next))
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
@ -250,6 +259,26 @@ function WizardStep6({
])
}
const continueToTodo = () => {
if (criteria.length === 0) {
setWorkflowErrors({})
onNext()
return
}
const requiredMessage = translate('::ListForms.Wizard.Required')
const nextErrors: Partial<Record<RequiredWorkflowField, string>> = {}
if (!workflow.approvalUserFieldName?.trim()) {
nextErrors.approvalUserFieldName = requiredMessage
}
if (!workflow.approvalStatusFieldName?.trim()) {
nextErrors.approvalStatusFieldName = requiredMessage
}
setWorkflowErrors(nextErrors)
if (Object.keys(nextErrors).length === 0) onNext()
}
return (
<div className="flex h-[calc(100vh-250px)] min-h-[500px] flex-col overflow-hidden">
<div className="flex-1 overflow-y-auto pr-1">
@ -280,8 +309,19 @@ function WizardStep6({
'approvalDescriptionFieldName',
'::ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName',
],
].map(([name, label]) => (
<FormItem key={name} label={translate(label)}>
].map(([name, label]) => {
const requiredForCriteria =
criteria.length > 0 &&
(name === 'approvalUserFieldName' || name === 'approvalStatusFieldName')
const error = workflowErrors[name as RequiredWorkflowField]
return (
<FormItem
key={name}
errorMessage={error}
invalid={Boolean(error)}
label={`${translate(label)}${requiredForCriteria ? ' *' : ''}`}
>
<Field name={name}>
{({ field, form }: FieldProps) => (
<Select
@ -294,6 +334,10 @@ function WizardStep6({
)}
onChange={(option: any) => {
form.setFieldValue(field.name, option?.value ?? '')
setWorkflowErrors((current) => ({
...current,
[field.name]: undefined,
}))
onWorkflowChange({
...values,
[field.name]: option?.value ?? '',
@ -304,9 +348,12 @@ function WizardStep6({
)}
</Field>
</FormItem>
))}
)
})}
<FormItem
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName')}
label={translate(
'::ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName',
)}
>
<Field name="approvalIsFilterUserName">
{({ field, form }: FieldProps<boolean>) => (
@ -402,7 +449,7 @@ function WizardStep6({
variant="solid"
type="button"
icon={<FaArrowRight />}
onClick={onNext}
onClick={continueToTodo}
>
{translate('::Next') || 'Next'}
</Button>

View file

@ -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<string>
@ -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<ListFormWizardDto>()
const { errors, touched, values, setFieldTouched, setFieldValue, validateForm } =
useFormikContext<ListFormWizardDto>()
const [validationError, setValidationError] = useState('')
const fieldOptions = useMemo<SelectBoxOption[]>(
() =>
@ -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,34 +97,23 @@ const WizardStep7 = ({
return (
<div className="pb-20">
<div className="mb-5 rounded-xl border border-indigo-200 bg-indigo-50/60 p-4 dark:border-indigo-900 dark:bg-indigo-950/20">
<div className="flex items-center gap-2 font-semibold text-indigo-700 dark:text-indigo-300">
<FaColumns /> Todo / Kanban ayrıntıları
</div>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Liste alanlarını Todo kartının başlık, durum ve diğer özellikleriyle eşleştirin.
</p>
</div>
{!values.todo ? (
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200">
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.
</div>
) : (
<>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{mappings.map((mapping) => (
{mappings.map((mapping) => {
const isRequired = Boolean(mapping.required && values.todo)
return (
<FormItem
key={mapping.name}
label={`${mapping.label}${mapping.required ? ' *' : ''}`}
label={`${translate(mapping.translationKey)}${isRequired ? ' *' : ''}`}
invalid={Boolean(touched[mapping.name] && errors[mapping.name])}
errorMessage={touched[mapping.name] ? errors[mapping.name] : undefined}
>
<Field name={mapping.name}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={!mapping.required}
isClearable={!isRequired}
options={fieldOptions}
value={fieldOptions.find((option) => option.value === values[mapping.name])}
onChange={(option) => {
@ -105,18 +124,19 @@ const WizardStep7 = ({
)}
</Field>
</FormItem>
))}
)
})}
</div>
<div className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
<FormItem label="Durum sırası (virgülle ayırın)">
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
<Field
name="todoStatusOrder"
component={Input}
placeholder="Inbox, Today, This Week, This Month, Later, Done"
placeholder={translate('::ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder')}
/>
</FormItem>
<FormItem label="Kartları kolonlar arasında sürüklemeye izin ver">
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}>
<Field name="todoAllowDragging">
{({ field }: FieldProps<boolean>) => (
<Checkbox
@ -127,15 +147,13 @@ const WizardStep7 = ({
</Field>
</FormItem>
</div>
</>
)}
{validationError && <p className="mt-4 text-sm text-red-500">{validationError}</p>}
<div className="fixed bottom-0 left-0 right-0 z-10 flex h-16 items-center border-t border-gray-200 bg-white px-6 dark:border-gray-700 dark:bg-gray-900">
<div className="flex w-full items-center justify-between gap-3">
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
{translate('::Back') || 'Geri'}
{translate('::Back')}
</Button>
<Button
size="sm"
@ -144,7 +162,7 @@ const WizardStep7 = ({
icon={<FaArrowRight />}
onClick={continueToDeploy}
>
{translate('::Next') || 'Deploy adımına geç'}
{translate('::Next')}
</Button>
</div>
</div>

View file

@ -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 = ({
</span>
) : (
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 (
<div
@ -397,7 +399,7 @@ const WizardStep8 = ({
>
<div className="flex min-w-0 items-center gap-2">
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400 truncate">
{item.dataField}
{item.fieldName}
</span>
{meta?.dataType && (
<span className="text-[10px] text-gray-400 truncate">
@ -574,17 +576,52 @@ const WizardStep8 = ({
{values.todo && (
<Section title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'}>
<div className="grid grid-cols-2 gap-x-4">
<Row label="Başlık alanı" value={values.todoTitleExpr} />
<Row label="Kanban / durum alanı" value={values.todoStatusExpr} />
<Row label="Açıklama alanı" value={values.todoDescriptionExpr} />
<Row label="Termin tarihi alanı" value={values.todoDueDateExpr} />
<Row label="Etiket alanı" value={values.todoTagExpr} />
<Row label="Atanan kişi alanı" value={values.todoAssigneeExpr} />
<Row label="Öncelik alanı" value={values.todoPriorityExpr} />
<Row label="Tamamlandı alanı" value={values.todoCompletedExpr} />
<Row label="Kart sıra alanı" value={values.todoOrderExpr} />
<Row label="Durum sırası" value={values.todoStatusOrder} />
<Row label="Sürükle-bırak" value={values.todoAllowDragging ? 'Aktif' : 'Pasif'} />
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoTitleExpr')}
value={values.todoTitleExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoStatusExpr')}
value={values.todoStatusExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoDescriptionExpr')}
value={values.todoDescriptionExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoDueDateExpr')}
value={values.todoDueDateExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoTagExpr')}
value={values.todoTagExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoAssigneeExpr')}
value={values.todoAssigneeExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoPriorityExpr')}
value={values.todoPriorityExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoCompletedExpr')}
value={values.todoCompletedExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoOrderExpr')}
value={values.todoOrderExpr}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}
value={values.todoStatusOrder}
/>
<Row
label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}
value={translate(
values.todoAllowDragging ? '::App.Status.Active' : '::App.Status.Inactive',
)}
/>
</div>
</Section>
)}
@ -643,12 +680,12 @@ const WizardStep8 = ({
</span>
{isDone && (
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
<FaCheckCircle /> {translate('::ListForms.Wizard.Step4.Success') || 'Başarılı'}
<FaCheckCircle /> {translate('::ListForms.Wizard.Step4.Success')}
</span>
)}
{hasError && (
<span className="text-xs text-red-500 font-semibold flex items-center gap-1">
<FaExclamationCircle /> {translate('::ListForms.Wizard.Step4.Error') || 'Hata'}
<FaExclamationCircle /> {translate('::ListForms.Wizard.Step4.Error')}
</span>
)}
</div>
@ -658,7 +695,7 @@ const WizardStep8 = ({
<div className="flex flex-col items-center justify-center h-full gap-3 py-10 select-none">
<FaRocket className="text-gray-700 text-3xl" />
<span className="text-xs text-gray-600 italic text-center">
{translate('::ListForms.Wizard.Step4.AllInfoReady') || 'Tüm bilgiler hazır.'}
{translate('::ListForms.Wizard.Step4.AllInfoReady')}
<br />
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
</span>

View file

@ -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<SqlTableRelation, 'id'> = {
relationshipType: 'OneToMany',
fkColumnName: '',
referencedTable: '',
referencedColumn: 'Id',
cascadeDelete: 'NoAction',
cascadeUpdate: 'Cascade',
isRequired: false,
description: '',
}
const EMPTY_INDEX: Omit<TableIndex, 'id'> = {
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<SqlTableRelation, 'id'> = {
relationshipType: 'OneToMany',
fkColumnName: '',
referencedTable: '',
referencedColumn: 'Id',
cascadeDelete: 'NoAction',
cascadeUpdate: 'Cascade',
isRequired: false,
description: '',
}
const EMPTY_INDEX: Omit<TableIndex, 'id'> = {
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 = ({
<>
<div className="flex items-center justify-between py-2">
<div className="flex items-center gap-2">
<Button size="xs" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
{translate('::App.SqlQueryManager.AddFullAuditedColumns')}
</Button>
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
</Button>
<Button size="xs" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
{translate('::App.SqlQueryManager.AddFullAuditedColumns')}
</Button>
<Button size="xs" variant="solid" color="indigo-600" onClick={addWorkflowColumns}>
{translate('::App.SqlQueryManager.AddWorkflowColumns')}
</Button>

View file

@ -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,6 +145,13 @@ const toCardColumn = (
})
return container
}
: 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,
@ -1239,7 +1247,6 @@ const CardView = (props: CardViewProps) => {
/>
<FilterPanel
visible={gridDto.gridOptions.filterPanelDto.visible}
filterEnabled={gridDto.gridOptions.filterPanelDto.filterEnabled}
texts={gridDto.gridOptions.filterPanelDto.texts}
/>
<HeaderFilter

View file

@ -2,8 +2,17 @@ import type { GridDto, TodoOptionDto } from '@/proxy/form/models'
import type { IdentityUserDto } from '@/proxy/admin/models'
import type { DataGridRef } from 'devextreme-react/data-grid'
import type CustomStore from 'devextreme/data/custom_store'
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
import TagBox from 'devextreme-react/tag-box'
import { AVATAR_URL } from '@/constants/app.constant'
import {
fontFamilyOptions,
fontSizeOptions,
fontValues,
headerOptions,
headerValues,
sizeValues,
} from '@/proxy/reports/data'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -60,6 +69,16 @@ interface UserOption {
const fieldValue = (row: TodoRow, field?: string) => (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) => {
<label className={labelClass}>
{translate('::ListForms.TodoBoard.Description')}
</label>
<textarea
className={`${inputClass} min-h-36 resize-y`}
disabled={!canEdit}
<HtmlEditor
height={220}
placeholder={translate('::ListForms.TodoBoard.DescriptionPlaceholder')}
readOnly={!canEdit}
value={String(draft[options.descriptionExpr] ?? '')}
onChange={(event) =>
onValueChanged={(event) =>
setDraft((current) => ({
...current,
[options.descriptionExpr!]: event.target.value,
[options.descriptionExpr!]: event.value,
}))
}
/>
>
<MediaResizing enabled={true} />
<ImageUpload fileUploadMode="base64" />
<Toolbar multiline>
<Item name="undo" />
<Item name="redo" />
<Item name="separator" />
<Item name="size" acceptedValues={sizeValues} options={fontSizeOptions} />
<Item name="font" acceptedValues={fontValues} options={fontFamilyOptions} />
<Item name="separator" />
<Item name="bold" />
<Item name="italic" />
<Item name="underline" />
<Item name="strike" />
<Item name="separator" />
<Item name="orderedList" />
<Item name="bulletList" />
<Item name="separator" />
<Item name="header" acceptedValues={headerValues} options={headerOptions} />
<Item name="separator" />
<Item name="color" />
<Item name="background" />
<Item name="separator" />
<Item name="alignLeft" />
<Item name="alignCenter" />
<Item name="alignRight" />
<Item name="alignJustify" />
<Item name="separator" />
<Item name="link" />
<Item name="image" />
<Item name="separator" />
<Item name="clear" />
</Toolbar>
</HtmlEditor>
</div>
)}
<div className="grid gap-4 sm:grid-cols-2">
@ -972,9 +1024,9 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
</form>
) : (
<>
<h3 className="min-w-0 truncate font-semibold" title={status}>
<h4 className="min-w-0 truncate font-sm" title={status}>
{status}
</h3>
</h4>
<button
type="button"
className="rounded p-1 text-gray-400 opacity-0 transition hover:bg-gray-200 hover:text-blue-600 group-hover:opacity-100 dark:hover:bg-gray-700"
@ -1152,7 +1204,7 @@ const TodoBoard = ({ listFormCode, searchParams, gridDto }: TodoBoardProps) => {
</div>
{fieldValue(row, options.descriptionExpr) && (
<p className="mt-1 line-clamp-2 text-xs leading-5 text-gray-500 dark:text-gray-400">
{String(fieldValue(row, options.descriptionExpr))}
{htmlToPlainText(fieldValue(row, options.descriptionExpr))}
</p>
)}
{due && (

View file

@ -922,6 +922,10 @@ const useListFormColumns = ({
column.allowEditing = colData?.allowEditing || colData?.allowAdding
if (colData.editorType2 === 'dxHtmlEditor') {
column.encodeHtml = false
}
// #region lookup ayarlari
if (colData.lookupDto?.dataSourceType) {
// UiColumnEditorTemplateTypeEnum : None:0, Table:1, TagBox:2