Compare commits

..

4 commits
1.1.08 ... main

Author SHA1 Message Date
Sedat Öztürk
ab6a33c340 Wizard Problemleri Giderildi 2026-07-17 23:23:43 +03:00
Sedat ÖZTÜRK
319afbcb13 npx eslint src --quiet hatalar kontrol edildi 2026-07-17 12:47:00 +03:00
Sedat ÖZTÜRK
b87d7a94c8 EditingFormItemDto kaldırıldı. Yerine tüm sütunlar ColumnFormatDto eklendi 2026-07-17 11:59:51 +03:00
Sedat ÖZTÜRK
95b0b0ce89 Genel güncellemeler 2026-07-16 17:20:25 +03:00
72 changed files with 4004 additions and 2793 deletions

View file

@ -10,6 +10,11 @@ namespace Sozsoft.Platform.ListForms;
public class ColumnFormatDto : AuditedEntityDto<Guid>
{
private static readonly JsonSerializerOptions EditorOptionsSerializerOptions = new()
{
PropertyNameCaseInsensitive = true
};
public ColumnFormatDto() { }
public string FieldName { get; set; }
@ -240,6 +245,41 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
public string Alignment { get; set; }
public string Format { get; set; }
public int? EditOrderNo { get; set; }
public int? EditGroupOrderNo { get; set; }
public string EditorType2 { get; set; }
public int? ColSpan { get; set; }
public string EditorOptions { get; set; }
public GridBoxOptionsDto GridBoxOptions =>
DeserializeEditorOptions<GridBoxOptionsDto>(PlatformConsts.EditorTypes.dxGridBox);
public TagBoxOptionsDto TagBoxOptions =>
DeserializeEditorOptions<TagBoxOptionsDto>(PlatformConsts.EditorTypes.dxTagBox);
public ImageUploadOptionsDto ImageUploadOptions =>
DeserializeEditorOptions<ImageUploadOptionsDto>(
PlatformConsts.EditorTypes.dxImageUpload,
PlatformConsts.EditorTypes.dxImageViewer);
public string EditorScript { get; set; }
private T DeserializeEditorOptions<T>(params string[] editorTypes) where T : class
{
if (!Array.Exists(
editorTypes,
editorType => string.Equals(EditorType2, editorType, StringComparison.OrdinalIgnoreCase)) ||
string.IsNullOrWhiteSpace(EditorOptions))
{
return null;
}
try
{
return JsonSerializer.Deserialize<T>(EditorOptions, EditorOptionsSerializerOptions);
}
catch (JsonException)
{
return null;
}
}
}

View file

@ -16,35 +16,5 @@ public class EditingFormDto
public string Caption { get; set; }
public int ColCount { get; set; } = 2;
public int ColSpan { get; set; } = 2;
/// <summary> Type: Array<Simple Form Item | Group Form Item | Tabbed Form Item | Empty Form Item | Button Form Item>
/// Bu kisma suan sadece string olarak doldurulacak
/// items: ['FirstName', 'LastName',
/// { dataField: 'Notes', editorType: 'dxTextArea', colSpan: 2, editorOptions: { height: 100, }, }
/// ],
/// </summary>
//[TextArea]
//public string[] Items { get; set; }
public EditingFormItemDto[] Items { get; set; }
}
public class EditingFormItemDto
{
/// <summary>
/// DataField Accepted Values: 'dxAutocomplete' | 'dxCalendar' | 'dxCheckBox' | 'dxColorBox' | 'dxDateBox' | 'dxDateRangeBox' | 'dxDropDownBox' | 'dxHtmlEditor' | 'dxLookup' | 'dxNumberBox' | 'dxRadioGroup' | 'dxRangeSlider' | 'dxSelectBox' | 'dxSlider' | 'dxSwitch' | 'dxTagBox' | 'dxTextArea' | 'dxTextBox' | 'dxImageViewer' | 'dxImageUpload';
/// </summary>
[JsonPropertyName("order")]
public int Order { get; set; }
[JsonPropertyName("dataField")]
public string DataField { get; set; }
[JsonPropertyName("editorType2")]
public string EditorType2 { get; set; }
[JsonPropertyName("colSpan")]
public int ColSpan { get; set; }
[JsonPropertyName("editorOptions")]
public string EditorOptions { get; set; }
public GridBoxOptionsDto GridBoxOptions { get; set; }
public TagBoxOptionsDto TagBoxOptions { get; set; }
[JsonPropertyName("editorScript")]
public string EditorScript { get; set; }
}

View file

@ -0,0 +1,11 @@
namespace Sozsoft.Platform.ListForms;
public class ImageUploadOptionsDto
{
public string UploadUrl { get; set; }
public string Accept { get; set; }
public bool? Multiple { get; set; }
public long? MaxFileSize { get; set; }
public int? Width { get; set; }
public int? Height { get; set; }
}

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

@ -5,7 +5,7 @@ namespace Sozsoft.Platform.ListForms;
public class WizardColumnItemInputDto
{
public string DataField { get; set; }
public string FieldName { get; set; }
public string CaptionName { get; set; }
public string EditorType { get; set; }
public string EditorOptions { get; set; }

View file

@ -47,26 +47,33 @@ public class ListFormFieldsAppService : CrudAppService<
protected override void MapToEntity(ColumnFormatEditDto updateInput, ListFormField entity)
{
entity.CultureName = updateInput.CultureName;
entity.RoleId = updateInput.RoleId;
entity.ListFormCode = updateInput.ListFormCode;
entity.FieldName = updateInput.FieldName;
entity.CultureName = updateInput.CultureName;
entity.CaptionName = updateInput.CaptionName;
entity.PlaceHolder = updateInput.PlaceHolder;
entity.BandName = updateInput.BandName;
entity.IsActive = updateInput.IsActive;
entity.Visible = updateInput.Visible;
entity.AllowSearch = updateInput.AllowSearch;
entity.AllowEditing = updateInput.AllowEditing;
entity.AllowAdding = updateInput.AllowAdding;
entity.ListOrderNo = updateInput.ListOrderNo;
entity.SourceDbType = updateInput.SourceDbType;
entity.Alignment = updateInput.Alignment;
entity.Format = updateInput.Format;
entity.EditorOptions = updateInput.EditorOptions;
entity.EditorScript = updateInput.EditorScript;
entity.ListOrderNo = updateInput.ListOrderNo;
entity.Width = updateInput.Width;
entity.SortDirection = updateInput.SortDirection;
entity.SortIndex = updateInput.SortIndex;
entity.Format = updateInput.Format;
entity.Alignment = updateInput.Alignment;
entity.EditorOptions = updateInput.EditorOptions;
entity.EditOrderNo = updateInput.EditOrderNo;
entity.EditGroupOrderNo = updateInput.EditGroupOrderNo;
entity.ColSpan = updateInput.ColSpan;
entity.EditorType2 = updateInput.EditorType2;
entity.AllowEditing = updateInput.AllowEditing;
entity.AllowAdding = updateInput.AllowAdding;
entity.PermissionJson = JsonSerializer.Serialize(updateInput.PermissionDto);
entity.LookupJson = JsonSerializer.Serialize(updateInput.LookupDto);
entity.ColumnCustomizationJson = JsonSerializer.Serialize(updateInput.ColumnCustomizationDto);
entity.ColumnFilterJson = JsonSerializer.Serialize(updateInput.ColumnFilterDto);
entity.ColumnHeaderJson = JsonSerializer.Serialize(updateInput.ColumnHeaderDto);
@ -74,12 +81,10 @@ public class ListFormFieldsAppService : CrudAppService<
entity.GroupSummaryJson = updateInput.ColumnGroupSummaryDto.SummaryType.IsNullOrEmpty() ? "" : JsonSerializer.Serialize(updateInput.ColumnGroupSummaryDto);
entity.TotalSummaryJson = updateInput.ColumnTotalSummaryDto.SummaryType.IsNullOrEmpty() ? "" : JsonSerializer.Serialize(updateInput.ColumnTotalSummaryDto);
entity.JoinTableJson = JsonSerializer.Serialize(updateInput.ColumnJoinTableDto);
entity.LookupJson = JsonSerializer.Serialize(updateInput.LookupDto);
entity.ColumnCssClass = updateInput.ColumnCssClass;
entity.ColumnCssValue = updateInput.ColumnCssValue;
entity.ValidationRuleJson = JsonSerializer.Serialize(updateInput.ValidationRuleDto);
entity.ColumnStylingJson = JsonSerializer.Serialize(updateInput.ColumnStylingDto);
entity.PermissionJson = JsonSerializer.Serialize(updateInput.PermissionDto);
entity.PivotSettingsJson = JsonSerializer.Serialize(updateInput.PivotSettingsDto);
}
@ -136,6 +141,9 @@ public class ListFormFieldsAppService : CrudAppService<
item.Alignment = input.Alignment;
item.Format = input.Format;
item.EditorOptions = input.EditorOptions;
item.IsActive = input.IsActive;
item.Visible = input.Visible;
item.AllowSearch = input.AllowSearch;
}
else if (input.EditType == ListFormFieldEditTabs.PermissionsForm)
{
@ -152,6 +160,11 @@ public class ListFormFieldsAppService : CrudAppService<
item.AllowSearch = input.AllowSearch;
item.AllowEditing = input.AllowEditing;
item.AllowAdding = input.AllowAdding;
item.EditGroupOrderNo = input.EditGroupOrderNo;
item.EditOrderNo = input.EditOrderNo;
item.ColSpan = input.ColSpan;
item.EditorType2 = input.EditorType2;
item.EditorScript = input.EditorScript;
}
else if (input.EditType == ListFormFieldEditTabs.CustomizationForm)
{
@ -257,6 +270,9 @@ public class ListFormFieldsAppService : CrudAppService<
field.CaptionName = sourceField.CaptionName;
field.PlaceHolder = sourceField.PlaceHolder;
field.SourceDbType = sourceField.SourceDbType;
field.Alignment = sourceField.Alignment;
field.Format = sourceField.Format;
field.EditorOptions = sourceField.EditorOptions;
}
if (input.CopiedFields.All || input.CopiedFields.Options)
{
@ -269,35 +285,40 @@ public class ListFormFieldsAppService : CrudAppService<
field.AllowSearch = sourceField.AllowSearch;
field.AllowEditing = sourceField.AllowEditing;
field.AllowAdding = sourceField.AllowAdding;
field.EditGroupOrderNo = sourceField.EditGroupOrderNo;
field.EditOrderNo = sourceField.EditOrderNo;
field.ColSpan = sourceField.ColSpan;
field.EditorType2 = sourceField.EditorType2;
field.EditorScript = sourceField.EditorScript;
}
else if (input.CopiedFields.All || input.CopiedFields.Permission)
if (input.CopiedFields.All || input.CopiedFields.Permission)
{
field.PermissionJson = sourceField.PermissionJson;
}
else if (input.CopiedFields.All || input.CopiedFields.Customization)
if (input.CopiedFields.All || input.CopiedFields.Customization)
field.ColumnCustomizationJson = sourceField.ColumnCustomizationJson;
else if (input.CopiedFields.All || input.CopiedFields.Filtering)
if (input.CopiedFields.All || input.CopiedFields.Filtering)
field.ColumnFilterJson = sourceField.ColumnFilterJson;
else if (input.CopiedFields.All || input.CopiedFields.Headering)
if (input.CopiedFields.All || input.CopiedFields.Headering)
field.ColumnHeaderJson = sourceField.ColumnHeaderJson;
else if (input.CopiedFields.All || input.CopiedFields.Grouping)
if (input.CopiedFields.All || input.CopiedFields.Grouping)
field.GroupingJson = sourceField.GroupingJson;
else if (input.CopiedFields.All || input.CopiedFields.GroupSummary)
if (input.CopiedFields.All || input.CopiedFields.GroupSummary)
field.GroupSummaryJson = sourceField.GroupSummaryJson;
else if (input.CopiedFields.All || input.CopiedFields.TotalSummary)
if (input.CopiedFields.All || input.CopiedFields.TotalSummary)
field.TotalSummaryJson = sourceField.TotalSummaryJson;
else if (input.CopiedFields.All || input.CopiedFields.JoinOptions)
if (input.CopiedFields.All || input.CopiedFields.JoinOptions)
field.JoinTableJson = sourceField.JoinTableJson;
else if (input.CopiedFields.All || input.CopiedFields.CssOptions)
if (input.CopiedFields.All || input.CopiedFields.CssOptions)
{
field.ColumnCssClass = sourceField.ColumnCssClass;
field.ColumnCssValue = sourceField.ColumnCssValue;
}
else if (input.CopiedFields.All || input.CopiedFields.ValidationRules)
if (input.CopiedFields.All || input.CopiedFields.ValidationRules)
field.ValidationRuleJson = sourceField.ValidationRuleJson;
else if (input.CopiedFields.All || input.CopiedFields.Formatting)
if (input.CopiedFields.All || input.CopiedFields.Formatting)
field.ColumnStylingJson = sourceField.ColumnStylingJson;
else if (input.CopiedFields.All || input.CopiedFields.PivotSetting)
if (input.CopiedFields.All || input.CopiedFields.PivotSetting)
field.PivotSettingsJson = sourceField.PivotSettingsJson;
await Repository.InsertAsync(field);

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;
@ -230,21 +237,9 @@ public class ListFormWizardAppService(
Caption = g.Caption,
ColCount = g.ColCount,
ColSpan = g.ColCount,
ItemType = "group",
Items = g.Items
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Select((it, ii) => new EditingFormItemDto
{
Order = ii + 1,
DataField = it.DataField,
EditorType2 = it.EditorType,
ColSpan = it.ColSpan,
EditorOptions = string.IsNullOrWhiteSpace(it.EditorOptions) ? null : it.EditorOptions,
EditorScript = string.IsNullOrWhiteSpace(it.EditorScript) ? null : it.EditorScript,
})
.ToArray()
ItemType = "group"
})
.Where(g => g.Items.Length > 0)
.Where((_, index) => input.Groups[index].Items.Any(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName))
.ToList();
//ListForm - varsa sil, yeniden ekle
@ -313,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,
@ -371,21 +366,36 @@ public class ListFormWizardAppService(
// ListFormField - each item in each group becomes a visible field record
var fieldOrder = 0;
var editGroupOrder = 0;
foreach (var group in input.Groups)
{
editGroupOrder++;
var editOrder = 0;
foreach (var item in group.Items)
{
fieldOrder++;
if (item.IncludeInEditingForm && item.FieldName != input.KeyFieldName)
{
editOrder++;
}
await repoListFormField.InsertAsync(new ListFormField
{
ListFormCode = input.ListFormCode,
FieldName = item.DataField,
FieldName = item.FieldName,
CaptionName = item.CaptionName,
Visible = item.DataField != input.KeyFieldName,
Visible = item.FieldName != input.KeyFieldName,
IsActive = true,
AllowSearch = true,
Width = 0,
ListOrderNo = fieldOrder,
EditGroupOrderNo = item.IncludeInEditingForm ? editGroupOrder : 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,
CultureName = PlatformConsts.DefaultLanguage,
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),

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

@ -1443,8 +1443,8 @@
{
"resourceName": "Platform",
"key": "App.Reports.List",
"en": "List Report",
"tr": "Liste Raporu"
"en": "Grid Report",
"tr": "Tablo Raporu"
},
{
"resourceName": "Platform",
@ -4440,6 +4440,24 @@
"en": "Tree",
"tr": "Ağaç"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabCard",
"en": "Card",
"tr": "Kart"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabChart",
"en": "Chart",
"tr": "Grafik"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabGrid",
"en": "Grid",
"tr": "Tablo"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabGantt",
@ -4668,6 +4686,12 @@
"en": "Scheduler Layout",
"tr": "Zamanlayıcı Düzeni"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout",
"en": "Todo Layout",
"tr": "İşler Düzeni"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout",
@ -5168,15 +5192,15 @@
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingFormFormFields",
"en": "Editing Form Fields",
"tr": "Düzenleme Form Alanları"
"key": "ListForms.ListFormEdit.EditingFormFormGroups",
"en": "Editing Form Groups",
"tr": "Düzenleme Form Grupları"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingFormFormFieldsDescription",
"en": "Edit formunda çıkacak field'lar",
"tr": "Form Sütunları"
"key": "ListForms.ListFormEdit.EditingFormFormGroupsDescription",
"en": "Groups that will appear in Edit form",
"tr": "Düzenleme formunda çıkacak gruplar"
},
{
"resourceName": "Platform",
@ -5898,6 +5922,30 @@
"en": "Field Name",
"tr": "Alan Adı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.EditorType",
"en": "Editor Type",
"tr": "Editör Tipi"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.EditFormGroup",
"en": "Edit Form Group",
"tr": "Düzenleme Form Grubu"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.EditFormOrderNo",
"en": "Edit Order No",
"tr": "Düzenleme Sıra No"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.EditFormColSpan",
"en": "Edit Col Span",
"tr": "Düzenleme Col Span"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.PlaceHolder",
@ -19419,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ı"
},
{
@ -19428,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",
@ -19695,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

@ -984,22 +984,22 @@
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Components",
"DisplayName": "App.DeveloperKit.Components",
"Code": "App.DeveloperKit.DynamicServices",
"DisplayName": "App.DeveloperKit.DynamicServices",
"Order": 4,
"Url": "/admin/developerkit/components",
"Icon": "FcBiohazard",
"RequiredPermissionName": "App.DeveloperKit.Components",
"Url": "/admin/developerkit/dynamic-services",
"Icon": "FcCommandLine",
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.DynamicServices",
"DisplayName": "App.DeveloperKit.DynamicServices",
"Code": "App.DeveloperKit.Components",
"DisplayName": "App.DeveloperKit.Components",
"Order": 5,
"Url": "/admin/developerkit/dynamic-services",
"Icon": "FcCommandLine",
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
"Url": "/admin/developerkit/components",
"Icon": "FcBiohazard",
"RequiredPermissionName": "App.DeveloperKit.Components",
"IsDisabled": false
},
{

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

@ -1,6 +1,6 @@
IF OBJECT_ID(N'[dbo].[Adm_D_Todos]', 'U') IS NULL
IF OBJECT_ID(N'[dbo].[Pro_T_Todo]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Adm_D_Todos]
CREATE TABLE [dbo].[Pro_T_Todo]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[Title] nvarchar(300) NOT NULL,
@ -12,6 +12,7 @@ BEGIN
[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

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

@ -1,9 +1,9 @@
{
"Wizard": {
"WizardName": "Todos",
"ListFormCode": "App.Wizard.Todos",
"MenuCode": "App.Wizard.Todos",
"IsTenant": false,
"WizardName": "Todo",
"ListFormCode": "App.Wizard.Todo",
"MenuCode": "App.Wizard.Todo",
"IsTenant": true,
"IsBranch": false,
"IsOrganizationUnit": false,
"AllowAdding": true,
@ -11,7 +11,7 @@
"AllowDeleting": true,
"AllowDetail": false,
"ConfirmDelete": true,
"DefaultLayout": "grid",
"DefaultLayout": "todo",
"Grid": true,
"Card": true,
"Pivot": true,
@ -20,22 +20,23 @@
"Gantt": true,
"Scheduler": true,
"Todo": true,
"LanguageTextMenuEn": "Todos",
"LanguageTextMenuTr": "Todos",
"LanguageTextTitleEn": "Todos",
"LanguageTextTitleTr": "Todos",
"LanguageTextDescEn": "Todos",
"LanguageTextDescTr": "Todos",
"LanguageTextMenuParentEn": "",
"LanguageTextMenuParentTr": "",
"PermissionGroupName": "App.Administration",
"MenuParentCode": "App.Administration",
"MenuParentIcon": "FcOrganization",
"MenuIcon": "FcAndroidOs",
"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": "Adm_D_Todos",
"SelectCommand": "Pro_T_Todo",
"KeyFieldName": "Id",
"KeyFieldDbSourceType": 9,
"TreeKeyExpr": "",
@ -60,7 +61,7 @@
"TodoPriorityExpr": "Priority",
"TodoCompletedExpr": "Completed",
"TodoOrderExpr": "SortOrder",
"TodoStatusOrder": "Inbox, Today, This Week, This Month, Later, Done",
"TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
"TodoAllowDragging": true,
"ApprovalUserFieldName": "",
"ApprovalDateFieldName": "",
@ -74,7 +75,7 @@
"ColCount": 1,
"Items": [
{
"DataField": "Id",
"FieldName": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -91,7 +92,7 @@
"LookupQuery": ""
},
{
"DataField": "Title",
"FieldName": "Title",
"CaptionName": "App.Listform.ListformField.Title",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -108,7 +109,7 @@
"LookupQuery": ""
},
{
"DataField": "Status",
"FieldName": "Status",
"CaptionName": "App.Listform.ListformField.Status",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -125,10 +126,10 @@
"LookupQuery": ""
},
{
"DataField": "Description",
"FieldName": "Description",
"CaptionName": "App.Listform.ListformField.Description",
"EditorType": "dxTextBox",
"EditorOptions": "",
"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,
@ -142,7 +143,7 @@
"LookupQuery": ""
},
{
"DataField": "DueDate",
"FieldName": "DueDate",
"CaptionName": "App.Listform.ListformField.DueDate",
"EditorType": "dxDateBox",
"EditorOptions": "",
@ -159,7 +160,7 @@
"LookupQuery": ""
},
{
"DataField": "Tags",
"FieldName": "Tags",
"CaptionName": "App.Listform.ListformField.Tags",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -176,7 +177,7 @@
"LookupQuery": ""
},
{
"DataField": "Assignees",
"FieldName": "Assignees",
"CaptionName": "App.Listform.ListformField.Assignees",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -193,7 +194,7 @@
"LookupQuery": ""
},
{
"DataField": "Priority",
"FieldName": "Priority",
"CaptionName": "App.Listform.ListformField.Priority",
"EditorType": "dxTextBox",
"EditorOptions": "",
@ -210,7 +211,7 @@
"LookupQuery": ""
},
{
"DataField": "Completed",
"FieldName": "Completed",
"CaptionName": "App.Listform.ListformField.Completed",
"EditorType": "dxCheckBox",
"EditorOptions": "",
@ -227,7 +228,7 @@
"LookupQuery": ""
},
{
"DataField": "SortOrder",
"FieldName": "SortOrder",
"CaptionName": "App.Listform.ListformField.SortOrder",
"EditorType": "dxNumberBox",
"EditorOptions": "",
@ -253,25 +254,27 @@
"IsCreatedField": false,
"InsertedRecords": {
"LanguageKeys": [
"App.Wizard.Todos",
"App.Wizard.Todos.Title",
"App.Wizard.Todos.Desc",
"App.Wizard.Todo",
"App.Wizard.Todo.Title",
"App.Wizard.Todo.Desc",
"App.Listform.ListformField.Assignees",
"App.Listform.ListformField.Completed",
"App.Listform.ListformField.SortOrder"
],
"PermissionGroupNames": [],
"PermissionGroupNames": [
"App.Wizard.Project"
],
"PermissionNames": [
"App.Wizard.Todos",
"App.Wizard.Todos.Create",
"App.Wizard.Todos.Update",
"App.Wizard.Todos.Delete",
"App.Wizard.Todos.Export",
"App.Wizard.Todos.Import",
"App.Wizard.Todos.Note"
"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.Todos"
"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);
@ -302,21 +305,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
Caption = g.Caption,
ColCount = g.ColCount,
ColSpan = g.ColCount,
ItemType = "group",
Items = g.Items
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Select((it, ii) => new EditingFormItemDto
{
Order = ii + 1,
DataField = it.DataField,
EditorType2 = it.EditorType,
ColSpan = it.ColSpan,
EditorOptions = string.IsNullOrWhiteSpace(it.EditorOptions) ? null : it.EditorOptions,
EditorScript = string.IsNullOrWhiteSpace(it.EditorScript) ? null : it.EditorScript,
})
.ToArray()
ItemType = "group"
})
.Where(g => g.Items.Length > 0)
.Where((_, index) => input.Groups[index].Items.Any(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName))
.ToList();
// ListForm - varsa sil, yeniden ekle
@ -341,8 +332,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
// EditingForm'da required olan alanları tespit et, ValidationRuleJson eklemek için
var editingFormFieldNames = input.Groups
.SelectMany(g => g.Items)
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Select(i => new { FieldName = i.DataField, Isrequired = i.IsRequired })
.Where(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName)
.Select(i => new { FieldName = i.FieldName, Isrequired = i.IsRequired })
.ToList();
// Heigth ve Width hesaplama
@ -350,7 +341,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
.Select(g =>
{
var items = g.Items
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Where(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName)
.ToList();
var columnCount = g.ColCount;
@ -422,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,
@ -454,21 +445,36 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
// ListFormFields
var fieldOrder = 0;
var editGroupOrder = 0;
foreach (var group in input.Groups)
{
editGroupOrder++;
var editOrder = 0;
foreach (var item in group.Items)
{
fieldOrder++;
if (item.IncludeInEditingForm && item.FieldName != input.KeyFieldName)
{
editOrder++;
}
await _repoListFormField.InsertAsync(new ListFormField
{
ListFormCode = input.ListFormCode,
FieldName = item.DataField,
FieldName = item.FieldName,
CaptionName = item.CaptionName,
Visible = item.DataField != input.KeyFieldName,
Visible = item.FieldName != input.KeyFieldName,
IsActive = true,
Width = 0, //Fit columns için hepsine sıfır veriyorum.
AllowSearch = true,
ListOrderNo = fieldOrder,
EditGroupOrderNo = item.IncludeInEditingForm ? editGroupOrder : 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,
CultureName = PlatformConsts.DefaultLanguage,
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),
@ -478,7 +484,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery)
? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery)
: null,
ValidationRuleJson = editingFormFieldNames.Any(f => f.FieldName == item.DataField && f.Isrequired)
ValidationRuleJson = editingFormFieldNames.Any(f => f.FieldName == item.FieldName && f.Isrequired)
? WizardConsts.DefaultValidationRuleRequiredJson
: null
}, autoSave: true);
@ -516,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

@ -758,6 +758,7 @@ public static class PlatformConsts
public const string dxDateBox = "dxDateBox";
public const string dxDateRangeBox = "dxDateRangeBox";
public const string dxDropDownBox = "dxDropDownBox";
public const string dxGridBox = "dxGridBox";
public const string dxHtmlEditor = "dxHtmlEditor";
public const string dxLookup = "dxHtmlEdidxLookuptor";
public const string dxNumberBox = "dxNumberBox";

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

@ -53,6 +53,12 @@ public class ListFormField : Entity<Guid>
public string Alignment { get; set; } = "left"; // Sütunun Sağa, sola veya ortalama gösterimi
public string Format { get; set; } // Cell bazında sütunun formatlanması
public int? EditOrderNo { get; set; }
public int? EditGroupOrderNo { get; set; }
public string EditorType2 { get; set; }
public int? ColSpan { get; set; }
public string EditorScript { get; set; } //Sütunun ekstra javascript ile yönetilmesi
public string EditorOptions { get; set; } //Sütunun ekstra özellikleri yönetilmesi
}

View file

@ -1,58 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Volo.Abp.Domain.Values;
namespace Sozsoft.Platform.Queries;
// public class EditingForm : ValueObject
// {
// public int Order { get; set; }
// public string ItemType { get; set; }
// public string Caption { get; set; }
// public int ColCount { get; set; }
// public int ColSpan { get; set; }
// /// <summary> Type: Array<Simple Form Item | Group Form Item | Tabbed Form Item | Empty Form Item | Button Form Item>
// /// Bu kisma suan sadece string olarak doldurulacak
// /// items: ['FirstName', 'LastName',
// /// { dataField: 'Notes', editorType: 'dxTextArea', colSpan: 2, editorOptions: { height: 100, }, }
// /// ],
// /// </summary>
// //[TextArea]
// //public string[] Items { get; set; }
// public EditingFormItem[] Items { get; set; }
// protected override IEnumerable<object> GetAtomicValues()
// {
// yield return ItemType;
// yield return Caption;
// yield return ColCount;
// yield return ColSpan;
// yield return Items;
// }
// }
// public class EditingFormItem : ValueObject
// {
// [JsonPropertyName("order")]
// public int Order { get; set; }
// [JsonPropertyName("dataField")]
// public string DataField { get; set; }
// [JsonPropertyName("editorType2")]
// public string EditorType2 { get; set; }
// [JsonPropertyName("colSpan")]
// public int ColSpan { get; set; }
// [JsonPropertyName("editorOptions")]
// public string EditorOptions { get; set; }
// [JsonPropertyName("isRequired")]
// public bool IsRequired { get; set; }
// protected override IEnumerable<object> GetAtomicValues()
// {
// yield return DataField;
// yield return EditorType2;
// yield return ColSpan;
// yield return EditorOptions;
// yield return IsRequired;
// }
// }

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);
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<string, object> parameters,
// DataSourceTypeEnum dataSourceType)
// {
// if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0)
// {
// return command;
// }
return Regex.Replace(
sql,
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\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,
// $@"(?<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

@ -455,6 +455,8 @@ public class PlatformDbContext :
b.Property(a => a.Alignment).HasMaxLength(20).HasDefaultValue("left");
b.Property(a => a.Format).HasMaxLength(128);
b.Property(a => a.EditorOptions).HasColumnType("text");
b.Property(a => a.EditorType2).HasMaxLength(64);
b.Property(a => a.EditorScript).HasColumnType("text");
b.HasIndex(x => new { x.ListFormCode, x.UserId, x.RoleId, x.CultureName, x.FieldName }).IsUnique().HasFilter(null);
});

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260716120523_Initial")]
[Migration("20260717065637_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -3415,6 +3415,9 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<int?>("ColSpan")
.HasColumnType("int");
b.Property<string>("ColumnCssClass")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
@ -3440,9 +3443,22 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int?>("EditGroupOrderNo")
.HasColumnType("int");
b.Property<int?>("EditOrderNo")
.HasColumnType("int");
b.Property<string>("EditorOptions")
.HasColumnType("text");
b.Property<string>("EditorScript")
.HasColumnType("text");
b.Property<string>("EditorType2")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("FieldName")
.IsRequired()
.HasMaxLength(128)

View file

@ -2791,6 +2791,11 @@ namespace Sozsoft.Platform.Migrations
PivotSettingsJson = table.Column<string>(type: "text", nullable: true),
Alignment = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true, defaultValue: "left"),
Format = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
EditOrderNo = table.Column<int>(type: "int", nullable: true),
EditGroupOrderNo = table.Column<int>(type: "int", nullable: true),
EditorType2 = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
ColSpan = table.Column<int>(type: "int", nullable: true),
EditorScript = table.Column<string>(type: "text", nullable: true),
EditorOptions = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>

View file

@ -3412,6 +3412,9 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<int?>("ColSpan")
.HasColumnType("int");
b.Property<string>("ColumnCssClass")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
@ -3437,9 +3440,22 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int?>("EditGroupOrderNo")
.HasColumnType("int");
b.Property<int?>("EditOrderNo")
.HasColumnType("int");
b.Property<string>("EditorOptions")
.HasColumnType("text");
b.Property<string>("EditorScript")
.HasColumnType("text");
b.Property<string>("EditorType2")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("FieldName")
.IsRequired()
.HasMaxLength(128)

View file

@ -542,14 +542,16 @@ public class DynamicFormReport : XtraReport
.Where(x => x.IsActive != false)
.ToDictionary(x => x.FieldName, StringComparer.OrdinalIgnoreCase);
var formItems = GetEditingFormItems(listForm)
.Where(x => !string.IsNullOrWhiteSpace(x.DataField))
var editFields = fields
.Where(x => x.EditOrderNo.HasValue)
.OrderBy(x => x.EditGroupOrderNo ?? 1)
.ThenBy(x => x.EditOrderNo)
.ToList();
var orderedFields = formItems.Count > 0
? formItems
.Where(x => fieldsByName.ContainsKey(x.DataField))
.Select(x => new FieldWithEditor(fieldsByName[x.DataField], x.EditorType2, x.ColSpan))
var orderedFields = editFields.Count > 0
? editFields
.Where(x => fieldsByName.ContainsKey(x.FieldName))
.Select(x => new FieldWithEditor(x, x.EditorType2, x.ColSpan ?? 1))
.ToList()
: fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
@ -582,10 +584,9 @@ public class DynamicFormReport : XtraReport
var usedFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var group in groups)
{
var groupFields = (group.Items ?? [])
.OrderBy(x => x.Order)
.Where(x => !string.IsNullOrWhiteSpace(x.DataField) && fieldsByName.ContainsKey(x.DataField))
.Select(x => fieldsByName[x.DataField])
var groupFields = fields
.Where(x => (x.Field.EditGroupOrderNo ?? 1) == group.Order)
.OrderBy(x => x.Field.EditOrderNo ?? int.MaxValue)
.ToList();
foreach (var field in groupFields)
@ -625,13 +626,6 @@ public class DynamicFormReport : XtraReport
}
}
private static IEnumerable<EditingFormItemDto> GetEditingFormItems(ListForm listForm)
{
return GetEditingFormGroups(listForm)
.OrderBy(x => x.Order)
.SelectMany(x => (x.Items ?? []).OrderBy(i => i.Order));
}
private static IEnumerable<(SubFormDto Form, string Filter)> GetListSubForms(
ListForm listForm,
IDictionary<string, object> row)

View file

@ -107,7 +107,7 @@ public class DynamicGridReport : XtraReport
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(fields);
var availableFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")

View file

@ -73,28 +73,16 @@ internal static class DynamicReportImageHelper
return map;
}
public static Dictionary<string, string> CreateEditorTypeMap(ListForm listForm)
public static Dictionary<string, string> CreateEditorTypeMap(IEnumerable<ListFormField> fields)
{
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson))
foreach (var field in fields ?? [])
{
return map;
}
try
{
var groups = JsonSerializer.Deserialize<List<EditingFormDto>>(listForm.EditingFormJson) ?? [];
foreach (var item in groups.SelectMany(x => x.Items ?? []))
if (!string.IsNullOrWhiteSpace(field.FieldName) && !string.IsNullOrWhiteSpace(field.EditorType2))
{
if (!string.IsNullOrWhiteSpace(item.DataField))
{
map[item.DataField] = item.EditorType2;
}
map[field.FieldName] = field.EditorType2;
}
}
catch (JsonException)
{
}
return map;
}
@ -452,25 +440,16 @@ internal static class DynamicReportImageHelper
return editorType is PlatformConsts.EditorTypes.dxImageUpload or PlatformConsts.EditorTypes.dxImageViewer;
}
public static bool HasImageEditor(ListForm listForm, string fieldName)
public static bool HasImageEditor(IEnumerable<ListFormField> fields, string fieldName)
{
if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson) || string.IsNullOrWhiteSpace(fieldName))
if (string.IsNullOrWhiteSpace(fieldName))
{
return false;
}
try
{
var groups = JsonSerializer.Deserialize<List<EditingFormDto>>(listForm.EditingFormJson) ?? [];
return groups
.SelectMany(x => x.Items ?? [])
.Any(x => string.Equals(x.DataField, fieldName, StringComparison.OrdinalIgnoreCase) &&
IsImageEditor(x.EditorType2));
}
catch (JsonException)
{
return false;
}
return (fields ?? []).Any(x =>
string.Equals(x.FieldName, fieldName, StringComparison.OrdinalIgnoreCase) &&
IsImageEditor(x.EditorType2));
}
public static float GetGridImageHeight() => DefaultGridImageHeight;

View file

@ -114,7 +114,7 @@ public class DynamicTreeReport : XtraReport
await selectQueryManager.PrepareQueriesAsync(listForm, fields, dataSourceType, customizations, queryParams);
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(fields);
var availableFields = fields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")

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

@ -3,7 +3,7 @@ import Drawer from '@/components/ui/Drawer'
import SidePanelContent, { SidePanelContentProps } from './SidePanelContent'
import CopyButton from '../ThemeConfigurator/CopyButton'
import withHeaderItem from '@/utils/hoc/withHeaderItem'
import { useStoreState, useStoreActions } from '@/store'
import { useStoreActions, useStoreState } from '@/store'
import type { CommonProps } from '@/proxy/common'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { FcEngineering } from 'react-icons/fc'

View file

@ -16,8 +16,7 @@ import {
import { useStoreState } from '@/store'
import useMenuActive from '@/utils/hooks/useMenuActive'
import isEmpty from 'lodash/isEmpty'
import React from 'react'
import { useEffect } from 'react'
import React, { useEffect } from 'react'
import { FaQuestionCircle } from 'react-icons/fa'
import { Link, useLocation } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import {
import React, {
Children,
cloneElement,
forwardRef,

View file

@ -1,4 +1,4 @@
import { createContext } from 'react'
import React, { createContext } from 'react'
export type CheckboxValue = string | number
export type CheckboxGroupValue = CheckboxValue[]

View file

@ -1,4 +1,4 @@
import {
import React, {
forwardRef,
useState,
useRef,

View file

@ -9,6 +9,7 @@ import type { CommonProps } from '../../@types/common'
import type { MonthBaseProps } from './components/Month'
import type { DayKeydownPayload } from './components/types'
import type { RefObject } from 'react'
import React from 'react'
export interface DateTableProps extends CommonProps, MonthBaseProps {
dateViewCount: number

View file

@ -1,4 +1,4 @@
import {
import React, {
forwardRef,
useState,
useRef,

View file

@ -1,4 +1,4 @@
import {
import React, {
forwardRef,
useRef,
useState,

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import React, { useState, useEffect, useCallback, useMemo } from 'react'
import classNames from 'classnames'
import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from 'react-icons/fa';

View file

@ -1,4 +1,4 @@
import {
import React, {
forwardRef,
useState,
useCallback,

View file

@ -1,4 +1,4 @@
import { useState, useCallback, useRef, useEffect } from 'react'
import React, { useState, useCallback, useRef, useEffect } from 'react'
import useCallbackRef from './useCallbackRef'
type UseControllableStateParams<T> = {

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

@ -15,7 +15,6 @@ import type {
StartEditAction,
StateStoreType,
} from 'devextreme/ui/data_grid_types'
import type { FormItemComponent } from 'devextreme/ui/form'
import { AuditedEntityDto } from '../abp'
import { EditorType2, RowMode } from '../../views/form/types'
import {
@ -195,7 +194,7 @@ export interface ColumnFilterDto {
}
export interface ColumnFormatDto extends AuditedEntityDto<string> {
fieldName?: string
fieldName: string
captionName?: string
placeHolder?: string
readOnly: boolean
@ -245,6 +244,14 @@ export interface ColumnFormatDto extends AuditedEntityDto<string> {
alignment?: HorizontalAlignment
format?: string
editorOptions?: string
editOrderNo?: number
editGroupOrderNo?: number
editorType2: EditorType2
colSpan?: number
gridBoxOptions?: GridBoxOptionsDto
tagBoxOptions?: TagBoxOptionsDto
imageUploadOptions?: ImageUploadOptionsDto
editorScript?: string
}
export enum AuthorizationTypeEnum {
@ -492,20 +499,6 @@ export interface EditingFormDto {
caption?: string
colCount: number
colSpan: number
items?: EditingFormItemDto[]
}
export interface EditingFormItemDto {
order: number
dataField: string
editorType: FormItemComponent
editorType2: EditorType2
colSpan?: number
editorOptions?: string
gridBoxOptions?: GridBoxOptionsDto
tagBoxOptions?: TagBoxOptionsDto
imageUploadOptions?: ImageUploadOptionsDto
editorScript?: string
}
export interface GridEditingPopupDto {

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import React, { useEffect, useState } from 'react'
function useTimeOutMessage(
interval = 3000,

View file

@ -300,6 +300,9 @@ const FormEdit = () => {
{translate('::ListForms.ListFormEdit.ExtraFilters')}
</TabNav>
)}
{visibleTabs.includes('editForm') && (
<TabNav value="editForm">{translate('::ListForms.ListFormEdit.EditingForm')}</TabNav>
)}
{visibleTabs.includes('fields') && (
<TabNav value="fields">{translate('::ListForms.ListFormEdit.TabFields')}</TabNav>
)}
@ -308,9 +311,6 @@ const FormEdit = () => {
{translate('::ListForms.ListFormEdit.TabCustomization')}
</TabNav>
)}
{visibleTabs.includes('editForm') && (
<TabNav value="editForm">{translate('::ListForms.ListFormEdit.EditingForm')}</TabNav>
)}
{visibleTabs.includes('pivots') && (
<TabNav value="pivots">{translate('::ListForms.ListFormEdit.TabPivot')}</TabNav>
)}
@ -464,6 +464,7 @@ const FormEdit = () => {
<TabContent value="fields" className="px-2">
<FormFields
listFormCode={listFormCode}
editingFormGroups={listFormValues.editingFormDto ?? []}
langOptions={langOptions}
roleList={roleList}
userList={userList}

View file

@ -31,6 +31,8 @@ const schema = Yup.object().shape({
chart: Yup.boolean(),
tree: Yup.boolean(),
gantt: Yup.boolean(),
scheduler: Yup.boolean(),
todo: Yup.boolean(),
defaultLayout: Yup.string(),
}),
})
@ -411,7 +413,11 @@ function FormTabDetails(
/>
</FormItem>
<FormItem label="Todo Layout">
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout')}
invalid={errors.layoutDto?.todo && touched.layoutDto?.todo}
errorMessage={errors.layoutDto?.todo}
>
<Field
className="w-20"
autoComplete="off"

View file

@ -59,7 +59,6 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Th>{translate('::ListForms.ListFormEdit.DetailsTitle')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormColumnCount')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormColumnSpan')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormItems')}</Th>
</Tr>
</THead>
<TBody>
@ -109,13 +108,6 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Td>{row.caption}</Td>
<Td>{row.colCount}</Td>
<Td>{row.colSpan}</Td>
<Td>
<ul className="list-disc">
{row.items?.map((i, idx) => (
<li key={`items-${i.dataField}-${idx}`}>{i.dataField}</li>
))}
</ul>
</Td>
</Tr>
))}
</TBody>

View file

@ -20,14 +20,17 @@ import FormFieldTabTotalSummary from './FormFieldTabTotalSummary'
import FormFieldTabValidationRules from './FormFieldTabValidationRules'
import { FormFieldEditProps } from './FormFields'
import FormFieldTabPivotSetting from './FormFieldTabPivotSetting'
import type { EditingFormDto } from '@/proxy/form/models'
function FormFieldEdit({
item,
onSubmit,
fields,
editingFormGroups,
}: {
item: ColumnFormatEditDto | null
fields: ColumnFormatEditDto[]
editingFormGroups: EditingFormDto[]
} & FormFieldEditProps) {
const { translate } = useLocalization()
@ -72,13 +75,18 @@ function FormFieldEdit({
<TabNav value="pivotSetting">
{translate('::ListForms.ListFormFieldEdit.TabPivotSettings')}
</TabNav>
</TabList>
</TabList>
<div className="pt-4">
<TabContent value="details">
<FormFieldTabDetails initialValues={item} onSubmit={onSubmit} />
</TabContent>
<TabContent value="options">
<FormFieldTabOptions initialValues={item} onSubmit={onSubmit} />
<FormFieldTabOptions
initialValues={item}
editingFormGroups={editingFormGroups}
fields={fields}
onSubmit={onSubmit}
/>
</TabContent>
<TabContent value="permissions">
<FormFieldTabPermissions initialValues={item} onSubmit={onSubmit} />
@ -96,18 +104,10 @@ function FormFieldEdit({
<FormFieldTabGrouping initialValues={item} onSubmit={onSubmit} />
</TabContent>
<TabContent value="groupSummary">
<FormFieldTabGroupSummary
initialValues={item}
fields={fields}
onSubmit={onSubmit}
/>
<FormFieldTabGroupSummary initialValues={item} fields={fields} onSubmit={onSubmit} />
</TabContent>
<TabContent value="totalSummary">
<FormFieldTabTotalSummary
initialValues={item}
fields={fields}
onSubmit={onSubmit}
/>
<FormFieldTabTotalSummary initialValues={item} fields={fields} onSubmit={onSubmit} />
</TabContent>
<TabContent value="joinOptions">
<FormFieldTabJoinOptions initialValues={item} onSubmit={onSubmit} />

View file

@ -1,11 +1,11 @@
import { Button, Card, FormItem, Input, Select } from '@/components/ui'
import { Button, Card, Checkbox, FormItem, Input, Select } from '@/components/ui'
import { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik'
import { useState } from 'react'
import { FaSlidersH } from 'react-icons/fa'
import { number, object, string } from 'yup'
import { bool, number, object, string } from 'yup'
import { dbSourceTypeOptions, listFormAlignmentOptions } from '../options'
import EditorOptionsBuilderDialog from '../json-row-operations/EditorOptionsBuilderDialog'
import { FormFieldEditProps } from './FormFields'
@ -16,10 +16,13 @@ const schema = object().shape({
captionName: string(),
placeHolder: string(),
bandName: string(),
isActive: bool().required(),
visible: bool().required(),
allowSearch: bool().required(),
sourceDbType: number().required(),
alignment: string(),
format: string(),
editorOptions: string().max(1000),
editorOptions: string(),
})
function FormFieldTabDetails({
@ -72,6 +75,32 @@ function FormFieldTabDetails({
>
<Field type="text" name="bandName" component={Input} />
</FormItem>
<div className="flex w-full gap-4">
<FormItem
className="flex-1"
label={translate('::ListForms.ListFormFieldEdit.HeaderingAllowSearch')}
>
<Field name="allowSearch" component={Checkbox} />
</FormItem>
<FormItem
className="flex-1"
label={translate('::ListForms.ListFormFieldEdit.OptionsIsActive')}
invalid={errors.isActive && touched.isActive}
errorMessage={errors.isActive}
>
<Field name="isActive" component={Checkbox} />
</FormItem>
<FormItem
className="flex-1"
label={translate('::ListForms.ListFormFieldEdit.OptionsVisible')}
invalid={errors.visible && touched.visible}
errorMessage={errors.visible}
>
<Field name="visible" component={Checkbox} />
</FormItem>
</div>
</Card>
<Card>
<FormItem label={translate('::ListForms.ListFormEdit.CustomValueType')}>
@ -132,7 +161,7 @@ function FormFieldTabDetails({
type="text"
name="editorOptions"
component={Input}
rows={4}
rows={3}
textArea={true}
/>
<Button
@ -154,7 +183,14 @@ function FormFieldTabDetails({
onClose={() => setIsEditorOptionsDialogOpen(false)}
onApply={(val) => setFieldValue('editorOptions', val)}
/>
<Button block size="sm" className="mt-1" variant="solid" loading={isSubmitting} type="submit">
<Button
block
size="sm"
className="mt-1"
variant="solid"
loading={isSubmitting}
type="submit"
>
{isSubmitting ? translate('::Saving') : translate('::Save')}
</Button>
</Form>

View file

@ -4,29 +4,50 @@ import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik'
import { bool, number, object, string } from 'yup'
import { sortDirectionOptions } from '../options'
import { columnEditorTypeListOptions, sortDirectionOptions } from '../options'
import { FormFieldEditProps } from './FormFields'
import { SortDirectionEnum } from '@/proxy/form/models'
import { EditingFormDto, SortDirectionEnum } from '@/proxy/form/models'
import { FaCode } from 'react-icons/fa'
import { useState } from 'react'
import EditorScriptBuilderDialog from '../json-row-operations/EditorScriptBuilderDialog'
const schema = object().shape({
listOrderNo: number(),
width: number(),
sortDirection: string().oneOf(Object.values(SortDirectionEnum) as string[]),
sortIndex: number(),
isActive: bool().required(),
visible: bool().required(),
allowSearch: bool().required(),
editGroupOrderNo: number().nullable(),
editOrderNo: number().nullable(),
colSpan: number().nullable(),
allowEditing: bool().required(),
allowAdding: bool().required(),
editorScript: string(),
})
function FormFieldTabOptions({
initialValues,
editingFormGroups,
fields,
onSubmit,
}: {
initialValues: ColumnFormatEditDto
editingFormGroups: EditingFormDto[]
fields: ColumnFormatEditDto[]
} & FormFieldEditProps) {
const { translate } = useLocalization()
const [isEditorScriptDialogOpen, setIsEditorScriptDialogOpen] = useState(false)
const editingFormGroupOptions: SelectBoxOption[] = [...editingFormGroups]
.sort((left, right) => left.order - right.order)
.map((group) => ({
value: String(group.order),
label: `${group.order} - ${group.caption || translate('::ListForms.ListFormFieldEdit.EditFormGroup')}`,
}))
const editorScriptFields: SelectBoxOption[] = fields
.filter((field) => field.fieldName)
.map((field) => ({
value: field.fieldName,
label: field.captionName || field.fieldName,
}))
return (
<Formik
@ -37,7 +58,7 @@ function FormFieldTabOptions({
await onSubmit(ListFormFieldEditTabs.OptionsForm, values, formikHelpers)
}}
>
{({ touched, errors, isSubmitting, values }) => (
{({ touched, errors, isSubmitting, values, setFieldValue }) => (
<Form>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<Card>
@ -82,25 +103,7 @@ function FormFieldTabOptions({
>
<Field type="number" name="sortIndex" component={Input} />
</FormItem>
</Card>
<Card>
<FormItem
label={translate('::ListForms.ListFormFieldEdit.OptionsIsActive')}
invalid={errors.isActive && touched.isActive}
errorMessage={errors.isActive}
>
<Field name="isActive" component={Checkbox} />
</FormItem>
<FormItem
label={translate('::ListForms.ListFormFieldEdit.OptionsVisible')}
invalid={errors.visible && touched.visible}
errorMessage={errors.visible}
>
<Field name="visible" component={Checkbox} />
</FormItem>
<FormItem label={translate('::ListForms.ListFormFieldEdit.HeaderingAllowSearch')}>
<Field name="allowSearch" component={Checkbox} />
</FormItem>
<FormItem label={translate('::ListForms.ListFormEdit.AllowEditing')}>
<Field name="allowEditing" component={Checkbox} />
</FormItem>
@ -108,9 +111,100 @@ function FormFieldTabOptions({
<Field name="allowAdding" component={Checkbox} />
</FormItem>
</Card>
<Card>
<FormItem
invalid={errors.editorType2 && touched.editorType2}
errorMessage={errors.editorType2}
label={translate('::ListForms.ListFormFieldEdit.EditorType')}
>
<Field name="editorType2">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
isClearable
options={columnEditorTypeListOptions}
value={columnEditorTypeListOptions.find(
(option) => option.value === field.value,
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
invalid={errors.editGroupOrderNo && touched.editGroupOrderNo}
errorMessage={errors.editGroupOrderNo}
label={translate('::ListForms.ListFormFieldEdit.EditFormGroup')}
>
<Field name="editGroupOrderNo">
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
isClearable
options={editingFormGroupOptions}
value={editingFormGroupOptions.find(
(option) => Number(option.value) === Number(field.value),
)}
onChange={(option) =>
form.setFieldValue(
field.name,
option?.value == null ? null : Number(option.value),
)
}
/>
)}
</Field>
</FormItem>
<FormItem
invalid={errors.editOrderNo && touched.editOrderNo}
errorMessage={errors.editOrderNo}
label={translate('::ListForms.ListFormFieldEdit.EditFormOrderNo')}
>
<Field type="number" name="editOrderNo" component={Input} />
</FormItem>
<FormItem
invalid={errors.colSpan && touched.colSpan}
errorMessage={errors.colSpan}
label={translate('::ListForms.ListFormFieldEdit.EditFormColSpan')}
>
<Field type="number" name="colSpan" component={Input} />
</FormItem>
<FormItem label={translate('::ListForms.Wizard.Step3.EditorScript')}>
<div className="flex gap-2 items-start">
<Field
type="text"
name="editorScript"
component={Input}
rows={3}
textArea={true}
/>
<Button
shape="circle"
variant="plain"
type="button"
size="sm"
title="Build editor script"
icon={<FaCode />}
onClick={() => setIsEditorScriptDialogOpen(true)}
/>
</div>
</FormItem>
</Card>
</div>
<Button block size="sm" className="mt-1" variant="solid" loading={isSubmitting} type="submit">
<EditorScriptBuilderDialog
isOpen={isEditorScriptDialogOpen}
value={values.editorScript}
currentField={values.fieldName}
fields={editorScriptFields}
onClose={() => setIsEditorScriptDialogOpen(false)}
onApply={(val) => setFieldValue('editorScript', val)}
/>
<Button
block
size="sm"
className="mt-1"
variant="solid"
loading={isSubmitting}
type="submit"
>
{isSubmitting ? translate('::Saving') : translate('::Save')}
</Button>
</Form>

View file

@ -43,6 +43,7 @@ import { dbSourceTypeOptions, sqlDataTypeToDbType } from '../options'
import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import type { EditingFormDto } from '@/proxy/form/models'
export interface FormFieldEditProps {
onSubmit: (
@ -71,12 +72,14 @@ const copyFieldSchema = object().shape({
function FormFields({
listFormCode,
editingFormGroups,
langOptions,
roleList,
userList,
columns,
}: {
listFormCode: string
editingFormGroups: EditingFormDto[]
langOptions: SelectBoxOption[]
roleList: SelectBoxOption[]
userList: SelectBoxOption[]
@ -219,42 +222,45 @@ function FormFields({
await getFields()
}
const getFields = useCallback(async (cultureName?: string, roleId?: string, userId?: string) => {
if (!listFormCode) {
return
}
const getFields = useCallback(
async (cultureName?: string, roleId?: string, userId?: string) => {
if (!listFormCode) {
return
}
try {
const resp = await getListFormFields({
listFormCode,
cultureName,
roleId,
userId,
sorting: 'ListOrderNo',
maxResultCount: 1000,
})
setNull(resp?.data?.items)
setFields(resp?.data?.items)
if (resp.data?.items) {
setFieldList(
resp?.data?.items.map((f: ColumnFormatEditDto) => ({
value: f.fieldName,
label: f.fieldName,
})),
try {
const resp = await getListFormFields({
listFormCode,
cultureName,
roleId,
userId,
sorting: 'ListOrderNo',
maxResultCount: 1000,
})
setNull(resp?.data?.items)
setFields(resp?.data?.items)
if (resp.data?.items) {
setFieldList(
resp?.data?.items.map((f: ColumnFormatEditDto) => ({
value: f.fieldName,
label: f.fieldName,
})),
)
}
} catch (error: any) {
toast.push(
<Notification type="danger" duration={2000}>
Alanlar getirilemedi
{error.toString()}
</Notification>,
{
placement: 'bottom-end',
},
)
}
} catch (error: any) {
toast.push(
<Notification type="danger" duration={2000}>
Alanlar getirilemedi
{error.toString()}
</Notification>,
{
placement: 'bottom-end',
},
)
}
}, [listFormCode])
},
[listFormCode],
)
useEffect(() => {
getFields()
@ -405,7 +411,7 @@ function FormFields({
<Table compact>
<THead>
<Tr>
<Th className='w-3/12'>
<Th className="w-3/12">
<div>
<Button
shape="circle"
@ -478,12 +484,19 @@ function FormFields({
}
}}
/>
<span>{field.listOrderNo}</span>
<span>{!field?.visible && <FaEyeSlash />}</span>
<span>{!field?.isActive && <FaTimes />}</span>
<span style={{ visibility: !field?.visible ? 'visible' : 'hidden' }}>
<FaEyeSlash />
</span>
<span style={{ visibility: !field?.isActive ? 'visible' : 'hidden' }}>
<FaTimes />
</span>
<span className="ml-5">
{field.listOrderNo}
{field.editOrderNo ? ` (${field.editOrderNo})` : ''}
</span>
</Td>
<Td className='!p-0'>{field.fieldName}</Td>
<Td className='align-middle text-center !p-0'>
<Td className="!p-0">{field.fieldName}</Td>
<Td className="align-middle text-center !p-0">
<Avatar
size={18}
shape="circle"
@ -505,7 +518,12 @@ function FormFields({
</Table>
</div>
<div className="2xl:col-span-3 lg:col-span-3 xl:col-span-2">
<FormFieldEdit item={row} fields={fields} onSubmit={onSubmit} />
<FormFieldEdit
item={row}
fields={fields}
editingFormGroups={editingFormGroups}
onSubmit={onSubmit}
/>
</div>
</div>
</AdaptableCard>

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>
<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"
/>
<div className="flex items-center gap-1">
<input
value={item.editorOptions}
onChange={(e) => onEditorOptionsChange(e.target.value)}
placeholder='{"readOnly": false}'
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,14 +596,41 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium">
{translate('::ListForms.Wizard.Step3.EditorScript')}
</span>
<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"
/>
<div className="flex items-center gap-1">
<input
value={item.editorScript}
onChange={(e) => onEditorScriptChange(e.target.value)}
placeholder="(e) => { /* e.component */ }"
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">
<div className="flex items-center gap-1">
@ -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,33 +309,51 @@ function WizardStep6({
'approvalDescriptionFieldName',
'::ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName',
],
].map(([name, label]) => (
<FormItem key={name} label={translate(label)}>
<Field name={name}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable
options={columnOptions}
value={columnOptions.find(
(option) => option.value === (values as any)[name],
)}
onChange={(option: any) => {
form.setFieldValue(field.name, option?.value ?? '')
onWorkflowChange({
...values,
[field.name]: option?.value ?? '',
criteria,
})
}}
/>
)}
</Field>
</FormItem>
))}
].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
field={field}
form={form}
isClearable
options={columnOptions}
value={columnOptions.find(
(option) => option.value === (values as any)[name],
)}
onChange={(option: any) => {
form.setFieldValue(field.name, option?.value ?? '')
setWorkflowErrors((current) => ({
...current,
[field.name]: undefined,
}))
onWorkflowChange({
...values,
[field.name]: option?.value ?? '',
criteria,
})
}}
/>
)}
</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,75 +97,63 @@ 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>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{mappings.map((mapping) => {
const isRequired = Boolean(mapping.required && values.todo)
{!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) => (
<FormItem
key={mapping.name}
label={`${mapping.label}${mapping.required ? ' *' : ''}`}
>
<Field name={mapping.name}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={!mapping.required}
options={fieldOptions}
value={fieldOptions.find((option) => option.value === values[mapping.name])}
onChange={(option) => {
form.setFieldValue(field.name, option?.value ?? '')
setValidationError('')
}}
/>
)}
</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)">
<Field
name="todoStatusOrder"
component={Input}
placeholder="Inbox, Today, This Week, This Month, Later, Done"
/>
</FormItem>
<FormItem label="Kartları kolonlar arasında sürüklemeye izin ver">
<Field name="todoAllowDragging">
{({ field }: FieldProps<boolean>) => (
<Checkbox
checked={Boolean(field.value)}
onChange={(checked) => setFieldValue(field.name, checked)}
return (
<FormItem
key={mapping.name}
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={!isRequired}
options={fieldOptions}
value={fieldOptions.find((option) => option.value === values[mapping.name])}
onChange={(option) => {
form.setFieldValue(field.name, option?.value ?? '')
setValidationError('')
}}
/>
)}
</Field>
</FormItem>
</div>
</>
)}
)
})}
</div>
<div className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
<Field
name="todoStatusOrder"
component={Input}
placeholder={translate('::ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder')}
/>
</FormItem>
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}>
<Field name="todoAllowDragging">
{({ field }: FieldProps<boolean>) => (
<Checkbox
checked={Boolean(field.value)}
onChange={(checked) => setFieldValue(field.name, checked)}
/>
)}
</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

@ -383,7 +383,7 @@ const OrganizationUnits = () => {
}
}
const Node = memo(({ node, style, dragHandle }: NodeRendererProps<any>) => {
const Node = memo(function Node({ node, style, dragHandle }: NodeRendererProps<any>) {
return (
<div
className={classNames(

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

@ -9,9 +9,9 @@ import type { GroupItem } from 'devextreme/ui/form'
import { useEffect, useRef, useState } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { GridColumnData } from '../list/GridColumnData'
import { addCss, addJs } from '../list/Utils'
import { addCss, addJs, getEditingFormGroups } from '../list/Utils'
import { PermissionResults, RowMode, SimpleItemWithColData } from './types'
import { EditingFormItemDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models'
import { ColumnFormatDto, GridDto, PlatformEditorTypes } from '@/proxy/form/models'
import { getAccessDeniedPath } from '@/utils/routing'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLookupDataSource } from './useLookupDataSource'
@ -218,10 +218,7 @@ const useGridData = (props: {
)
setDataSource(dataSource)
const items = gridDto?.gridOptions.editingFormDto
?.sort((a: any, b: any) => {
return a.order >= b.order ? 1 : -1
})
const items = getEditingFormGroups(gridDto)
.map((e: any) => {
return {
itemType: e.itemType,
@ -230,12 +227,12 @@ const useGridData = (props: {
caption: e.caption,
items: e.items
?.sort((a: any, b: any) => {
return a.order >= b.order ? 1 : -1
return (a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1
})
.map((i: EditingFormItemDto) => {
.map((i: ColumnFormatDto) => {
let editorOptions: Record<string, any> = {}
let parsedEditorOptions: Record<string, any> = {}
const colData = gridDto.columnFormats.find((x) => x.fieldName === i.dataField)
const colData = i
parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {}
const lookupEditorOptions = colData?.lookupDto?.dataSourceType
@ -250,26 +247,14 @@ const useGridData = (props: {
...parsedEditorOptions,
}
const item: SimpleItemWithColData = {
canRead:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canRead ??
false,
canUpdate:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canUpdate ??
false,
canCreate:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canCreate ??
false,
canExport:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)?.canExport ??
false,
allowEditing:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)
?.allowEditing ?? true,
allowAdding:
gridDto.columnFormats.find((x: any) => x.fieldName === i.dataField)
?.allowAdding ?? true,
dataField: i.dataField,
name: i.dataField,
canRead: i.canRead ?? false,
canUpdate: i.canUpdate ?? false,
canCreate: i.canCreate ?? false,
canExport: i.canExport ?? false,
allowEditing: i.allowEditing ?? true,
allowAdding: i.allowAdding ?? true,
dataField: i.fieldName,
name: i.fieldName,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
@ -286,8 +271,8 @@ const useGridData = (props: {
imageUploadOptions: i.imageUploadOptions,
editorScript: i.editorScript,
}
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
if (i.fieldName?.indexOf(':') >= 0) {
item.label = { text: captionize(i.fieldName.split(':')[1]) }
}
if ((mode == 'edit' && !item.canUpdate) || (mode == 'new' && !item.canCreate)) {
item.editorOptions = {
@ -348,7 +333,7 @@ const useGridData = (props: {
const updateItems = (items: any[] = []): any[] =>
items.map((item) => {
const colData = gridDto.columnFormats.find((x) => x.fieldName === item.dataField)
const colData = item.colData
if (colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) {
const currentDataSource = item.editorOptions?.dataSource
const keepCustomDataSource =

View file

@ -4,8 +4,8 @@ import { ImportDashboard } from '@/components/importManager/ImportDashboard'
import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import {
ColumnFormatDto,
DbTypeEnum,
EditingFormItemDto,
GridDto,
PlatformEditorTypes,
SubFormTabTypeEnum,
@ -41,7 +41,12 @@ import GridFilterDialogs from './GridFilterDialogs'
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
import { ImageViewerEditorComponent } from './editors/ImageViewerEditorComponent'
import { extractSearchParamsFields, GridExtraFilterState, safeJsonParse } from './Utils'
import {
extractSearchParamsFields,
getEditingFormGroups,
GridExtraFilterState,
safeJsonParse,
} from './Utils'
import { useFilters } from './useFilters'
import { useListFormColumns } from './useListFormColumns'
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
@ -94,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({
@ -139,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,
@ -788,12 +801,9 @@ const CardView = (props: CardViewProps) => {
}, [filterToolbarData, toolbarData])
const mapEditingFormItem = useCallback(
(item: EditingFormItemDto) => {
const fieldName = item.dataField.split(':')[0]
const column = gridDto?.columnFormats.find(
(candidate) => candidate.fieldName?.toLowerCase() === fieldName.toLowerCase(),
)
if (!column) return undefined
(item: ColumnFormatDto) => {
const fieldName = item.fieldName!
const column = item
if (
mode === 'new' &&
fieldName.toLowerCase() === gridDto?.gridOptions.keyFieldName?.toLowerCase()
@ -813,7 +823,7 @@ const CardView = (props: CardViewProps) => {
try {
editorOptions = item.editorOptions ? JSON.parse(item.editorOptions) : {}
} catch (error) {
console.error('EditorOptions parse error:', item.dataField, error)
console.error('EditorOptions parse error:', item.fieldName, error)
}
if (column.sourceDbType === DbTypeEnum.Date) {
@ -862,8 +872,8 @@ const CardView = (props: CardViewProps) => {
name: fieldName,
colSpan: item.colSpan,
label: {
text: item.dataField.includes(':')
? item.dataField.split(':')[1]
text: item.fieldName!.includes(':')
? item.fieldName!.split(':')[1]
: translate('::' + (column.captionName || column.fieldName)),
},
editorType:
@ -888,7 +898,7 @@ const CardView = (props: CardViewProps) => {
const editingFormItems = useMemo(() => {
if (!gridDto || mode === 'view') return []
const groups = [...(gridDto.gridOptions.editingFormDto ?? [])].sort(
const groups = [...(getEditingFormGroups(gridDto) ?? [])].sort(
(left, right) => left.order - right.order,
)
const tabbedGroups = groups.filter((group) => group.itemType === 'tabbed')
@ -896,7 +906,7 @@ const CardView = (props: CardViewProps) => {
groups.forEach((group) => {
const items = [...(group.items ?? [])]
.sort((left, right) => left.order - right.order)
.sort((left, right) => (left.editOrderNo ?? 0) - (right.editOrderNo ?? 0))
.map(mapEditingFormItem)
.filter(Boolean)
@ -918,7 +928,7 @@ const CardView = (props: CardViewProps) => {
title: tab.caption,
colCount: useMobileEditPopup ? 1 : tab.colCount || 1,
items: [...(tab.items ?? [])]
.sort((left, right) => left.order - right.order)
.sort((left, right) => (left.editOrderNo ?? 0) - (right.editOrderNo ?? 0))
.map(mapEditingFormItem)
.filter(Boolean),
}))
@ -1237,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

@ -3,8 +3,8 @@ import '@/utils/registerDevExtremeEditors'
import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import {
ColumnFormatDto,
DbTypeEnum,
EditingFormItemDto,
FieldCustomValueTypeEnum,
GridDto,
PlatformEditorTypes,
@ -53,6 +53,8 @@ import {
autoNumber,
controlStyleCondition,
extractSearchParamsFields,
getEditingFormFields,
getEditingFormGroups,
GridExtraFilterState,
safeJsonParse,
setFormEditingExtraItemValues,
@ -218,13 +220,6 @@ const getActiveEditingForm = (component: any) => {
return editForm?.getEditor || editForm?.itemOption ? editForm : undefined
}
const flattenEditingFormItems = (items: any[] = []): EditingFormItemDto[] =>
items.flatMap((item) => [
...(item?.dataField ? [item] : []),
...flattenEditingFormItems(item?.items || []),
...(item?.tabs || []).flatMap((tab: any) => flattenEditingFormItems(tab?.items || [])),
])
const getValueByField = (data: Record<string, any> = {}, field?: string) => {
if (!field) return undefined
if (Object.prototype.hasOwnProperty.call(data, field)) return data[field]
@ -609,7 +604,7 @@ const Grid = (props: GridProps) => {
.map((f) => f.fieldName)
const filteredData: any = {}
for (const key of allowedFields) {
if (e.data.hasOwnProperty(key) && key) {
if (key && Object.prototype.hasOwnProperty.call(e.data, key)) {
filteredData[key] = e.data[key]
}
}
@ -725,11 +720,10 @@ const Grid = (props: GridProps) => {
const onEditorPreparing = useCallback(
(editor: DataGridTypes.EditorPreparingEvent<any, any>) => {
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.find((i) => i.dataField === editor.dataField)
const fieldName = editor.dataField.split(':')[0]
const columnFormat = gridDto.columnFormats.find((column) => column.fieldName === fieldName)
const columnFormat = gridDto.columnFormats.find(
(column) => column.fieldName === editor.dataField,
)
const formItem = columnFormat?.editOrderNo != null ? columnFormat : undefined
const isNewRow = Boolean((editor as any).row?.isNewRow) || mode === 'new'
const hasReadOnlyEditorOption = editor.editorOptions?.readOnly === true
@ -786,9 +780,9 @@ const Grid = (props: GridProps) => {
(pf: string) => !rowData[pf],
)
try {
const childFormItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.find((i) => i.dataField === childFieldName)
const childFormItem = gridDto.columnFormats.find(
(column) => column.fieldName === childFieldName,
)
const childEditorOptions = childFormItem?.editorOptions
? JSON.parse(childFormItem.editorOptions)
: {}
@ -858,7 +852,7 @@ const Grid = (props: GridProps) => {
setFormData,
})
} catch (err) {
console.error('Script exec error', formItem.dataField, err)
console.error('Script exec error', formItem.fieldName, err)
}
}
}
@ -1136,7 +1130,7 @@ const Grid = (props: GridProps) => {
// Form items mapper'ı memoize et - popup açılışını hızlandırır
const mapFormItem = useCallback(
(i: EditingFormItemDto) => {
(i: ColumnFormatDto) => {
let editorOptions: EditorOptionsWithButtons = {}
let parsedEditorOptions: EditorOptionsWithButtons = {}
const forcedEditorOptions: EditorOptionsWithButtons = {}
@ -1148,10 +1142,10 @@ const Grid = (props: GridProps) => {
const parsed = JSON.parse(rawFilter)
const filters = extractSearchParamsFields(parsed)
const hasFilter = filters.some(([field]) => field === i.dataField)
const hasFilter = filters.some(([field]) => field === i.fieldName)
if (hasFilter) {
const existsInExtra = extraFilters.some((f) => f.fieldName === i.dataField && !!f.value)
const existsInExtra = extraFilters.some((f) => f.fieldName === i.fieldName && !!f.value)
if (!existsInExtra) {
forcedEditorOptions.readOnly = true
@ -1159,11 +1153,11 @@ const Grid = (props: GridProps) => {
}
}
} catch (err) {
console.error('EditorOptions parse error:', i.dataField, err)
console.error('EditorOptions parse error:', i.fieldName, err)
}
const fieldName = i.dataField.split(':')[0]
const listFormField = gridDto?.columnFormats.find((x: any) => x.fieldName === fieldName)
const fieldName = i.fieldName!
const listFormField = i
const defaultEditorOptions: EditorOptionsWithButtons = {}
@ -1221,8 +1215,8 @@ const Grid = (props: GridProps) => {
canExport: listFormField?.canExport ?? false,
allowEditing: listFormField?.allowEditing ?? true,
allowAdding: listFormField?.allowAdding ?? true,
dataField: i.dataField,
name: i.dataField,
dataField: i.fieldName,
name: i.fieldName,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
@ -1240,12 +1234,12 @@ const Grid = (props: GridProps) => {
item.template = 'cellEditImageViewer'
item.editorOptions = {
...item.editorOptions,
value: getValueByField(editingFormDataRef.current, i.dataField),
value: getValueByField(editingFormDataRef.current, i.fieldName),
}
}
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
if (i.fieldName?.indexOf(':') >= 0) {
item.label = { text: captionize(i.fieldName.split(':')[1]) }
}
if ((mode == 'edit' && !item.canUpdate) || (mode == 'new' && !item.canCreate)) {
@ -1614,9 +1608,7 @@ const Grid = (props: GridProps) => {
}
const runReadOnlyScripts = () => {
const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) =>
flattenEditingFormItems([group]),
)
const formItems = getEditingFormFields(gridDto)
const scriptItems = formItems.filter((formItem) =>
shouldRunEditorScriptOnContentReady(formItem.editorScript),
)
@ -1627,9 +1619,9 @@ const Grid = (props: GridProps) => {
const editorValues = formItems.reduce<Record<string, any>>(
(values, formItem) => {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorInstance = form?.getEditor?.(formItem.fieldName)
if (editorInstance?.option) {
values[formItem.dataField] = editorInstance.option('value')
values[formItem.fieldName!] = editorInstance.option('value')
}
return values
},
@ -1644,17 +1636,17 @@ const Grid = (props: GridProps) => {
scriptItems.forEach((formItem) => {
try {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorInstance = form?.getEditor?.(formItem.fieldName)
const editorValue =
editorInstance?.option?.('value') ??
getValueByField(formData, formItem.dataField)
getValueByField(formData, formItem.fieldName)
const editor = {
dataField: formItem.dataField,
dataField: formItem.fieldName,
component: grid,
}
const e = {
component: form,
dataField: formItem.dataField,
dataField: formItem.fieldName,
scriptEvent: 'contentReady',
value: editorValue,
}
@ -1669,7 +1661,7 @@ const Grid = (props: GridProps) => {
} catch (err) {
console.error(
'Script exec error on contentReady',
formItem.dataField,
formItem.fieldName!,
err,
)
}
@ -1685,11 +1677,10 @@ const Grid = (props: GridProps) => {
...(e.component?.option?.('formData') || {}),
}
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
const formItem = getEditingFormFields(gridDto)
.find(
(i) =>
String(i.dataField || '').toLowerCase() ===
String(i.fieldName || '').toLowerCase() ===
String(e.dataField || '').toLowerCase(),
)
if (formItem?.editorScript) {
@ -1732,9 +1723,9 @@ const Grid = (props: GridProps) => {
}
},
items:
gridDto.gridOptions.editingFormDto?.length > 0
getEditingFormGroups(gridDto)?.length > 0
? (() => {
const sortedFormDto = gridDto.gridOptions.editingFormDto
const sortedFormDto = getEditingFormGroups(gridDto)
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
@ -1756,7 +1747,9 @@ const Grid = (props: GridProps) => {
colSpan: effectiveColSpan,
caption: e.caption, // Group'larda caption olmalı
items: e.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
?.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.map(mapFormItem)
.filter((a: any) => {
if (mode === 'view') {
@ -1785,7 +1778,9 @@ const Grid = (props: GridProps) => {
title: tabbedItem.caption, // Her tab'ın title'ı
colCount: effectiveColCount,
items: tabbedItem.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
?.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.map(mapFormItem)
.filter((a: any) => {
if (mode === 'view') {

View file

@ -10,7 +10,7 @@ interface IGridColumnData extends DataGridTypes.Column {
colData?: ColumnFormatDto
extras?: {
multiValue: boolean
editorOptions?: string
editorOptions?: Record<string, any>
tagBoxOptions?: TagBoxOptionsDto
gridBoxOptions?: GridBoxOptionsDto
imageUploadOptions?: ImageUploadOptionsDto

View file

@ -1,4 +1,4 @@
import { useState } from 'react'
import React, { useState } from 'react'
import { GridExtraFilterState } from './Utils'
import { useLocalization } from '@/utils/hooks/useLocalization'

View file

@ -179,6 +179,7 @@ const List: React.FC = () => {
variant={viewMode === 'todo' ? 'solid' : 'default'}
onClick={() => setLayout('todo')}
onMouseEnter={() => preload.todo()}
title={translate('::ListForms.ListFormEdit.TabTodo')}
/>
)}
{gridDto?.gridOptions?.layoutDto.scheduler &&
@ -190,6 +191,7 @@ const List: React.FC = () => {
variant={viewMode === 'scheduler' ? 'solid' : 'default'}
onClick={() => setLayout('scheduler')}
onMouseEnter={() => preload.scheduler()}
title={translate('::ListForms.ListFormEdit.TabScheduler')}
></Button>
)}
@ -202,6 +204,7 @@ const List: React.FC = () => {
variant={viewMode === 'gantt' ? 'solid' : 'default'}
onClick={() => setLayout('gantt')}
onMouseEnter={() => preload.gantt()}
title={translate('::ListForms.ListFormEdit.TabGantt')}
></Button>
)}
@ -213,6 +216,7 @@ const List: React.FC = () => {
variant={viewMode === 'tree' ? 'solid' : 'default'}
onClick={() => setLayout('tree')}
onMouseEnter={() => preload.tree()}
title={translate('::ListForms.ListFormEdit.TabTree')}
></Button>
)}
@ -222,6 +226,7 @@ const List: React.FC = () => {
variant={viewMode === 'grid' ? 'solid' : 'default'}
onClick={() => setLayout('grid')}
onMouseEnter={() => preload.grid()}
title={translate('::ListForms.ListFormEdit.TabGrid')}
></Button>
{gridDto.gridOptions.layoutDto.card && (
@ -231,6 +236,7 @@ const List: React.FC = () => {
variant={viewMode === 'card' ? 'solid' : 'default'}
onClick={() => setLayout('card')}
onMouseEnter={() => preload.card()}
title={translate('::ListForms.ListFormEdit.TabCard')}
></Button>
)}
@ -241,6 +247,7 @@ const List: React.FC = () => {
variant={viewMode === 'pivot' ? 'solid' : 'default'}
onClick={() => setLayout('pivot')}
onMouseEnter={() => preload.pivot()}
title={translate('::ListForms.ListFormEdit.TabPivot')}
></Button>
)}
@ -251,6 +258,7 @@ const List: React.FC = () => {
variant={viewMode === 'chart' ? 'solid' : 'default'}
onClick={() => setLayout('chart')}
onMouseEnter={() => preload.chart()}
title={translate('::ListForms.ListFormEdit.TabChart')}
></Button>
)}
</div>

View file

@ -2,11 +2,11 @@ import Container from '@/components/shared/Container'
import '@/utils/registerDevExtremeEditors'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import {
ColumnFormatDto,
DbTypeEnum,
GridDto,
PlatformEditorTypes,
UiLookupDataSourceTypeEnum,
EditingFormItemDto,
} from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Scheduler, {
@ -21,7 +21,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import { getList } from '@/services/form.service'
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { addCss, addJs, autoNumber } from './Utils'
import {
addCss,
addJs,
autoNumber,
getEditingFormGroups,
} from './Utils'
import { layoutTypes } from '../admin/listForm/edit/types'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -289,11 +294,11 @@ const SchedulerView = (props: SchedulerViewProps) => {
},
])
// EditingFormDto'dan form items oluştur
// Grup düzenini EditingFormDto'dan, alanları ColumnFormatDto'dan oluştur.
const result: any[] = []
if (gridDto.gridOptions.editingFormDto?.length > 0) {
const sortedFormDto = gridDto.gridOptions.editingFormDto
if (getEditingFormGroups(gridDto)?.length > 0) {
const sortedFormDto = getEditingFormGroups(gridDto)
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
@ -301,7 +306,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
const tabbedItems = sortedFormDto.filter((e: any) => e.itemType === 'tabbed')
// Ortak item mapper fonksiyonu - hem group hem tab için kullanılır
const mapFormItem = (i: EditingFormItemDto) => {
const mapFormItem = (i: ColumnFormatDto) => {
let editorOptions: any = {}
try {
if (i.editorOptions) {
@ -311,8 +316,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
console.log(err)
}
const fieldName = i.dataField.split(':')[0]
const listFormField = gridDto.columnFormats.find((x: any) => x.fieldName === fieldName)
const fieldName = i.fieldName!
const listFormField = i
if (listFormField?.sourceDbType === DbTypeEnum.Date) {
editorOptions = {
@ -351,7 +356,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
}
// EditorType belirleme
let editorType: any = i.editorType2 || i.editorType
let editorType: any = i.editorType2
if (i.editorType2 === PlatformEditorTypes.dxGridBox) {
editorType = 'dxDropDownBox'
} else if (i.editorType2) {
@ -409,8 +414,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
canExport: listFormField?.canExport ?? false,
allowEditing: listFormField?.allowEditing ?? true,
allowAdding: listFormField?.allowAdding ?? true,
dataField: i.dataField,
name: i.dataField,
dataField: i.fieldName,
name: i.fieldName,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
@ -424,8 +429,8 @@ const SchedulerView = (props: SchedulerViewProps) => {
editorScript: i.editorScript,
}
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
if (i.fieldName?.indexOf(':') >= 0) {
item.label = { text: captionize(i.fieldName.split(':')[1]) }
}
if (
@ -445,7 +450,9 @@ const SchedulerView = (props: SchedulerViewProps) => {
// Items'ları da order'a göre sırala
const sortedItems = (e.items || [])
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
const groupItems = sortedItems.map(mapFormItem)
@ -467,7 +474,9 @@ const SchedulerView = (props: SchedulerViewProps) => {
title: tabbedItem.caption, // Her tab'ın title'ı
colCount: effectiveColCount,
items: tabbedItem.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
?.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.map(mapFormItem)
.filter((a: any) => {
if (currentMode === 'new') {
@ -490,11 +499,11 @@ const SchedulerView = (props: SchedulerViewProps) => {
// Form'u tamamen yeniden baştan yapılandır
e.form.option(
'colCount',
hasTabbedItems ? 1 : gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2,
hasTabbedItems ? 1 : getEditingFormGroups(gridDto)?.[0]?.colCount || 2,
)
e.form.option(
'colCount',
hasTabbedItems ? 1 : gridDto.gridOptions.editingFormDto?.[0]?.colCount || 2,
hasTabbedItems ? 1 : getEditingFormGroups(gridDto)?.[0]?.colCount || 2,
)
e.form.option('showValidationSummary', false)
e.form.option('items', result)

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

@ -4,8 +4,8 @@ import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { executeEditorScript } from '@/utils/editorScriptRuntime'
import {
ColumnFormatDto,
DbTypeEnum,
EditingFormItemDto,
GridDto,
PlatformEditorTypes,
SubFormTabTypeEnum,
@ -44,6 +44,8 @@ import {
addJs,
autoNumber,
controlStyleCondition,
getEditingFormFields,
getEditingFormGroups,
GridExtraFilterState,
safeJsonParse,
setFormEditingExtraItemValues,
@ -206,13 +208,6 @@ const getActiveEditingForm = (component: any) => {
return editForm?.getEditor || editForm?.itemOption ? editForm : undefined
}
const flattenEditingFormItems = (items: any[] = []): EditingFormItemDto[] =>
items.flatMap((item) => [
...(item?.dataField ? [item] : []),
...flattenEditingFormItems(item?.items || []),
...(item?.tabs || []).flatMap((tab: any) => flattenEditingFormItems(tab?.items || [])),
])
const getValueByField = (data: Record<string, any> = {}, field?: string) => {
if (!field) return undefined
if (Object.prototype.hasOwnProperty.call(data, field)) return data[field]
@ -664,11 +659,10 @@ const Tree = (props: TreeProps) => {
function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) {
if (editor.parentType === 'dataRow' && editor.dataField && gridDto) {
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.find((i) => i.dataField === editor.dataField)
const fieldName = editor.dataField.split(':')[0]
const columnFormat = gridDto.columnFormats.find((column) => column.fieldName === fieldName)
const columnFormat = gridDto.columnFormats.find(
(column) => column.fieldName === editor.dataField,
)
const formItem = columnFormat?.editOrderNo != null ? columnFormat : undefined
const isNewRow = Boolean((editor as any).row?.isNewRow) || mode === 'new'
const hasReadOnlyEditorOption = editor.editorOptions?.readOnly === true
@ -689,7 +683,7 @@ const Tree = (props: TreeProps) => {
}
// Cascade disabled mantığı
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
const colFormat = columnFormat
if (colFormat?.lookupDto?.cascadeParentFields) {
const parentFields = colFormat.lookupDto.cascadeParentFields
.split(',')
@ -733,11 +727,8 @@ const Tree = (props: TreeProps) => {
try {
const editorInstance = formInstance.getEditor(col.fieldName!)
if (editorInstance) {
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.find((i) => i.dataField === col.fieldName)
const editorOptions = formItem?.editorOptions
? JSON.parse(formItem.editorOptions)
const editorOptions = col.editorOptions
? JSON.parse(col.editorOptions)
: {}
editorInstance.option(
@ -804,7 +795,7 @@ const Tree = (props: TreeProps) => {
setFormData,
})
} catch (err) {
console.error('Script exec error', formItem.dataField, err)
console.error('Script exec error', formItem.fieldName, err)
}
}
}
@ -1269,9 +1260,7 @@ const Tree = (props: TreeProps) => {
}
const runReadOnlyScripts = () => {
const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) =>
flattenEditingFormItems([group]),
)
const formItems = getEditingFormFields(gridDto)
const scriptItems = formItems.filter((formItem) =>
shouldRunEditorScriptOnContentReady(formItem.editorScript),
)
@ -1282,9 +1271,9 @@ const Tree = (props: TreeProps) => {
const editorValues = formItems.reduce<Record<string, any>>(
(values, formItem) => {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorInstance = form?.getEditor?.(formItem.fieldName)
if (editorInstance?.option) {
values[formItem.dataField] = editorInstance.option('value')
values[formItem.fieldName!] = editorInstance.option('value')
}
return values
},
@ -1299,17 +1288,17 @@ const Tree = (props: TreeProps) => {
scriptItems.forEach((formItem) => {
try {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorInstance = form?.getEditor?.(formItem.fieldName)
const editorValue =
editorInstance?.option?.('value') ??
getValueByField(formData, formItem.dataField)
getValueByField(formData, formItem.fieldName)
const editor = {
dataField: formItem.dataField,
dataField: formItem.fieldName,
component: grid,
}
const e = {
component: form,
dataField: formItem.dataField,
dataField: formItem.fieldName,
scriptEvent: 'contentReady',
value: editorValue,
}
@ -1324,7 +1313,7 @@ const Tree = (props: TreeProps) => {
} catch (err) {
console.error(
'Script exec error on contentReady',
formItem.dataField,
formItem.fieldName!,
err,
)
}
@ -1340,11 +1329,10 @@ const Tree = (props: TreeProps) => {
...(e.component?.option?.('formData') || {}),
}
const formItem = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
const formItem = getEditingFormFields(gridDto)
.find(
(i) =>
String(i.dataField || '').toLowerCase() ===
String(i.fieldName || '').toLowerCase() ===
String(e.dataField || '').toLowerCase(),
)
if (formItem?.editorScript) {
@ -1387,9 +1375,9 @@ const Tree = (props: TreeProps) => {
}
},
items:
gridDto.gridOptions.editingFormDto?.length > 0
getEditingFormGroups(gridDto)?.length > 0
? (() => {
const sortedFormDto = gridDto.gridOptions.editingFormDto
const sortedFormDto = getEditingFormGroups(gridDto)
.slice()
.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
@ -1400,7 +1388,7 @@ const Tree = (props: TreeProps) => {
const result: any[] = []
// Helper function: item mapper
const mapFormItem = (i: EditingFormItemDto) => {
const mapFormItem = (i: ColumnFormatDto) => {
let editorOptions: EditorOptionsWithButtons = {}
let parsedEditorOptions: EditorOptionsWithButtons = {}
const forcedEditorOptions: EditorOptionsWithButtons = {}
@ -1415,12 +1403,12 @@ const Tree = (props: TreeProps) => {
const filters = extractSearchParamsFields(parsed)
const hasFilter = filters.some(
([field, op, val]) => field === i.dataField,
([field, op, val]) => field === i.fieldName,
)
if (hasFilter) {
const existsInExtra = extraFilters.some(
(f) => f.fieldName === i.dataField && !!f.value,
(f) => f.fieldName === i.fieldName && !!f.value,
)
if (!existsInExtra) {
@ -1429,13 +1417,11 @@ const Tree = (props: TreeProps) => {
}
}
} catch (err){
console.error('EditorOptions parse error:', i.dataField, err)
console.error('EditorOptions parse error:', i.fieldName, err)
}
const fieldName = i.dataField.split(':')[0]
const listFormField = gridDto.columnFormats.find(
(x: any) => x.fieldName === fieldName,
)
const fieldName = i.fieldName!
const listFormField = i
const defaultEditorOptions: EditorOptionsWithButtons = {}
@ -1484,8 +1470,8 @@ const Tree = (props: TreeProps) => {
canExport: listFormField?.canExport ?? false,
allowEditing: listFormField?.allowEditing ?? true,
allowAdding: listFormField?.allowAdding ?? true,
dataField: i.dataField,
name: i.dataField,
dataField: i.fieldName,
name: i.fieldName,
editorType2: i.editorType2,
editorType:
i.editorType2 == PlatformEditorTypes.dxGridBox
@ -1503,12 +1489,12 @@ const Tree = (props: TreeProps) => {
item.template = 'cellEditImageViewer'
item.editorOptions = {
...item.editorOptions,
value: getValueByField(editingFormDataRef.current, i.dataField),
value: getValueByField(editingFormDataRef.current, i.fieldName),
}
}
if (i.dataField.indexOf(':') >= 0) {
item.label = { text: captionize(i.dataField.split(':')[1]) }
if (i.fieldName?.indexOf(':') >= 0) {
item.label = { text: captionize(i.fieldName.split(':')[1]) }
}
if (
@ -1532,7 +1518,9 @@ const Tree = (props: TreeProps) => {
colSpan: e.colSpan,
caption: e.caption, // Group'larda caption olmalı
items: e.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
?.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.map(mapFormItem)
.filter((a: any) => {
if (mode === 'view') {
@ -1561,7 +1549,9 @@ const Tree = (props: TreeProps) => {
title: tabbedItem.caption, // Her tab'ın title'ı
colCount: effectiveColCount,
items: tabbedItem.items
?.sort((a: any, b: any) => (a.order >= b.order ? 1 : -1))
?.sort((a: any, b: any) =>
(a.editOrderNo ?? 0) >= (b.editOrderNo ?? 0) ? 1 : -1,
)
.map(mapFormItem)
.filter((a: any) => {
if (mode === 'view') {

View file

@ -2,6 +2,24 @@ import type { DataType } from 'devextreme/common'
import type { PivotGridDataType } from 'devextreme/ui/pivot_grid/data_source'
import { MULTIVALUE_DELIMITER } from '../../constants/app.constant'
import { ChartSeriesDto } from '@/proxy/admin/charts/models'
import type { GridDto } from '@/proxy/form/models'
/** EditingFormDto gruplarını ColumnFormatDto alanlarıyla DevExtreme için birleştirir. */
export const getEditingFormFields = (gridDto: GridDto) =>
gridDto.columnFormats
.filter((field) => field.editOrderNo != null && field.fieldName)
.sort((left, right) => left.editOrderNo! - right.editOrderNo!)
export const getEditingFormGroups = (gridDto: GridDto) => {
const fields = getEditingFormFields(gridDto)
return [...(gridDto.gridOptions.editingFormDto ?? [])]
.sort((left, right) => left.order - right.order)
.map((group) => ({
...group,
items: fields.filter((field) => (field.editGroupOrderNo ?? 1) === group.order),
}))
}
export interface GridExtraFilterState {
fieldName: string
@ -131,19 +149,15 @@ export function pivotFieldConvertDataType(fieldDbType?: DataType): PivotGridData
export function setGridPanelColor(color: any) {
//DataGrid icin
const pnlGrid = document.getElementsByClassName(
'dx-datagrid-header-panel',
) as HTMLCollectionOf<HTMLElement>
if (pnlGrid?.length) {
pnlGrid[0].style.borderBottom = `3px solid ${color}`
const pnlGrid = document.querySelector<HTMLElement>('.dx-datagrid-header-panel')
if (pnlGrid) {
pnlGrid.style.borderBottom = `3px solid ${color}`
}
//TreeList icin
const pnlTree = document.getElementsByClassName(
'dx-treelist-header-panel',
) as HTMLCollectionOf<HTMLElement>
if (pnlTree?.length) {
pnlTree[0].style.borderBottom = `3px solid ${color}`
const pnlTree = document.querySelector<HTMLElement>('.dx-treelist-header-panel')
if (pnlTree) {
pnlTree.style.borderBottom = `3px solid ${color}`
}
}

View file

@ -11,13 +11,12 @@ import { dynamicFetch } from '@/services/form.service'
import { GridColumnData } from './GridColumnData'
import {
ColumnFormatDto,
EditingFormItemDto,
GridDto,
PlatformEditorTypes,
UiCommandButtonPositionTypeEnum,
UiLookupDataSourceTypeEnum,
} from '@/proxy/form/models'
import { addCss } from './Utils'
import { addCss, getEditingFormFields } from './Utils'
const NO_IMAGE = '/img/others/no-image.png'
@ -923,15 +922,18 @@ 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
const allItems = gridDto.gridOptions.editingFormDto.flatMap((group) => group.items)
const formItem = allItems.find((a) => a?.dataField === colData.fieldName)
const formItem = colData.editOrderNo != null ? colData : undefined
if (formItem?.editorType2 === PlatformEditorTypes.dxTagBox) {
column.extras = {
multiValue: true,
editorOptions: formItem.editorOptions,
editorOptions,
tagBoxOptions: formItem.tagBoxOptions,
}
column.editCellTemplate = 'cellEditTagBox'
@ -940,7 +942,7 @@ const useListFormColumns = ({
} else if (formItem?.editorType2 === PlatformEditorTypes.dxGridBox) {
column.extras = {
multiValue: false,
editorOptions: formItem.editorOptions,
editorOptions,
gridBoxOptions: formItem.gridBoxOptions,
}
column.editCellTemplate = 'cellEditGridBox'
@ -976,8 +978,7 @@ const useListFormColumns = ({
// #region image upload editor
if (!colData.lookupDto?.dataSourceType) {
const allFormItems = gridDto.gridOptions.editingFormDto.flatMap((group) => group.items)
const imageFormItem = allFormItems.find((a) => a?.dataField === colData.fieldName)
const imageFormItem = colData.editOrderNo != null ? colData : undefined
const isImageUploadEditor =
imageFormItem?.editorType2 === PlatformEditorTypes.dxImageUpload
const isImageViewerEditor =
@ -1006,7 +1007,7 @@ const useListFormColumns = ({
}
column.extras = {
multiValue: imageUploadOptions?.multiple ?? false,
editorOptions: imageFormItem?.editorOptions ?? colData.editorOptions,
editorOptions,
imageUploadOptions: imageUploadOptions,
}
if (isImageUploadEditor) {
@ -1091,25 +1092,19 @@ const useListFormColumns = ({
}
}
// FormEditingExtraItem
// Devexpress Gridde kaydete basılınca
// formda gözükecek olan EditingFormDto.Items elemanları gelmiyor
// Sadece grid'de tanımlanmış columnları getiriyor.
// Bu elemanları da getirmesi için aşağıdaki şekilde,
// columns'da olmayan alanları da gizli olarak ekliyoruz
// Formda kullanılan fakat listede görünmeyen ColumnFormatDto alanlarını
// DevExtreme'in düzenleme modeline gizli kolon olarak ekliyoruz.
if (columns?.length) {
gridDto.gridOptions.editingFormDto.forEach((group) => {
group.items?.forEach((item: EditingFormItemDto) => {
if (!columns.some((a) => a.dataField === item.dataField)) {
columns.push({
dataField: item.dataField,
visible: false,
showInColumnChooser: false,
allowSearch: false
})
insertedColumns.push(item.dataField)
}
})
getEditingFormFields(gridDto).forEach((item: ColumnFormatDto) => {
if (!columns.some((a) => a.dataField === item.fieldName)) {
columns.push({
dataField: item.fieldName,
visible: false,
showInColumnChooser: false,
allowSearch: false,
})
insertedColumns.push(item.fieldName)
}
})
}