Compare commits

...

14 commits
1.1.07 ... 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
Sedat ÖZTÜRK
24e92fcb82 Todo komponenti 2026-07-16 17:01:18 +03:00
Sedat ÖZTÜRK
412ad236cc CardView komponenti 2026-07-16 11:19:17 +03:00
Sedat ÖZTÜRK
d38e4f851a HostData düzenelemsi 2026-07-16 08:49:30 +03:00
Sedat Öztürk
6174253a40 Lisans bilgileri takip ekranı 2026-07-14 23:25:04 +03:00
Sedat ÖZTÜRK
248af7ca1a Licence 2026-07-14 17:54:50 +03:00
Sedat ÖZTÜRK
535ed6835f Check for Updates 2026-07-14 17:16:08 +03:00
Sedat ÖZTÜRK
4805e8c4d9 Check New Version eklendi 2026-07-14 16:43:05 +03:00
Sedat ÖZTÜRK
28ec641275 Update new version checking and Report Categories 2026-07-14 10:37:13 +03:00
Sedat Öztürk
1d05d24e41 Form içerisindeki Workflow custom butonlar 2026-07-13 20:29:53 +03:00
Sedat ÖZTÜRK
b53a4243ab AiBot Tenantlı ve MenuManager düzenlendi 2026-07-13 16:51:13 +03:00
132 changed files with 11562 additions and 3977 deletions

View file

@ -10,6 +10,11 @@ namespace Sozsoft.Platform.ListForms;
public class ColumnFormatDto : AuditedEntityDto<Guid> public class ColumnFormatDto : AuditedEntityDto<Guid>
{ {
private static readonly JsonSerializerOptions EditorOptionsSerializerOptions = new()
{
PropertyNameCaseInsensitive = true
};
public ColumnFormatDto() { } public ColumnFormatDto() { }
public string FieldName { get; set; } public string FieldName { get; set; }
@ -239,7 +244,42 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
} }
public string Alignment { get; set; } public string Alignment { get; set; }
public string Format { get; set; } public string Format { get; set; }
public string EditorOptions { 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 string Caption { get; set; }
public int ColCount { get; set; } = 2; public int ColCount { get; set; } = 2;
public int ColSpan { 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

@ -190,6 +190,19 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
set { SchedulerOptionJson = JsonSerializer.Serialize(value); } set { SchedulerOptionJson = JsonSerializer.Serialize(value); }
} }
[JsonIgnore]
public string TodoOptionJson { get; set; }
public TodoOptionDto TodoOptionDto
{
get
{
if (!string.IsNullOrEmpty(TodoOptionJson))
return JsonSerializer.Deserialize<TodoOptionDto>(TodoOptionJson);
return new TodoOptionDto();
}
set { TodoOptionJson = JsonSerializer.Serialize(value); }
}
[JsonIgnore] [JsonIgnore]
public string PagerOptionJson { get; set; } public string PagerOptionJson { get; set; }
public GridPagerOptionDto PagerOptionDto public GridPagerOptionDto PagerOptionDto

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

@ -3,11 +3,13 @@
public class LayoutDto public class LayoutDto
{ {
public bool Grid { get; set; } = true; public bool Grid { get; set; } = true;
public bool Card { get; set; } = true;
public bool Pivot { get; set; } = true; public bool Pivot { get; set; } = true;
public bool Chart { get; set; } = true; public bool Chart { get; set; } = true;
public bool Tree { get; set; } = true; public bool Tree { get; set; } = true;
public bool Gantt { get; set; } = true; public bool Gantt { get; set; } = true;
public bool Scheduler { get; set; } = true; public bool Scheduler { get; set; } = true;
public bool Todo { get; set; } = false;
public string DefaultLayout { get; set; } = "grid"; public string DefaultLayout { get; set; } = "grid";
} }

View file

@ -0,0 +1,20 @@
namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Dinamik bir listeyi Todo/Kanban panosu olarak gostermek icin alan eslemeleri.
/// TitleExpr ve StatusExpr zorunlu, diger alanlar opsiyoneldir.
/// </summary>
public class TodoOptionDto
{
public string TitleExpr { get; set; }
public string StatusExpr { get; set; }
public string DescriptionExpr { get; set; }
public string DueDateExpr { get; set; }
public string TagExpr { get; set; }
public string AssigneeExpr { get; set; }
public string PriorityExpr { get; set; }
public string CompletedExpr { get; set; }
public string OrderExpr { get; set; }
public string StatusOrder { get; set; }
public bool AllowDragging { get; set; } = true;
}

View file

@ -5,10 +5,10 @@ namespace Sozsoft.Platform.ListForms;
public class WorkflowDto public class WorkflowDto
{ {
public string ApprovalUserFieldName { get; set; } public string ApprovalUserFieldName { get; set; }
public bool IsFilterUserName { get; set; }
public string ApprovalDateFieldName { get; set; } public string ApprovalDateFieldName { get; set; }
public string ApprovalStatusFieldName { get; set; } public string ApprovalStatusFieldName { get; set; }
public string ApprovalDescriptionFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; }
public bool ApprovalIsFilterUserName { get; set; }
public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = []; public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
} }

View file

@ -49,6 +49,7 @@ public class ListFormEditTabs
public const string TreeForm = "tree"; public const string TreeForm = "tree";
public const string GanttForm = "gantt"; public const string GanttForm = "gantt";
public const string SchedulerForm = "scheduler"; public const string SchedulerForm = "scheduler";
public const string TodoForm = "todo";
public const string PagerForm = "pager"; public const string PagerForm = "pager";
public const string StateForm = "state"; public const string StateForm = "state";
public const string SubFormJsonRow = "subForm"; public const string SubFormJsonRow = "subForm";

View file

@ -22,11 +22,13 @@ public class ListFormWizardDto
public string DefaultLayout { get; set; } public string DefaultLayout { get; set; }
public bool Grid { get; set; } public bool Grid { get; set; }
public bool Card { get; set; }
public bool Pivot { get; set; } public bool Pivot { get; set; }
public bool Tree { get; set; } public bool Tree { get; set; }
public bool Chart { get; set; } public bool Chart { get; set; }
public bool Gantt { get; set; } public bool Gantt { get; set; }
public bool Scheduler { get; set; } public bool Scheduler { get; set; }
public bool Todo { get; set; }
public string LanguageTextMenuEn { get; set; } public string LanguageTextMenuEn { get; set; }
public string LanguageTextMenuTr { get; set; } public string LanguageTextMenuTr { get; set; }
@ -39,6 +41,7 @@ public class ListFormWizardDto
public string PermissionGroupName { get; set; } public string PermissionGroupName { get; set; }
public string MenuParentCode { get; set; } public string MenuParentCode { get; set; }
public string MenuParentShortName { get; set; }
public string MenuParentIcon { get; set; } public string MenuParentIcon { get; set; }
public string MenuIcon { get; set; } public string MenuIcon { get; set; }
public string DataSourceCode { get; set; } public string DataSourceCode { get; set; }
@ -68,9 +71,28 @@ public class ListFormWizardDto
public string SchedulerStartDateExpr { get; set; } public string SchedulerStartDateExpr { get; set; }
public string SchedulerEndDateExpr { get; set; } public string SchedulerEndDateExpr { get; set; }
// Todo options (required when DefaultLayout = "todo")
public string TodoTitleExpr { get; set; }
public string TodoStatusExpr { get; set; }
public string TodoDescriptionExpr { get; set; }
public string TodoDueDateExpr { get; set; }
public string TodoTagExpr { get; set; }
public string TodoAssigneeExpr { get; set; }
public string TodoPriorityExpr { get; set; }
public string TodoCompletedExpr { get; set; }
public string TodoOrderExpr { get; set; }
public string TodoStatusOrder { get; set; }
public bool TodoAllowDragging { get; set; } = true;
// Workflow options (required when DefaultLayout = "workflow")
public string ApprovalUserFieldName { get; set; }
public string ApprovalDateFieldName { get; set; }
public string ApprovalStatusFieldName { get; set; }
public string ApprovalDescriptionFieldName { get; set; }
public bool ApprovalIsFilterUserName { get; set; }
public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
public List<WizardColumnGroupInputDto> Groups { get; set; } = new(); public List<WizardColumnGroupInputDto> Groups { get; set; } = new();
public List<SubFormDto> SubForms { get; set; } = new(); public List<SubFormDto> SubForms { get; set; } = new();
public List<WidgetEditDto> Widgets { get; set; } = new(); public List<WidgetEditDto> Widgets { get; set; } = new();
public WorkflowDto Workflow { get; set; } = new();
public List<ListFormWorkflowCriteriaDto> WorkflowCriteria { get; set; } = new();
} }

View file

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

View file

@ -185,6 +185,7 @@ public static class LookupQueryValues
$"LEFT OUTER JOIN \"{FullNameTable(TableNameEnum.LanguageText)}\" " + $"LEFT OUTER JOIN \"{FullNameTable(TableNameEnum.LanguageText)}\" " +
$"ON \"{FullNameTable(TableNameEnum.Product)}\".\"Name\" = \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Key\" " + $"ON \"{FullNameTable(TableNameEnum.Product)}\".\"Name\" = \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Key\" " +
$"WHERE \"{FullNameTable(TableNameEnum.Product)}\".\"IsDeleted\" = 'false' " + $"WHERE \"{FullNameTable(TableNameEnum.Product)}\".\"IsDeleted\" = 'false' " +
$"AND \"{FullNameTable(TableNameEnum.LanguageText)}\".\"CultureName\" = 'tr' " +
$"ORDER BY \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Value\";"; $"ORDER BY \"{FullNameTable(TableNameEnum.LanguageText)}\".\"Value\";";
public static string MenuCodeValues = public static string MenuCodeValues =

View file

@ -6,6 +6,7 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Collections.Generic;
namespace Sozsoft.Platform.AiBots; namespace Sozsoft.Platform.AiBots;
@ -25,16 +26,31 @@ public class AiBotAppService : CrudAppService<
{ {
await CheckGetListPolicyAsync(); await CheckGetListPolicyAsync();
var query = await CreateFilteredQueryAsync(input);
if (CurrentTenant.IsAvailable && !string.IsNullOrWhiteSpace(CurrentTenant.Name)) if (CurrentTenant.IsAvailable && !string.IsNullOrWhiteSpace(CurrentTenant.Name))
{ {
var tenantName = CurrentTenant.Name; var tenantName = CurrentTenant.Name;
query = query.Where(aiBot => List<AiBot> tenantEntities;
string.IsNullOrWhiteSpace(aiBot.Tenants) ||
aiBot.Tenants.Contains($"\"{tenantName}\"")); using (CurrentTenant.Change(null))
{
var hostQuery = await CreateFilteredQueryAsync(input);
tenantEntities = await AsyncExecuter.ToListAsync(hostQuery);
}
var filteredQuery = tenantEntities
.Where(aiBot => IsAvailableForTenant(aiBot.Tenants, tenantName))
.AsQueryable();
var tenantTotalCount = filteredQuery.LongCount();
filteredQuery = ApplySorting(filteredQuery, input);
filteredQuery = ApplyPaging(filteredQuery, input);
var tenantDtos = await MapToGetListOutputDtosAsync(filteredQuery.ToList());
return new PagedResultDto<AiBotDto>(tenantTotalCount, tenantDtos);
} }
var query = await CreateFilteredQueryAsync(input);
var totalCount = await AsyncExecuter.CountAsync(query); var totalCount = await AsyncExecuter.CountAsync(query);
query = ApplySorting(query, input); query = ApplySorting(query, input);
query = ApplyPaging(query, input); query = ApplyPaging(query, input);
@ -44,5 +60,17 @@ public class AiBotAppService : CrudAppService<
return new PagedResultDto<AiBotDto>(totalCount, dtos); return new PagedResultDto<AiBotDto>(totalCount, dtos);
} }
private static bool IsAvailableForTenant(string tenants, string tenantName)
{
if (string.IsNullOrWhiteSpace(tenants))
{
return true;
}
return tenants
.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Any(name => string.Equals(name, tenantName, StringComparison.OrdinalIgnoreCase));
}
} }

View file

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

View file

@ -166,6 +166,10 @@ public class ListFormsAppService : CrudAppService<
{ {
item.SchedulerOptionJson = JsonSerializer.Serialize(input.SchedulerOptionDto); item.SchedulerOptionJson = JsonSerializer.Serialize(input.SchedulerOptionDto);
} }
else if (input.EditType == ListFormEditTabs.TodoForm)
{
item.TodoOptionJson = JsonSerializer.Serialize(input.TodoOptionDto);
}
else if (input.EditType == ListFormEditTabs.PagerForm) else if (input.EditType == ListFormEditTabs.PagerForm)
{ {
item.PageSize = input.PageSize; item.PageSize = input.PageSize;

View file

@ -180,11 +180,18 @@ public class ListFormWizardAppService(
Code = input.MenuParentCode, Code = input.MenuParentCode,
DisplayName = input.MenuParentCode, DisplayName = input.MenuParentCode,
IsDisabled = false, IsDisabled = false,
ShortName = input.MenuParentShortName?.Trim(),
Icon = menuParentIcon, Icon = menuParentIcon,
Order = maxRootOrder + 1, Order = maxRootOrder + 1,
}, autoSave: false); }, autoSave: false);
inserted.MenuCodes.Add(input.MenuParentCode); 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 //Menu
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0; 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, Caption = g.Caption,
ColCount = g.ColCount, ColCount = g.ColCount,
ColSpan = g.ColCount, ColSpan = g.ColCount,
ItemType = "group", 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()
}) })
.Where(g => g.Items.Length > 0) .Where((_, index) => input.Groups[index].Items.Any(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName))
.ToList(); .ToList();
//ListForm - varsa sil, yeniden ekle //ListForm - varsa sil, yeniden ekle
@ -269,10 +264,18 @@ public class ListFormWizardAppService(
var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand); var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
var isDeleted = tableColumns.Contains("IsDeleted"); var isDeleted = tableColumns.Contains("IsDeleted");
var isCreated = tableColumns.Contains("CreatorId"); var isCreated = tableColumns.Contains("CreatorId");
input.Workflow ??= new WorkflowDto(); input.Criteria ??= [];
input.Workflow.Criteria = input.WorkflowCriteria; EnsureUniqueWorkflowCriteriaTitles(input.Criteria);
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria);
NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria); var workflow = new WorkflowDto
{
ApprovalUserFieldName = input.ApprovalUserFieldName,
ApprovalIsFilterUserName = input.ApprovalIsFilterUserName,
ApprovalDateFieldName = input.ApprovalDateFieldName,
ApprovalStatusFieldName = input.ApprovalStatusFieldName,
ApprovalDescriptionFieldName = input.ApprovalDescriptionFieldName,
Criteria = input.Criteria,
};
await repoListForm.InsertAsync(new ListForm await repoListForm.InsertAsync(new ListForm
{ {
@ -280,8 +283,8 @@ public class ListFormWizardAppService(
PageSize = 10, PageSize = 10,
ExportJson = WizardConsts.DefaultExportJson, ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false, IsSubForm = false,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
Name = nameLangKey, Name = nameLangKey,
@ -301,18 +304,18 @@ public class ListFormWizardAppService(
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(), HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(), SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(), GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone), SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Criteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(), ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code), PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultDeleteFieldsJsonOnlyId(input.KeyFieldDbSourceType),
InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultInsertFieldsJsonOnlyId(input.KeyFieldDbSourceType),
PagerOptionJson = WizardConsts.DefaultPagerOptionJson, PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail), 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, EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null, SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null, WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
WorkflowJson = HasWorkflow(input.Workflow, input.WorkflowCriteria) ? JsonSerializer.Serialize(input.Workflow) : null, WorkflowJson = HasWorkflow(workflow, input.Criteria) ? JsonSerializer.Serialize(workflow) : null,
TreeOptionJson = (input.Tree || input.DefaultLayout == "tree") && !string.IsNullOrEmpty(input.TreeParentIdExpr) TreeOptionJson = (input.Tree || input.DefaultLayout == "tree") && !string.IsNullOrEmpty(input.TreeParentIdExpr)
? JsonSerializer.Serialize(new TreeOptionDto ? JsonSerializer.Serialize(new TreeOptionDto
{ {
@ -341,25 +344,58 @@ public class ListFormWizardAppService(
EndDateExpr = input.SchedulerEndDateExpr, EndDateExpr = input.SchedulerEndDateExpr,
}) })
: null, : null,
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
!string.IsNullOrEmpty(input.TodoTitleExpr) &&
!string.IsNullOrEmpty(input.TodoStatusExpr)
? JsonSerializer.Serialize(new TodoOptionDto
{
TitleExpr = input.TodoTitleExpr,
StatusExpr = input.TodoStatusExpr,
DescriptionExpr = input.TodoDescriptionExpr,
DueDateExpr = input.TodoDueDateExpr,
TagExpr = input.TodoTagExpr,
AssigneeExpr = input.TodoAssigneeExpr,
PriorityExpr = input.TodoPriorityExpr,
CompletedExpr = input.TodoCompletedExpr,
OrderExpr = input.TodoOrderExpr,
StatusOrder = input.TodoStatusOrder,
AllowDragging = input.TodoAllowDragging,
})
: null,
}, autoSave: true); }, autoSave: true);
// ListFormField - each item in each group becomes a visible field record // ListFormField - each item in each group becomes a visible field record
var fieldOrder = 0; var fieldOrder = 0;
var editGroupOrder = 0;
foreach (var group in input.Groups) foreach (var group in input.Groups)
{ {
editGroupOrder++;
var editOrder = 0;
foreach (var item in group.Items) foreach (var item in group.Items)
{ {
fieldOrder++; fieldOrder++;
if (item.IncludeInEditingForm && item.FieldName != input.KeyFieldName)
{
editOrder++;
}
await repoListFormField.InsertAsync(new ListFormField await repoListFormField.InsertAsync(new ListFormField
{ {
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
FieldName = item.DataField, FieldName = item.FieldName,
CaptionName = item.CaptionName, CaptionName = item.CaptionName,
Visible = item.DataField != input.KeyFieldName, Visible = item.FieldName != input.KeyFieldName,
IsActive = true, IsActive = true,
AllowSearch = true, AllowSearch = true,
Width = 0, Width = 0,
ListOrderNo = fieldOrder, 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, SourceDbType = item.DbSourceType,
CultureName = PlatformConsts.DefaultLanguage, CultureName = PlatformConsts.DefaultLanguage,
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code), PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),
@ -373,7 +409,7 @@ public class ListFormWizardAppService(
} }
} }
foreach (var criteria in input.WorkflowCriteria) foreach (var criteria in input.Criteria)
{ {
if (string.IsNullOrWhiteSpace(criteria.Id)) if (string.IsNullOrWhiteSpace(criteria.Id))
{ {

View file

@ -15,6 +15,7 @@ using Volo.Abp.PermissionManagement;
using Volo.Abp.TenantManagement; using Volo.Abp.TenantManagement;
using static Sozsoft.Platform.Data.Seeds.SeedConsts; using static Sozsoft.Platform.Data.Seeds.SeedConsts;
using Sozsoft.Languages; using Sozsoft.Languages;
using Sozsoft.Languages.Languages;
using Sozsoft.Platform.ListForms; using Sozsoft.Platform.ListForms;
namespace Sozsoft.Platform.Menus; namespace Sozsoft.Platform.Menus;
@ -131,6 +132,30 @@ public class MenuAppService : CrudAppService<
.ToList(); .ToList();
entityDtos = await base.MapToGetListOutputDtosAsync(entities); 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>( return new PagedResultDto<MenuDto>(

View file

@ -1443,8 +1443,8 @@
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Reports.List", "key": "App.Reports.List",
"en": "List Report", "en": "Grid Report",
"tr": "Liste Raporu" "tr": "Tablo Raporu"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
@ -1506,6 +1506,18 @@
"en": "Settings", "en": "Settings",
"tr": "Ayarlar" "tr": "Ayarlar"
}, },
{
"resourceName": "Platform",
"key": "App.Licence",
"en": "Licence",
"tr": "Lisans"
},
{
"resourceName": "Platform",
"key": "App.LicenceItem",
"en": "Licence Items",
"tr": "Lisans Kalemleri"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Settings", "key": "App.Settings",
@ -4428,6 +4440,24 @@
"en": "Tree", "en": "Tree",
"tr": "Ağaç" "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", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabGantt", "key": "ListForms.ListFormEdit.TabGantt",
@ -4656,6 +4686,12 @@
"en": "Scheduler Layout", "en": "Scheduler Layout",
"tr": "Zamanlayıcı Düzeni" "tr": "Zamanlayıcı Düzeni"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout",
"en": "Todo Layout",
"tr": "İşler Düzeni"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout", "key": "ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout",
@ -4668,6 +4704,12 @@
"en": "Grid Layout", "en": "Grid Layout",
"tr": "Liste Düzeni" "tr": "Liste Düzeni"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.CardLayout",
"en": "Card Layout",
"tr": "Kart Düzeni"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout", "key": "ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout",
@ -5150,15 +5192,15 @@
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingFormFormFields", "key": "ListForms.ListFormEdit.EditingFormFormGroups",
"en": "Editing Form Fields", "en": "Editing Form Groups",
"tr": "Düzenleme Form Alanları" "tr": "Düzenleme Form Grupları"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingFormFormFieldsDescription", "key": "ListForms.ListFormEdit.EditingFormFormGroupsDescription",
"en": "Edit formunda çıkacak field'lar", "en": "Groups that will appear in Edit form",
"tr": "Form Sütunları" "tr": "Düzenleme formunda çıkacak gruplar"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
@ -5880,6 +5922,30 @@
"en": "Field Name", "en": "Field Name",
"tr": "Alan Adı" "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", "resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.PlaceHolder", "key": "ListForms.ListFormFieldEdit.PlaceHolder",
@ -9318,12 +9384,6 @@
"tr": "Seçili bölümün düzenlenebilir alanları burada gösterilir.", "tr": "Seçili bölümün düzenlenebilir alanları burada gösterilir.",
"en": "Select a block on the page to edit its properties here." "en": "Select a block on the page to edit its properties here."
}, },
{
"resourceName": "Platform",
"key": "Public.designer.save",
"tr": "Kaydet",
"en": "Save"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.DeveloperKit.Component.Description", "key": "App.DeveloperKit.Component.Description",
@ -10152,12 +10212,6 @@
"en": "Save Entity", "en": "Save Entity",
"tr": "Varlığı Kaydet" "tr": "Varlığı Kaydet"
}, },
{
"resourceName": "Platform",
"key": "App.DeveloperKit.EntityEditor.Saving",
"en": "Saving...",
"tr": "Kaydediliyor..."
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.DeveloperKit.EntityEditor.BasicInfo", "key": "App.DeveloperKit.EntityEditor.BasicInfo",
@ -10290,18 +10344,6 @@
"en": "Inactive", "en": "Inactive",
"tr": "Pasif" "tr": "Pasif"
}, },
{
"resourceName": "Platform",
"key": "App.DeveloperKit.ComponentEditor.Save",
"en": "Save",
"tr": "Kaydet"
},
{
"resourceName": "Platform",
"key": "App.DeveloperKit.ComponentEditor.Saving",
"en": "Saving...",
"tr": "Kaydediliyor..."
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.DeveloperKit.ComponentEditor.ValidationError.Title", "key": "App.DeveloperKit.ComponentEditor.ValidationError.Title",
@ -11598,12 +11640,6 @@
"tr": "Hedef Data Sourcelar", "tr": "Hedef Data Sourcelar",
"en": "Target Data Sources" "en": "Target Data Sources"
}, },
{
"resourceName": "Platform",
"key": "App.Platform.Save",
"tr": "Kaydet",
"en": "Save"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.SaveQuery", "key": "App.Platform.SaveQuery",
@ -12306,12 +12342,6 @@
"tr": "Alan seçin...", "tr": "Alan seçin...",
"en": "Select field..." "en": "Select field..."
}, },
{
"resourceName": "Platform",
"key": "App.Platform.ChartDrawer.Save",
"tr": "Kaydet",
"en": "Save"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.ChartDrawer.ChartSaved", "key": "App.Platform.ChartDrawer.ChartSaved",
@ -18792,12 +18822,6 @@
"en": "If checked, existing object in the target will be dropped and recreated.", "en": "If checked, existing object in the target will be dropped and recreated.",
"tr": "İşaretlenirse hedefteki mevcut obje silinip yeniden oluşturulur." "tr": "İşaretlenirse hedefteki mevcut obje silinip yeniden oluşturulur."
}, },
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.Save",
"en": "Save",
"tr": "Kaydet"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.SqlQueryManager.Next", "key": "App.SqlQueryManager.Next",
@ -19374,6 +19398,396 @@
"en": "Successfully Saved", "en": "Successfully Saved",
"tr": "Başarıyla Kaydedildi" "tr": "Başarıyla Kaydedildi"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoTitleExpr",
"en": "Title Field",
"tr": "Başlık alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoStatusExpr",
"en": "Column / Status Field",
"tr": "Kolon / durum alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoDescriptionExpr",
"en": "Description Field",
"tr": "Açıklama alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoDueDateExpr",
"en": "Due Date Field",
"tr": "Termin tarihi alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoTagExpr",
"en": "Tag Field",
"tr": "Etiket alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoAssigneeExpr",
"en": "Assignee Field",
"tr": "Atanan kişi alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoPriorityExpr",
"en": "Priority Field",
"tr": "Öncelik alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoCompletedExpr",
"en": "Completed Field",
"tr": "Tamamlandı alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoOrderExpr",
"en": "Card Order Field",
"tr": "Kart sıra alanı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoFieldsLoadFailed",
"en": "Fields could not be loaded: {0}",
"tr": "Alanlar getirilemedi: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoStatusOrder",
"en": "Status order (comma-separated)",
"tr": "Durum sırası (virgülle ayırın)"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder",
"en": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
"tr": "Gelen Kutusu, Bugün, Bu Hafta, Bu Ay, Daha Sonra, Tamamlandı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormFieldEdit.TodoAllowDragging",
"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",
"en": "Uncategorized",
"tr": "Kategorisiz"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityRating",
"en": "{0}/5 priority",
"tr": "{0}/5 öncelik"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.NoPriority",
"en": "No priority",
"tr": "Öncelik yok"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DuplicateColumnName",
"en": "A Kanban column with this name already exists.",
"tr": "Bu isimde bir kanban zaten var."
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ColumnNotEmptyMessage",
"en": "This Kanban column cannot be deleted. First move its {0} todo item(s) to another column.",
"tr": "Bu kanban silinemez. Önce içindeki {0} todoyu başka bir kanbana taşıyın."
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ConfirmDeleteColumn",
"en": "Delete the “{0}” Kanban column?",
"tr": "“{0}” kanbanı silinsin mi?"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.RenameRequiresUpdateService",
"en": "An update service is required to rename a Kanban column that contains todo items.",
"tr": "Todo içeren bir kanbanı yeniden adlandırmak için update servisi gereklidir."
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ColumnNameSaveFailed",
"en": "The Kanban column name could not be saved. Please try again.",
"tr": "Kanban adı kaydedilemedi. Lütfen tekrar deneyin."
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ConfirmDeleteTodo",
"en": "Delete “{0}”?",
"tr": "“{0}” silinsin mi?"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Loading",
"en": "Loading…",
"tr": "Yükleniyor…"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.BackToBoard",
"en": "Back to board",
"tr": "Panoya dön"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.EditRequiresUpdateService",
"en": "An update service must be defined to edit.",
"tr": "Düzenlemek için update servisi tanımlanmalıdır."
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DeleteTodo",
"en": "Delete todo",
"tr": "Todo sil"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Title",
"en": "Title",
"tr": "Başlık"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Description",
"en": "Description",
"tr": "Açıklama"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DescriptionPlaceholder",
"en": "Enter a todo description…",
"tr": "Todo açıklamasını yazın…"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Assignees",
"en": "Assignees",
"tr": "Atanan kişiler"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.UsersLoading",
"en": "Loading users…",
"tr": "Kullanıcılar yükleniyor…"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.SelectUser",
"en": "Select a user",
"tr": "Kullanıcı seçin"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Tags",
"en": "Tags",
"tr": "Etiketler"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.TagsPlaceholder",
"en": "Select a tag or enter a new one",
"tr": "Etiket seçin veya yeni etiket yazın"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Status",
"en": "Kanban / Status",
"tr": "Kanban / Durum"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DueDate",
"en": "Due date",
"tr": "Termin tarihi"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Priority",
"en": "Priority",
"tr": "Öncelik"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityVeryLow",
"en": "Very low",
"tr": "Çok düşük"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityLow",
"en": "Low",
"tr": "Düşük"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityMedium",
"en": "Medium",
"tr": "Orta"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityHigh",
"en": "High",
"tr": "Yüksek"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.PriorityUrgent",
"en": "Urgent",
"tr": "Acil"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Completed",
"en": "Completed",
"tr": "Tamamlandı"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.UndoChanges",
"en": "Undo changes",
"tr": "Değişiklikleri geri al"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ReorderColumn",
"en": "Reorder Kanban column",
"tr": "Kanbanı sırala"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ColumnName",
"en": "Kanban column name",
"tr": "Kanban adı"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Cancel",
"en": "Cancel",
"tr": "Vazgeç"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.RenameColumn",
"en": "Rename Kanban column",
"tr": "Kanban adını değiştir"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.NewTodo",
"en": "New todo",
"tr": "Yeni todo"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ColumnWithTodosCannotBeDeleted",
"en": "A Kanban column containing todo items cannot be deleted",
"tr": "İçinde todo bulunan kanban silinemez"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DeleteColumn",
"en": "Delete Kanban column",
"tr": "Kanbanı sil"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.Close",
"en": "Close",
"tr": "Kapat"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.TodoTitlePlaceholder",
"en": "Todo title…",
"tr": "Todo başlığı…"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.DropHere",
"en": "Drop here",
"tr": "Buraya bırak"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ChangePriority",
"en": "Change priority",
"tr": "Önceliği değiştir"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.ReopenTodo",
"en": "Reopen todo",
"tr": "Todoyu yeniden aç"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.CompleteTodo",
"en": "Complete todo",
"tr": "Todoyu tamamla"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.NewColumn",
"en": "New Kanban",
"tr": "Yeni kanban"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.AddNewColumn",
"en": "Add a new Kanban",
"tr": "Yeni kanban ekle"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.NewColumnName",
"en": "New Kanban column name",
"tr": "Yeni kanban adı"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.NewColumnPlaceholder",
"en": "E.g. In Review",
"tr": "Örn. İnceleniyor"
},
{
"resourceName": "Platform",
"key": "ListForms.TodoBoard.AddColumn",
"en": "Add Kanban column",
"tr": "Kanbanı ekle"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.TabTodo",
"en": "Todo",
"tr": "İşler"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.Workflow.Criteria", "key": "ListForms.ListFormEdit.Workflow.Criteria",
@ -19448,7 +19862,7 @@
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.Workflow.IsFilterUserName", "key": "ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName",
"en": "Filter User Name?", "en": "Filter User Name?",
"tr": "Kullanıcı Adı Filtresin mi?" "tr": "Kullanıcı Adı Filtresin mi?"
}, },
@ -19949,6 +20363,72 @@
"key": "App.Tenant.Create", "key": "App.Tenant.Create",
"en": "Tenant Create", "en": "Tenant Create",
"tr": "Tenant Oluştur" "tr": "Tenant Oluştur"
},
{
"resourceName": "Platform",
"key": "App.CheckUpdate",
"en": "Checking...",
"tr": "Kontrol ediliyor..."
},
{
"resourceName": "Platform",
"key": "App.CheckForUpdates",
"en": "Check for Updates",
"tr": "Güncellemeyi Kontrol Et"
},
{
"resourceName": "Platform",
"key": "App.NotSupportServiceWorkerUpdate",
"en": "Your browser does not support service worker updates.",
"tr": "Tarayıcınız service worker güncellemelerini desteklemiyor."
},
{
"resourceName": "Platform",
"key": "App.NewVersionAvailable",
"en": "A new service worker has been found. The application will automatically update when the files are ready.",
"tr": "Dosyalar hazırlandığında uygulama otomatik olarak güncellenecek."
},
{
"resourceName": "Platform",
"key": "App.ManualUpdateFailed",
"en": "Please check your connection and try again.",
"tr": "Lütfen bağlantınızı kontrol edip tekrar deneyin."
},
{
"resourceName": "Platform",
"key": "App.ChangeLogDescription",
"en": "Check if there's a new application version available on the server.",
"tr": "Sunucuda yeni bir uygulama sürümü olup olmadığını kontrol edin."
},
{
"resourceName": "Platform",
"key": "App.NoNewVersionFound",
"en": "No new version was found available.",
"tr": "Kullanılabilir yeni bir sürüm bulunamadı."
},
{
"resourceName": "Platform",
"key": "App.UpToDate",
"en": "The app is up to date.",
"tr": "Uygulama güncel"
},
{
"resourceName": "Platform",
"key": "App.UpdateCouldNotBeChecked",
"en": "Update could not be checked",
"tr": "Güncelleme kontrol edilemedi"
},
{
"resourceName": "Platform",
"key": "App.NewVersionFound",
"en": "New version found.",
"tr": "Yeni sürüm bulundu"
},
{
"resourceName": "Platform",
"key": "App.UpdateCheckFailed",
"en": "Update check failed.",
"tr": "Güncelleme kontrolü başarısız"
} }
] ]
} }

View file

@ -67,11 +67,13 @@ public static class ListFormSeeder_DefaultJsons
public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new LayoutDto() public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new LayoutDto()
{ {
Grid = true, Grid = true,
Card = true,
Pivot = true, Pivot = true,
Chart = true, Chart = true,
Tree = true, Tree = true,
Gantt = true, Gantt = true,
Scheduler = true, Scheduler = true,
Todo = true,
DefaultLayout = DefaultLayout, DefaultLayout = DefaultLayout,
}); });
public static readonly string DefaultExportJson = JsonSerializer.Serialize(new ChartExportDto public static readonly string DefaultExportJson = JsonSerializer.Serialize(new ChartExportDto

View file

@ -984,22 +984,22 @@
}, },
{ {
"ParentCode": "App.DeveloperKit", "ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Components", "Code": "App.DeveloperKit.DynamicServices",
"DisplayName": "App.DeveloperKit.Components", "DisplayName": "App.DeveloperKit.DynamicServices",
"Order": 4, "Order": 4,
"Url": "/admin/developerkit/components", "Url": "/admin/developerkit/dynamic-services",
"Icon": "FcBiohazard", "Icon": "FcCommandLine",
"RequiredPermissionName": "App.DeveloperKit.Components", "RequiredPermissionName": "App.DeveloperKit.DynamicServices",
"IsDisabled": false "IsDisabled": false
}, },
{ {
"ParentCode": "App.DeveloperKit", "ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.DynamicServices", "Code": "App.DeveloperKit.Components",
"DisplayName": "App.DeveloperKit.DynamicServices", "DisplayName": "App.DeveloperKit.Components",
"Order": 5, "Order": 5,
"Url": "/admin/developerkit/dynamic-services", "Url": "/admin/developerkit/components",
"Icon": "FcCommandLine", "Icon": "FcBiohazard",
"RequiredPermissionName": "App.DeveloperKit.DynamicServices", "RequiredPermissionName": "App.DeveloperKit.Components",
"IsDisabled": false "IsDisabled": false
}, },
{ {
@ -1083,11 +1083,21 @@
"RequiredPermissionName": "App.Definitions.OrgChart", "RequiredPermissionName": "App.Definitions.OrgChart",
"IsDisabled": false "IsDisabled": false
}, },
{
"ParentCode": "App.Administration",
"Code": "App.Licence",
"DisplayName": "App.Licence",
"Order": 2,
"Url": "/admin/form/App.Licence",
"Icon": "FcRules",
"RequiredPermissionName": "App.Licence",
"IsDisabled": false
},
{ {
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Setting", "Code": "App.Setting",
"DisplayName": "App.Setting", "DisplayName": "App.Setting",
"Order": 2, "Order": 3,
"Url": "/admin/settings", "Url": "/admin/settings",
"Icon": "FcSettings", "Icon": "FcSettings",
"RequiredPermissionName": "App.Setting", "RequiredPermissionName": "App.Setting",
@ -1097,7 +1107,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Administration.Intranet", "Code": "App.Administration.Intranet",
"DisplayName": "App.Intranet", "DisplayName": "App.Intranet",
"Order": 3, "Order": 4,
"Url": null, "Url": null,
"Icon": "FcConferenceCall", "Icon": "FcConferenceCall",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -1167,7 +1177,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Administration.Restrictions", "Code": "App.Administration.Restrictions",
"DisplayName": "App.Restrictions", "DisplayName": "App.Restrictions",
"Order": 4, "Order": 5,
"Url": null, "Url": null,
"Icon": "FaLock", "Icon": "FaLock",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -1197,7 +1207,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "Abp.Identity", "Code": "Abp.Identity",
"DisplayName": "Abp.Identity", "DisplayName": "Abp.Identity",
"Order": 5, "Order": 6,
"Url": null, "Url": null,
"Icon": "FcConferenceCall", "Icon": "FcConferenceCall",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -1287,7 +1297,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Reports.Management", "Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management", "DisplayName": "App.Reports.Management",
"Order": 6, "Order": 7,
"Url": null, "Url": null,
"Icon": "FcDocument", "Icon": "FcDocument",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -1317,7 +1327,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Videoroom.Dashboard", "Code": "App.Videoroom.Dashboard",
"DisplayName": "App.Videoroom.Dashboard", "DisplayName": "App.Videoroom.Dashboard",
"Order": 7, "Order": 8,
"Url": "/admin/videoroom/dashboard", "Url": "/admin/videoroom/dashboard",
"Icon": "FcVideoCall", "Icon": "FcVideoCall",
"RequiredPermissionName": "App.Videoroom.Dashboard", "RequiredPermissionName": "App.Videoroom.Dashboard",
@ -1327,7 +1337,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Files", "Code": "App.Files",
"DisplayName": "App.Files", "DisplayName": "App.Files",
"Order": 8, "Order": 9,
"Url": "/admin/files", "Url": "/admin/files",
"Icon": "FcFolder", "Icon": "FcFolder",
"RequiredPermissionName": "App.Files", "RequiredPermissionName": "App.Files",
@ -1337,7 +1347,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Forum", "Code": "App.Forum",
"DisplayName": "App.Forum", "DisplayName": "App.Forum",
"Order": 9, "Order": 10,
"Url": "/admin/forum", "Url": "/admin/forum",
"Icon": "FcLink", "Icon": "FcLink",
"RequiredPermissionName": "App.ForumManagement.Publish", "RequiredPermissionName": "App.ForumManagement.Publish",

View file

@ -4175,7 +4175,7 @@
"DisplayName": "App.Intranet.Survey", "DisplayName": "App.Intranet.Survey",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4184,7 +4184,7 @@
"DisplayName": "Create", "DisplayName": "Create",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4193,7 +4193,7 @@
"DisplayName": "Update", "DisplayName": "Update",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4202,7 +4202,7 @@
"DisplayName": "Delete", "DisplayName": "Delete",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4211,7 +4211,7 @@
"DisplayName": "Export", "DisplayName": "Export",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4220,7 +4220,7 @@
"DisplayName": "Import", "DisplayName": "Import",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4229,7 +4229,7 @@
"DisplayName": "Note", "DisplayName": "Note",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4238,7 +4238,7 @@
"DisplayName": "Widget", "DisplayName": "Widget",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4247,7 +4247,7 @@
"DisplayName": "App.Intranet.SurveyQuestion", "DisplayName": "App.Intranet.SurveyQuestion",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4256,7 +4256,7 @@
"DisplayName": "Create", "DisplayName": "Create",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4265,7 +4265,7 @@
"DisplayName": "Update", "DisplayName": "Update",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4274,7 +4274,7 @@
"DisplayName": "Delete", "DisplayName": "Delete",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4283,7 +4283,7 @@
"DisplayName": "Export", "DisplayName": "Export",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4292,7 +4292,7 @@
"DisplayName": "Import", "DisplayName": "Import",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4301,7 +4301,7 @@
"DisplayName": "Note", "DisplayName": "Note",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4310,7 +4310,7 @@
"DisplayName": "App.Intranet.SurveyQuestionOption", "DisplayName": "App.Intranet.SurveyQuestionOption",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4319,7 +4319,7 @@
"DisplayName": "Create", "DisplayName": "Create",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4328,7 +4328,7 @@
"DisplayName": "Update", "DisplayName": "Update",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4337,7 +4337,7 @@
"DisplayName": "Delete", "DisplayName": "Delete",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4346,7 +4346,7 @@
"DisplayName": "Export", "DisplayName": "Export",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4355,7 +4355,7 @@
"DisplayName": "Import", "DisplayName": "Import",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4364,7 +4364,7 @@
"DisplayName": "App.Intranet.SurveyResponse", "DisplayName": "App.Intranet.SurveyResponse",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4373,7 +4373,7 @@
"DisplayName": "Create", "DisplayName": "Create",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4382,7 +4382,7 @@
"DisplayName": "Update", "DisplayName": "Update",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4391,7 +4391,7 @@
"DisplayName": "Delete", "DisplayName": "Delete",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4400,7 +4400,7 @@
"DisplayName": "Export", "DisplayName": "Export",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4409,7 +4409,7 @@
"DisplayName": "Import", "DisplayName": "Import",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4418,7 +4418,7 @@
"DisplayName": "Note", "DisplayName": "Note",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4427,7 +4427,7 @@
"DisplayName": "App.Intranet.SurveyAnswer", "DisplayName": "App.Intranet.SurveyAnswer",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4436,7 +4436,7 @@
"DisplayName": "Create", "DisplayName": "Create",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4445,7 +4445,7 @@
"DisplayName": "Update", "DisplayName": "Update",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4454,7 +4454,7 @@
"DisplayName": "Delete", "DisplayName": "Delete",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4463,7 +4463,7 @@
"DisplayName": "Export", "DisplayName": "Export",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4472,7 +4472,7 @@
"DisplayName": "Import", "DisplayName": "Import",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4480,8 +4480,8 @@
"ParentName": null, "ParentName": null,
"DisplayName": "App.Videoroom", "DisplayName": "App.Videoroom",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2, "MultiTenancySide": 3,
"MenuGroup": "Kurs" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4489,8 +4489,8 @@
"ParentName": "App.Videoroom", "ParentName": "App.Videoroom",
"DisplayName": "App.Videoroom.Dashboard", "DisplayName": "App.Videoroom.Dashboard",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2, "MultiTenancySide": 3,
"MenuGroup": "Kurs" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4498,8 +4498,8 @@
"ParentName": "App.Videoroom", "ParentName": "App.Videoroom",
"DisplayName": "App.Videoroom.List", "DisplayName": "App.Videoroom.List",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2, "MultiTenancySide": 3,
"MenuGroup": "Kurs" "MenuGroup": "Erp|Kurs"
}, },
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
@ -4507,8 +4507,116 @@
"ParentName": "App.Videoroom", "ParentName": "App.Videoroom",
"DisplayName": "App.Videoroom.RoomDetail", "DisplayName": "App.Videoroom.RoomDetail",
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2, "MultiTenancySide": 3,
"MenuGroup": "Kurs" "MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence",
"ParentName": null,
"DisplayName": "App.Licence",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence.Create",
"ParentName": "App.Licence",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence.Update",
"ParentName": "App.Licence",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence.Delete",
"ParentName": "App.Licence",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence.Export",
"ParentName": "App.Licence",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Licence.Import",
"ParentName": "App.Licence",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem",
"ParentName": null,
"DisplayName": "App.LicenceItem",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem.Create",
"ParentName": "App.LicenceItem",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem.Update",
"ParentName": "App.LicenceItem",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem.Delete",
"ParentName": "App.LicenceItem",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem.Export",
"ParentName": "App.LicenceItem",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.LicenceItem.Import",
"ParentName": "App.LicenceItem",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
} }
] ]
} }

View file

@ -0,0 +1,917 @@
CREATE OR REPLACE FUNCTION dbo."Sas_H_ListFormFieldsDictionaryJson"
(
p_definition_culture_name VARCHAR(10) DEFAULT 'en',
p_ui_culture_name VARCHAR(10) DEFAULT 'tr',
p_list_form_code VARCHAR(64) DEFAULT NULL,
p_only_base_definitions BOOLEAN DEFAULT TRUE,
p_resource_name VARCHAR(64) DEFAULT NULL
)
RETURNS JSONB
LANGUAGE plpgsql
AS $$
DECLARE
v_result JSONB;
BEGIN
WITH list_form_source AS
(
SELECT
l."Id",
l."ListFormCode",
l."CultureName",
l."Name",
lt_name."Value" AS name_value,
l."Title",
lt_title."Value" AS title_value,
l."Description",
lt_desc."Value" AS description_value,
l."TableName",
NULLIF(
BTRIM(l."SelectCommand"::TEXT),
''
) AS select_command_text,
l."KeyFieldName",
l."UserId",
l."RoleId"
FROM dbo."Sas_H_ListForm" l
/*
SQL Server OUTER APPLY TOP (1)
PostgreSQL LEFT JOIN LATERAL ... LIMIT 1
*/
LEFT JOIN LATERAL
(
SELECT lt."Value"
FROM dbo."Sas_H_LanguageText" lt
WHERE lt."CultureName" = p_ui_culture_name
AND lt."Key" = l."Name"
AND
(
p_resource_name IS NULL
OR lt."ResourceName" = p_resource_name
)
ORDER BY lt."ResourceName"
LIMIT 1
) lt_name ON TRUE
LEFT JOIN LATERAL
(
SELECT lt."Value"
FROM dbo."Sas_H_LanguageText" lt
WHERE lt."CultureName" = p_ui_culture_name
AND lt."Key" = l."Title"
AND
(
p_resource_name IS NULL
OR lt."ResourceName" = p_resource_name
)
ORDER BY lt."ResourceName"
LIMIT 1
) lt_title ON TRUE
LEFT JOIN LATERAL
(
SELECT lt."Value"
FROM dbo."Sas_H_LanguageText" lt
WHERE lt."CultureName" = p_ui_culture_name
AND lt."Key" = l."Description"
AND
(
p_resource_name IS NULL
OR lt."ResourceName" = p_resource_name
)
ORDER BY lt."ResourceName"
LIMIT 1
) lt_desc ON TRUE
WHERE
(
p_definition_culture_name IS NULL
OR l."CultureName" = p_definition_culture_name
)
AND
(
p_list_form_code IS NULL
OR l."ListFormCode" = p_list_form_code
)
AND
(
p_only_base_definitions = FALSE
OR
(
l."UserId" IS NULL
AND l."RoleId" IS NULL
)
)
),
list_forms_json AS
(
SELECT
jsonb_build_object
(
'listFormCode',
l."ListFormCode",
'displayName',
COALESCE(
l.name_value::TEXT,
l."Name",
''
),
'title',
COALESCE(
l.title_value::TEXT,
l."Title",
''
),
'description',
COALESCE(
l.description_value::TEXT,
l."Description",
''
),
'tableName',
COALESCE(
l."TableName",
''
),
'sqlSourceName',
COALESCE(
NULLIF(
l."TableName",
''
),
l.select_command_text,
''
),
'sourceKind',
CASE
WHEN COALESCE(
NULLIF(
l."TableName",
''
),
l.select_command_text
) IS NULL
THEN 'unknown'
WHEN UPPER(
BTRIM(
COALESCE(
NULLIF(
l."TableName",
''
),
l.select_command_text
)
)
) LIKE 'SELECT%'
THEN 'selectSql'
ELSE 'table'
END,
'keyFieldName',
COALESCE(
l."KeyFieldName",
''
),
'searchText',
CONCAT_WS(
' ',
l."ListFormCode",
l."TableName",
l.select_command_text,
COALESCE(
l.name_value::TEXT,
l."Name"
),
COALESCE(
l.title_value::TEXT,
l."Title"
),
COALESCE(
l.description_value::TEXT,
l."Description"
)
),
'fields',
COALESCE
(
(
SELECT
jsonb_agg
(
jsonb_build_object
(
'field',
f."FieldName",
'displayName',
COALESCE(
lt_cap."Value"::TEXT,
f."CaptionName",
''
),
'sqlDataType',
sc.sql_data_type,
'listOrderNo',
f."ListOrderNo",
'isVisible',
COALESCE(
f."Visible",
TRUE
),
'isBooleanColumn',
CASE
WHEN sc.sql_data_type = 'boolean'
THEN TRUE
WHEN f."FieldName" LIKE 'Is%'
THEN TRUE
WHEN f."FieldName" LIKE 'Has%'
THEN TRUE
WHEN f."FieldName" IN
(
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
)
THEN TRUE
WHEN f."FieldName" IN
(
'EmailConfirmed',
'TwoFactorEnabled',
'IsVerified',
'IsDefault',
'IsPublic',
'IsEnabled',
'IsPublished',
'IsPinned',
'IsAnonymous',
'IsFixed',
'IsCustomized',
'IsEncrypted',
'IsInherited',
'ZipRequired',
'StateRequired'
)
THEN TRUE
ELSE FALSE
END,
'lookupJsonText',
lj.lookup_json_text_decoded,
'lookupTableName',
COALESCE
(
CASE
WHEN lj.lookup_json IS NOT NULL
THEN COALESCE
(
lj.lookup_json ->> 'tableName',
lj.lookup_json ->> 'sqlTableName',
lj.lookup_json ->> 'lookupTableName',
lj.lookup_json #>> '{dataSource,tableName}',
lj.lookup_json #>> '{dataSource,sqlTableName}'
)
END,
lkp.parsed_lookup_table_name
),
'lookupValueColumn',
COALESCE
(
CASE
WHEN lj.lookup_json IS NOT NULL
THEN COALESCE
(
lj.lookup_json ->> 'valueExpr',
lj.lookup_json ->> 'valueField',
lj.lookup_json ->> 'valueColumn',
lj.lookup_json ->> 'keyField',
lj.lookup_json ->> 'keyFieldName'
)
END,
lkp.parsed_lookup_value_column
),
'lookupDisplayColumn',
COALESCE
(
CASE
WHEN lj.lookup_json IS NOT NULL
THEN COALESCE
(
lj.lookup_json ->> 'displayExpr',
lj.lookup_json ->> 'displayField',
lj.lookup_json ->> 'displayColumn',
lj.lookup_json ->> 'textField',
lj.lookup_json ->> 'textColumn',
lj.lookup_json ->> 'nameField',
lj.lookup_json ->> 'nameColumn'
)
END,
lkp.parsed_lookup_display_column
),
'lookupJoinMode',
CASE
WHEN lj.lookup_json_text_decoded IS NOT NULL
AND f."FieldName" NOT LIKE '%Id'
AND COALESCE(
lkp.parsed_lookup_value_column,
''
) IN
(
'Name',
'Code',
'Key',
'Value',
'key',
'value'
)
THEN 'direct-string'
WHEN lj.lookup_json_text_decoded IS NOT NULL
AND f."FieldName" NOT LIKE '%Id'
THEN 'direct-string'
WHEN lj.lookup_json_text_decoded IS NOT NULL
OR f."FieldName" LIKE '%Id'
THEN 'convert-nvarchar-safe'
ELSE NULL
END,
'lookupFallbackToSourceColumn',
CASE
WHEN
lj.lookup_json_text_decoded IS NOT NULL
AND f."FieldName" NOT LIKE '%Id'
THEN TRUE
ELSE FALSE
END,
'lookupSearchText',
CASE
WHEN lj.lookup_json_text_decoded IS NOT NULL
THEN CONCAT_WS
(
' ',
f."FieldName",
f."CaptionName",
lt_cap."Value"::TEXT,
lj.lookup_json_text_decoded
)
END,
'isActiveStatusColumn',
f."FieldName" IN
(
'IsActive',
'IsEnabled',
'IsPublished'
),
'isDeletedStatusColumn',
f."FieldName" IN
(
'IsDeleted',
'Deleted',
'IsRemoved'
),
'isSensitive',
CASE
WHEN f."FieldName" LIKE '%Password%'
OR f."FieldName" LIKE '%ConnectionString%'
OR f."FieldName" LIKE '%Token%'
OR f."FieldName" LIKE '%Secret%'
OR f."FieldName" LIKE '%ApiKey%'
OR f."FieldName" LIKE '%PrivateKey%'
THEN TRUE
ELSE FALSE
END,
'isDefaultSelectColumn',
CASE
WHEN
COALESCE(
f."Visible",
TRUE
) = TRUE
AND COALESCE(
f."IsActive",
TRUE
) = TRUE
AND f."FieldName" NOT LIKE '%Password%'
AND f."FieldName" NOT LIKE '%ConnectionString%'
AND f."FieldName" NOT LIKE '%Token%'
AND f."FieldName" NOT LIKE '%Secret%'
AND f."FieldName" NOT LIKE '%ApiKey%'
AND f."FieldName" NOT LIKE '%PrivateKey%'
AND f."FieldName" NOT IN
(
'Avatar',
'ExtraProperties'
)
THEN TRUE
ELSE FALSE
END
)
ORDER BY
COALESCE(
f."ListOrderNo",
999999
),
f."FieldName"
)
FROM dbo."Sas_H_ListFormField" f
/*
SQL Server:
sys.tables
sys.columns
TYPE_NAME()
PostgreSQL:
information_schema.columns
*/
LEFT JOIN LATERAL
(
SELECT
c.data_type AS sql_data_type
FROM information_schema.columns c
WHERE
c.table_schema = 'dbo'
AND c.table_name =
l."TableName"
AND c.column_name =
f."FieldName"
LIMIT 1
) sc ON TRUE
/*
LookupJson decode
*/
LEFT JOIN LATERAL
(
SELECT
NULLIF
(
BTRIM
(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
NULLIF(
BTRIM(
f."LookupJson"::TEXT
),
''
),
'\\u0022',
'"'
),
'\u0022',
'"'
),
'\\u0027',
''''
),
'\u0027',
''''
),
'&quot;',
'"'
)
),
''
) AS lookup_json_text_decoded
) lj_text ON TRUE
/*
PostgreSQL'de ISJSON yerine
güvenli JSON parse işlemi gerekiyor.
LookupJson kolonu zaten JSON/JSONB ise
doğrudan kullanılabilir.
*/
LEFT JOIN LATERAL
(
SELECT
lj_text.lookup_json_text_decoded,
CASE
WHEN
lj_text.lookup_json_text_decoded
~ '^\s*[\{\[]'
THEN
lj_text.lookup_json_text_decoded::JSONB
ELSE NULL
END AS lookup_json
) lj ON TRUE
/*
LookupQuery değerini al
*/
LEFT JOIN LATERAL
(
SELECT
CASE
WHEN lj.lookup_json IS NOT NULL
THEN
lj.lookup_json
->> 'LookupQuery'
END AS lookup_query_text
) lq ON TRUE
/*
LookupQuery temizleme
*/
LEFT JOIN LATERAL
(
SELECT
NULLIF
(
BTRIM
(
REGEXP_REPLACE
(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
COALESCE(
lq.lookup_query_text,
''
),
'"',
''
),
'[',
''
),
']',
''
),
'`',
''
),
'\s+',
' ',
'g'
)
),
''
) AS clean_lookup_query
) lqc ON TRUE
/*
SQL Server prosedürü LookupQuery içerisindeki
SELECT ... FROM ... ifadesini string işlemleri
ile parse ediyor.
PostgreSQL'de regexp_match daha temiz.
*/
LEFT JOIN LATERAL
(
SELECT
(
REGEXP_MATCH
(
lqc.clean_lookup_query,
'(?i)\bFROM\s+([a-zA-Z0-9_\."]+)'
)
)[1] AS parsed_table,
COALESCE
(
NULLIF(
lj.lookup_json
->> 'ValueExpr',
''
),
NULLIF(
lj.lookup_json
->> 'valueExpr',
''
),
NULLIF(
lj.lookup_json
->> 'valueField',
''
),
NULLIF(
lj.lookup_json
->> 'valueColumn',
''
),
NULLIF(
lj.lookup_json
->> 'keyField',
''
),
NULLIF(
lj.lookup_json
->> 'keyFieldName',
''
)
)
AS parsed_lookup_value_column,
COALESCE
(
NULLIF(
lj.lookup_json
->> 'DisplayExpr',
''
),
NULLIF(
lj.lookup_json
->> 'displayExpr',
''
),
NULLIF(
lj.lookup_json
->> 'displayField',
''
),
NULLIF(
lj.lookup_json
->> 'displayColumn',
''
),
NULLIF(
lj.lookup_json
->> 'textField',
''
),
NULLIF(
lj.lookup_json
->> 'textColumn',
''
),
NULLIF(
lj.lookup_json
->> 'nameField',
''
),
NULLIF(
lj.lookup_json
->> 'nameColumn',
''
)
)
AS parsed_lookup_display_column
) parsed ON TRUE
LEFT JOIN LATERAL
(
SELECT
NULLIF
(
REGEXP_REPLACE
(
COALESCE(
parsed.parsed_table,
''
),
'^(?i:dbo\.)',
''
),
''
)
AS parsed_lookup_table_name,
parsed.parsed_lookup_value_column,
parsed.parsed_lookup_display_column
) lkp ON TRUE
/*
Caption translation
*/
LEFT JOIN LATERAL
(
SELECT
lt."Value"
FROM dbo."Sas_H_LanguageText" lt
WHERE
lt."CultureName" =
p_ui_culture_name
AND lt."Key" =
f."CaptionName"
AND
(
p_resource_name IS NULL
OR lt."ResourceName" =
p_resource_name
)
ORDER BY
lt."ResourceName"
LIMIT 1
) lt_cap ON TRUE
WHERE
f."ListFormCode" =
l."ListFormCode"
AND f."CultureName" =
l."CultureName"
AND
(
p_definition_culture_name IS NULL
OR f."CultureName" =
p_definition_culture_name
)
AND
(
p_only_base_definitions = FALSE
OR
(
f."UserId" IS NULL
AND f."RoleId" IS NULL
)
)
),
'[]'::JSONB
)
)
AS form_json,
l."ListFormCode"
FROM list_form_source l
)
SELECT
jsonb_build_object
(
'listForms',
COALESCE
(
jsonb_agg
(
form_json
ORDER BY
"ListFormCode"
),
'[]'::JSONB
)
)
INTO v_result
FROM list_forms_json;
RETURN COALESCE
(
v_result,
jsonb_build_object
(
'listForms',
'[]'::JSONB
)
);
END;
$$;

View file

@ -0,0 +1,239 @@
CREATE OR REPLACE FUNCTION dbo."Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns"
(
p_tenant_name VARCHAR(128) DEFAULT 'Sozsoft',
p_definition_culture_name VARCHAR(10) DEFAULT 'en',
p_ui_culture_name VARCHAR(10) DEFAULT 'tr',
p_list_form_code VARCHAR(64) DEFAULT NULL,
p_only_base_definitions BOOLEAN DEFAULT TRUE,
p_resource_name VARCHAR(64) DEFAULT NULL,
p_max_forms INTEGER DEFAULT 500,
p_deactivate_previous BOOLEAN DEFAULT TRUE,
p_note VARCHAR(4000) DEFAULT NULL
)
RETURNS TABLE
(
"TenantName" VARCHAR,
"DictionaryType" VARCHAR,
"DefinitionCultureName" VARCHAR,
"UiCultureName" VARCHAR,
"ListFormCode" VARCHAR,
"OnlyBaseDefinitions" BOOLEAN,
"ResourceName" VARCHAR,
"MaxForms" INTEGER,
"CacheKey" TEXT,
"FormCount" INTEGER,
"FieldCount" INTEGER,
"JsonLength" INTEGER,
"IsActive" BOOLEAN,
"RefreshedAt" TIMESTAMP,
"SourceProcedure" VARCHAR,
"Note" VARCHAR
)
LANGUAGE plpgsql
AS $$
DECLARE
v_json_data JSONB;
v_cache_key TEXT;
v_form_count INTEGER := 0;
v_field_count INTEGER := 0;
v_json_length INTEGER := 0;
BEGIN
/*
SQL Server:
EXEC dbo.Sas_H_ListFormFieldsDictionaryJson
PostgreSQL tarafında bunun JSON / JSONB döndüren bir FUNCTION
olduğu varsayılmıştır.
*/
SELECT dbo."Sas_H_ListFormFieldsDictionaryJson"
(
p_definition_culture_name,
p_ui_culture_name,
p_list_form_code,
p_only_base_definitions,
p_resource_name
)
INTO v_json_data;
/*
JSON boş kontrolü
*/
IF v_json_data IS NULL THEN
RAISE EXCEPTION
'JSON veri sözlüğü boş döndü. dbo.Sas_H_ListFormFieldsDictionaryJson çıktısını kontrol edin.'
USING ERRCODE = 'P0001';
END IF;
/*
CacheKey oluşturulması
*/
v_cache_key :=
'ListFormJson|'
|| COALESCE(p_tenant_name, '')
|| '|'
|| COALESCE(p_definition_culture_name, '')
|| '|'
|| COALESCE(p_ui_culture_name, '')
|| '|'
|| COALESCE(p_list_form_code, '')
|| '|'
|| CASE
WHEN COALESCE(p_only_base_definitions, TRUE)
THEN '1'
ELSE '0'
END
|| '|'
|| COALESCE(p_resource_name, '')
|| '|'
|| COALESCE(p_max_forms, 500)::TEXT;
/*
Form sayısı
JSON:
{
"listForms": [...]
}
*/
IF jsonb_typeof(v_json_data -> 'listForms') = 'array' THEN
v_form_count :=
jsonb_array_length(v_json_data -> 'listForms');
END IF;
/*
Toplam Field sayısı
Her listForms elemanının:
{
"fields": [...]
}
alanındaki kayıtlar sayılır.
*/
SELECT COUNT(*)::INTEGER
INTO v_field_count
FROM jsonb_array_elements(
COALESCE(
v_json_data -> 'listForms',
'[]'::JSONB
)
) AS lf
CROSS JOIN LATERAL jsonb_array_elements(
CASE
WHEN jsonb_typeof(lf -> 'fields') = 'array'
THEN lf -> 'fields'
ELSE '[]'::JSONB
END
) AS ff;
/*
JSON karakter uzunluğu
*/
v_json_length := LENGTH(v_json_data::TEXT);
/*
PostgreSQL function çağrısı kendi transaction'ı içerisinde
çalıştığı için SQL Server'daki BEGIN TRAN / COMMIT burada
ayrıca kullanılmaz.
*/
/*
Önceki aktif kaydı pasif yap
*/
IF p_deactivate_previous THEN
UPDATE dbo."Sas_H_DbTableColumn"
SET "IsActive" = FALSE
WHERE "DictionaryType" = 'ListFormJson'
AND "CacheKey" = v_cache_key
AND COALESCE("IsActive", FALSE) = TRUE;
END IF;
/*
Yeni JSON kaydını ekle
*/
INSERT INTO dbo."Sas_H_DbTableColumn"
(
"TenantName",
"DictionaryType",
"DefinitionCultureName",
"UiCultureName",
"ListFormCode",
"OnlyBaseDefinitions",
"ResourceName",
"MaxForms",
"CacheKey",
"JsonData",
"FormCount",
"FieldCount",
"JsonLength",
"IsActive",
"RefreshedAt",
"SourceProcedure",
"Note"
)
VALUES
(
COALESCE(p_tenant_name, 'Sozsoft'),
'ListFormJson',
COALESCE(p_definition_culture_name, 'en'),
COALESCE(p_ui_culture_name, 'tr'),
p_list_form_code,
COALESCE(p_only_base_definitions, TRUE),
p_resource_name,
COALESCE(p_max_forms, 500),
v_cache_key,
v_json_data,
v_form_count,
v_field_count,
v_json_length,
TRUE,
CURRENT_TIMESTAMP,
'dbo.Sas_H_ListFormFieldsDictionaryJson',
p_note
);
/*
Eklenen aktif kaydı döndür
*/
RETURN QUERY
SELECT
T."TenantName",
T."DictionaryType",
T."DefinitionCultureName",
T."UiCultureName",
T."ListFormCode",
T."OnlyBaseDefinitions",
T."ResourceName",
T."MaxForms",
T."CacheKey",
T."FormCount",
T."FieldCount",
T."JsonLength",
T."IsActive",
T."RefreshedAt",
T."SourceProcedure",
T."Note"
FROM dbo."Sas_H_DbTableColumn" T
WHERE T."DictionaryType" = 'ListFormJson'
AND T."CacheKey" = v_cache_key
AND T."IsActive" = TRUE
ORDER BY T."RefreshedAt" DESC
LIMIT 1;
END;
$$;

View file

@ -0,0 +1,22 @@
CREATE OR REPLACE VIEW dbo."Sas_H_OrderItem_ByTenantId"
AS
SELECT
oi."Id",
o."TenantId",
oi."ProductId",
oi."ProductName",
oi."BillingCycle",
oi."Period",
oi."Quantity",
oi."Price",
oi."VatRate",
oi."VatAmount",
oi."TotalPrice",
oi."OrderId",
o."IsActive"
FROM dbo."Sas_H_Order" o
INNER JOIN dbo."Sas_H_OrderItem" oi
ON o."Id" = oi."OrderId";

View file

@ -0,0 +1,8 @@
-- Create View
CREATE OR ALTER VIEW [dbo].[Sas_H_OrderItem_ByTenantId]
AS
SELECT dbo.Sas_H_OrderItem.Id, dbo.Sas_H_Order.TenantId, dbo.Sas_H_OrderItem.ProductId, dbo.Sas_H_OrderItem.ProductName, dbo.Sas_H_OrderItem.BillingCycle, dbo.Sas_H_OrderItem.Period, dbo.Sas_H_OrderItem.Quantity,
dbo.Sas_H_OrderItem.Price, dbo.Sas_H_OrderItem.VatRate, dbo.Sas_H_OrderItem.VatAmount, dbo.Sas_H_OrderItem.TotalPrice, dbo.Sas_H_OrderItem.OrderId, dbo.Sas_H_Order.IsActive
FROM dbo.Sas_H_Order INNER JOIN
dbo.Sas_H_OrderItem ON dbo.Sas_H_Order.Id = dbo.Sas_H_OrderItem.OrderId

View file

@ -0,0 +1,18 @@
IF OBJECT_ID(N'[dbo].[Pro_T_Approval]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Pro_T_Approval]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[ApprovalUserName] nvarchar(256) NULL,
[ApprovalStatus] nvarchar(50) NULL,
[ApprovalDate] datetime NULL,
[ApprovalDescription] nvarchar(200) NULL,
[TenantId] uniqueidentifier NULL,
[Title] nvarchar(100) NULL,
CONSTRAINT [PK_Pro_T_Approval] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,22 @@
IF OBJECT_ID(N'[dbo].[Pro_T_Todo]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Pro_T_Todo]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[Title] nvarchar(300) NOT NULL,
[Status] nvarchar(50) NOT NULL DEFAULT N'Inbox',
[Description] nvarchar(MAX) NULL,
[DueDate] datetime2 NULL,
[Tags] nvarchar(500) NULL,
[Assignees] nvarchar(500) NULL,
[Priority] nvarchar(50) NULL,
[Completed] bit NOT NULL DEFAULT 0,
[SortOrder] int NOT NULL DEFAULT 0,
[TenantId] uniqueidentifier NULL,
CONSTRAINT [PK_Table] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -1,25 +0,0 @@
IF OBJECT_ID(N'[dbo].[Sal_T_Approval]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Sal_T_Approval]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
[TenantId] uniqueidentifier NULL,
[ApprovalUserName] nvarchar(256) NULL,
[ApprovalStatus] nvarchar(50) NULL,
[ApprovalDate] datetime NULL,
[ApprovalDescription] nvarchar(200) NULL,
[Name] nvarchar(100) NULL,
CONSTRAINT [PK_Sal_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

@ -13,27 +13,30 @@
"ConfirmDelete": true, "ConfirmDelete": true,
"DefaultLayout": "grid", "DefaultLayout": "grid",
"Grid": true, "Grid": true,
"Card": true,
"Pivot": true, "Pivot": true,
"Tree": true, "Tree": true,
"Chart": true, "Chart": true,
"Gantt": true, "Gantt": true,
"Scheduler": true, "Scheduler": true,
"Todo": false,
"LanguageTextMenuEn": "Approval", "LanguageTextMenuEn": "Approval",
"LanguageTextMenuTr": "Approval", "LanguageTextMenuTr": "Onaylama",
"LanguageTextTitleEn": "Approval", "LanguageTextTitleEn": "Approval",
"LanguageTextTitleTr": "Onaylama", "LanguageTextTitleTr": "Onaylama",
"LanguageTextDescEn": "Approval", "LanguageTextDescEn": "Approval",
"LanguageTextDescTr": "Onaylama", "LanguageTextDescTr": "Onaylama",
"LanguageTextMenuParentEn": "Sales", "LanguageTextMenuParentEn": "Project",
"LanguageTextMenuParentTr": "Sat\u0131\u015F", "LanguageTextMenuParentTr": "Project",
"PermissionGroupName": "App.Wizard.Sales", "PermissionGroupName": "App.Wizard.Project",
"MenuParentCode": "App.Wizard.Sales", "MenuParentCode": "App.Wizard.Project",
"MenuParentIcon": "FcAssistant", "MenuParentShortName": "Pro",
"MenuIcon": "FcAndroidOs", "MenuParentIcon": "FcVideoProjector",
"MenuIcon": "FcApproval",
"DataSourceCode": "Default", "DataSourceCode": "Default",
"DataSourceConnectionString": "", "DataSourceConnectionString": "",
"SelectCommandType": 1, "SelectCommandType": 1,
"SelectCommand": "Sal_T_Approval", "SelectCommand": "Pro_T_Approval",
"KeyFieldName": "Id", "KeyFieldName": "Id",
"KeyFieldDbSourceType": 9, "KeyFieldDbSourceType": 9,
"TreeKeyExpr": "", "TreeKeyExpr": "",
@ -49,13 +52,103 @@
"SchedulerTextExpr": "", "SchedulerTextExpr": "",
"SchedulerStartDateExpr": "", "SchedulerStartDateExpr": "",
"SchedulerEndDateExpr": "", "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": [ "Groups": [
{ {
"Caption": "", "Caption": "",
"ColCount": 1, "ColCount": 1,
"Items": [ "Items": [
{ {
"DataField": "Id", "FieldName": "Id",
"CaptionName": "App.Listform.ListformField.Id", "CaptionName": "App.Listform.ListformField.Id",
"EditorType": "dxTextBox", "EditorType": "dxTextBox",
"EditorOptions": "", "EditorOptions": "",
@ -72,24 +165,7 @@
"LookupQuery": "" "LookupQuery": ""
}, },
{ {
"DataField": "Name", "FieldName": "ApprovalUserName",
"CaptionName": "App.Listform.ListformField.Name",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Name",
"EnglishCaption": "Name",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "ApprovalUserName",
"CaptionName": "App.Listform.ListformField.ApprovalUserName", "CaptionName": "App.Listform.ListformField.ApprovalUserName",
"EditorType": "dxTextBox", "EditorType": "dxTextBox",
"EditorOptions": "", "EditorOptions": "",
@ -106,7 +182,7 @@
"LookupQuery": "" "LookupQuery": ""
}, },
{ {
"DataField": "ApprovalStatus", "FieldName": "ApprovalStatus",
"CaptionName": "App.Listform.ListformField.ApprovalStatus", "CaptionName": "App.Listform.ListformField.ApprovalStatus",
"EditorType": "dxTextBox", "EditorType": "dxTextBox",
"EditorOptions": "", "EditorOptions": "",
@ -123,7 +199,7 @@
"LookupQuery": "" "LookupQuery": ""
}, },
{ {
"DataField": "ApprovalDate", "FieldName": "ApprovalDate",
"CaptionName": "App.Listform.ListformField.ApprovalDate", "CaptionName": "App.Listform.ListformField.ApprovalDate",
"EditorType": "dxDateBox", "EditorType": "dxDateBox",
"EditorOptions": "", "EditorOptions": "",
@ -140,7 +216,7 @@
"LookupQuery": "" "LookupQuery": ""
}, },
{ {
"DataField": "ApprovalDescription", "FieldName": "ApprovalDescription",
"CaptionName": "App.Listform.ListformField.ApprovalDescription", "CaptionName": "App.Listform.ListformField.ApprovalDescription",
"EditorType": "dxTextBox", "EditorType": "dxTextBox",
"EditorOptions": "", "EditorOptions": "",
@ -155,219 +231,39 @@
"ValueExpr": "Key", "ValueExpr": "Key",
"DisplayExpr": "Name", "DisplayExpr": "Name",
"LookupQuery": "" "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": [], "SubForms": [],
"Widgets": [], "Widgets": []
"Workflow": {
"ApprovalUserFieldName": "ApprovalUserName",
"IsFilterUserName": true,
"ApprovalDateFieldName": "ApprovalDate",
"ApprovalStatusFieldName": "ApprovalStatus",
"ApprovalDescriptionFieldName": "ApprovalDescription",
"Criteria": [
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Start",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "N002",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 34,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N001"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "admin@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N003",
"NextOnReject": "N004",
"PositionX": 323,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N002"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay2",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "demo@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N004",
"NextOnReject": "N004",
"PositionX": 586,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N003"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Inform",
"Title": "Bilgilendirme1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "system@sozsoft.com",
"NextOnStart": "N005",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 458,
"PositionY": 411,
"CompareOutcomes": [],
"Id": "N004"
},
{
"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": 792,
"PositionY": 412,
"CompareOutcomes": [],
"Id": "N005"
}
]
},
"WorkflowCriteria": [
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Start",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "N002",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 34,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N001"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "admin@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N003",
"NextOnReject": "N004",
"PositionX": 323,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N002"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay2",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "demo@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N004",
"NextOnReject": "N004",
"PositionX": 586,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N003"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Inform",
"Title": "Bilgilendirme1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "system@sozsoft.com",
"NextOnStart": "N005",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 458,
"PositionY": 411,
"CompareOutcomes": [],
"Id": "N004"
},
{
"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": 792,
"PositionY": 412,
"CompareOutcomes": [],
"Id": "N005"
}
]
}, },
"IsDeletedField": true, "IsDeletedField": false,
"IsCreatedField": true, "IsCreatedField": false,
"InsertedRecords": { "InsertedRecords": {
"LanguageKeys": [ "LanguageKeys": [
"App.Wizard.Approval", "App.Wizard.Approval",
"App.Wizard.Approval.Title", "App.Wizard.Approval.Title",
"App.Wizard.Approval.Desc", "App.Wizard.Approval.Desc"
"App.Listform.ListformField.ApprovalUserName",
"App.Listform.ListformField.ApprovalStatus",
"App.Listform.ListformField.ApprovalDate",
"App.Listform.ListformField.ApprovalDescription"
],
"PermissionGroupNames": [
"App.Wizard.Sales"
], ],
"PermissionGroupNames": [],
"PermissionNames": [ "PermissionNames": [
"App.Wizard.Approval", "App.Wizard.Approval",
"App.Wizard.Approval.Create", "App.Wizard.Approval.Create",

View file

@ -0,0 +1,281 @@
{
"Wizard": {
"WizardName": "Todo",
"ListFormCode": "App.Wizard.Todo",
"MenuCode": "App.Wizard.Todo",
"IsTenant": true,
"IsBranch": false,
"IsOrganizationUnit": false,
"AllowAdding": true,
"AllowUpdating": true,
"AllowDeleting": true,
"AllowDetail": false,
"ConfirmDelete": true,
"DefaultLayout": "todo",
"Grid": true,
"Card": true,
"Pivot": true,
"Tree": true,
"Chart": true,
"Gantt": true,
"Scheduler": true,
"Todo": true,
"LanguageTextMenuEn": "Todo",
"LanguageTextMenuTr": "\u0130\u015Fler",
"LanguageTextTitleEn": "Todo",
"LanguageTextTitleTr": "\u0130\u015Fler",
"LanguageTextDescEn": "Todo",
"LanguageTextDescTr": "\u0130\u015Fler",
"LanguageTextMenuParentEn": "Project",
"LanguageTextMenuParentTr": "Project",
"PermissionGroupName": "App.Wizard.Project",
"MenuParentCode": "App.Wizard.Project",
"MenuParentShortName": "Pro",
"MenuParentIcon": "FcVideoProjector",
"MenuIcon": "FcTodoList",
"DataSourceCode": "Default",
"DataSourceConnectionString": "",
"SelectCommandType": 1,
"SelectCommand": "Pro_T_Todo",
"KeyFieldName": "Id",
"KeyFieldDbSourceType": 9,
"TreeKeyExpr": "",
"TreeParentIdExpr": "",
"TreeAutoExpandAll": false,
"GanttKeyExpr": "",
"GanttParentIdExpr": "",
"GanttAutoExpandAll": false,
"GanttTitleExpr": "",
"GanttStartExpr": "",
"GanttEndExpr": "",
"GanttProgressExpr": "",
"SchedulerTextExpr": "",
"SchedulerStartDateExpr": "",
"SchedulerEndDateExpr": "",
"TodoTitleExpr": "Title",
"TodoStatusExpr": "Status",
"TodoDescriptionExpr": "Description",
"TodoDueDateExpr": "DueDate",
"TodoTagExpr": "Tags",
"TodoAssigneeExpr": "Assignees",
"TodoPriorityExpr": "Priority",
"TodoCompletedExpr": "Completed",
"TodoOrderExpr": "SortOrder",
"TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
"TodoAllowDragging": true,
"ApprovalUserFieldName": "",
"ApprovalDateFieldName": "",
"ApprovalStatusFieldName": "",
"ApprovalDescriptionFieldName": "",
"ApprovalIsFilterUserName": false,
"Criteria": [],
"Groups": [
{
"Caption": "",
"ColCount": 1,
"Items": [
{
"FieldName": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 9,
"TurkishCaption": "Id",
"EnglishCaption": "Id",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Title",
"CaptionName": "App.Listform.ListformField.Title",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Title",
"EnglishCaption": "Title",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Status",
"CaptionName": "App.Listform.ListformField.Status",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Status",
"EnglishCaption": "Status",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Description",
"CaptionName": "App.Listform.ListformField.Description",
"EditorType": "dxHtmlEditor",
"EditorOptions": "{\u0022mediaResizing\u0022:{\u0022enabled\u0022:true},\u0022imageUpload\u0022:{\u0022tabs\u0022:[\u0022file\u0022,\u0022url\u0022],\u0022fileUploadMode\u0022:\u0022base64\u0022},\u0022toolbar\u0022:{\u0022multiline\u0022:true,\u0022items\u0022:[{\u0022name\u0022:\u0022undo\u0022},{\u0022name\u0022:\u0022redo\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022size\u0022,\u0022acceptedValues\u0022:[\u00228pt\u0022,\u002210pt\u0022,\u002212pt\u0022,\u002214pt\u0022,\u002218pt\u0022,\u002224pt\u0022,\u002236pt\u0022],\u0022options\u0022:{\u0022inputAttr\u0022:{\u0022aria-label\u0022:\u0022Font size\u0022}}},{\u0022name\u0022:\u0022font\u0022,\u0022acceptedValues\u0022:[\u0022Arial\u0022,\u0022Courier New\u0022,\u0022Georgia\u0022,\u0022Impact\u0022,\u0022Lucida Console\u0022,\u0022Tahoma\u0022,\u0022Times New Roman\u0022,\u0022Verdana\u0022],\u0022options\u0022:{\u0022inputAttr\u0022:{\u0022aria-label\u0022:\u0022Font family\u0022}}},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022bold\u0022},{\u0022name\u0022:\u0022italic\u0022},{\u0022name\u0022:\u0022strike\u0022},{\u0022name\u0022:\u0022underline\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022alignLeft\u0022},{\u0022name\u0022:\u0022alignCenter\u0022},{\u0022name\u0022:\u0022alignRight\u0022},{\u0022name\u0022:\u0022alignJustify\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022orderedList\u0022},{\u0022name\u0022:\u0022bulletList\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022header\u0022,\u0022acceptedValues\u0022:[false,1,2,3,4,5]},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022color\u0022},{\u0022name\u0022:\u0022background\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022link\u0022},{\u0022name\u0022:\u0022image\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022clear\u0022},{\u0022name\u0022:\u0022codeBlock\u0022},{\u0022name\u0022:\u0022blockquote\u0022},{\u0022name\u0022:\u0022separator\u0022},{\u0022name\u0022:\u0022insertTable\u0022},{\u0022name\u0022:\u0022deleteTable\u0022},{\u0022name\u0022:\u0022insertRowAbove\u0022},{\u0022name\u0022:\u0022insertRowBelow\u0022},{\u0022name\u0022:\u0022deleteRow\u0022},{\u0022name\u0022:\u0022insertColumnLeft\u0022},{\u0022name\u0022:\u0022insertColumnRight\u0022},{\u0022name\u0022:\u0022deleteColumn\u0022},{\u0022name\u0022:\u0022cellProperties\u0022},{\u0022name\u0022:\u0022tableProperties\u0022}]}}",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Description",
"EnglishCaption": "Description",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "DueDate",
"CaptionName": "App.Listform.ListformField.DueDate",
"EditorType": "dxDateBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 6,
"TurkishCaption": "Due Date",
"EnglishCaption": "Due Date",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Tags",
"CaptionName": "App.Listform.ListformField.Tags",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Tags",
"EnglishCaption": "Tags",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Assignees",
"CaptionName": "App.Listform.ListformField.Assignees",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Assignees",
"EnglishCaption": "Assignees",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Priority",
"CaptionName": "App.Listform.ListformField.Priority",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Priority",
"EnglishCaption": "Priority",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "Completed",
"CaptionName": "App.Listform.ListformField.Completed",
"EditorType": "dxCheckBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": false,
"DbSourceType": 3,
"TurkishCaption": "Completed",
"EnglishCaption": "Completed",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"FieldName": "SortOrder",
"CaptionName": "App.Listform.ListformField.SortOrder",
"EditorType": "dxNumberBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": false,
"DbSourceType": 11,
"TurkishCaption": "Sort Order",
"EnglishCaption": "Sort Order",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
}
]
}
],
"SubForms": [],
"Widgets": []
},
"IsDeletedField": false,
"IsCreatedField": false,
"InsertedRecords": {
"LanguageKeys": [
"App.Wizard.Todo",
"App.Wizard.Todo.Title",
"App.Wizard.Todo.Desc",
"App.Listform.ListformField.Assignees",
"App.Listform.ListformField.Completed",
"App.Listform.ListformField.SortOrder"
],
"PermissionGroupNames": [
"App.Wizard.Project"
],
"PermissionNames": [
"App.Wizard.Todo",
"App.Wizard.Todo.Create",
"App.Wizard.Todo.Update",
"App.Wizard.Todo.Delete",
"App.Wizard.Todo.Export",
"App.Wizard.Todo.Import",
"App.Wizard.Todo.Note"
],
"MenuCodes": [
"App.Wizard.Todo"
],
"DataSourceCodes": []
}
}

View file

@ -107,6 +107,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var fileName = Path.GetFileName(filePath); var fileName = Path.GetFileName(filePath);
await BackfillMenuParentShortNameAsync(seedFile.Wizard);
// Zaten seeded mi kontrol et (ListForm var mı?) // Zaten seeded mi kontrol et (ListForm var mı?)
if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode)) if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode))
{ {
@ -132,14 +134,17 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var isCreated = seedFile.IsCreatedField; var isCreated = seedFile.IsCreatedField;
input.SubForms ??= new List<SubFormDto>(); input.SubForms ??= new List<SubFormDto>();
input.Widgets ??= new List<WidgetEditDto>(); input.Widgets ??= new List<WidgetEditDto>();
input.Workflow ??= new WorkflowDto(); input.Criteria ??= [];
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>(); EnsureUniqueWorkflowCriteriaTitles(input.Criteria);
if (input.WorkflowCriteria.Count == 0 && input.Workflow.Criteria?.Count > 0) var workflow = new WorkflowDto
{ {
input.WorkflowCriteria = input.Workflow.Criteria; ApprovalUserFieldName = input.ApprovalUserFieldName,
} ApprovalIsFilterUserName = input.ApprovalIsFilterUserName,
input.Workflow.Criteria = input.WorkflowCriteria; ApprovalDateFieldName = input.ApprovalDateFieldName,
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); ApprovalStatusFieldName = input.ApprovalStatusFieldName,
ApprovalDescriptionFieldName = input.ApprovalDescriptionFieldName,
Criteria = input.Criteria,
};
var wizardName = input.WizardName.Trim(); var wizardName = input.WizardName.Trim();
var code = string.IsNullOrWhiteSpace(input.MenuCode) var code = string.IsNullOrWhiteSpace(input.MenuCode)
@ -148,7 +153,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode) var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
? code ? code
: input.ListFormCode.Trim(); : input.ListFormCode.Trim();
NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria); NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria);
var titleLangKey = $"{listFormCode}.Title"; var titleLangKey = $"{listFormCode}.Title";
var nameLangKey = code; var nameLangKey = code;
var descLangKey = $"{listFormCode}.Desc"; var descLangKey = $"{listFormCode}.Desc";
@ -250,6 +255,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
Code = input.MenuParentCode, Code = input.MenuParentCode,
DisplayName = input.MenuParentCode, DisplayName = input.MenuParentCode,
IsDisabled = false, IsDisabled = false,
ShortName = input.MenuParentShortName,
Icon = menuParentIcon, Icon = menuParentIcon,
Order = maxRootOrder + 1, Order = maxRootOrder + 1,
}, autoSave: true); }, autoSave: true);
@ -299,21 +305,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
Caption = g.Caption, Caption = g.Caption,
ColCount = g.ColCount, ColCount = g.ColCount,
ColSpan = g.ColCount, ColSpan = g.ColCount,
ItemType = "group", 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()
}) })
.Where(g => g.Items.Length > 0) .Where((_, index) => input.Groups[index].Items.Any(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName))
.ToList(); .ToList();
// ListForm - varsa sil, yeniden ekle // ListForm - varsa sil, yeniden ekle
@ -338,8 +332,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
// EditingForm'da required olan alanları tespit et, ValidationRuleJson eklemek için // EditingForm'da required olan alanları tespit et, ValidationRuleJson eklemek için
var editingFormFieldNames = input.Groups var editingFormFieldNames = input.Groups
.SelectMany(g => g.Items) .SelectMany(g => g.Items)
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName) .Where(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName)
.Select(i => new { FieldName = i.DataField, Isrequired = i.IsRequired }) .Select(i => new { FieldName = i.FieldName, Isrequired = i.IsRequired })
.ToList(); .ToList();
// Heigth ve Width hesaplama // Heigth ve Width hesaplama
@ -347,7 +341,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
.Select(g => .Select(g =>
{ {
var items = g.Items var items = g.Items
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName) .Where(i => i.IncludeInEditingForm && i.FieldName != input.KeyFieldName)
.ToList(); .ToList();
var columnCount = g.ColCount; var columnCount = g.ColCount;
@ -392,8 +386,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
PageSize = 10, PageSize = 10,
ExportJson = WizardConsts.DefaultExportJson, ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false, IsSubForm = false,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
CultureName = LanguageCodes.En, CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
Name = nameLangKey, Name = nameLangKey,
@ -413,41 +407,74 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(), HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(), SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(), GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone), SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Criteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(), ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code), PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
DeleteFieldsDefaultValueJson = isDeleted DeleteFieldsDefaultValueJson = isDeleted
? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType)
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), : WizardConsts.DefaultDeleteFieldsJsonOnlyId(input.KeyFieldDbSourceType),
InsertFieldsDefaultValueJson = isCreated InsertFieldsDefaultValueJson = isCreated
? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType)
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType), : WizardConsts.DefaultInsertFieldsJsonOnlyId(input.KeyFieldDbSourceType),
PagerOptionJson = WizardConsts.DefaultPagerOptionJson, PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, formWidth, formHeight, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail), 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, EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null, SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null, WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
WorkflowJson = HasWorkflow(input.Workflow, input.WorkflowCriteria) ? JsonSerializer.Serialize(input.Workflow) : null, WorkflowJson = HasWorkflow(workflow, input.Criteria) ? JsonSerializer.Serialize(workflow) : null,
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
!string.IsNullOrEmpty(input.TodoTitleExpr) &&
!string.IsNullOrEmpty(input.TodoStatusExpr)
? JsonSerializer.Serialize(new TodoOptionDto
{
TitleExpr = input.TodoTitleExpr,
StatusExpr = input.TodoStatusExpr,
DescriptionExpr = input.TodoDescriptionExpr,
DueDateExpr = input.TodoDueDateExpr,
TagExpr = input.TodoTagExpr,
AssigneeExpr = input.TodoAssigneeExpr,
PriorityExpr = input.TodoPriorityExpr,
CompletedExpr = input.TodoCompletedExpr,
OrderExpr = input.TodoOrderExpr,
StatusOrder = input.TodoStatusOrder,
AllowDragging = input.TodoAllowDragging,
})
: null,
}, autoSave: true); }, autoSave: true);
// ListFormFields // ListFormFields
var fieldOrder = 0; var fieldOrder = 0;
var editGroupOrder = 0;
foreach (var group in input.Groups) foreach (var group in input.Groups)
{ {
editGroupOrder++;
var editOrder = 0;
foreach (var item in group.Items) foreach (var item in group.Items)
{ {
fieldOrder++; fieldOrder++;
if (item.IncludeInEditingForm && item.FieldName != input.KeyFieldName)
{
editOrder++;
}
await _repoListFormField.InsertAsync(new ListFormField await _repoListFormField.InsertAsync(new ListFormField
{ {
ListFormCode = input.ListFormCode, ListFormCode = input.ListFormCode,
FieldName = item.DataField, FieldName = item.FieldName,
CaptionName = item.CaptionName, CaptionName = item.CaptionName,
Visible = item.DataField != input.KeyFieldName, Visible = item.FieldName != input.KeyFieldName,
IsActive = true, IsActive = true,
Width = 0, //Fit columns için hepsine sıfır veriyorum. Width = 0, //Fit columns için hepsine sıfır veriyorum.
AllowSearch = true, AllowSearch = true,
ListOrderNo = fieldOrder, 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, SourceDbType = item.DbSourceType,
CultureName = PlatformConsts.DefaultLanguage, CultureName = PlatformConsts.DefaultLanguage,
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code), PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),
@ -457,7 +484,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery) LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery)
? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery) ? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery)
: null, : null,
ValidationRuleJson = editingFormFieldNames.Any(f => f.FieldName == item.DataField && f.Isrequired) ValidationRuleJson = editingFormFieldNames.Any(f => f.FieldName == item.FieldName && f.Isrequired)
? WizardConsts.DefaultValidationRuleRequiredJson ? WizardConsts.DefaultValidationRuleRequiredJson
: null : null
}, autoSave: true); }, autoSave: true);
@ -466,7 +493,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
} }
} }
foreach (var criteria in input.WorkflowCriteria) foreach (var criteria in input.Criteria)
{ {
if (string.IsNullOrWhiteSpace(criteria.Id)) if (string.IsNullOrWhiteSpace(criteria.Id))
{ {
@ -495,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) private static bool HasWorkflow(WorkflowDto workflow, List<ListFormWorkflowCriteriaDto> criteria)
{ {
return workflow != null && ( return workflow != null && (

View file

@ -63,6 +63,7 @@ public enum TableNameEnum
InstallmentOption, InstallmentOption,
Order, Order,
OrderItem, OrderItem,
OrderItem_ByTenantId,
BlogCategory, BlogCategory,
BlogPost, BlogPost,
Demo, Demo,
@ -94,5 +95,5 @@ public enum TableNameEnum
VideoroomAttandance, VideoroomAttandance,
VideoroomChat, VideoroomChat,
Sequence, Sequence,
DbTableColumn DbTableColumn,
} }

View file

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

View file

@ -59,6 +59,7 @@ public static class TableNameResolver
{ nameof(TableNameEnum.InstallmentOption), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.InstallmentOption), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.Order), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Order), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.OrderItem), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.OrderItem), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.OrderItem_ByTenantId), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BlogCategory), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BlogCategory), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BlogPost), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BlogPost), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.Demo), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Demo), (TablePrefix.PlatformByName, MenuPrefix.Saas) },

View file

@ -84,14 +84,16 @@ public static class WizardConsts
Margin = 10 Margin = 10
}); });
public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Pivot = true, bool Chart = true, bool Tree = true, bool Gantt = true, bool Scheduler = true) => JsonSerializer.Serialize(new public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Card = true, bool Pivot = true, bool Chart = true, bool Tree = true, bool Gantt = true, bool Scheduler = true, bool Todo = true) => JsonSerializer.Serialize(new
{ {
Grid = Grid, Grid = Grid,
Card = Card,
Pivot = Pivot, Pivot = Pivot,
Chart = Chart, Chart = Chart,
Tree = Tree, Tree = Tree,
Gantt = Gantt, Gantt = Gantt,
Scheduler = Scheduler, Scheduler = Scheduler,
Todo = Todo,
DefaultLayout = DefaultLayout, DefaultLayout = DefaultLayout,
}); });
@ -196,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) public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid)
{ {
return JsonSerializer.Serialize(new[] return JsonSerializer.Serialize(new[]
@ -205,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[] 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

@ -416,6 +416,8 @@ public static class SeedConsts
public const string RecurringJobs = Default + ".RecurringJobs"; public const string RecurringJobs = Default + ".RecurringJobs";
public const string Jobs = Default + ".Jobs"; public const string Jobs = Default + ".Jobs";
} }
public const string Licence = Prefix.App + ".Licence";
public const string LicenceItem = Prefix.App + ".LicenceItem";
//Web Site //Web Site
public const string Home = Prefix.App + ".Home"; public const string Home = Prefix.App + ".Home";

View file

@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities namespace Sozsoft.Platform.Entities
{ {
public class ReportCategory : FullAuditedEntity<string>, IMultiTenant public class ReportCategory : FullAuditedEntity<Guid>, IMultiTenant
{ {
public Guid? TenantId { get; set; } public Guid? TenantId { get; set; }
@ -15,9 +15,12 @@ namespace Sozsoft.Platform.Entities
public ICollection<ReportTemplate> ReportTemplates { get; set; } public ICollection<ReportTemplate> ReportTemplates { get; set; }
public ReportCategory(string id) protected ReportCategory()
{
}
public ReportCategory(Guid id) : base(id)
{ {
Id = id;
} }
} }
} }

View file

@ -12,7 +12,7 @@ namespace Sozsoft.Platform.Entities
public string Description { get; set; } public string Description { get; set; }
public string HtmlContent { get; set; } public string HtmlContent { get; set; }
public string CategoryId { get; set; } public Guid CategoryId { get; set; }
public ReportCategory ReportCategory { get; set; } public ReportCategory ReportCategory { get; set; }
public string Status { get; set; } public string Status { get; set; }
@ -23,7 +23,7 @@ namespace Sozsoft.Platform.Entities
string name, string name,
string description, string description,
string htmlContent, string htmlContent,
string categoryId, Guid categoryId,
string status string status
) : base(id) ) : base(id)
{ {

View file

@ -36,6 +36,7 @@ public class ListForm : Entity<Guid>
public string TreeOptionJson { get; set; } // Tree yapisi ile ilgili ayarlar public string TreeOptionJson { get; set; } // Tree yapisi ile ilgili ayarlar
public string GanttOptionJson { get; set; } // Gantt yapisi ile ilgili ayarlar public string GanttOptionJson { get; set; } // Gantt yapisi ile ilgili ayarlar
public string SchedulerOptionJson { get; set; } // Scheduler yapisi ile ilgili ayarlar public string SchedulerOptionJson { get; set; } // Scheduler yapisi ile ilgili ayarlar
public string TodoOptionJson { get; set; } // Todo board alan esleme ayarlari
public string FilterRowJson { get; set; } // Filtre ayarlari, Json olarak tutulur, donus sinifi FilterRowDto public string FilterRowJson { get; set; } // Filtre ayarlari, Json olarak tutulur, donus sinifi FilterRowDto
public string RowJson { get; set; } // Row ayarları, Json olarak tutulur, donus sinifi FilterRowDto public string RowJson { get; set; } // Row ayarları, Json olarak tutulur, donus sinifi FilterRowDto
public string HeaderFilterJson { get; set; } // Header filtreleme ayarlari, Json olarak tutulur public string HeaderFilterJson { get; set; } // Header filtreleme ayarlari, Json olarak tutulur

View file

@ -53,7 +53,13 @@ public class ListFormField : Entity<Guid>
public string Alignment { get; set; } = "left"; // Sütunun Sağa, sola veya ortalama gösterimi 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 string Format { get; set; } // Cell bazında sütunun formatlanması
public string EditorOptions { get; set; } //Sütunun ekstra özellikleri yönetilmesi
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

@ -52,7 +52,9 @@ public class QueryHelper
case DbType.String: case DbType.String:
case DbType.AnsiString: case DbType.AnsiString:
var strValues = values.Select(a => a?.ToString()).ToArray(); var strValues = values.Select(a => a?.ToString()).ToArray();
value = string.Join(PlatformConsts.MultiValueDelimiter, strValues); value = isArray
? string.Join(PlatformConsts.MultiValueDelimiter, strValues)
: strValues[0];
break; break;
case DbType.Date: case DbType.Date:
case DbType.DateTime: case DbType.DateTime:

View file

@ -170,7 +170,8 @@ public class QueryManager : PlatformDomainService, IQueryManager
// oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir // oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir
if (!string.IsNullOrEmpty(command)) if (!string.IsNullOrEmpty(command))
{ {
sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType); // sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType);
sql = command;
} }
else else
{ {
@ -188,12 +189,6 @@ public class QueryManager : PlatformDomainService, IQueryManager
} }
else if (op == OperationEnum.Update) 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 updateFields = parameters.Select(a => $"\"{a.Key}\" = @{a.Key}").ToList();
var val = QueryHelper.GetFormattedValue(listForm.KeyFieldDbSourceType, keys); var val = QueryHelper.GetFormattedValue(listForm.KeyFieldDbSourceType, keys);
if (!parameters.ContainsKey(listForm.KeyFieldName)) if (!parameters.ContainsKey(listForm.KeyFieldName))
@ -202,6 +197,18 @@ public class QueryManager : PlatformDomainService, IQueryManager
listForm.KeyFieldName, listForm.KeyFieldName,
val.GetType().IsArray ? val : new object[] { val }); 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}"; sql = $"UPDATE \"{listForm.SelectCommand}\" SET {string.Join(',', updateFields)} WHERE {where}";
} }
else if (op == OperationEnum.Delete) else if (op == OperationEnum.Delete)
@ -212,12 +219,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
{ {
where = string.Join( where = string.Join(
" AND ", " AND ",
parameters.Select(a => dataSourceType switch parameters.Select(a => BuildWhereCondition(a.Key, a.Value, dataSourceType)).ToList());
{
DataSourceTypeEnum.Mssql => $"\"{a.Key}\" IN @{a.Key}",
DataSourceTypeEnum.Postgresql => $"\"{a.Key}\" = ANY(@{a.Key})",
_ => "1 = 0",
}).ToList());
} }
else else
{ {
@ -240,77 +242,101 @@ public class QueryManager : PlatformDomainService, IQueryManager
return sql; return sql;
} }
private static string NormalizeCollectionParameterCommand( private static bool IsCollectionValue(object value)
string command,
Dictionary<string, object> parameters,
DataSourceTypeEnum dataSourceType)
{
if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0)
{
return command;
}
var sql = command;
foreach (var parameter in parameters)
{
if (!IsCollectionParameter(parameter.Value))
{
continue;
}
var escapedParameterName = Regex.Escape(parameter.Key);
sql = dataSourceType switch
{
DataSourceTypeEnum.Mssql => NormalizeMssqlCollectionParameter(sql, escapedParameterName, parameter.Key),
DataSourceTypeEnum.Postgresql => NormalizePostgresqlCollectionParameter(sql, escapedParameterName, parameter.Key),
_ => sql
};
}
return sql;
}
private static bool IsCollectionParameter(object value)
{ {
return value is System.Collections.IEnumerable return value is System.Collections.IEnumerable
&& value is not string && value is not string
&& value is not byte[]; && value is not byte[];
} }
private static string NormalizeMssqlCollectionParameter( private static string BuildWhereCondition(
string sql, string fieldName,
string escapedParameterName, object value,
string parameterName) DataSourceTypeEnum dataSourceType)
{ {
sql = Regex.Replace( return dataSourceType switch
sql, {
$@"IN\s*\(\s*@{escapedParameterName}\s*\)", DataSourceTypeEnum.Mssql when value is null or DBNull => $"\"{fieldName}\" IS NULL",
$"IN @{parameterName}", DataSourceTypeEnum.Postgresql when value is null or DBNull => $"\"{fieldName}\" IS NULL",
RegexOptions.IgnoreCase); DataSourceTypeEnum.Mssql when IsCollectionValue(value) => $"\"{fieldName}\" IN @{fieldName}",
DataSourceTypeEnum.Postgresql when IsCollectionValue(value) => $"\"{fieldName}\" = ANY(@{fieldName})",
return Regex.Replace( DataSourceTypeEnum.Mssql => $"\"{fieldName}\" = @{fieldName}",
sql, DataSourceTypeEnum.Postgresql => $"\"{fieldName}\" = @{fieldName}",
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", _ => "1 = 0",
match => $"{match.Groups["column"].Value} IN @{parameterName}", };
RegexOptions.IgnoreCase);
} }
private static string NormalizePostgresqlCollectionParameter( // private static string NormalizeCollectionParameterCommand(
string sql, // string command,
string escapedParameterName, // Dictionary<string, object> parameters,
string parameterName) // DataSourceTypeEnum dataSourceType)
{ // {
sql = Regex.Replace( // if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0)
sql, // {
$@"IN\s*\(\s*@{escapedParameterName}\s*\)", // return command;
$"= ANY(@{parameterName})", // }
RegexOptions.IgnoreCase);
return Regex.Replace( // var sql = command;
sql, // foreach (var parameter in parameters)
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b", // {
match => $"{match.Groups["column"].Value} = ANY(@{parameterName})", // if (!IsCollectionParameter(parameter.Value))
RegexOptions.IgnoreCase); // {
} // 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

@ -469,7 +469,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
if (listform.WorkflowJson.IsNullOrWhiteSpace() == false) if (listform.WorkflowJson.IsNullOrWhiteSpace() == false)
{ {
var workflow = JsonSerializer.Deserialize<Workflow>(listform.WorkflowJson); var workflow = JsonSerializer.Deserialize<Workflow>(listform.WorkflowJson);
if (workflow != null && workflow.IsFilterUserName) if (workflow != null && workflow.ApprovalIsFilterUserName)
{ {
if (whereParts.Any()) if (whereParts.Any())
{ {

View file

@ -6,18 +6,18 @@ namespace Sozsoft.Platform.Queries;
public class Workflow : ValueObject public class Workflow : ValueObject
{ {
public string ApprovalUserFieldName { get; set; } public string ApprovalUserFieldName { get; set; }
public bool IsFilterUserName { get; set; }
public string ApprovalDateFieldName { get; set; } public string ApprovalDateFieldName { get; set; }
public string ApprovalStatusFieldName { get; set; } public string ApprovalStatusFieldName { get; set; }
public string ApprovalDescriptionFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; }
public bool ApprovalIsFilterUserName { get; set; }
protected override IEnumerable<object> GetAtomicValues() protected override IEnumerable<object> GetAtomicValues()
{ {
yield return ApprovalUserFieldName; yield return ApprovalUserFieldName;
yield return IsFilterUserName;
yield return ApprovalDateFieldName; yield return ApprovalDateFieldName;
yield return ApprovalStatusFieldName; yield return ApprovalStatusFieldName;
yield return ApprovalDescriptionFieldName; yield return ApprovalDescriptionFieldName;
yield return ApprovalIsFilterUserName;
} }
} }

View file

@ -378,6 +378,7 @@ public class PlatformDbContext :
b.Property(a => a.WorkflowJson).HasColumnType("text"); b.Property(a => a.WorkflowJson).HasColumnType("text");
b.Property(a => a.ExtraFilterJson).HasColumnType("text"); b.Property(a => a.ExtraFilterJson).HasColumnType("text");
b.Property(a => a.LayoutJson).HasColumnType("text"); b.Property(a => a.LayoutJson).HasColumnType("text");
b.Property(a => a.TodoOptionJson).HasColumnType("text");
b.Property(a => a.CommonJson).HasColumnType("text"); b.Property(a => a.CommonJson).HasColumnType("text");
b.Property(a => a.DataSourceJson).HasColumnType("text"); b.Property(a => a.DataSourceJson).HasColumnType("text");
b.Property(a => a.AdaptiveLayoutJson).HasColumnType("text"); b.Property(a => a.AdaptiveLayoutJson).HasColumnType("text");
@ -454,6 +455,8 @@ public class PlatformDbContext :
b.Property(a => a.Alignment).HasMaxLength(20).HasDefaultValue("left"); b.Property(a => a.Alignment).HasMaxLength(20).HasDefaultValue("left");
b.Property(a => a.Format).HasMaxLength(128); b.Property(a => a.Format).HasMaxLength(128);
b.Property(a => a.EditorOptions).HasColumnType("text"); 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); b.HasIndex(x => new { x.ListFormCode, x.UserId, x.RoleId, x.CultureName, x.FieldName }).IsUnique().HasFilter(null);
}); });
@ -660,7 +663,6 @@ public class PlatformDbContext :
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportCategory)), Prefix.DbSchema); b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportCategory)), Prefix.DbSchema);
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Id).HasMaxLength(256);
b.Property(x => x.Name).IsRequired().HasMaxLength(256); b.Property(x => x.Name).IsRequired().HasMaxLength(256);
b.Property(x => x.Description).HasMaxLength(4096); // JSON string b.Property(x => x.Description).HasMaxLength(4096); // JSON string
b.Property(x => x.Icon).HasMaxLength(64); b.Property(x => x.Icon).HasMaxLength(64);
@ -674,7 +676,6 @@ public class PlatformDbContext :
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(256); b.Property(x => x.Name).IsRequired().HasMaxLength(256);
b.Property(x => x.CategoryId).HasMaxLength(256);
b.Property(x => x.Description).HasMaxLength(1024); b.Property(x => x.Description).HasMaxLength(1024);
b.Property(x => x.HtmlContent).IsRequired(); b.Property(x => x.HtmlContent).IsRequired();
b.Property(x => x.Status).HasMaxLength(20); b.Property(x => x.Status).HasMaxLength(20);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations namespace Sozsoft.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20260713103339_Initial")] [Migration("20260717065637_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -3264,6 +3264,9 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("TitleJson") b.Property<string>("TitleJson")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("TodoOptionJson")
.HasColumnType("text");
b.Property<string>("TooltipJson") b.Property<string>("TooltipJson")
.HasColumnType("text"); .HasColumnType("text");
@ -3412,6 +3415,9 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("nvarchar(256)");
b.Property<int?>("ColSpan")
.HasColumnType("int");
b.Property<string>("ColumnCssClass") b.Property<string>("ColumnCssClass")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("nvarchar(128)");
@ -3437,9 +3443,22 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(10) .HasMaxLength(10)
.HasColumnType("nvarchar(10)"); .HasColumnType("nvarchar(10)");
b.Property<int?>("EditGroupOrderNo")
.HasColumnType("int");
b.Property<int?>("EditOrderNo")
.HasColumnType("int");
b.Property<string>("EditorOptions") b.Property<string>("EditorOptions")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("EditorScript")
.HasColumnType("text");
b.Property<string>("EditorType2")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("FieldName") b.Property<string>("FieldName")
.IsRequired() .IsRequired()
.HasMaxLength(128) .HasMaxLength(128)
@ -4482,9 +4501,8 @@ namespace Sozsoft.Platform.Migrations
modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b => modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b =>
{ {
b.Property<string>("Id") b.Property<Guid>("Id")
.HasMaxLength(256) .HasColumnType("uniqueidentifier");
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime") b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2") .HasColumnType("datetime2")
@ -4547,9 +4565,8 @@ namespace Sozsoft.Platform.Migrations
b.Property<Guid>("Id") b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
b.Property<string>("CategoryId") b.Property<Guid>("CategoryId")
.HasMaxLength(256) .HasColumnType("uniqueidentifier");
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime") b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2") .HasColumnType("datetime2")
@ -8820,7 +8837,8 @@ namespace Sozsoft.Platform.Migrations
b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory") b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory")
.WithMany("ReportTemplates") .WithMany("ReportTemplates")
.HasForeignKey("CategoryId") .HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("ReportCategory"); b.Navigation("ReportCategory");
}); });

View file

@ -650,7 +650,7 @@ namespace Sozsoft.Platform.Migrations
name: "Adm_T_ReportCategory", name: "Adm_T_ReportCategory",
columns: table => new columns: table => new
{ {
Id = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true), Description = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true),
@ -1392,6 +1392,7 @@ namespace Sozsoft.Platform.Migrations
TreeOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true), TreeOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
GanttOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true), GanttOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
SchedulerOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true), SchedulerOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
TodoOptionJson = table.Column<string>(type: "text", nullable: true),
FilterRowJson = table.Column<string>(type: "text", nullable: true), FilterRowJson = table.Column<string>(type: "text", nullable: true),
RowJson = table.Column<string>(type: "nvarchar(max)", nullable: true), RowJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
HeaderFilterJson = table.Column<string>(type: "text", nullable: true), HeaderFilterJson = table.Column<string>(type: "text", nullable: true),
@ -2259,7 +2260,7 @@ namespace Sozsoft.Platform.Migrations
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false), Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true), Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
HtmlContent = table.Column<string>(type: "nvarchar(max)", nullable: false), HtmlContent = table.Column<string>(type: "nvarchar(max)", nullable: false),
CategoryId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), CategoryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Status = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true), Status = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
@ -2790,6 +2791,11 @@ namespace Sozsoft.Platform.Migrations
PivotSettingsJson = table.Column<string>(type: "text", nullable: true), PivotSettingsJson = table.Column<string>(type: "text", nullable: true),
Alignment = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true, defaultValue: "left"), Alignment = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true, defaultValue: "left"),
Format = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true), 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) EditorOptions = table.Column<string>(type: "text", nullable: true)
}, },
constraints: table => constraints: table =>

View file

@ -3261,6 +3261,9 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("TitleJson") b.Property<string>("TitleJson")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("TodoOptionJson")
.HasColumnType("text");
b.Property<string>("TooltipJson") b.Property<string>("TooltipJson")
.HasColumnType("text"); .HasColumnType("text");
@ -3409,6 +3412,9 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("nvarchar(256)"); .HasColumnType("nvarchar(256)");
b.Property<int?>("ColSpan")
.HasColumnType("int");
b.Property<string>("ColumnCssClass") b.Property<string>("ColumnCssClass")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("nvarchar(128)"); .HasColumnType("nvarchar(128)");
@ -3434,9 +3440,22 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(10) .HasMaxLength(10)
.HasColumnType("nvarchar(10)"); .HasColumnType("nvarchar(10)");
b.Property<int?>("EditGroupOrderNo")
.HasColumnType("int");
b.Property<int?>("EditOrderNo")
.HasColumnType("int");
b.Property<string>("EditorOptions") b.Property<string>("EditorOptions")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("EditorScript")
.HasColumnType("text");
b.Property<string>("EditorType2")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("FieldName") b.Property<string>("FieldName")
.IsRequired() .IsRequired()
.HasMaxLength(128) .HasMaxLength(128)
@ -4479,9 +4498,8 @@ namespace Sozsoft.Platform.Migrations
modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b => modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b =>
{ {
b.Property<string>("Id") b.Property<Guid>("Id")
.HasMaxLength(256) .HasColumnType("uniqueidentifier");
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime") b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2") .HasColumnType("datetime2")
@ -4544,9 +4562,8 @@ namespace Sozsoft.Platform.Migrations
b.Property<Guid>("Id") b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
b.Property<string>("CategoryId") b.Property<Guid>("CategoryId")
.HasMaxLength(256) .HasColumnType("uniqueidentifier");
.HasColumnType("nvarchar(256)");
b.Property<DateTime>("CreationTime") b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2") .HasColumnType("datetime2")
@ -8817,7 +8834,8 @@ namespace Sozsoft.Platform.Migrations
b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory") b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory")
.WithMany("ReportTemplates") .WithMany("ReportTemplates")
.HasForeignKey("CategoryId") .HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("ReportCategory"); b.Navigation("ReportCategory");
}); });

View file

@ -491,7 +491,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<GlobalSearch, int> _globalSearch; private readonly IRepository<GlobalSearch, int> _globalSearch;
private readonly IRepository<CustomEndpoint, Guid> _customEndpointRepository; private readonly IRepository<CustomEndpoint, Guid> _customEndpointRepository;
private readonly IRepository<CustomComponent, Guid> _customComponentRepository; private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
private readonly IRepository<ReportCategory, string> _reportCategoriesRepository; private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository;
private readonly IRepository<ReportTemplate, Guid> _reportTemplatesRepository; private readonly IRepository<ReportTemplate, Guid> _reportTemplatesRepository;
private readonly IRepository<Home, Guid> _homeRepository; private readonly IRepository<Home, Guid> _homeRepository;
private readonly IRepository<About, Guid> _aboutRepository; private readonly IRepository<About, Guid> _aboutRepository;
@ -548,7 +548,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<SkillLevel, string> skillLevelRepository, IRepository<SkillLevel, string> skillLevelRepository,
IRepository<CustomEndpoint, Guid> customEndpointRepository, IRepository<CustomEndpoint, Guid> customEndpointRepository,
IRepository<CustomComponent, Guid> customComponentRepository, IRepository<CustomComponent, Guid> customComponentRepository,
IRepository<ReportCategory, string> reportCategoriesRepository, IRepository<ReportCategory, Guid> reportCategoriesRepository,
IRepository<ReportTemplate, Guid> reportTemplatesRepository, IRepository<ReportTemplate, Guid> reportTemplatesRepository,
IRepository<Home, Guid> homeRepository, IRepository<Home, Guid> homeRepository,
IRepository<About, Guid> aboutRepository, IRepository<About, Guid> aboutRepository,
@ -867,7 +867,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
if (!exists) if (!exists)
{ {
await _reportCategoriesRepository.InsertAsync(new ReportCategory(item.Name) await _reportCategoriesRepository.InsertAsync(new ReportCategory(Guid.NewGuid())
{ {
Name = item.Name, Name = item.Name,
Description = item.Description, Description = item.Description,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,29 @@
{ {
"commit": "e7be51a", "commit": "8146adf",
"releases": [ "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",
"commit": "50cb3eaf979eb5027172a92a4e941ed64aa3671e",
"changeLog": [
"- localStorage tüm bilgiler easy-peasy içerisinde birleştirildi.",
"- Codex 5.6 sol optimizasyon yapıldı. Bir çok komponent Lazy load ile yüklendi.",
"- Workflow komponentinden birden fazla kişi onay verebilir.",
"- Posta Kodu eklendi.",
"- Sales Order kısmında Yeni ve Eski müşteri sipariş ayrımı yapıldı"
]
},
{ {
"version": "1.1.06", "version": "1.1.06",
"buildDate": "2026-07-06", "buildDate": "2026-07-06",

View file

@ -193,12 +193,8 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
const [foundTenantName, setFoundTenantName] = useState('') const [foundTenantName, setFoundTenantName] = useState('')
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useLocalization() const { translate } = useLocalization()
const { tenantData, tenantFormDraft } = useStoreState( const { tenantData, tenantFormDraft } = useStoreState((state) => state.client.order)
(state) => state.client.order, const { setTenantFormDraft, clearTenant } = useStoreActions((actions) => actions.client.order)
)
const { setTenantFormDraft, clearTenant } = useStoreActions(
(actions) => actions.client.order,
)
const hasRestoredTenantData = useRef(false) const hasRestoredTenantData = useRef(false)
useEffect(() => { useEffect(() => {
@ -681,7 +677,9 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FaPhone className="w-4 h-4 text-gray-500" /> <FaPhone className="w-4 h-4 text-gray-500" />
<span className="font-medium">{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}:</span> <span className="font-medium">
{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}:
</span>
<span>{formData.phoneNumber}</span> <span>{formData.phoneNumber}</span>
</div> </div>
@ -999,41 +997,6 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div> </div>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxOffice')}
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="Sarıgazi"
value={formData.taxOffice}
onChange={(e) => handleInputChange('taxOffice', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxNumber')}
</label>
<div className="relative">
<FaDollarSign className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="1234567890"
value={formData.vknTckn}
onChange={(e) => handleInputChange('vknTckn', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
@ -1094,6 +1057,41 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxOffice')}
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="Sarıgazi"
value={formData.taxOffice}
onChange={(e) => handleInputChange('taxOffice', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxNumber')}
</label>
<div className="relative">
<FaDollarSign className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="1234567890"
value={formData.vknTckn}
onChange={(e) => handleInputChange('vknTckn', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
</div> </div>
)} )}

View file

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

View file

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

View file

@ -6,22 +6,31 @@ import withHeaderItem from '@/utils/hoc/withHeaderItem'
import useAuth from '@/utils/hooks/useAuth' import useAuth from '@/utils/hooks/useAuth'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import classNames from 'classnames' import classNames from 'classnames'
import type { ReactElement } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Avatar } from '../ui' import { Avatar } from '../ui'
import { FcEngineering, FcNext, FcNfcSign, FcVoicePresentation } from 'react-icons/fc' import {
FcEngineering,
FcNext,
FcNfcSign,
FcSynchronize,
FcVoicePresentation,
} from 'react-icons/fc'
import { useManualUpdateCheck } from '@/views/version/ChangeLog'
type DropdownList = { type DropdownList = {
label: string label: string
path: string path: string
icon: JSX.Element icon: ReactElement
} }
const _UserDropdown = ({ className }: CommonProps) => { const UserDropdownBase = ({ className }: CommonProps) => {
const { setPanelExpand } = useStoreActions((actions) => actions.theme) const { setPanelExpand } = useStoreActions((actions) => actions.theme)
const { userName, name, email, avatar } = useStoreState((state) => state.auth.user) const { userName, name, email, avatar } = useStoreState((state) => state.auth.user)
const tenant = useStoreState((state) => state.abpConfig.config?.currentTenant) const tenant = useStoreState((state) => state.abpConfig.config?.currentTenant)
const { translate } = useLocalization() const { translate } = useLocalization()
const { signOut } = useAuth() const { signOut } = useAuth()
const { isCheckingUpdate, checkForUpdate } = useManualUpdateCheck()
const displayName = name || userName const displayName = name || userName
const tenantName = tenant?.name const tenantName = tenant?.name
@ -113,6 +122,23 @@ const _UserDropdown = ({ className }: CommonProps) => {
) )
})} })}
<Dropdown.Item
eventKey="Check New Version"
className="mb-1 px-0"
onClick={checkForUpdate}
>
<span className="flex h-full w-full items-center gap-2 px-2">
<span className={classNames('text-xl opacity-50', isCheckingUpdate && 'animate-spin')}>
<FcSynchronize />
</span>
<span>
{isCheckingUpdate
? translate('::App.CheckUpdate')
: translate('::App.CheckForUpdates')}
</span>
</span>
</Dropdown.Item>
{/* <Dropdown.Item variant="divider" /> */} {/* <Dropdown.Item variant="divider" /> */}
<Dropdown.Item eventKey="Sign Out" className="mb-1 px-2" onClick={signOut}> <Dropdown.Item eventKey="Sign Out" className="mb-1 px-2" onClick={signOut}>
<span className="text-xl opacity-50"> <span className="text-xl opacity-50">
@ -124,6 +150,6 @@ const _UserDropdown = ({ className }: CommonProps) => {
) )
} }
const UserDropdown = withHeaderItem(_UserDropdown) const UserDropdown = withHeaderItem(UserDropdownBase)
export default UserDropdown export default UserDropdown

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { import React, {
forwardRef, forwardRef,
useRef, useRef,
useState, 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 classNames from 'classnames'
import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from 'react-icons/fa'; import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from 'react-icons/fa';

View file

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

View file

@ -1,10 +1,10 @@
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa'; import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa';
import type { TypeAttributes, CommonProps } from '../@types/common' import type { TypeAttributes, CommonProps } from '../@types/common'
import type { ReactNode } from 'react' import type { ReactElement, ReactNode } from 'react'
export interface StatusIconProps extends CommonProps { export interface StatusIconProps extends CommonProps {
type: TypeAttributes.Status type: TypeAttributes.Status
custom?: ReactNode | JSX.Element custom?: ReactNode | ReactElement
iconColor?: string iconColor?: string
} }
@ -12,7 +12,7 @@ const ICONS: Record<
TypeAttributes.Status, TypeAttributes.Status,
{ {
color: string color: string
icon: JSX.Element icon: ReactElement
} }
> = { > = {
success: { success: {

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' import useCallbackRef from './useCallbackRef'
type UseControllableStateParams<T> = { type UseControllableStateParams<T> = {

View file

@ -4,6 +4,22 @@ body {
font-size: 14px; /* bodyye font-size uygulanmaz */ font-size: 14px; /* bodyye font-size uygulanmaz */
} }
.list-card-view.dx-cardview {
padding: 0;
border-radius: 0;
background-color: transparent;
}
.list-card-view .dx-cardview-card,
.list-card-view .dx-cardview-card-header .dx-toolbar,
.list-card-view .dx-cardview-card-footer {
border-radius: 0 !important;
}
.list-card-view .dx-toolbar .dx-button-text {
text-transform: none !important;
}
.dx-form-group-with-caption > .dx-form-group-content { .dx-form-group-with-caption > .dx-form-group-content {
padding-top: 2px !important; padding-top: 2px !important;
} }

View file

@ -33,4 +33,5 @@ export interface ListFormJsonRowDto {
export interface ListState { export interface ListState {
listFormCode: string listFormCode: string
layout: ListViewLayoutType layout: ListViewLayoutType
cardColumnCount?: number
} }

View file

@ -42,6 +42,7 @@ export const ListFormEditTabs = {
TreeForm: 'tree', TreeForm: 'tree',
GanttForm: 'gantt', GanttForm: 'gantt',
SchedulerForm: 'scheduler', SchedulerForm: 'scheduler',
TodoForm: 'todo',
PagerForm: 'pager', PagerForm: 'pager',
StateForm: 'state', StateForm: 'state',
SubForm: 'subForm', SubForm: 'subForm',
@ -93,6 +94,7 @@ export const tabVisibilityConfig: Record<string, string[]> = {
'tree', 'tree',
'gantt', 'gantt',
'scheduler', 'scheduler',
'todo',
'pager', 'pager',
'state', 'state',
'extrafilter', 'extrafilter',

View file

@ -1,14 +1,13 @@
import { SelectCommandTypeEnum } from '@/proxy/form/models'
import { import {
ListFormWorkflowCriteriaDto, ListFormWorkflowCriteriaDto,
SubFormDto, SubFormDto,
WidgetEditDto, WidgetEditDto,
WorkflowDto, SelectCommandTypeEnum,
} from '@/proxy/form/models' } from '@/proxy/form/models'
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types' import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
export interface ListFormWizardColumnItemDto { export interface ListFormWizardColumnItemDto {
dataField: string fieldName: string
editorType: string editorType: string
editorOptions: string editorOptions: string
editorScript: string editorScript: string
@ -39,11 +38,13 @@ export interface ListFormWizardDto {
defaultLayout: ListViewLayoutType defaultLayout: ListViewLayoutType
grid: boolean grid: boolean
card: boolean
pivot: boolean pivot: boolean
tree: boolean tree: boolean
chart: boolean chart: boolean
gantt: boolean gantt: boolean
scheduler: boolean scheduler: boolean
todo: boolean
languageTextMenuEn: string languageTextMenuEn: string
languageTextMenuTr: string languageTextMenuTr: string
@ -55,6 +56,7 @@ export interface ListFormWizardDto {
languageTextMenuParentTr: string languageTextMenuParentTr: string
permissionGroupName: string permissionGroupName: string
menuParentCode: string menuParentCode: string
menuParentShortName?: string
menuParentIcon?: string menuParentIcon?: string
menuIcon: string menuIcon: string
dataSourceCode: string dataSourceCode: string
@ -83,11 +85,30 @@ export interface ListFormWizardDto {
schedulerStartDateExpr?: string schedulerStartDateExpr?: string
schedulerEndDateExpr?: string schedulerEndDateExpr?: string
// Todo options (required when defaultLayout = 'todo')
todoTitleExpr: string
todoStatusExpr: string
todoDescriptionExpr: string
todoDueDateExpr: string
todoTagExpr: string
todoAssigneeExpr: string
todoPriorityExpr: string
todoCompletedExpr: string
todoOrderExpr: string
todoStatusOrder: string
todoAllowDragging: boolean
// Workflow options (required when defaultLayout = 'workflow')
approvalUserFieldName: string
approvalDateFieldName: string
approvalStatusFieldName: string
approvalDescriptionFieldName: string
approvalIsFilterUserName: boolean
criteria: ListFormWorkflowCriteriaDto[]
groups?: ListFormWizardColumnGroupDto[] groups?: ListFormWizardColumnGroupDto[]
subForms?: SubFormDto[] subForms?: SubFormDto[]
widgets?: WidgetEditDto[] widgets?: WidgetEditDto[]
workflow?: WorkflowDto
workflowCriteria?: ListFormWorkflowCriteriaDto[]
} }
export interface WizardFileInfoDto { export interface WizardFileInfoDto {
@ -109,7 +130,7 @@ export interface WizardInsertedRecordsDto {
} }
export interface WizardSeedFileItemDto { export interface WizardSeedFileItemDto {
dataField: string fieldName: string
editorType: string editorType: string
editorOptions: string editorOptions: string
editorScript: string editorScript: string

View file

@ -15,7 +15,6 @@ import type {
StartEditAction, StartEditAction,
StateStoreType, StateStoreType,
} from 'devextreme/ui/data_grid_types' } from 'devextreme/ui/data_grid_types'
import type { FormItemComponent } from 'devextreme/ui/form'
import { AuditedEntityDto } from '../abp' import { AuditedEntityDto } from '../abp'
import { EditorType2, RowMode } from '../../views/form/types' import { EditorType2, RowMode } from '../../views/form/types'
import { import {
@ -195,7 +194,7 @@ export interface ColumnFilterDto {
} }
export interface ColumnFormatDto extends AuditedEntityDto<string> { export interface ColumnFormatDto extends AuditedEntityDto<string> {
fieldName?: string fieldName: string
captionName?: string captionName?: string
placeHolder?: string placeHolder?: string
readOnly: boolean readOnly: boolean
@ -245,6 +244,14 @@ export interface ColumnFormatDto extends AuditedEntityDto<string> {
alignment?: HorizontalAlignment alignment?: HorizontalAlignment
format?: string format?: string
editorOptions?: string editorOptions?: string
editOrderNo?: number
editGroupOrderNo?: number
editorType2: EditorType2
colSpan?: number
gridBoxOptions?: GridBoxOptionsDto
tagBoxOptions?: TagBoxOptionsDto
imageUploadOptions?: ImageUploadOptionsDto
editorScript?: string
} }
export enum AuthorizationTypeEnum { export enum AuthorizationTypeEnum {
@ -442,6 +449,20 @@ export interface SchedulerOptionDto {
resources?: SchedulerResourceDto[] resources?: SchedulerResourceDto[]
} }
export interface TodoOptionDto {
titleExpr?: string
statusExpr?: string
descriptionExpr?: string
dueDateExpr?: string
tagExpr?: string
assigneeExpr?: string
priorityExpr?: string
completedExpr?: string
orderExpr?: string
statusOrder?: string
allowDragging?: boolean
}
export interface SchedulerResourceDto { export interface SchedulerResourceDto {
fieldExpr?: string fieldExpr?: string
dataSource?: any[] dataSource?: any[]
@ -478,20 +499,6 @@ export interface EditingFormDto {
caption?: string caption?: string
colCount: number colCount: number
colSpan: 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 { export interface GridEditingPopupDto {
@ -557,6 +564,8 @@ export interface GridOptionsDto extends AuditedEntityDto<string> {
ganttOptionDto: GanttOptionDto ganttOptionDto: GanttOptionDto
schedulerOptionJson?: string schedulerOptionJson?: string
schedulerOptionDto: SchedulerOptionDto schedulerOptionDto: SchedulerOptionDto
todoOptionJson?: string
todoOptionDto: TodoOptionDto
pagerOptionJson?: string pagerOptionJson?: string
pagerOptionDto: GridPagerOptionDto pagerOptionDto: GridPagerOptionDto
editingOptionJson?: string editingOptionJson?: string
@ -911,10 +920,10 @@ export interface WidgetEditDto {
export interface WorkflowDto { export interface WorkflowDto {
approvalUserFieldName: string approvalUserFieldName: string
isFilterUserName: boolean
approvalDateFieldName: string approvalDateFieldName: string
approvalStatusFieldName: string approvalStatusFieldName: string
approvalDescriptionFieldName: string approvalDescriptionFieldName: string
approvalIsFilterUserName: boolean
criteria: ListFormWorkflowCriteriaDto[] criteria: ListFormWorkflowCriteriaDto[]
} }
@ -951,11 +960,13 @@ export interface ListFormWorkflowCriteriaDto {
export interface LayoutDto { export interface LayoutDto {
grid: boolean grid: boolean
card: boolean
pivot: boolean pivot: boolean
tree: boolean tree: boolean
chart: boolean chart: boolean
gantt: boolean gantt: boolean
scheduler: boolean scheduler: boolean
todo: boolean
defaultLayout: ListViewLayoutType defaultLayout: ListViewLayoutType
} }

View file

@ -1,11 +1,11 @@
import { LayoutType } from '../theme/models' import { LayoutType } from '../theme/models'
import type { LazyExoticComponent, ReactNode } from 'react' import type { LazyExoticComponent, ReactElement, ReactNode } from 'react'
export interface Meta { export interface Meta {
pageContainerType?: 'default' | 'gutterless' | 'contained' pageContainerType?: 'default' | 'gutterless' | 'contained'
header?: string | ReactNode header?: string | ReactNode
headerContainer?: boolean headerContainer?: boolean
extraHeader?: LazyExoticComponent<() => JSX.Element> extraHeader?: LazyExoticComponent<() => ReactElement>
footer?: boolean footer?: boolean
layout?: LayoutType layout?: LayoutType
} }
@ -13,7 +13,7 @@ export interface Meta {
export type Route = { export type Route = {
key: string key: string
path: string path: string
component: LazyExoticComponent<<T extends Meta>(props: T) => JSX.Element> component: LazyExoticComponent<<T extends Meta>(props: T) => ReactElement>
authority: string[] authority: string[]
meta?: Meta meta?: Meta
} }

View file

@ -82,7 +82,7 @@ export const adminModel: AdminModel = {
setStates: action((state, payload) => { setStates: action((state, payload) => {
const stateId = state.states.findIndex((s) => s.listFormCode === payload.listFormCode) const stateId = state.states.findIndex((s) => s.listFormCode === payload.listFormCode)
if (stateId !== -1) { if (stateId !== -1) {
state.states[stateId] = payload state.states[stateId] = { ...state.states[stateId], ...payload }
} else { } else {
state.states = [...state.states, payload] state.states = [...state.states, payload]
} }

View file

@ -45,6 +45,7 @@ export const store = createStore<StoreModel>(
}, },
{ {
allow: ['auth', 'theme', 'locale', 'client', 'admin'], allow: ['auth', 'theme', 'locale', 'client', 'admin'],
storage: 'localStorage',
}, },
), ),
{ {

View file

@ -3,6 +3,7 @@ import { useLocation } from 'react-router-dom'
import { FaUser } from 'react-icons/fa' import { FaUser } from 'react-icons/fa'
import navigationIcon from '@/proxy/menus/navigation-icon.config' import navigationIcon from '@/proxy/menus/navigation-icon.config'
import { navigationTreeToFlat } from '@/utils/navigation' import { navigationTreeToFlat } from '@/utils/navigation'
import { ReactElement } from 'react'
const extractListFormCode = (path: string): string | null => { const extractListFormCode = (path: string): string | null => {
const p = (path ?? '').toLowerCase() const p = (path ?? '').toLowerCase()
@ -14,7 +15,7 @@ const extractListFormCode = (path: string): string | null => {
return null return null
} }
export function useCurrentMenuIcon(className = 'w-6 h-6'): JSX.Element { export function useCurrentMenuIcon(className = 'w-6 h-6'): ReactElement {
const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu) const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu)
const location = useLocation() const location = useLocation()
const menus = navigationTreeToFlat(mainMenu) const menus = navigationTreeToFlat(mainMenu)

View file

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

View file

@ -27,7 +27,11 @@ import FormTabSubForm from './FormTabSubForm'
import FormFields from './form-fields/FormFields' import FormFields from './form-fields/FormFields'
import { putListForms } from '@/services/admin/list-form.service' import { putListForms } from '@/services/admin/list-form.service'
import { getRoles, getUsers } from '@/services/identity.service' import { getRoles, getUsers } from '@/services/identity.service'
import { SelectCommandTypeEnum, GridOptionsEditDto, ListFormCustomizationDto } from '@/proxy/form/models' import {
SelectCommandTypeEnum,
GridOptionsEditDto,
ListFormCustomizationDto,
} from '@/proxy/form/models'
import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models' import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models'
import { getListFormCustomizations } from '@/services/admin/list-form-customization.service' import { getListFormCustomizations } from '@/services/admin/list-form-customization.service'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service' import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
@ -54,6 +58,7 @@ import FormTabSorting from './FormTabSorting'
import FormTabRow from './FormTabRow' import FormTabRow from './FormTabRow'
import FormTabGantt from './FormTabGantt' import FormTabGantt from './FormTabGantt'
import FormTabScheduler from './FormTabScheduler' import FormTabScheduler from './FormTabScheduler'
import FormTabTodo from './FormTabTodo'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
import { FormTabWorkflow } from './FormTabWorkflow' import { FormTabWorkflow } from './FormTabWorkflow'
@ -295,6 +300,9 @@ const FormEdit = () => {
{translate('::ListForms.ListFormEdit.ExtraFilters')} {translate('::ListForms.ListFormEdit.ExtraFilters')}
</TabNav> </TabNav>
)} )}
{visibleTabs.includes('editForm') && (
<TabNav value="editForm">{translate('::ListForms.ListFormEdit.EditingForm')}</TabNav>
)}
{visibleTabs.includes('fields') && ( {visibleTabs.includes('fields') && (
<TabNav value="fields">{translate('::ListForms.ListFormEdit.TabFields')}</TabNav> <TabNav value="fields">{translate('::ListForms.ListFormEdit.TabFields')}</TabNav>
)} )}
@ -303,9 +311,6 @@ const FormEdit = () => {
{translate('::ListForms.ListFormEdit.TabCustomization')} {translate('::ListForms.ListFormEdit.TabCustomization')}
</TabNav> </TabNav>
)} )}
{visibleTabs.includes('editForm') && (
<TabNav value="editForm">{translate('::ListForms.ListFormEdit.EditingForm')}</TabNav>
)}
{visibleTabs.includes('pivots') && ( {visibleTabs.includes('pivots') && (
<TabNav value="pivots">{translate('::ListForms.ListFormEdit.TabPivot')}</TabNav> <TabNav value="pivots">{translate('::ListForms.ListFormEdit.TabPivot')}</TabNav>
)} )}
@ -318,6 +323,7 @@ const FormEdit = () => {
{visibleTabs.includes('scheduler') && ( {visibleTabs.includes('scheduler') && (
<TabNav value="scheduler">{translate('::ListForms.ListFormEdit.TabScheduler')}</TabNav> <TabNav value="scheduler">{translate('::ListForms.ListFormEdit.TabScheduler')}</TabNav>
)} )}
{visibleTabs.includes('todo') && <TabNav value="todo">Todo</TabNav>}
{visibleTabs.includes('subForms') && ( {visibleTabs.includes('subForms') && (
<TabNav value="subForms">{translate('::ListForms.ListFormEdit.SubForms')}</TabNav> <TabNav value="subForms">{translate('::ListForms.ListFormEdit.SubForms')}</TabNav>
)} )}
@ -431,6 +437,9 @@ const FormEdit = () => {
<TabContent value="scheduler" className="px-2"> <TabContent value="scheduler" className="px-2">
<FormTabScheduler onSubmit={onSubmit} /> <FormTabScheduler onSubmit={onSubmit} />
</TabContent> </TabContent>
<TabContent value="todo" className="px-2">
<FormTabTodo onSubmit={onSubmit} />
</TabContent>
<TabContent value="pager" className="px-2"> <TabContent value="pager" className="px-2">
<FormTabPager onSubmit={onSubmit} /> <FormTabPager onSubmit={onSubmit} />
</TabContent> </TabContent>
@ -455,6 +464,7 @@ const FormEdit = () => {
<TabContent value="fields" className="px-2"> <TabContent value="fields" className="px-2">
<FormFields <FormFields
listFormCode={listFormCode} listFormCode={listFormCode}
editingFormGroups={listFormValues.editingFormDto ?? []}
langOptions={langOptions} langOptions={langOptions}
roleList={roleList} roleList={roleList}
userList={userList} userList={userList}

View file

@ -26,10 +26,13 @@ const schema = Yup.object().shape({
defaultLayout: Yup.string(), defaultLayout: Yup.string(),
layoutDto: Yup.object().shape({ layoutDto: Yup.object().shape({
grid: Yup.boolean(), grid: Yup.boolean(),
card: Yup.boolean(),
pivot: Yup.boolean(), pivot: Yup.boolean(),
chart: Yup.boolean(), chart: Yup.boolean(),
tree: Yup.boolean(), tree: Yup.boolean(),
gantt: Yup.boolean(), gantt: Yup.boolean(),
scheduler: Yup.boolean(),
todo: Yup.boolean(),
defaultLayout: Yup.string(), defaultLayout: Yup.string(),
}), }),
}) })
@ -312,6 +315,22 @@ function FormTabDetails(
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
invalid={errors.layoutDto?.card && touched.layoutDto?.card}
errorMessage={errors.layoutDto?.card}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.card"
placeholder={translate(
'::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout',
)}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot} invalid={errors.layoutDto?.pivot && touched.layoutDto?.pivot}
@ -393,6 +412,19 @@ function FormTabDetails(
component={Checkbox} component={Checkbox}
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout')}
invalid={errors.layoutDto?.todo && touched.layoutDto?.todo}
errorMessage={errors.layoutDto?.todo}
>
<Field
className="w-20"
autoComplete="off"
name="layoutDto.todo"
component={Checkbox}
/>
</FormItem>
</div> </div>
</> </>
)} )}

View file

@ -59,7 +59,6 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Th>{translate('::ListForms.ListFormEdit.DetailsTitle')}</Th> <Th>{translate('::ListForms.ListFormEdit.DetailsTitle')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormColumnCount')}</Th> <Th>{translate('::ListForms.ListFormEdit.EditingFormColumnCount')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormColumnSpan')}</Th> <Th>{translate('::ListForms.ListFormEdit.EditingFormColumnSpan')}</Th>
<Th>{translate('::ListForms.ListFormEdit.EditingFormItems')}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <TBody>
@ -109,13 +108,6 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Td>{row.caption}</Td> <Td>{row.caption}</Td>
<Td>{row.colCount}</Td> <Td>{row.colCount}</Td>
<Td>{row.colSpan}</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> </Tr>
))} ))}
</TBody> </TBody>

View file

@ -0,0 +1,137 @@
import {
Button,
Checkbox,
FormContainer,
FormItem,
Input,
Notification,
Select,
toast,
} from '@/components/ui'
import { ListFormEditTabs } from '@/proxy/admin/list-form/options'
import type { GridOptionsEditDto } from '@/proxy/form/models'
import { getListFormFields } from '@/services/admin/list-form-field.service'
import { useStoreState } from '@/store'
import type { SelectBoxOption } from '@/types/shared'
import { Field, FieldProps, Form, Formik } from 'formik'
import groupBy from 'lodash/groupBy'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useParams } from 'react-router-dom'
import type { FormEditProps } from './FormEdit'
import { useLocalization } from '@/utils/hooks/useLocalization'
const mappings: Array<{
name: keyof GridOptionsEditDto['todoOptionDto']
labelKey: string
required?: boolean
}> = [
{ name: 'titleExpr', labelKey: 'TodoTitleExpr', required: true },
{ name: 'statusExpr', labelKey: 'TodoStatusExpr', required: true },
{ name: 'descriptionExpr', labelKey: 'TodoDescriptionExpr' },
{ name: 'dueDateExpr', labelKey: 'TodoDueDateExpr' },
{ name: 'tagExpr', labelKey: 'TodoTagExpr' },
{ name: 'assigneeExpr', labelKey: 'TodoAssigneeExpr' },
{ name: 'priorityExpr', labelKey: 'TodoPriorityExpr' },
{ name: 'completedExpr', labelKey: 'TodoCompletedExpr' },
{ name: 'orderExpr', labelKey: 'TodoOrderExpr' },
]
const FormTabTodo = ({ onSubmit }: FormEditProps) => {
const { listFormCode } = useParams()
const values = useStoreState((state) => state.admin.lists.values)
const [fields, setFields] = useState<SelectBoxOption[]>([])
const { translate } = useLocalization()
const translateRef = useRef(translate)
translateRef.current = translate
const loadFields = useCallback(async () => {
if (!listFormCode) return
try {
const response = await getListFormFields({
listFormCode,
sorting: 'ListOrderNo',
maxResultCount: 1000,
})
const names = groupBy(response.data?.items ?? [], 'fieldName')
setFields(Object.keys(names).map((name) => ({ value: name, label: name })))
} catch (error: any) {
toast.push(
<Notification type="danger">
{translateRef.current('::ListForms.ListFormFieldEdit.TodoFieldsLoadFailed', {
0: error.toString(),
})}
</Notification>,
)
}
}, [listFormCode])
useEffect(() => {
loadFields()
}, [loadFields])
if (!values) return null
return (
<Formik
initialValues={values}
onSubmit={(formValues, helpers) => onSubmit(ListFormEditTabs.TodoForm, formValues, helpers)}
>
{({ isSubmitting, values: formValues }) => (
<Form>
<FormContainer size="sm">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4">
{mappings.map((mapping) => (
<FormItem
key={mapping.name}
label={`${translate(
`::ListForms.ListFormFieldEdit.${mapping.labelKey}`,
)}${mapping.required ? ' *' : ''}`}
>
<Field name={`todoOptionDto.${mapping.name}`}>
{({ field, form }: FieldProps) => (
<Select
field={field}
form={form}
isClearable={!mapping.required}
options={fields}
value={fields.find(
(option) => option.value === formValues.todoOptionDto?.[mapping.name],
)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
))}
</div>
<div className="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
<Field
name="todoOptionDto.statusOrder"
component={Input}
placeholder={translate(
'::ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder',
)}
/>
</FormItem>
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}>
<Field name="todoOptionDto.allowDragging" component={Checkbox} />
</FormItem>
</div>
<Button
block
size="sm"
variant="solid"
loading={isSubmitting}
type="submit"
className="my-3"
>
{translate('::Save')}
</Button>
</FormContainer>
</Form>
)}
</Formik>
)
}
export default FormTabTodo

View file

@ -314,10 +314,10 @@ export function FormTabWorkflow(
const schema = object().shape({ const schema = object().shape({
workflowDto: object().shape({ workflowDto: object().shape({
approvalUserFieldName: string().required(), approvalUserFieldName: string().required(),
isFilterUserName: bool(),
approvalStatusFieldName: string().required(), approvalStatusFieldName: string().required(),
approvalDateFieldName: string(), approvalDateFieldName: string(),
approvalDescriptionFieldName: string(), approvalDescriptionFieldName: string(),
approvalIsFilterUserName: bool(),
}), }),
}) })
@ -442,16 +442,16 @@ export function FormTabWorkflow(
</FormItem> </FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.Workflow.IsFilterUserName')} label={translate('::ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName')}
invalid={ invalid={
!!( !!(
errors.workflowDto?.isFilterUserName && errors.workflowDto?.approvalIsFilterUserName &&
touched.workflowDto?.isFilterUserName touched.workflowDto?.approvalIsFilterUserName
) )
} }
errorMessage={errors.workflowDto?.isFilterUserName as string} errorMessage={errors.workflowDto?.approvalIsFilterUserName as string}
> >
<Field name="workflowDto.isFilterUserName" component={Checkbox} /> <Field name="workflowDto.approvalIsFilterUserName" component={Checkbox} />
</FormItem> </FormItem>
</div> </div>

View file

@ -20,14 +20,17 @@ import FormFieldTabTotalSummary from './FormFieldTabTotalSummary'
import FormFieldTabValidationRules from './FormFieldTabValidationRules' import FormFieldTabValidationRules from './FormFieldTabValidationRules'
import { FormFieldEditProps } from './FormFields' import { FormFieldEditProps } from './FormFields'
import FormFieldTabPivotSetting from './FormFieldTabPivotSetting' import FormFieldTabPivotSetting from './FormFieldTabPivotSetting'
import type { EditingFormDto } from '@/proxy/form/models'
function FormFieldEdit({ function FormFieldEdit({
item, item,
onSubmit, onSubmit,
fields, fields,
editingFormGroups,
}: { }: {
item: ColumnFormatEditDto | null item: ColumnFormatEditDto | null
fields: ColumnFormatEditDto[] fields: ColumnFormatEditDto[]
editingFormGroups: EditingFormDto[]
} & FormFieldEditProps) { } & FormFieldEditProps) {
const { translate } = useLocalization() const { translate } = useLocalization()
@ -72,13 +75,18 @@ function FormFieldEdit({
<TabNav value="pivotSetting"> <TabNav value="pivotSetting">
{translate('::ListForms.ListFormFieldEdit.TabPivotSettings')} {translate('::ListForms.ListFormFieldEdit.TabPivotSettings')}
</TabNav> </TabNav>
</TabList> </TabList>
<div className="pt-4"> <div className="pt-4">
<TabContent value="details"> <TabContent value="details">
<FormFieldTabDetails initialValues={item} onSubmit={onSubmit} /> <FormFieldTabDetails initialValues={item} onSubmit={onSubmit} />
</TabContent> </TabContent>
<TabContent value="options"> <TabContent value="options">
<FormFieldTabOptions initialValues={item} onSubmit={onSubmit} /> <FormFieldTabOptions
initialValues={item}
editingFormGroups={editingFormGroups}
fields={fields}
onSubmit={onSubmit}
/>
</TabContent> </TabContent>
<TabContent value="permissions"> <TabContent value="permissions">
<FormFieldTabPermissions initialValues={item} onSubmit={onSubmit} /> <FormFieldTabPermissions initialValues={item} onSubmit={onSubmit} />
@ -96,18 +104,10 @@ function FormFieldEdit({
<FormFieldTabGrouping initialValues={item} onSubmit={onSubmit} /> <FormFieldTabGrouping initialValues={item} onSubmit={onSubmit} />
</TabContent> </TabContent>
<TabContent value="groupSummary"> <TabContent value="groupSummary">
<FormFieldTabGroupSummary <FormFieldTabGroupSummary initialValues={item} fields={fields} onSubmit={onSubmit} />
initialValues={item}
fields={fields}
onSubmit={onSubmit}
/>
</TabContent> </TabContent>
<TabContent value="totalSummary"> <TabContent value="totalSummary">
<FormFieldTabTotalSummary <FormFieldTabTotalSummary initialValues={item} fields={fields} onSubmit={onSubmit} />
initialValues={item}
fields={fields}
onSubmit={onSubmit}
/>
</TabContent> </TabContent>
<TabContent value="joinOptions"> <TabContent value="joinOptions">
<FormFieldTabJoinOptions initialValues={item} onSubmit={onSubmit} /> <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 { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik' import { Field, FieldProps, Form, Formik } from 'formik'
import { useState } from 'react' import { useState } from 'react'
import { FaSlidersH } from 'react-icons/fa' 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 { dbSourceTypeOptions, listFormAlignmentOptions } from '../options'
import EditorOptionsBuilderDialog from '../json-row-operations/EditorOptionsBuilderDialog' import EditorOptionsBuilderDialog from '../json-row-operations/EditorOptionsBuilderDialog'
import { FormFieldEditProps } from './FormFields' import { FormFieldEditProps } from './FormFields'
@ -16,10 +16,13 @@ const schema = object().shape({
captionName: string(), captionName: string(),
placeHolder: string(), placeHolder: string(),
bandName: string(), bandName: string(),
isActive: bool().required(),
visible: bool().required(),
allowSearch: bool().required(),
sourceDbType: number().required(), sourceDbType: number().required(),
alignment: string(), alignment: string(),
format: string(), format: string(),
editorOptions: string().max(1000), editorOptions: string(),
}) })
function FormFieldTabDetails({ function FormFieldTabDetails({
@ -72,6 +75,32 @@ function FormFieldTabDetails({
> >
<Field type="text" name="bandName" component={Input} /> <Field type="text" name="bandName" component={Input} />
</FormItem> </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>
<Card> <Card>
<FormItem label={translate('::ListForms.ListFormEdit.CustomValueType')}> <FormItem label={translate('::ListForms.ListFormEdit.CustomValueType')}>
@ -132,7 +161,7 @@ function FormFieldTabDetails({
type="text" type="text"
name="editorOptions" name="editorOptions"
component={Input} component={Input}
rows={4} rows={3}
textArea={true} textArea={true}
/> />
<Button <Button
@ -154,7 +183,14 @@ function FormFieldTabDetails({
onClose={() => setIsEditorOptionsDialogOpen(false)} onClose={() => setIsEditorOptionsDialogOpen(false)}
onApply={(val) => setFieldValue('editorOptions', val)} 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')} {isSubmitting ? translate('::Saving') : translate('::Save')}
</Button> </Button>
</Form> </Form>

View file

@ -4,29 +4,50 @@ import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik' import { Field, FieldProps, Form, Formik } from 'formik'
import { bool, number, object, string } from 'yup' import { bool, number, object, string } from 'yup'
import { sortDirectionOptions } from '../options' import { columnEditorTypeListOptions, sortDirectionOptions } from '../options'
import { FormFieldEditProps } from './FormFields' 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({ const schema = object().shape({
listOrderNo: number(), listOrderNo: number(),
width: number(), width: number(),
sortDirection: string().oneOf(Object.values(SortDirectionEnum) as string[]), sortDirection: string().oneOf(Object.values(SortDirectionEnum) as string[]),
sortIndex: number(), sortIndex: number(),
isActive: bool().required(), editGroupOrderNo: number().nullable(),
visible: bool().required(), editOrderNo: number().nullable(),
allowSearch: bool().required(), colSpan: number().nullable(),
allowEditing: bool().required(), allowEditing: bool().required(),
allowAdding: bool().required(), allowAdding: bool().required(),
editorScript: string(),
}) })
function FormFieldTabOptions({ function FormFieldTabOptions({
initialValues, initialValues,
editingFormGroups,
fields,
onSubmit, onSubmit,
}: { }: {
initialValues: ColumnFormatEditDto initialValues: ColumnFormatEditDto
editingFormGroups: EditingFormDto[]
fields: ColumnFormatEditDto[]
} & FormFieldEditProps) { } & FormFieldEditProps) {
const { translate } = useLocalization() 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 ( return (
<Formik <Formik
@ -37,7 +58,7 @@ function FormFieldTabOptions({
await onSubmit(ListFormFieldEditTabs.OptionsForm, values, formikHelpers) await onSubmit(ListFormFieldEditTabs.OptionsForm, values, formikHelpers)
}} }}
> >
{({ touched, errors, isSubmitting, values }) => ( {({ touched, errors, isSubmitting, values, setFieldValue }) => (
<Form> <Form>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<Card> <Card>
@ -82,25 +103,7 @@ function FormFieldTabOptions({
> >
<Field type="number" name="sortIndex" component={Input} /> <Field type="number" name="sortIndex" component={Input} />
</FormItem> </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')}> <FormItem label={translate('::ListForms.ListFormEdit.AllowEditing')}>
<Field name="allowEditing" component={Checkbox} /> <Field name="allowEditing" component={Checkbox} />
</FormItem> </FormItem>
@ -108,9 +111,100 @@ function FormFieldTabOptions({
<Field name="allowAdding" component={Checkbox} /> <Field name="allowAdding" component={Checkbox} />
</FormItem> </FormItem>
</Card> </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> </div>
<EditorScriptBuilderDialog
<Button block size="sm" className="mt-1" variant="solid" loading={isSubmitting} type="submit"> 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')} {isSubmitting ? translate('::Saving') : translate('::Save')}
</Button> </Button>
</Form> </Form>

View file

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

View file

@ -224,11 +224,13 @@ export const listFormTypeOptions = [
export const listFormDefaultLayoutOptions = [ export const listFormDefaultLayoutOptions = [
{ value: 'grid', label: 'Grid' }, { value: 'grid', label: 'Grid' },
{ value: 'card', label: 'Card' },
{ value: 'pivot', label: 'Pivot' }, { value: 'pivot', label: 'Pivot' },
{ value: 'chart', label: 'Chart' }, { value: 'chart', label: 'Chart' },
{ value: 'tree', label: 'Tree' }, { value: 'tree', label: 'Tree' },
{ value: 'gantt', label: 'Gantt' }, { value: 'gantt', label: 'Gantt' },
{ value: 'scheduler', label: 'Scheduler' }, { value: 'scheduler', label: 'Scheduler' },
{ value: 'todo', label: 'Todo' },
] ]
export const listFormAlignmentOptions = [ export const listFormAlignmentOptions = [

View file

@ -1,12 +1,22 @@
export type ChartOperation = '' | 'select' | 'insert' | 'update' | 'delete' export type ChartOperation = '' | 'select' | 'insert' | 'update' | 'delete'
export type ChartDialogType = '' | 'pane' | 'serie' | 'annotation' | 'axis' export type ChartDialogType = '' | 'pane' | 'serie' | 'annotation' | 'axis'
export type ListViewLayoutType = 'grid' | 'pivot' | 'tree' | 'chart' | 'gantt' | 'scheduler' export type ListViewLayoutType =
| 'grid'
| 'card'
| 'pivot'
| 'tree'
| 'chart'
| 'gantt'
| 'scheduler'
| 'todo'
export const layoutTypes = { export const layoutTypes = {
grid: 'Grid', grid: 'Grid',
card: 'Card',
pivot: 'Pivot', pivot: 'Pivot',
tree: 'Tree', tree: 'Tree',
chart: 'Chart', chart: 'Chart',
gantt: 'Gantt', gantt: 'Gantt',
scheduler: 'Scheduler', scheduler: 'Scheduler',
todo: 'Todo',
} }

View file

@ -24,6 +24,7 @@ import WizardStep1, {
import WizardStep2 from './WizardStep2' import WizardStep2 from './WizardStep2'
import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3' import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3'
import WizardStep7 from './WizardStep7' import WizardStep7 from './WizardStep7'
import WizardStep8 from './WizardStep8'
import WizardStep4 from './WizardStep4' import WizardStep4 from './WizardStep4'
import WizardStep5 from './WizardStep5' import WizardStep5 from './WizardStep5'
import WizardStep6 from './WizardStep6' import WizardStep6 from './WizardStep6'
@ -57,11 +58,24 @@ const initialValues: ListFormWizardDto = {
allowDetail: false, allowDetail: false,
defaultLayout: 'grid', defaultLayout: 'grid',
grid: true, grid: true,
card: true,
pivot: true, pivot: true,
tree: true, tree: true,
chart: true, chart: true,
gantt: true, gantt: true,
scheduler: true, scheduler: true,
todo: false,
todoTitleExpr: '',
todoStatusExpr: '',
todoDescriptionExpr: '',
todoDueDateExpr: '',
todoTagExpr: '',
todoAssigneeExpr: '',
todoPriorityExpr: '',
todoCompletedExpr: '',
todoOrderExpr: '',
todoStatusOrder: 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging',
todoAllowDragging: true,
languageTextMenuEn: '', languageTextMenuEn: '',
languageTextMenuTr: '', languageTextMenuTr: '',
languageTextTitleEn: '', languageTextTitleEn: '',
@ -72,6 +86,7 @@ const initialValues: ListFormWizardDto = {
languageTextMenuParentTr: '', languageTextMenuParentTr: '',
permissionGroupName: '', permissionGroupName: '',
menuParentCode: '', menuParentCode: '',
menuParentShortName: '',
menuParentIcon: '', menuParentIcon: '',
menuIcon: '', menuIcon: '',
dataSourceCode: '', dataSourceCode: '',
@ -95,15 +110,12 @@ const initialValues: ListFormWizardDto = {
schedulerEndDateExpr: '', schedulerEndDateExpr: '',
subForms: [], subForms: [],
widgets: [], widgets: [],
workflow: { approvalUserFieldName: '',
approvalUserFieldName: '', approvalDateFieldName: '',
isFilterUserName: false, approvalStatusFieldName: '',
approvalDateFieldName: '', approvalDescriptionFieldName: '',
approvalStatusFieldName: '', approvalIsFilterUserName: false,
approvalDescriptionFieldName: '', criteria: [],
criteria: [],
},
workflowCriteria: [],
} }
const step1ValidationSchema = Yup.object().shape({ const step1ValidationSchema = Yup.object().shape({
@ -135,7 +147,26 @@ const step2ValidationSchema = Yup.object().shape({
confirmDelete: Yup.boolean(), 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 ─────────────────────────────────────────────────────────────────── // ─── Wizard ───────────────────────────────────────────────────────────────────
@ -225,13 +256,13 @@ const Wizard = () => {
const [widgets, setWidgets] = useState<WidgetEditDto[]>([]) const [widgets, setWidgets] = useState<WidgetEditDto[]>([])
const [workflow, setWorkflow] = useState<WorkflowDto>({ const [workflow, setWorkflow] = useState<WorkflowDto>({
approvalUserFieldName: '', approvalUserFieldName: '',
isFilterUserName: false,
approvalDateFieldName: '', approvalDateFieldName: '',
approvalStatusFieldName: '', approvalStatusFieldName: '',
approvalDescriptionFieldName: '', approvalDescriptionFieldName: '',
approvalIsFilterUserName: false,
criteria: [], criteria: [],
}) })
const [workflowCriteria, setWorkflowCriteria] = useState<ListFormWorkflowCriteriaDto[]>([]) const [criteria, setCriteria] = useState<ListFormWorkflowCriteriaDto[]>([])
// Audit columns that should not be selected by default // Audit columns that should not be selected by default
const AUDIT_COLUMNS = new Set([ const AUDIT_COLUMNS = new Set([
@ -255,7 +286,7 @@ const Wizard = () => {
const removeTenantGroupItems = (groups: WizardGroup[]) => const removeTenantGroupItems = (groups: WizardGroup[]) =>
groups.map((group) => ({ groups.map((group) => ({
...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) => { const loadColumns = async (dsCode: string, schema: string, name: string) => {
@ -406,11 +437,26 @@ const Wizard = () => {
allowDetail: w.allowDetail ?? false, allowDetail: w.allowDetail ?? false,
defaultLayout: w.defaultLayout ?? 'grid', defaultLayout: w.defaultLayout ?? 'grid',
grid: w.grid ?? true, grid: w.grid ?? true,
card: w.card ?? true,
pivot: w.pivot ?? true, pivot: w.pivot ?? true,
tree: w.tree ?? true, tree: w.tree ?? true,
chart: w.chart ?? true, chart: w.chart ?? true,
gantt: w.gantt ?? true, gantt: w.gantt ?? true,
scheduler: w.scheduler ?? true, scheduler: w.scheduler ?? true,
todo: w.todo ?? true,
todoTitleExpr: w.todoTitleExpr ?? '',
todoStatusExpr: w.todoStatusExpr ?? '',
todoDescriptionExpr: w.todoDescriptionExpr ?? '',
todoDueDateExpr: w.todoDueDateExpr ?? '',
todoTagExpr: w.todoTagExpr ?? '',
todoAssigneeExpr: w.todoAssigneeExpr ?? '',
todoPriorityExpr: w.todoPriorityExpr ?? '',
todoCompletedExpr: w.todoCompletedExpr ?? '',
todoOrderExpr: w.todoOrderExpr ?? '',
todoStatusOrder:
w.todoStatusOrder ??
'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging',
todoAllowDragging: w.todoAllowDragging ?? true,
languageTextMenuEn: w.languageTextMenuEn ?? '', languageTextMenuEn: w.languageTextMenuEn ?? '',
languageTextMenuTr: w.languageTextMenuTr ?? '', languageTextMenuTr: w.languageTextMenuTr ?? '',
languageTextTitleEn: w.languageTextTitleEn ?? '', languageTextTitleEn: w.languageTextTitleEn ?? '',
@ -421,6 +467,7 @@ const Wizard = () => {
languageTextMenuParentTr: w.languageTextMenuParentTr ?? '', languageTextMenuParentTr: w.languageTextMenuParentTr ?? '',
permissionGroupName: w.permissionGroupName ?? '', permissionGroupName: w.permissionGroupName ?? '',
menuParentCode: w.menuParentCode ?? '', menuParentCode: w.menuParentCode ?? '',
menuParentShortName: w.menuParentShortName ?? '',
menuParentIcon: w.menuParentIcon ?? '', menuParentIcon: w.menuParentIcon ?? '',
menuIcon: w.menuIcon ?? '', menuIcon: w.menuIcon ?? '',
dataSourceCode: w.dataSourceCode ?? '', dataSourceCode: w.dataSourceCode ?? '',
@ -444,15 +491,12 @@ const Wizard = () => {
schedulerEndDateExpr: w.schedulerEndDateExpr ?? '', schedulerEndDateExpr: w.schedulerEndDateExpr ?? '',
subForms: w.subForms ?? [], subForms: w.subForms ?? [],
widgets: w.widgets ?? [], widgets: w.widgets ?? [],
workflow: w.workflow ?? { approvalUserFieldName: w.approvalUserFieldName ?? '',
approvalUserFieldName: '', approvalDateFieldName: w.approvalDateFieldName ?? '',
isFilterUserName: false, approvalStatusFieldName: w.approvalStatusFieldName ?? '',
approvalDateFieldName: '', approvalDescriptionFieldName: w.approvalDescriptionFieldName ?? '',
approvalStatusFieldName: '', approvalIsFilterUserName: w.approvalIsFilterUserName ?? false,
approvalDescriptionFieldName: '', criteria: w.criteria ?? [],
criteria: [],
},
workflowCriteria: w.workflowCriteria ?? w.workflow?.criteria ?? [],
}) })
// Queue column load to run once dbObjects is available // Queue column load to run once dbObjects is available
@ -476,19 +520,19 @@ const Wizard = () => {
caption: g.caption ?? '', caption: g.caption ?? '',
colCount: g.colCount ?? 2, colCount: g.colCount ?? 2,
items: (g.items ?? []).map( items: (g.items ?? []).map(
(it: any, ii: number) => (it: WizardGroupItem, ii: number) =>
({ ({
id: `${it.dataField}_edit_${ii}_${Date.now()}`, id: `${it.fieldName}_edit_${ii}_${Date.now()}`,
dataField: it.dataField ?? '', fieldName: it.fieldName ?? '',
editorType: it.editorType ?? 'dxTextBox', editorType: it.editorType ?? 'dxTextBox',
editorOptions: it.editorOptions ?? '', editorOptions: it.editorOptions ?? '',
editorScript: it.editorScript ?? '', editorScript: it.editorScript ?? '',
colSpan: it.colSpan ?? 1, colSpan: it.colSpan ?? 1,
isRequired: it.isRequired ?? false, isRequired: it.isRequired ?? false,
includeInEditingForm: it.includeInEditingForm ?? true, includeInEditingForm: it.includeInEditingForm ?? true,
turkishCaption: it.turkishCaption ?? it.dataField, turkishCaption: it.turkishCaption ?? it.fieldName,
englishCaption: it.englishCaption ?? it.dataField, englishCaption: it.englishCaption ?? it.fieldName,
captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`, captionName: it.captionName ?? `App.Listform.ListformField.${it.fieldName}`,
lookupDataSourceType: lookupDataSourceType:
(it.lookupDataSourceType as UiLookupDataSourceTypeEnum) ?? (it.lookupDataSourceType as UiLookupDataSourceTypeEnum) ??
UiLookupDataSourceTypeEnum.StaticData, UiLookupDataSourceTypeEnum.StaticData,
@ -501,23 +545,21 @@ const Wizard = () => {
setEditingGroups(restoredGroups) setEditingGroups(restoredGroups)
// Restore selectedColumns from all group items // 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) setSelectedColumns(allFields)
} }
setSubForms(w.subForms ?? []) setSubForms(w.subForms ?? [])
setWidgets(w.widgets ?? []) setWidgets(w.widgets ?? [])
setWorkflow( setWorkflow({
w.workflow ?? { approvalUserFieldName: w.approvalUserFieldName ?? '',
approvalUserFieldName: '', approvalDateFieldName: w.approvalDateFieldName ?? '',
isFilterUserName: false, approvalStatusFieldName: w.approvalStatusFieldName ?? '',
approvalDateFieldName: '', approvalDescriptionFieldName: w.approvalDescriptionFieldName ?? '',
approvalStatusFieldName: '', approvalIsFilterUserName: w.approvalIsFilterUserName ?? false,
approvalDescriptionFieldName: '', criteria: w.criteria ?? [],
criteria: [], })
}, setCriteria(w.criteria ?? [])
)
setWorkflowCriteria(w.workflowCriteria ?? w.workflow?.criteria ?? [])
} catch { } catch {
toast.push( toast.push(
<Notification type="danger"> <Notification type="danger">
@ -619,8 +661,16 @@ const Wizard = () => {
formikRef.current?.setFieldValue('menuParentCode', code) formikRef.current?.setFieldValue('menuParentCode', code)
const selectedMenu = rawMenuItems.find((item) => item.code === code) const selectedMenu = rawMenuItems.find((item) => item.code === code)
formikRef.current?.setFieldValue('menuParentIcon', selectedMenu?.icon ?? '') formikRef.current?.setFieldValue('menuParentIcon', selectedMenu?.icon ?? '')
if (!code) return formikRef.current?.setFieldValue('languageTextMenuParentEn', selectedMenu?.menuTextEn ?? '')
applyPermissionGroupFromRoot(findRootCode(rawMenuItems, code)) 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: { const handleMenuCreated = async (menu: {
@ -629,6 +679,7 @@ const Wizard = () => {
menuTextEn: string menuTextEn: string
menuTextTr: string menuTextTr: string
icon?: string icon?: string
shortName?: string
}) => { }) => {
formikRef.current?.setFieldValue('menuParentCode', menu.code) formikRef.current?.setFieldValue('menuParentCode', menu.code)
formikRef.current?.setFieldValue('menuParentIcon', menu.icon ?? '') formikRef.current?.setFieldValue('menuParentIcon', menu.icon ?? '')
@ -636,6 +687,11 @@ const Wizard = () => {
formikRef.current?.setFieldValue('languageTextMenuParentTr', menu.menuTextTr) formikRef.current?.setFieldValue('languageTextMenuParentTr', menu.menuTextTr)
const rootCode = menu.parentCode ? findRootCode(rawMenuItems, menu.parentCode) : menu.code 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) applyPermissionGroupFromRoot(rootCode)
await getMenuList() await getMenuList()
@ -707,10 +763,10 @@ const Wizard = () => {
caption: g.caption, caption: g.caption,
colCount: g.colCount, colCount: g.colCount,
items: g.items.map((item) => { items: g.items.map((item) => {
const col = selectCommandColumns.find((c) => c.columnName === item.dataField) const col = selectCommandColumns.find((c) => c.columnName === item.fieldName)
return { return {
dataField: item.dataField, fieldName: item.fieldName,
editorType: item.editorType, editorType: item.editorType,
editorOptions: item.editorOptions ?? '', editorOptions: item.editorOptions ?? '',
editorScript: item.editorScript ?? '', editorScript: item.editorScript ?? '',
@ -730,11 +786,12 @@ const Wizard = () => {
})), })),
subForms, subForms,
widgets, widgets,
workflow: { approvalUserFieldName: workflow.approvalUserFieldName,
...workflow, approvalDateFieldName: workflow.approvalDateFieldName,
criteria: workflowCriteria, approvalStatusFieldName: workflow.approvalStatusFieldName,
}, approvalDescriptionFieldName: workflow.approvalDescriptionFieldName,
workflowCriteria, approvalIsFilterUserName: workflow.approvalIsFilterUserName,
criteria,
}) })
// ✅ sonra config çek // ✅ sonra config çek
@ -780,6 +837,7 @@ const Wizard = () => {
<Steps.Item title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'} /> <Steps.Item title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'} />
<Steps.Item title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'} /> <Steps.Item title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'} />
<Steps.Item title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'} /> <Steps.Item title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'} />
<Steps.Item title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'} />
<Steps.Item title={translate('::App.Platform.Deploy') || 'Deploy'} /> <Steps.Item title={translate('::App.Platform.Deploy') || 'Deploy'} />
</Steps> </Steps>
</div> </div>
@ -815,6 +873,7 @@ const Wizard = () => {
onMenuParentChange={handleMenuParentChange} onMenuParentChange={handleMenuParentChange}
onClearMenuParent={() => { onClearMenuParent={() => {
formikRef.current?.setFieldValue('menuParentCode', '') formikRef.current?.setFieldValue('menuParentCode', '')
formikRef.current?.setFieldValue('menuParentShortName', '')
formikRef.current?.setFieldValue('menuParentIcon', '') formikRef.current?.setFieldValue('menuParentIcon', '')
}} }}
onMenuCreated={handleMenuCreated} onMenuCreated={handleMenuCreated}
@ -900,19 +959,30 @@ const Wizard = () => {
<WizardStep6 <WizardStep6
listFormCode={values.listFormCode} listFormCode={values.listFormCode}
workflow={workflow} workflow={workflow}
criteria={workflowCriteria} criteria={criteria}
selectCommandColumns={selectCommandColumns} selectCommandColumns={selectCommandColumns}
translate={translate} translate={translate}
onWorkflowChange={setWorkflow} onWorkflowChange={setWorkflow}
onCriteriaChange={setWorkflowCriteria} onCriteriaChange={setCriteria}
onBack={() => setCurrentStep(4)} onBack={() => setCurrentStep(4)}
onNext={() => setCurrentStep(6)} onNext={() => setCurrentStep(6)}
/> />
)} )}
{/* ─── Step 7: Deploy ───────────────────────────── */} {/* ─── Step 7: Todo / Kanban ────────────────────── */}
{currentStep === 6 && ( {currentStep === 6 && (
<WizardStep7 <WizardStep7
selectedColumns={selectedColumns}
selectCommandColumns={selectCommandColumns}
translate={translate}
onBack={() => setCurrentStep(5)}
onNext={() => setCurrentStep(7)}
/>
)}
{/* ─── Step 8: Deploy ───────────────────────────── */}
{currentStep === 7 && (
<WizardStep8
values={values} values={values}
wizardName={values.wizardName} wizardName={values.wizardName}
selectedColumns={selectedColumns} selectedColumns={selectedColumns}
@ -921,9 +991,9 @@ const Wizard = () => {
subForms={subForms} subForms={subForms}
widgets={widgets} widgets={widgets}
workflow={workflow} workflow={workflow}
workflowCriteria={workflowCriteria} criteria={criteria}
translate={translate} translate={translate}
onBack={() => setCurrentStep(5)} onBack={() => setCurrentStep(6)}
onSubmit={handleDeploy} onSubmit={handleDeploy}
/> />
)} )}

View file

@ -26,6 +26,7 @@ import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
export interface MenuTreeNode { export interface MenuTreeNode {
code: string code: string
displayName: string displayName: string
shortName?: string
icon?: string icon?: string
url?: string url?: string
order?: number order?: number
@ -41,6 +42,7 @@ export function buildMenuTree(items: MenuItem[]): MenuTreeNode[] {
map.set(item.code!, { map.set(item.code!, {
code: item.code!, code: item.code!,
displayName: item.displayName ?? item.code!, displayName: item.displayName ?? item.code!,
shortName: item.shortName ?? undefined,
icon: item.icon ?? undefined, icon: item.icon ?? undefined,
url: item.url ?? undefined, url: item.url ?? undefined,
order: item.order ?? undefined, order: item.order ?? undefined,

View file

@ -516,6 +516,20 @@ const WizardStep2 = ({
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
invalid={errors?.card && touched?.card}
errorMessage={errors?.card}
>
<Field
className="w-20"
autoComplete="off"
name="card"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.CardLayout')}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')} label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
invalid={errors?.pivot && touched?.pivot} invalid={errors?.pivot && touched?.pivot}
@ -585,6 +599,20 @@ const WizardStep2 = ({
component={Checkbox} component={Checkbox}
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout')}
invalid={errors?.todo && touched?.todo}
errorMessage={errors?.todo}
>
<Field
className="w-20"
autoComplete="off"
name="todo"
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TodoLayout')}
component={Checkbox}
/>
</FormItem>
</div> </div>
</div> </div>

View file

@ -1,4 +1,5 @@
import { Button, Dialog } from '@/components/ui' import { Button, Dialog } from '@/components/ui'
import type { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { import {
columnEditorTypeListOptions, columnEditorTypeListOptions,
@ -28,14 +29,17 @@ import {
FaTrash, FaTrash,
FaArrowRight, FaArrowRight,
FaCode, FaCode,
FaSlidersH,
} from 'react-icons/fa' } from 'react-icons/fa'
import { UiLookupDataSourceTypeEnum } from '@/proxy/form/models' 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' import { buildLookupQuery } from '../lookupQuery'
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
export interface WizardGroupItem { export interface WizardGroupItem {
id: string id: string
dataField: string fieldName: string
editorType: string editorType: string
editorOptions: string editorOptions: string
editorScript: string editorScript: string
@ -109,7 +113,7 @@ function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupIte
const sqlType = meta?.dataType ?? '' const sqlType = meta?.dataType ?? ''
return { return {
id: `${colName}_${Date.now()}`, id: `${colName}_${Date.now()}`,
dataField: colName, fieldName: colName,
editorType: inferEditorType(sqlType), editorType: inferEditorType(sqlType),
editorOptions: '', editorOptions: '',
editorScript: '', editorScript: '',
@ -169,6 +173,7 @@ interface SortableItemProps {
groupColCount: number groupColCount: number
dbObjects: SqlObjectExplorerDto | null dbObjects: SqlObjectExplorerDto | null
dsCode: string dsCode: string
editorScriptFields: SelectBoxOption[]
onTurkishCaptionChange: (val: string) => void onTurkishCaptionChange: (val: string) => void
onEnglishCaptionChange: (val: string) => void onEnglishCaptionChange: (val: string) => void
onEditorTypeChange: (val: string) => void onEditorTypeChange: (val: string) => void
@ -190,6 +195,7 @@ function SortableItem({
groupColCount, groupColCount,
dbObjects, dbObjects,
dsCode, dsCode,
editorScriptFields,
onTurkishCaptionChange, onTurkishCaptionChange,
onEnglishCaptionChange, onEnglishCaptionChange,
onEditorTypeChange, onEditorTypeChange,
@ -207,6 +213,8 @@ function SortableItem({
}: SortableItemProps) { }: SortableItemProps) {
const { translate } = useLocalization() const { translate } = useLocalization()
const [isTablePickerOpen, setIsTablePickerOpen] = useState(false) const [isTablePickerOpen, setIsTablePickerOpen] = useState(false)
const [isEditorOptionsDialogOpen, setIsEditorOptionsDialogOpen] = useState(false)
const [isEditorScriptDialogOpen, setIsEditorScriptDialogOpen] = useState(false)
const [tableSearch, setTableSearch] = useState('') const [tableSearch, setTableSearch] = useState('')
const [pickerStep, setPickerStep] = useState<'table' | 'columns'>('table') const [pickerStep, setPickerStep] = useState<'table' | 'columns'>('table')
const [pickerTable, setPickerTable] = useState<{ schemaName: string; tableName: string } | null>( const [pickerTable, setPickerTable] = useState<{ schemaName: string; tableName: string } | null>(
@ -248,7 +256,7 @@ function SortableItem({
tabIndex={-1} tabIndex={-1}
/> />
<span className="flex-1 text-xs font-semibold text-indigo-600 dark:text-indigo-400 truncate"> <span className="flex-1 text-xs font-semibold text-indigo-600 dark:text-indigo-400 truncate">
{item.dataField} {item.fieldName}
</span> </span>
<Button <Button
type="button" type="button"
@ -564,12 +572,23 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium"> <span className="text-[10px] text-gray-400 font-medium">
{translate('::ListForms.Wizard.Step3.EditorOptions')} {translate('::ListForms.Wizard.Step3.EditorOptions')}
</span> </span>
<input <div className="flex items-center gap-1">
value={item.editorOptions} <input
onChange={(e) => onEditorOptionsChange(e.target.value)} value={item.editorOptions}
placeholder='{"readOnly": false}' onChange={(e) => onEditorOptionsChange(e.target.value)}
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" 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> </div>
{/* Editor Script */} {/* Editor Script */}
@ -577,14 +596,41 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium"> <span className="text-[10px] text-gray-400 font-medium">
{translate('::ListForms.Wizard.Step3.EditorScript')} {translate('::ListForms.Wizard.Step3.EditorScript')}
</span> </span>
<input <div className="flex items-center gap-1">
value={item.editorScript} <input
onChange={(e) => onEditorScriptChange(e.target.value)} value={item.editorScript}
placeholder="(e) => { /* e.component */ }" onChange={(e) => onEditorScriptChange(e.target.value)}
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" 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> </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 */} {/* Bottom row: ColSpan + Editing Form + Required */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -639,6 +685,7 @@ interface GroupCardProps {
hasAvailable: boolean hasAvailable: boolean
dbObjects: SqlObjectExplorerDto | null dbObjects: SqlObjectExplorerDto | null
dsCode: string dsCode: string
editorScriptFields: SelectBoxOption[]
onCaptionChange: (val: string) => void onCaptionChange: (val: string) => void
onColCountChange: (val: number) => void onColCountChange: (val: number) => void
onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void
@ -653,6 +700,7 @@ function GroupCard({
hasAvailable, hasAvailable,
dbObjects, dbObjects,
dsCode, dsCode,
editorScriptFields,
onCaptionChange, onCaptionChange,
onColCountChange, onColCountChange,
onItemChange, onItemChange,
@ -759,6 +807,7 @@ function GroupCard({
groupColCount={group.colCount} groupColCount={group.colCount}
dbObjects={dbObjects} dbObjects={dbObjects}
dsCode={dsCode} dsCode={dsCode}
editorScriptFields={editorScriptFields}
onCaptionNameChange={(val) => onItemChange(item.id, { captionName: val })} onCaptionNameChange={(val) => onItemChange(item.id, { captionName: val })}
onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })} onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })}
onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })} onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })}
@ -809,7 +858,7 @@ const WizardStep3 = ({
// 1. Remove items that are no longer in selectedColumns // 1. Remove items that are no longer in selectedColumns
const cleaned = groups.map((g) => ({ const cleaned = groups.map((g) => ({
...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 // 2. Ensure at least one empty group exists for the user to drag into
@ -835,8 +884,14 @@ const WizardStep3 = ({
}, [selectedColumns]) }, [selectedColumns])
// ── Available columns = selected but NOT yet placed ─────────────────────── // ── 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 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 ─────────────────────────────────────────────────────────────── // ── Helpers ───────────────────────────────────────────────────────────────
const colMeta = (name: string) => selectCommandColumns.find((c) => c.columnName === name) const colMeta = (name: string) => selectCommandColumns.find((c) => c.columnName === name)
@ -878,7 +933,7 @@ const WizardStep3 = ({
} }
const addAllToGroup = (groupId: string) => { 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)) const toAdd = [...selectedColumns].filter((c) => !placed.has(c))
if (toAdd.length === 0) return if (toAdd.length === 0) return
onGroupsChange( 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"> <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" /> <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"> <span className="text-xs font-semibold text-indigo-600 bg-indigo-100 px-2 py-0.5 rounded">
{item.dataField} {item.fieldName}
</span> </span>
<span className="text-xs text-gray-500">{item.editorType}</span> <span className="text-xs text-gray-500">{item.editorType}</span>
</div> </div>
@ -1087,6 +1142,7 @@ const WizardStep3 = ({
hasAvailable={availableColumns.length > 0} hasAvailable={availableColumns.length > 0}
dbObjects={dbObjects} dbObjects={dbObjects}
dsCode={dsCode} dsCode={dsCode}
editorScriptFields={editorScriptFields}
onCaptionChange={(val) => updateGroup(group.id, { caption: val })} onCaptionChange={(val) => updateGroup(group.id, { caption: val })}
onColCountChange={(val) => updateGroup(group.id, { colCount: val })} onColCountChange={(val) => updateGroup(group.id, { colCount: val })}
onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)} onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)}

View file

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

View file

@ -1,714 +1,169 @@
import { Button } from '@/components/ui' import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models' import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import React, { useState } from 'react' import type { SelectBoxOption } from '@/types/shared'
import { import { Field, type FieldProps, useFormikContext } from 'formik'
FaArrowLeft, import { useMemo, useState } from 'react'
FaCheckCircle, import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
FaChevronDown,
FaChevronRight,
FaCircle,
FaExclamationCircle,
FaRocket,
FaSpinner,
} from 'react-icons/fa'
import { WizardGroup } from './WizardStep3'
import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
import {
ListFormWorkflowCriteriaDto,
SubFormDto,
WidgetEditDto,
WorkflowDto,
} from '@/proxy/form/models'
import { formatWorkflowApprovers } from '@/utils/workflow/workflowHelpers'
// ─── Types ──────────────────────────────────────────────────────────────────── interface WizardStep7Props {
export interface WizardStep7Props {
values: ListFormWizardDto
wizardName: string
selectedColumns: Set<string> selectedColumns: Set<string>
selectCommandColumns: DatabaseColumnDto[] selectCommandColumns: DatabaseColumnDto[]
groups: WizardGroup[]
subForms: SubFormDto[]
widgets: WidgetEditDto[]
workflow: WorkflowDto
workflowCriteria: ListFormWorkflowCriteriaDto[]
translate: (key: string) => string translate: (key: string) => string
onBack: () => void onBack: () => void
onSubmit: () => Promise<void> onNext: () => void
} }
type LogStatus = 'pending' | 'running' | 'success' | 'error' const mappings: Array<{
name:
| 'todoTitleExpr'
| 'todoStatusExpr'
| 'todoDescriptionExpr'
| 'todoDueDateExpr'
| 'todoTagExpr'
| 'todoAssigneeExpr'
| 'todoPriorityExpr'
| 'todoCompletedExpr'
| 'todoOrderExpr'
translationKey: string
required?: boolean
}> = [
{
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' },
]
interface LogEntry {
id: number
label: string
status: LogStatus
detail?: string
}
// ─── Deploy log steps ─────────────────────────────────────────────────────────
function buildLogSteps(
values: ListFormWizardDto,
groups: WizardGroup[],
translate: (key: string, params?: Record<string, string | number>) => string,
): Omit<LogEntry, 'status'>[] {
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
return [
{ id: 1, label: translate('::ListForms.Wizard.Step4.Log.ValidatingConfig') },
{
id: 2,
label: translate('::ListForms.Wizard.Step4.Log.CreatingMenu', { 0: values.menuCode }),
detail: `Parent: ${values.menuParentCode}`,
},
{
id: 3,
label: translate('::ListForms.Wizard.Step4.Log.SavingLanguageTexts'),
detail: `EN: ${values.languageTextMenuEn} / TR: ${values.languageTextMenuTr}`,
},
{
id: 4,
label: translate('::ListForms.Wizard.Step4.Log.ConfiguringPermission', {
0: values.permissionGroupName,
}),
},
{
id: 5,
label: translate('::ListForms.Wizard.Step4.Log.ConnectingDataSource', {
0: values.dataSourceCode,
}),
},
{
id: 6,
label: translate('::ListForms.Wizard.Step4.Log.CreatingListForm', { 0: values.listFormCode }),
detail: `Key: ${values.keyFieldName}`,
},
{
id: 7,
label: translate('::ListForms.Wizard.Step4.Log.SavingFormGroups', {
0: groups.length,
1: totalFields,
}),
},
{ id: 8, label: translate('::ListForms.Wizard.Step4.Log.Deploying') },
{ id: 9, label: translate('::ListForms.Wizard.Step4.Log.Completed') },
]
}
// ─── Mini-components ──────────────────────────────────────────────────────────
function LogIcon({ status }: { status: LogStatus }) {
if (status === 'running') return <FaSpinner className="text-indigo-500 animate-spin shrink-0" />
if (status === 'success') return <FaCheckCircle className="text-emerald-500 shrink-0" />
if (status === 'error') return <FaExclamationCircle className="text-red-500 shrink-0" />
return <FaCircle className="text-gray-300 dark:text-gray-600 shrink-0 text-[8px] mt-1" />
}
interface SectionProps {
title: string
badge?: string | number
children: React.ReactNode
defaultOpen?: boolean
}
function Section({ title, badge, children, defaultOpen = true }: SectionProps) {
const [open, setOpen] = useState(defaultOpen)
return (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<Button
type="button"
onClick={() => setOpen((v) => !v)}
variant="plain"
shape="none"
className="w-full !h-auto !items-center !justify-between !rounded-none !bg-gray-50 !px-4 !py-2.5 transition-colors hover:!bg-gray-100 dark:!bg-gray-800 dark:hover:!bg-gray-700"
>
<div className="flex items-center gap-2">
{open ? (
<FaChevronDown className="text-gray-400 text-xs" />
) : (
<FaChevronRight className="text-gray-400 text-xs" />
)}
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">{title}</span>
</div>
{badge !== undefined && (
<span className="text-xs bg-indigo-100 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded-full font-medium">
{badge}
</span>
)}
</Button>
{open && <div className="px-4 py-3 bg-white dark:bg-gray-900">{children}</div>}
</div>
)
}
function Row({ label, value }: { label: string; value?: string | number }) {
if (!value && value !== 0) return null
return (
<div className="flex gap-2 py-1 border-b border-gray-100 dark:border-gray-800 last:border-0">
<span className="text-xs text-gray-400 w-40 shrink-0">{label}</span>
<span className="text-xs text-gray-700 dark:text-gray-200 font-medium break-all">
{value}
</span>
</div>
)
}
// ─── WizardStep7 ──────────────────────────────────────────────────────────────
const WizardStep7 = ({ const WizardStep7 = ({
values,
wizardName,
selectedColumns, selectedColumns,
selectCommandColumns, selectCommandColumns,
groups,
subForms,
widgets,
workflow,
workflowCriteria,
translate, translate,
onBack, onBack,
onSubmit, onNext,
}: WizardStep7Props) => { }: WizardStep7Props) => {
const [logs, setLogs] = useState<LogEntry[]>([]) const { errors, touched, values, setFieldTouched, setFieldValue, validateForm } =
const [isDeploying, setIsDeploying] = useState(false) useFormikContext<ListFormWizardDto>()
const [isDone, setIsDone] = useState(false) const [validationError, setValidationError] = useState('')
const [hasError, setHasError] = useState(false) const fieldOptions = useMemo<SelectBoxOption[]>(
() =>
selectCommandColumns
.filter((column) => selectedColumns.has(column.columnName))
.map((column) => ({ value: column.columnName, label: column.columnName })),
[selectCommandColumns, selectedColumns],
)
const steps = buildLogSteps(values, groups, translate) const continueToDeploy = async () => {
await Promise.all([
setFieldTouched('todoTitleExpr', true, false),
setFieldTouched('todoStatusExpr', true, false),
])
const validationErrors = await validateForm()
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) if (validationErrors.todoTitleExpr || validationErrors.todoStatusExpr) {
setValidationError(translate('::ListForms.ListFormFieldEdit.TodoRequiredFields'))
const runDeploy = async () => { return
if (isDeploying) return
setIsDeploying(true)
setHasError(false)
setIsDone(false)
// Initialize all as pending
setLogs(steps.map((s) => ({ ...s, status: 'pending' })))
const setStatus = (id: number, status: LogStatus) =>
setLogs((prev) => prev.map((l) => (l.id === id ? { ...l, status } : l)))
// Steps 1-7: pre-deploy simulation (fast)
for (let i = 0; i < steps.length - 2; i++) {
const step = steps[i]
setStatus(step.id, 'running')
await sleep(300 + Math.random() * 200)
setStatus(step.id, 'success')
}
// Step 8: actual API call
const deployStep = steps[steps.length - 2]
setStatus(deployStep.id, 'running')
try {
await onSubmit()
setStatus(deployStep.id, 'success')
await sleep(200)
// Step 9: done
const doneStep = steps[steps.length - 1]
setStatus(doneStep.id, 'running')
await sleep(300)
setStatus(doneStep.id, 'success')
setIsDone(true)
} catch (err: any) {
setStatus(deployStep.id, 'error')
setLogs((prev) => [
...prev,
{
id: 999,
label: `Hata: ${err?.message ?? 'Bilinmeyen hata'}`,
status: 'error',
},
])
setHasError(true)
} finally {
setIsDeploying(false)
} }
setValidationError('')
onNext()
} }
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)
.map((item) => ({ ...item, groupCaption: g.caption })),
)
const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.dataField)))
const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col))
const hasWorkflowFields = Boolean(
workflow.approvalUserFieldName ||
workflow.approvalDateFieldName ||
workflow.approvalStatusFieldName ||
workflow.approvalDescriptionFieldName,
)
const workflowItems = workflowCriteria.length > 0 ? workflowCriteria : (workflow.criteria ?? [])
return ( return (
<div className="grid grid-cols-[3fr_2fr] gap-5 pb-24 items-start"> <div className="pb-20">
{/* ── Left: Summary ──────────────────────────────────────────── */} <div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
<div className="flex flex-col gap-3"> {mappings.map((mapping) => {
<div className="grid grid-cols-2 gap-3 items-start"> const isRequired = Boolean(mapping.required && values.todo)
<Section title={translate('::ListForms.Wizard.Step4.MenuInfo')}>
<Row label={translate('::ListForms.Wizard.Step4.WizardName')} value={wizardName} />
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
<Row
label={translate('::ListForms.Wizard.Step4.MenuParent')}
value={values.menuParentCode}
/>
<Row
label={translate('::ListForms.Wizard.Step4.PermissionGroup')}
value={values.permissionGroupName}
/>
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
<Row
label={translate('::ListForms.Wizard.Step4.MenuTr')}
value={values.languageTextMenuTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuEn')}
value={values.languageTextMenuEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuParentTr')}
value={values.languageTextMenuParentTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuParentEn')}
value={values.languageTextMenuParentEn}
/>
</Section>
<Section title={translate('::ListForms.Wizard.Step4.ListFormSettings')}> return (
<Row <FormItem
label={translate('::ListForms.Wizard.Step4.ListFormCode')} key={mapping.name}
value={values.listFormCode} label={`${translate(mapping.translationKey)}${isRequired ? ' *' : ''}`}
/> invalid={Boolean(touched[mapping.name] && errors[mapping.name])}
<Row errorMessage={touched[mapping.name] ? errors[mapping.name] : undefined}
label={translate('::ListForms.Wizard.Step4.TitleTr')}
value={values.languageTextTitleTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.TitleEn')}
value={values.languageTextTitleEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DescTr')}
value={values.languageTextDescTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DescEn')}
value={values.languageTextDescEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DataSource')}
value={values.dataSourceCode}
/>
<Row
label={translate('::App.Listform.ListformField.ConnectionString')}
value={values.dataSourceConnectionString}
/>
<Row
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
value={`${values.selectCommand} (${
selectCommandTypeOptions.find((o: any) => o.value === values.selectCommandType)
?.label ?? String(values.selectCommandType)
})`}
/>
<Row
label={translate('::ListForms.Wizard.Step4.KeyField')}
value={`${values.keyFieldName} (${
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
?.label ?? String(values.keyFieldDbSourceType)
})`}
/>
</Section>
</div>
<Section
title={
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
}
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
>
<div className="mb-3 grid grid-cols-3 gap-2">
{[
{
label: translate('::ListForms.Wizard.Step4.SelectedColumns'),
value: selectedColumns.size,
className: 'text-indigo-600 dark:text-indigo-400',
},
{
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
value: editingFormFields.length,
className: 'text-emerald-600 dark:text-emerald-400',
},
{
label: translate('::ListForms.Wizard.Step4.FormGroups'),
value: groups.length,
className: 'text-gray-700 dark:text-gray-200',
},
].map((item) => (
<div
key={item.label}
className="rounded-lg border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 px-3 py-2"
>
<div className={`text-lg font-semibold leading-none ${item.className}`}>
{item.value}
</div>
<div className="mt-1 text-[11px] text-gray-400 truncate">{item.label}</div>
</div>
))}
</div>
<div className="flex flex-col gap-2">
{groups.map((g) => (
<div
key={g.id}
className="overflow-hidden rounded-lg border border-gray-100 dark:border-gray-800"
>
<div className="flex items-center justify-between gap-3 bg-gray-50 dark:bg-gray-800 px-3 py-2">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-200 truncate">
{g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
</span>
<div className="flex items-center gap-1.5 text-[10px] text-gray-400 shrink-0">
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
{g.items.length} {translate('::ListForms.Wizard.Step4.StatField')}
</span>
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
{g.colCount} {translate('::App.Listform.ListformField.Column')}
</span>
</div>
</div>
<div className="divide-y divide-gray-100 dark:divide-gray-800">
{g.items.length === 0 ? (
<span className="block px-3 py-2 text-xs text-gray-300 italic">
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
</span>
) : (
g.items.map((item) => {
const meta = selectCommandColumns.find((c) => c.columnName === item.dataField)
const isKeyField = item.dataField === values.keyFieldName
const isPopupField = item.includeInEditingForm && !isKeyField
return (
<div
key={item.id}
className="grid grid-cols-[minmax(150px,1fr)_auto] items-center gap-3 px-3 py-1.5"
>
<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}
</span>
{meta?.dataType && (
<span className="text-[10px] text-gray-400 truncate">
{meta.dataType}
</span>
)}
</div>
<div className="flex flex-wrap justify-end gap-1">
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
</span>
{isPopupField && (
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
</span>
)}
{isKeyField && (
<span className="rounded bg-amber-50 dark:bg-amber-900/30 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300">
{translate('::ListForms.Wizard.Step4.KeyField')}
</span>
)}
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
{item.editorType}
</span>
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
span-{item.colSpan}
{item.isRequired && <span className="ml-1 text-red-400">*</span>}
</span>
</div>
</div>
)
})
)}
</div>
</div>
))}
{ungroupedSelectedColumns.length > 0 && (
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
</div>
<div className="flex flex-wrap gap-1.5">
{ungroupedSelectedColumns.map((col) => {
const meta = selectCommandColumns.find((c) => c.columnName === col)
return (
<span
key={col}
className="inline-flex items-center gap-1 rounded-full border border-amber-200 dark:border-amber-800 bg-white dark:bg-gray-900 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300"
>
{col}
{meta?.dataType && (
<span className="text-[10px] text-amber-500 opacity-70">
{meta.dataType}
</span>
)}
</span>
)
})}
</div>
</div>
)}
</div>
</Section>
{subForms.length > 0 && (
<Section
title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'}
badge={subForms.length}
>
<div className="flex flex-col gap-3">
{subForms.map((subForm, index) => (
<div
key={`${subForm.code}-${index}`}
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
>
<Row label="Tab Title" value={subForm.tabTitle} />
<Row label="List Form Code" value={subForm.code} />
<Row label="Tab Type" value={subForm.tabType} />
<Row label="Refresh" value={subForm.isRefresh ? 'Active' : 'Inactive'} />
{(subForm.relation || []).length > 0 && (
<div className="pt-2">
<span className="text-xs text-gray-400">Relations</span>
<div className="mt-1 flex flex-wrap gap-1.5">
{subForm.relation.map((relation, relationIndex) => (
<span
key={`${relation.parentFieldName}-${relation.childFieldName}-${relationIndex}`}
className="rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs text-gray-600 dark:text-gray-300"
>
{relation.parentFieldName} : {relation.childFieldName}
</span>
))}
</div>
</div>
)}
</div>
))}
</div>
</Section>
)}
{widgets.length > 0 && (
<Section
title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'}
badge={widgets.length}
>
<div className="flex flex-col gap-3">
{widgets.map((widget, index) => (
<div
key={`${widget.title}-${widget.value}-${index}`}
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
>
<Row label="Title Field" value={widget.title} />
<Row label="Value Field" value={widget.value} />
<Row label="Color Field" value={widget.color} />
<Row label="Icon Field" value={widget.icon} />
<Row label="Column Gap" value={widget.colGap} />
<Row label="Column Span" value={widget.colSpan} />
<Row label="Class Name" value={widget.className} />
<Row label="Value Class Name" value={widget.valueClassName} />
<Row label="Status" value={widget.isActive ? 'Active' : 'Inactive'} />
<Row label="Sql Query" value={widget.sqlQuery} />
</div>
))}
</div>
</Section>
)}
{(hasWorkflowFields || workflowItems.length > 0) && (
<Section
title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'}
badge={workflowItems.length}
>
<div className="grid grid-cols-2 gap-3">
<div>
<Row label="Approval User" value={workflow.approvalUserFieldName} />
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
<Row
label="Is Filter User Name?"
value={workflow.isFilterUserName === true ? 'Yes' : 'No'}
/>
</div>
{workflowItems.length > 0 && (
<div className="flex flex-col gap-2">
{workflowItems.map((criteria, index) => (
<div
key={criteria.id || index}
className="rounded bg-gray-50 dark:bg-gray-800 px-3 py-2"
>
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
</div>
{criteria.kind === 'Compare' && (
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
{criteria.compareColumn} {criteria.compareOperator}{' '}
{criteria.compareValue}
</div>
)}
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
criteria.approver && (
<div className="text-[11px] text-gray-500 dark:text-gray-400">
Approver: {formatWorkflowApprovers(criteria.approver)}
</div>
)}
</div>
))}
</div>
)}
</div>
</Section>
)}
</div>
{/* ── Right: Deploy ──────────────────────────────────────────── */}
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
{/* Stats */}
<div className="grid grid-cols-7 gap-2">
{[
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
{
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
value: editingFormFields.length,
},
{
label: translate('::App.Listform.ListformField.Column'),
value: selectedColumns.size,
},
{
label: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
value: subForms.length,
},
{
label: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
value: widgets.length,
},
{
label: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
value: workflowItems.length,
},
].map((s) => (
<div
key={s.label}
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3 text-center shadow-sm"
> >
<div className="text-2xl font-bold text-indigo-600 dark:text-indigo-400"> <Field name={mapping.name}>
{s.value} {({ field, form }: FieldProps) => (
</div> <Select
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div> field={field}
</div> form={form}
))} isClearable={!isRequired}
</div> options={fieldOptions}
value={fieldOptions.find((option) => option.value === values[mapping.name])}
{/* Log panel — grows to fill remaining height */} onChange={(option) => {
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1 min-h-0"> form.setFieldValue(field.name, option?.value ?? '')
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0"> setValidationError('')
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2"> }}
<FaRocket className="text-indigo-400 text-xs" /> />
{translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'}
</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ı'}
</span>
)}
{hasError && (
<span className="text-xs text-red-500 font-semibold flex items-center gap-1">
<FaExclamationCircle /> {translate('::ListForms.Wizard.Step4.Error') || 'Hata'}
</span>
)}
</div>
<div className="flex-1 overflow-y-auto p-4 bg-[#0d1117] dark:bg-black font-mono min-h-[360px]">
{logs.length === 0 ? (
<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.'}
<br />
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
</span>
</div>
) : (
<div className="flex flex-col gap-2">
{logs.map((log) => (
<div key={log.id} className="flex items-start gap-2.5">
<span className="mt-0.5 shrink-0">
<LogIcon status={log.status} />
</span>
<div>
<span
className={`text-xs leading-relaxed ${
log.status === 'success'
? 'text-emerald-400'
: log.status === 'error'
? 'text-red-400'
: log.status === 'running'
? 'text-yellow-300'
: 'text-gray-600'
}`}
>
{log.label}
</span>
{log.detail && (
<div className="text-[10px] text-gray-600 mt-0.5">{log.detail}</div>
)}
</div>
</div>
))}
{isDone && (
<div className="mt-4 rounded-lg border border-emerald-800 bg-emerald-950/40 px-4 py-2.5 text-xs text-emerald-400 text-center font-semibold">
🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')}
</div>
)} )}
</div> </Field>
)} </FormItem>
</div> )
</div> })}
</div> </div>
{/* ── Fixed Footer ─────────────────────────────────────────────── */} <div className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center"> <FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
<div className="flex items-center gap-3 w-full"> <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')}
</Button>
<Button <Button
size="sm" size="sm"
variant="default" variant="solid"
type="button" type="button"
icon={<FaArrowLeft />} icon={<FaArrowRight />}
onClick={onBack} onClick={continueToDeploy}
disabled={isDeploying}
> >
{translate('::Back') || 'Back'} {translate('::Next')}
</Button> </Button>
<div className="flex-1 flex items-center justify-end">
<Button
size="sm"
variant="solid"
type="button"
icon={<FaRocket />}
loading={isDeploying}
disabled={isDeploying || isDone}
onClick={runDeploy}
>
{isDeploying
? translate('::ListForms.Wizard.Step4.Log.Deploying')
: isDone
? `${translate('::ListForms.Wizard.Step4.Log.Completed')}`
: translate('::ListForms.Wizard.Step4.DeployAndSave')}
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,777 @@
import { Button } from '@/components/ui'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import React, { useState } from 'react'
import {
FaArrowLeft,
FaCheckCircle,
FaChevronDown,
FaChevronRight,
FaCircle,
FaExclamationCircle,
FaRocket,
FaSpinner,
} from 'react-icons/fa'
import { WizardGroup } from './WizardStep3'
import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
import {
ListFormWorkflowCriteriaDto,
SubFormDto,
WidgetEditDto,
WorkflowDto,
} from '@/proxy/form/models'
import { formatWorkflowApprovers } from '@/utils/workflow/workflowHelpers'
// ─── Types ────────────────────────────────────────────────────────────────────
export interface WizardStep8Props {
values: ListFormWizardDto
wizardName: string
selectedColumns: Set<string>
selectCommandColumns: DatabaseColumnDto[]
groups: WizardGroup[]
subForms: SubFormDto[]
widgets: WidgetEditDto[]
workflow: WorkflowDto
criteria: ListFormWorkflowCriteriaDto[]
translate: (key: string) => string
onBack: () => void
onSubmit: () => Promise<void>
}
type LogStatus = 'pending' | 'running' | 'success' | 'error'
interface LogEntry {
id: number
label: string
status: LogStatus
detail?: string
}
// ─── Deploy log steps ─────────────────────────────────────────────────────────
function buildLogSteps(
values: ListFormWizardDto,
groups: WizardGroup[],
translate: (key: string, params?: Record<string, string | number>) => string,
): Omit<LogEntry, 'status'>[] {
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
return [
{ id: 1, label: translate('::ListForms.Wizard.Step4.Log.ValidatingConfig') },
{
id: 2,
label: translate('::ListForms.Wizard.Step4.Log.CreatingMenu', { 0: values.menuCode }),
detail: `Parent: ${values.menuParentCode}`,
},
{
id: 3,
label: translate('::ListForms.Wizard.Step4.Log.SavingLanguageTexts'),
detail: `EN: ${values.languageTextMenuEn} / TR: ${values.languageTextMenuTr}`,
},
{
id: 4,
label: translate('::ListForms.Wizard.Step4.Log.ConfiguringPermission', {
0: values.permissionGroupName,
}),
},
{
id: 5,
label: translate('::ListForms.Wizard.Step4.Log.ConnectingDataSource', {
0: values.dataSourceCode,
}),
},
{
id: 6,
label: translate('::ListForms.Wizard.Step4.Log.CreatingListForm', { 0: values.listFormCode }),
detail: `Key: ${values.keyFieldName}`,
},
{
id: 7,
label: translate('::ListForms.Wizard.Step4.Log.SavingFormGroups', {
0: groups.length,
1: totalFields,
}),
},
{ id: 8, label: translate('::ListForms.Wizard.Step4.Log.Deploying') },
{ id: 9, label: translate('::ListForms.Wizard.Step4.Log.Completed') },
]
}
// ─── Mini-components ──────────────────────────────────────────────────────────
function LogIcon({ status }: { status: LogStatus }) {
if (status === 'running') return <FaSpinner className="text-indigo-500 animate-spin shrink-0" />
if (status === 'success') return <FaCheckCircle className="text-emerald-500 shrink-0" />
if (status === 'error') return <FaExclamationCircle className="text-red-500 shrink-0" />
return <FaCircle className="text-gray-300 dark:text-gray-600 shrink-0 text-[8px] mt-1" />
}
interface SectionProps {
title: string
badge?: string | number
children: React.ReactNode
defaultOpen?: boolean
}
function Section({ title, badge, children, defaultOpen = true }: SectionProps) {
const [open, setOpen] = useState(defaultOpen)
return (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<Button
type="button"
variant="plain"
shape="none"
className="w-full !h-auto !items-center !justify-between !rounded-none !bg-gray-50 !px-4 !py-2.5 transition-colors hover:!bg-gray-100 dark:!bg-gray-800 dark:hover:!bg-gray-700"
onClick={() => setOpen((v) => !v)}
>
<div className="flex items-center gap-2">
{open ? (
<FaChevronDown className="text-gray-400 text-xs" />
) : (
<FaChevronRight className="text-gray-400 text-xs" />
)}
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">{title}</span>
</div>
{badge !== undefined && (
<span className="text-xs bg-indigo-100 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded-full font-medium">
{badge}
</span>
)}
</Button>
{open && <div className="px-4 py-3 bg-white dark:bg-gray-900">{children}</div>}
</div>
)
}
function Row({ label, value }: { label: string; value?: string | number }) {
if (!value && value !== 0) return null
return (
<div className="flex gap-2 py-1 border-b border-gray-100 dark:border-gray-800 last:border-0">
<span className="text-xs text-gray-400 w-40 shrink-0">{label}</span>
<span className="text-xs text-gray-700 dark:text-gray-200 font-medium break-all">
{value}
</span>
</div>
)
}
// ─── WizardStep8 ──────────────────────────────────────────────────────────────
const WizardStep8 = ({
values,
wizardName,
selectedColumns,
selectCommandColumns,
groups,
subForms,
widgets,
workflow,
criteria,
translate,
onBack,
onSubmit,
}: WizardStep8Props) => {
const [logs, setLogs] = useState<LogEntry[]>([])
const [isDeploying, setIsDeploying] = useState(false)
const [isDone, setIsDone] = useState(false)
const [hasError, setHasError] = useState(false)
const steps = buildLogSteps(values, groups, translate)
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const runDeploy = async () => {
if (isDeploying) return
setIsDeploying(true)
setHasError(false)
setIsDone(false)
// Initialize all as pending
setLogs(steps.map((s) => ({ ...s, status: 'pending' })))
const setStatus = (id: number, status: LogStatus) =>
setLogs((prev) => prev.map((l) => (l.id === id ? { ...l, status } : l)))
// Steps 1-7: pre-deploy simulation (fast)
for (let i = 0; i < steps.length - 2; i++) {
const step = steps[i]
setStatus(step.id, 'running')
await sleep(300 + Math.random() * 200)
setStatus(step.id, 'success')
}
// Step 8: actual API call
const deployStep = steps[steps.length - 2]
setStatus(deployStep.id, 'running')
try {
await onSubmit()
setStatus(deployStep.id, 'success')
await sleep(200)
// Step 9: done
const doneStep = steps[steps.length - 1]
setStatus(doneStep.id, 'running')
await sleep(300)
setStatus(doneStep.id, 'success')
setIsDone(true)
} catch (err: any) {
setStatus(deployStep.id, 'error')
setLogs((prev) => [
...prev,
{
id: 999,
label: `${translate('::ListForms.Wizard.Step4.Error')}: ${
err?.message ?? translate('::App.Platform.Unknown')
}`,
status: 'error',
},
])
setHasError(true)
} finally {
setIsDeploying(false)
}
}
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
const editingFormFields = groups.flatMap((g) =>
g.items
.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.fieldName)))
const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col))
const hasWorkflowFields = Boolean(
workflow.approvalUserFieldName ||
workflow.approvalDateFieldName ||
workflow.approvalStatusFieldName ||
workflow.approvalDescriptionFieldName,
)
const workflowItems = criteria.length > 0 ? criteria : (workflow.criteria ?? [])
return (
<div className="grid grid-cols-[3fr_2fr] gap-5 pb-24 items-start">
{/* ── Left: Summary ──────────────────────────────────────────── */}
<div className="flex flex-col gap-3">
<div className="grid grid-cols-2 gap-3 items-start">
<Section title={translate('::ListForms.Wizard.Step4.MenuInfo')}>
<Row label={translate('::ListForms.Wizard.Step4.WizardName')} value={wizardName} />
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
<Row
label={translate('::ListForms.Wizard.Step4.MenuParent')}
value={values.menuParentCode}
/>
<Row
label={translate('::ListForms.Wizard.Step4.PermissionGroup')}
value={values.permissionGroupName}
/>
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
<Row
label={translate('::ListForms.Wizard.Step4.MenuTr')}
value={values.languageTextMenuTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuEn')}
value={values.languageTextMenuEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuParentTr')}
value={values.languageTextMenuParentTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.MenuParentEn')}
value={values.languageTextMenuParentEn}
/>
</Section>
<Section title={translate('::ListForms.Wizard.Step4.ListFormSettings')}>
<Row
label={translate('::ListForms.Wizard.Step4.ListFormCode')}
value={values.listFormCode}
/>
<Row
label={translate('::ListForms.Wizard.Step4.TitleTr')}
value={values.languageTextTitleTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.TitleEn')}
value={values.languageTextTitleEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DescTr')}
value={values.languageTextDescTr}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DescEn')}
value={values.languageTextDescEn}
/>
<Row
label={translate('::ListForms.Wizard.Step4.DataSource')}
value={values.dataSourceCode}
/>
<Row
label={translate('::App.Listform.ListformField.ConnectionString')}
value={values.dataSourceConnectionString}
/>
<Row
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
value={`${values.selectCommand} (${
selectCommandTypeOptions.find((o: any) => o.value === values.selectCommandType)
?.label ?? String(values.selectCommandType)
})`}
/>
<Row
label={translate('::ListForms.Wizard.Step4.KeyField')}
value={`${values.keyFieldName} (${
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
?.label ?? String(values.keyFieldDbSourceType)
})`}
/>
</Section>
</div>
<Section
title={
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
}
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
>
<div className="mb-3 grid grid-cols-3 gap-2">
{[
{
label: translate('::ListForms.Wizard.Step4.SelectedColumns'),
value: selectedColumns.size,
className: 'text-indigo-600 dark:text-indigo-400',
},
{
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
value: editingFormFields.length,
className: 'text-emerald-600 dark:text-emerald-400',
},
{
label: translate('::ListForms.Wizard.Step4.FormGroups'),
value: groups.length,
className: 'text-gray-700 dark:text-gray-200',
},
].map((item) => (
<div
key={item.label}
className="rounded-lg border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 px-3 py-2"
>
<div className={`text-lg font-semibold leading-none ${item.className}`}>
{item.value}
</div>
<div className="mt-1 text-[11px] text-gray-400 truncate">{item.label}</div>
</div>
))}
</div>
<div className="flex flex-col gap-2">
{groups.map((g) => (
<div
key={g.id}
className="overflow-hidden rounded-lg border border-gray-100 dark:border-gray-800"
>
<div className="flex items-center justify-between gap-3 bg-gray-50 dark:bg-gray-800 px-3 py-2">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-200 truncate">
{g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
</span>
<div className="flex items-center gap-1.5 text-[10px] text-gray-400 shrink-0">
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
{g.items.length} {translate('::ListForms.Wizard.Step4.StatField')}
</span>
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
{g.colCount} {translate('::App.Listform.ListformField.Column')}
</span>
</div>
</div>
<div className="divide-y divide-gray-100 dark:divide-gray-800">
{g.items.length === 0 ? (
<span className="block px-3 py-2 text-xs text-gray-300 italic">
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
</span>
) : (
g.items.map((item) => {
const meta = selectCommandColumns.find((c) => c.columnName === item.fieldName)
const isKeyField = item.fieldName === values.keyFieldName
const isPopupField = item.includeInEditingForm && !isKeyField
return (
<div
key={item.id}
className="grid grid-cols-[minmax(150px,1fr)_auto] items-center gap-3 px-3 py-1.5"
>
<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.fieldName}
</span>
{meta?.dataType && (
<span className="text-[10px] text-gray-400 truncate">
{meta.dataType}
</span>
)}
</div>
<div className="flex flex-wrap justify-end gap-1">
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
</span>
{isPopupField && (
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
</span>
)}
{isKeyField && (
<span className="rounded bg-amber-50 dark:bg-amber-900/30 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300">
{translate('::ListForms.Wizard.Step4.KeyField')}
</span>
)}
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
{item.editorType}
</span>
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
span-{item.colSpan}
{item.isRequired && <span className="ml-1 text-red-400">*</span>}
</span>
</div>
</div>
)
})
)}
</div>
</div>
))}
{ungroupedSelectedColumns.length > 0 && (
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
</div>
<div className="flex flex-wrap gap-1.5">
{ungroupedSelectedColumns.map((col) => {
const meta = selectCommandColumns.find((c) => c.columnName === col)
return (
<span
key={col}
className="inline-flex items-center gap-1 rounded-full border border-amber-200 dark:border-amber-800 bg-white dark:bg-gray-900 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300"
>
{col}
{meta?.dataType && (
<span className="text-[10px] text-amber-500 opacity-70">
{meta.dataType}
</span>
)}
</span>
)
})}
</div>
</div>
)}
</div>
</Section>
{subForms.length > 0 && (
<Section
title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'}
badge={subForms.length}
>
<div className="flex flex-col gap-3">
{subForms.map((subForm, index) => (
<div
key={`${subForm.code}-${index}`}
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
>
<Row label="Tab Title" value={subForm.tabTitle} />
<Row label="List Form Code" value={subForm.code} />
<Row label="Tab Type" value={subForm.tabType} />
<Row label="Refresh" value={subForm.isRefresh ? 'Active' : 'Inactive'} />
{(subForm.relation || []).length > 0 && (
<div className="pt-2">
<span className="text-xs text-gray-400">Relations</span>
<div className="mt-1 flex flex-wrap gap-1.5">
{subForm.relation.map((relation, relationIndex) => (
<span
key={`${relation.parentFieldName}-${relation.childFieldName}-${relationIndex}`}
className="rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs text-gray-600 dark:text-gray-300"
>
{relation.parentFieldName} : {relation.childFieldName}
</span>
))}
</div>
</div>
)}
</div>
))}
</div>
</Section>
)}
{widgets.length > 0 && (
<Section
title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'}
badge={widgets.length}
>
<div className="flex flex-col gap-3">
{widgets.map((widget, index) => (
<div
key={`${widget.title}-${widget.value}-${index}`}
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
>
<Row label="Title Field" value={widget.title} />
<Row label="Value Field" value={widget.value} />
<Row label="Color Field" value={widget.color} />
<Row label="Icon Field" value={widget.icon} />
<Row label="Column Gap" value={widget.colGap} />
<Row label="Column Span" value={widget.colSpan} />
<Row label="Class Name" value={widget.className} />
<Row label="Value Class Name" value={widget.valueClassName} />
<Row label="Status" value={widget.isActive ? 'Active' : 'Inactive'} />
<Row label="Sql Query" value={widget.sqlQuery} />
</div>
))}
</div>
</Section>
)}
{(hasWorkflowFields || workflowItems.length > 0) && (
<Section
title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'}
badge={workflowItems.length}
>
<div className="grid grid-cols-2 gap-3">
<div>
<Row label="Approval User" value={workflow.approvalUserFieldName} />
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
<Row
label="Is Filter User Name?"
value={workflow.approvalIsFilterUserName === true ? 'Yes' : 'No'}
/>
</div>
{workflowItems.length > 0 && (
<div className="flex flex-col gap-2">
{workflowItems.map((criteria, index) => (
<div
key={criteria.id || index}
className="rounded bg-gray-50 dark:bg-gray-800 px-3 py-2"
>
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
</div>
{criteria.kind === 'Compare' && (
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
{criteria.compareColumn} {criteria.compareOperator}{' '}
{criteria.compareValue}
</div>
)}
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
criteria.approver && (
<div className="text-[11px] text-gray-500 dark:text-gray-400">
Approver: {formatWorkflowApprovers(criteria.approver)}
</div>
)}
</div>
))}
</div>
)}
</div>
</Section>
)}
{values.todo && (
<Section title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'}>
<div className="grid grid-cols-2 gap-x-4">
<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>
)}
</div>
{/* ── Right: Deploy ──────────────────────────────────────────── */}
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
{/* Stats */}
<div className="grid grid-cols-8 gap-2">
{[
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
{
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
value: editingFormFields.length,
},
{
label: translate('::App.Listform.ListformField.Column'),
value: selectedColumns.size,
},
{
label: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
value: subForms.length,
},
{
label: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
value: widgets.length,
},
{
label: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
value: workflowItems.length,
},
{
label: translate('::ListForms.ListFormEdit.TabTodo') || 'Todo',
value: values.todo ? 1 : 0,
},
].map((s) => (
<div
key={s.label}
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3 text-center shadow-sm"
>
<div className="text-2xl font-bold text-indigo-600 dark:text-indigo-400">
{s.value}
</div>
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div>
</div>
))}
</div>
{/* Log panel — grows to fill remaining height */}
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1 min-h-0">
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0">
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
<FaRocket className="text-indigo-400 text-xs" />
{translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'}
</span>
{isDone && (
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
<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')}
</span>
)}
</div>
<div className="flex-1 overflow-y-auto p-4 bg-[#0d1117] dark:bg-black font-mono min-h-[360px]">
{logs.length === 0 ? (
<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')}
<br />
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
</span>
</div>
) : (
<div className="flex flex-col gap-2">
{logs.map((log) => (
<div key={log.id} className="flex items-start gap-2.5">
<span className="mt-0.5 shrink-0">
<LogIcon status={log.status} />
</span>
<div>
<span
className={`text-xs leading-relaxed ${
log.status === 'success'
? 'text-emerald-400'
: log.status === 'error'
? 'text-red-400'
: log.status === 'running'
? 'text-yellow-300'
: 'text-gray-600'
}`}
>
{log.label}
</span>
{log.detail && (
<div className="text-[10px] text-gray-600 mt-0.5">{log.detail}</div>
)}
</div>
</div>
))}
{isDone && (
<div className="mt-4 rounded-lg border border-emerald-800 bg-emerald-950/40 px-4 py-2.5 text-xs text-emerald-400 text-center font-semibold">
🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')}
</div>
)}
</div>
)}
</div>
</div>
</div>
{/* ── Fixed Footer ─────────────────────────────────────────────── */}
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
<div className="flex items-center gap-3 w-full">
<Button
size="sm"
variant="default"
type="button"
icon={<FaArrowLeft />}
disabled={isDeploying}
onClick={onBack}
>
{translate('::Back') || 'Back'}
</Button>
<div className="flex-1 flex items-center justify-end">
<Button
size="sm"
variant="solid"
type="button"
icon={<FaRocket />}
loading={isDeploying}
disabled={isDeploying || isDone}
onClick={runDeploy}
>
{isDeploying
? translate('::ListForms.Wizard.Step4.Log.Deploying')
: isDone
? `${translate('::ListForms.Wizard.Step4.Log.Completed')}`
: translate('::ListForms.Wizard.Step4.DeployAndSave')}
</Button>
</div>
</div>
</div>
</div>
)
}
export default WizardStep8

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 ( return (
<div <div
className={classNames( className={classNames(

Some files were not shown because too many files have changed in this diff Show more