From 24e92fcb82eef0ee26ca5d65e08c5404f132a09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:01:18 +0300 Subject: [PATCH] Todo komponenti --- .../GridOptionsDto/GridOptionsDto.cs | 13 + .../ListForms/GridOptionsDto/LayoutDto.cs | 1 + .../ListForms/GridOptionsDto/TodoOptionDto.cs | 20 + .../ListForms/GridOptionsDto/WorkflowDto.cs | 2 +- .../ListForms/ListFormEditTabs.cs | 1 + .../ListForms/Wizard/ListFormWizardDto.cs | 24 +- .../Administration/ListFormsAppService.cs | 4 + .../ListForms/ListFormWizardAppService.cs | 44 +- .../Seeds/LanguagesData.json | 422 ++++- .../Seeds/ListFormSeeder_DefaultJsons.cs | 1 + .../Seeds/SqlData/Adm_D_Todos.sql | 21 + .../Seeds/WizardData/202607161522_Todos.json | 278 ++++ .../Seeds/WizardDataSeeder.cs | 47 +- .../WizardConsts.cs | 3 +- .../Entities/Tenant/ListForm/ListForm.cs | 1 + .../Queries/SelectQueryManager.cs | 2 +- .../Queries/Workflow.cs | 4 +- .../EntityFrameworkCore/PlatformDbContext.cs | 1 + ....cs => 20260716120523_Initial.Designer.cs} | 8 +- ...9_Initial.cs => 20260716120523_Initial.cs} | 1 + .../PlatformDbContextModelSnapshot.cs | 6 +- ui/src/proxy/admin/list-form/models.ts | 1 + ui/src/proxy/admin/list-form/options.ts | 2 + ui/src/proxy/admin/wizard/models.ts | 27 +- ui/src/proxy/form/models.ts | 19 +- ui/src/store/admin.model.ts | 2 +- ui/src/views/admin/listForm/edit/FormEdit.tsx | 11 +- .../admin/listForm/edit/FormTabDetails.tsx | 9 + .../views/admin/listForm/edit/FormTabTodo.tsx | 137 ++ .../admin/listForm/edit/FormTabWorkflow.tsx | 12 +- ui/src/views/admin/listForm/edit/options.ts | 1 + ui/src/views/admin/listForm/edit/types.ts | 2 + ui/src/views/admin/listForm/wizard/Wizard.tsx | 114 +- .../admin/listForm/wizard/WizardStep2.tsx | 14 + .../admin/listForm/wizard/WizardStep6.tsx | 8 +- .../admin/listForm/wizard/WizardStep7.tsx | 789 ++-------- .../admin/listForm/wizard/WizardStep8.tsx | 740 +++++++++ ui/src/views/developerKit/ComponentEditor.tsx | 4 +- .../developerKit/SqlTableDesignerDialog.tsx | 131 +- ui/src/views/list/CardView.tsx | 266 ++-- ui/src/views/list/ChartDrawer.tsx | 2 +- ui/src/views/list/List.tsx | 29 + ui/src/views/list/TodoBoard.tsx | 1364 +++++++++++++++++ ui/src/views/list/useListFormStateStoring.tsx | 11 +- .../views/public/designer/DesignerDrawer.tsx | 2 +- 45 files changed, 3696 insertions(+), 905 deletions(-) create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/TodoOptionDto.cs create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Adm_D_Todos.sql create mode 100644 api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607161522_Todos.json rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260713103339_Initial.Designer.cs => 20260716120523_Initial.Designer.cs} (99%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260713103339_Initial.cs => 20260716120523_Initial.cs} (99%) create mode 100644 ui/src/views/admin/listForm/edit/FormTabTodo.tsx create mode 100644 ui/src/views/admin/listForm/wizard/WizardStep8.tsx create mode 100644 ui/src/views/list/TodoBoard.tsx diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs index 01e9a89..4491b0f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs @@ -190,6 +190,19 @@ public class GridOptionsDto : AuditedEntityDto set { SchedulerOptionJson = JsonSerializer.Serialize(value); } } + [JsonIgnore] + public string TodoOptionJson { get; set; } + public TodoOptionDto TodoOptionDto + { + get + { + if (!string.IsNullOrEmpty(TodoOptionJson)) + return JsonSerializer.Deserialize(TodoOptionJson); + return new TodoOptionDto(); + } + set { TodoOptionJson = JsonSerializer.Serialize(value); } + } + [JsonIgnore] public string PagerOptionJson { get; set; } public GridPagerOptionDto PagerOptionDto diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/LayoutDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/LayoutDto.cs index 7dd339d..40f91f3 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/LayoutDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/LayoutDto.cs @@ -9,6 +9,7 @@ public class LayoutDto public bool Tree { get; set; } = true; public bool Gantt { get; set; } = true; public bool Scheduler { get; set; } = true; + public bool Todo { get; set; } = false; public string DefaultLayout { get; set; } = "grid"; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/TodoOptionDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/TodoOptionDto.cs new file mode 100644 index 0000000..ac400e0 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/TodoOptionDto.cs @@ -0,0 +1,20 @@ +namespace Sozsoft.Platform.ListForms; + +/// +/// Dinamik bir listeyi Todo/Kanban panosu olarak gostermek icin alan eslemeleri. +/// TitleExpr ve StatusExpr zorunlu, diger alanlar opsiyoneldir. +/// +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; +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs index c0942b0..b3b8579 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs @@ -5,10 +5,10 @@ namespace Sozsoft.Platform.ListForms; public class WorkflowDto { public string ApprovalUserFieldName { get; set; } - public bool IsFilterUserName { get; set; } public string ApprovalDateFieldName { get; set; } public string ApprovalStatusFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; } + public bool ApprovalIsFilterUserName { get; set; } public List Criteria { get; set; } = []; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs index d013a7c..567ab2a 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs @@ -49,6 +49,7 @@ public class ListFormEditTabs public const string TreeForm = "tree"; public const string GanttForm = "gantt"; public const string SchedulerForm = "scheduler"; + public const string TodoForm = "todo"; public const string PagerForm = "pager"; public const string StateForm = "state"; public const string SubFormJsonRow = "subForm"; diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs index ea5b1ac..bfe84ab 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs @@ -28,6 +28,7 @@ public class ListFormWizardDto public bool Chart { get; set; } public bool Gantt { get; set; } public bool Scheduler { get; set; } + public bool Todo { get; set; } public string LanguageTextMenuEn { get; set; } public string LanguageTextMenuTr { get; set; } @@ -69,9 +70,28 @@ public class ListFormWizardDto public string SchedulerStartDateExpr { 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 Criteria { get; set; } = []; + public List Groups { get; set; } = new(); public List SubForms { get; set; } = new(); public List Widgets { get; set; } = new(); - public WorkflowDto Workflow { get; set; } = new(); - public List WorkflowCriteria { get; set; } = new(); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs index 6c8ff4d..be59a99 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs @@ -166,6 +166,10 @@ public class ListFormsAppService : CrudAppService< { item.SchedulerOptionJson = JsonSerializer.Serialize(input.SchedulerOptionDto); } + else if (input.EditType == ListFormEditTabs.TodoForm) + { + item.TodoOptionJson = JsonSerializer.Serialize(input.TodoOptionDto); + } else if (input.EditType == ListFormEditTabs.PagerForm) { item.PageSize = input.PageSize; diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 14007c8..fcb1498 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -269,10 +269,18 @@ public class ListFormWizardAppService( var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand); var isDeleted = tableColumns.Contains("IsDeleted"); var isCreated = tableColumns.Contains("CreatorId"); - input.Workflow ??= new WorkflowDto(); - input.Workflow.Criteria = input.WorkflowCriteria; - EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); - NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria); + input.Criteria ??= []; + EnsureUniqueWorkflowCriteriaTitles(input.Criteria); + NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria); + 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 { @@ -280,8 +288,8 @@ public class ListFormWizardAppService( PageSize = 10, ExportJson = WizardConsts.DefaultExportJson, IsSubForm = false, - ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, - LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), + ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0, + LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo), CultureName = LanguageCodes.En, ListFormCode = input.ListFormCode, Name = nameLangKey, @@ -301,7 +309,7 @@ public class ListFormWizardAppService( HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(), SearchPanelJson = WizardConsts.DefaultSearchPanelJson(), 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(), PermissionJson = WizardConsts.DefaultPermissionJson(code), DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, @@ -312,7 +320,7 @@ public class ListFormWizardAppService( EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : 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) ? JsonSerializer.Serialize(new TreeOptionDto { @@ -341,6 +349,24 @@ public class ListFormWizardAppService( EndDateExpr = input.SchedulerEndDateExpr, }) : 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); // ListFormField - each item in each group becomes a visible field record @@ -373,7 +399,7 @@ public class ListFormWizardAppService( } } - foreach (var criteria in input.WorkflowCriteria) + foreach (var criteria in input.Criteria) { if (string.IsNullOrWhiteSpace(criteria.Id)) { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index e6a9f73..85dc62d 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -9336,12 +9336,6 @@ "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." }, - { - "resourceName": "Platform", - "key": "Public.designer.save", - "tr": "Kaydet", - "en": "Save" - }, { "resourceName": "Platform", "key": "App.DeveloperKit.Component.Description", @@ -10170,12 +10164,6 @@ "en": "Save Entity", "tr": "Varlığı Kaydet" }, - { - "resourceName": "Platform", - "key": "App.DeveloperKit.EntityEditor.Saving", - "en": "Saving...", - "tr": "Kaydediliyor..." - }, { "resourceName": "Platform", "key": "App.DeveloperKit.EntityEditor.BasicInfo", @@ -10308,18 +10296,6 @@ "en": "Inactive", "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", "key": "App.DeveloperKit.ComponentEditor.ValidationError.Title", @@ -11616,12 +11592,6 @@ "tr": "Hedef Data Sourcelar", "en": "Target Data Sources" }, - { - "resourceName": "Platform", - "key": "App.Platform.Save", - "tr": "Kaydet", - "en": "Save" - }, { "resourceName": "Platform", "key": "App.Platform.SaveQuery", @@ -12324,12 +12294,6 @@ "tr": "Alan seçin...", "en": "Select field..." }, - { - "resourceName": "Platform", - "key": "App.Platform.ChartDrawer.Save", - "tr": "Kaydet", - "en": "Save" - }, { "resourceName": "Platform", "key": "App.Platform.ChartDrawer.ChartSaved", @@ -18810,12 +18774,6 @@ "en": "If checked, existing object in the target will be dropped and recreated.", "tr": "İşaretlenirse hedefteki mevcut obje silinip yeniden oluşturulur." }, - { - "resourceName": "Platform", - "key": "App.SqlQueryManager.Save", - "en": "Save", - "tr": "Kaydet" - }, { "resourceName": "Platform", "key": "App.SqlQueryManager.Next", @@ -19392,6 +19350,384 @@ "en": "Successfully Saved", "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": "Inbox, Today, This Week, This Month, Later, Done", + "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.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} todo’yu 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": "Todo’yu yeniden aç" + }, + { + "resourceName": "Platform", + "key": "ListForms.TodoBoard.CompleteTodo", + "en": "Complete todo", + "tr": "Todo’yu tamamla" + }, + { + "resourceName": "Platform", + "key": "ListForms.TodoBoard.NewColumn", + "en": "New Kanban column", + "tr": "Yeni kanban" + }, + { + "resourceName": "Platform", + "key": "ListForms.TodoBoard.AddNewColumn", + "en": "Add a new Kanban column", + "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", "key": "ListForms.ListFormEdit.Workflow.Criteria", @@ -19466,7 +19802,7 @@ }, { "resourceName": "Platform", - "key": "ListForms.ListFormEdit.Workflow.IsFilterUserName", + "key": "ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName", "en": "Filter User Name?", "tr": "Kullanıcı Adı Filtresin mi?" }, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs index e2123ee..74a6bf3 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_DefaultJsons.cs @@ -73,6 +73,7 @@ public static class ListFormSeeder_DefaultJsons Tree = true, Gantt = true, Scheduler = true, + Todo = true, DefaultLayout = DefaultLayout, }); public static readonly string DefaultExportJson = JsonSerializer.Serialize(new ChartExportDto diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Adm_D_Todos.sql b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Adm_D_Todos.sql new file mode 100644 index 0000000..8ca4581 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Adm_D_Todos.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID(N'[dbo].[Adm_D_Todos]', 'U') IS NULL +BEGIN + CREATE TABLE [dbo].[Adm_D_Todos] + ( + [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, + CONSTRAINT [PK_Table] PRIMARY KEY NONCLUSTERED + ( + [Id] ASC + )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] + ) ON [PRIMARY] +END +GO \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607161522_Todos.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607161522_Todos.json new file mode 100644 index 0000000..03275b7 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardData/202607161522_Todos.json @@ -0,0 +1,278 @@ +{ + "Wizard": { + "WizardName": "Todos", + "ListFormCode": "App.Wizard.Todos", + "MenuCode": "App.Wizard.Todos", + "IsTenant": false, + "IsBranch": false, + "IsOrganizationUnit": false, + "AllowAdding": true, + "AllowUpdating": true, + "AllowDeleting": true, + "AllowDetail": false, + "ConfirmDelete": true, + "DefaultLayout": "grid", + "Grid": true, + "Card": true, + "Pivot": true, + "Tree": true, + "Chart": true, + "Gantt": true, + "Scheduler": true, + "Todo": true, + "LanguageTextMenuEn": "Todos", + "LanguageTextMenuTr": "Todos", + "LanguageTextTitleEn": "Todos", + "LanguageTextTitleTr": "Todos", + "LanguageTextDescEn": "Todos", + "LanguageTextDescTr": "Todos", + "LanguageTextMenuParentEn": "", + "LanguageTextMenuParentTr": "", + "PermissionGroupName": "App.Administration", + "MenuParentCode": "App.Administration", + "MenuParentIcon": "FcOrganization", + "MenuIcon": "FcAndroidOs", + "DataSourceCode": "Default", + "DataSourceConnectionString": "", + "SelectCommandType": 1, + "SelectCommand": "Adm_D_Todos", + "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": "Inbox, Today, This Week, This Month, Later, Done", + "TodoAllowDragging": true, + "ApprovalUserFieldName": "", + "ApprovalDateFieldName": "", + "ApprovalStatusFieldName": "", + "ApprovalDescriptionFieldName": "", + "ApprovalIsFilterUserName": false, + "Criteria": [], + "Groups": [ + { + "Caption": "", + "ColCount": 1, + "Items": [ + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "Description", + "CaptionName": "App.Listform.ListformField.Description", + "EditorType": "dxTextBox", + "EditorOptions": "", + "EditorScript": "", + "ColSpan": 1, + "IsRequired": false, + "IncludeInEditingForm": false, + "DbSourceType": 16, + "TurkishCaption": "Description", + "EnglishCaption": "Description", + "LookupDataSourceType": 1, + "ValueExpr": "Key", + "DisplayExpr": "Name", + "LookupQuery": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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": "" + }, + { + "DataField": "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.Todos", + "App.Wizard.Todos.Title", + "App.Wizard.Todos.Desc", + "App.Listform.ListformField.Assignees", + "App.Listform.ListformField.Completed", + "App.Listform.ListformField.SortOrder" + ], + "PermissionGroupNames": [], + "PermissionNames": [ + "App.Wizard.Todos", + "App.Wizard.Todos.Create", + "App.Wizard.Todos.Update", + "App.Wizard.Todos.Delete", + "App.Wizard.Todos.Export", + "App.Wizard.Todos.Import", + "App.Wizard.Todos.Note" + ], + "MenuCodes": [ + "App.Wizard.Todos" + ], + "DataSourceCodes": [] + } +} \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs index 0d4fd34..414d792 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs @@ -132,14 +132,17 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency var isCreated = seedFile.IsCreatedField; input.SubForms ??= new List(); input.Widgets ??= new List(); - input.Workflow ??= new WorkflowDto(); - input.WorkflowCriteria ??= new List(); - if (input.WorkflowCriteria.Count == 0 && input.Workflow.Criteria?.Count > 0) + input.Criteria ??= []; + EnsureUniqueWorkflowCriteriaTitles(input.Criteria); + var workflow = new WorkflowDto { - input.WorkflowCriteria = input.Workflow.Criteria; - } - input.Workflow.Criteria = input.WorkflowCriteria; - EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); + ApprovalUserFieldName = input.ApprovalUserFieldName, + ApprovalIsFilterUserName = input.ApprovalIsFilterUserName, + ApprovalDateFieldName = input.ApprovalDateFieldName, + ApprovalStatusFieldName = input.ApprovalStatusFieldName, + ApprovalDescriptionFieldName = input.ApprovalDescriptionFieldName, + Criteria = input.Criteria, + }; var wizardName = input.WizardName.Trim(); var code = string.IsNullOrWhiteSpace(input.MenuCode) @@ -148,7 +151,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode) ? code : input.ListFormCode.Trim(); - NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria); + NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria); var titleLangKey = $"{listFormCode}.Title"; var nameLangKey = code; var descLangKey = $"{listFormCode}.Desc"; @@ -392,8 +395,8 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency PageSize = 10, ExportJson = WizardConsts.DefaultExportJson, IsSubForm = false, - ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0, - LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler), + ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0, + LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo), CultureName = LanguageCodes.En, ListFormCode = input.ListFormCode, Name = nameLangKey, @@ -413,7 +416,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(), SearchPanelJson = WizardConsts.DefaultSearchPanelJson(), 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(), PermissionJson = WizardConsts.DefaultPermissionJson(code), DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, @@ -428,7 +431,25 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : 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); // ListFormFields @@ -466,7 +487,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency } } - foreach (var criteria in input.WorkflowCriteria) + foreach (var criteria in input.Criteria) { if (string.IsNullOrWhiteSpace(criteria.Id)) { diff --git a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs index 97d88e4..3c7f4df 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs @@ -84,7 +84,7 @@ public static class WizardConsts Margin = 10 }); - 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) => 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, Card = Card, @@ -93,6 +93,7 @@ public static class WizardConsts Tree = Tree, Gantt = Gantt, Scheduler = Scheduler, + Todo = Todo, DefaultLayout = DefaultLayout, }); diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs index f0e28e9..bf3c27f 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs @@ -36,6 +36,7 @@ public class ListForm : Entity public string TreeOptionJson { get; set; } // Tree 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 TodoOptionJson { get; set; } // Todo board alan esleme ayarlari 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 HeaderFilterJson { get; set; } // Header filtreleme ayarlari, Json olarak tutulur diff --git a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs index feed8c3..b29fc98 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs @@ -469,7 +469,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager if (listform.WorkflowJson.IsNullOrWhiteSpace() == false) { var workflow = JsonSerializer.Deserialize(listform.WorkflowJson); - if (workflow != null && workflow.IsFilterUserName) + if (workflow != null && workflow.ApprovalIsFilterUserName) { if (whereParts.Any()) { diff --git a/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs b/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs index 355c5b6..9febf40 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs @@ -6,18 +6,18 @@ namespace Sozsoft.Platform.Queries; public class Workflow : ValueObject { public string ApprovalUserFieldName { get; set; } - public bool IsFilterUserName { get; set; } public string ApprovalDateFieldName { get; set; } public string ApprovalStatusFieldName { get; set; } public string ApprovalDescriptionFieldName { get; set; } + public bool ApprovalIsFilterUserName { get; set; } protected override IEnumerable GetAtomicValues() { yield return ApprovalUserFieldName; - yield return IsFilterUserName; yield return ApprovalDateFieldName; yield return ApprovalStatusFieldName; yield return ApprovalDescriptionFieldName; + yield return ApprovalIsFilterUserName; } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 57383ba..0f05c53 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -378,6 +378,7 @@ public class PlatformDbContext : b.Property(a => a.WorkflowJson).HasColumnType("text"); b.Property(a => a.ExtraFilterJson).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.DataSourceJson).HasColumnType("text"); b.Property(a => a.AdaptiveLayoutJson).HasColumnType("text"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.Designer.cs index 8bedfd3..d828e90 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260713103339_Initial")] + [Migration("20260716120523_Initial")] partial class Initial { /// @@ -3264,6 +3264,9 @@ namespace Sozsoft.Platform.Migrations b.Property("TitleJson") .HasColumnType("text"); + b.Property("TodoOptionJson") + .HasColumnType("text"); + b.Property("TooltipJson") .HasColumnType("text"); @@ -8818,7 +8821,8 @@ namespace Sozsoft.Platform.Migrations b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory") .WithMany("ReportTemplates") .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict); + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); b.Navigation("ReportCategory"); }); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.cs index d062a0d..d6c3013 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260713103339_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260716120523_Initial.cs @@ -1392,6 +1392,7 @@ namespace Sozsoft.Platform.Migrations TreeOptionJson = table.Column(type: "nvarchar(max)", nullable: true), GanttOptionJson = table.Column(type: "nvarchar(max)", nullable: true), SchedulerOptionJson = table.Column(type: "nvarchar(max)", nullable: true), + TodoOptionJson = table.Column(type: "text", nullable: true), FilterRowJson = table.Column(type: "text", nullable: true), RowJson = table.Column(type: "nvarchar(max)", nullable: true), HeaderFilterJson = table.Column(type: "text", nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 05a6e2a..d3e3cad 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -3261,6 +3261,9 @@ namespace Sozsoft.Platform.Migrations b.Property("TitleJson") .HasColumnType("text"); + b.Property("TodoOptionJson") + .HasColumnType("text"); + b.Property("TooltipJson") .HasColumnType("text"); @@ -8815,7 +8818,8 @@ namespace Sozsoft.Platform.Migrations b.HasOne("Sozsoft.Platform.Entities.ReportCategory", "ReportCategory") .WithMany("ReportTemplates") .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Restrict); + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); b.Navigation("ReportCategory"); }); diff --git a/ui/src/proxy/admin/list-form/models.ts b/ui/src/proxy/admin/list-form/models.ts index f3751a6..3812592 100644 --- a/ui/src/proxy/admin/list-form/models.ts +++ b/ui/src/proxy/admin/list-form/models.ts @@ -33,4 +33,5 @@ export interface ListFormJsonRowDto { export interface ListState { listFormCode: string layout: ListViewLayoutType + cardColumnCount?: number } diff --git a/ui/src/proxy/admin/list-form/options.ts b/ui/src/proxy/admin/list-form/options.ts index 9bb0869..8cb3798 100644 --- a/ui/src/proxy/admin/list-form/options.ts +++ b/ui/src/proxy/admin/list-form/options.ts @@ -42,6 +42,7 @@ export const ListFormEditTabs = { TreeForm: 'tree', GanttForm: 'gantt', SchedulerForm: 'scheduler', + TodoForm: 'todo', PagerForm: 'pager', StateForm: 'state', SubForm: 'subForm', @@ -93,6 +94,7 @@ export const tabVisibilityConfig: Record = { 'tree', 'gantt', 'scheduler', + 'todo', 'pager', 'state', 'extrafilter', diff --git a/ui/src/proxy/admin/wizard/models.ts b/ui/src/proxy/admin/wizard/models.ts index 04630f1..d81170f 100644 --- a/ui/src/proxy/admin/wizard/models.ts +++ b/ui/src/proxy/admin/wizard/models.ts @@ -2,8 +2,7 @@ import { ListFormWorkflowCriteriaDto, SubFormDto, WidgetEditDto, - WorkflowDto, - SelectCommandTypeEnum + SelectCommandTypeEnum, } from '@/proxy/form/models' import { ListViewLayoutType } from '@/views/admin/listForm/edit/types' @@ -45,6 +44,7 @@ export interface ListFormWizardDto { chart: boolean gantt: boolean scheduler: boolean + todo: boolean languageTextMenuEn: string languageTextMenuTr: string @@ -84,11 +84,30 @@ export interface ListFormWizardDto { schedulerStartDateExpr?: 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[] subForms?: SubFormDto[] widgets?: WidgetEditDto[] - workflow?: WorkflowDto - workflowCriteria?: ListFormWorkflowCriteriaDto[] } export interface WizardFileInfoDto { diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts index 4288f08..d107bec 100644 --- a/ui/src/proxy/form/models.ts +++ b/ui/src/proxy/form/models.ts @@ -442,6 +442,20 @@ export interface SchedulerOptionDto { 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 { fieldExpr?: string dataSource?: any[] @@ -557,6 +571,8 @@ export interface GridOptionsDto extends AuditedEntityDto { ganttOptionDto: GanttOptionDto schedulerOptionJson?: string schedulerOptionDto: SchedulerOptionDto + todoOptionJson?: string + todoOptionDto: TodoOptionDto pagerOptionJson?: string pagerOptionDto: GridPagerOptionDto editingOptionJson?: string @@ -911,10 +927,10 @@ export interface WidgetEditDto { export interface WorkflowDto { approvalUserFieldName: string - isFilterUserName: boolean approvalDateFieldName: string approvalStatusFieldName: string approvalDescriptionFieldName: string + approvalIsFilterUserName: boolean criteria: ListFormWorkflowCriteriaDto[] } @@ -957,6 +973,7 @@ export interface LayoutDto { chart: boolean gantt: boolean scheduler: boolean + todo: boolean defaultLayout: ListViewLayoutType } diff --git a/ui/src/store/admin.model.ts b/ui/src/store/admin.model.ts index 2db41be..0551383 100644 --- a/ui/src/store/admin.model.ts +++ b/ui/src/store/admin.model.ts @@ -82,7 +82,7 @@ export const adminModel: AdminModel = { setStates: action((state, payload) => { const stateId = state.states.findIndex((s) => s.listFormCode === payload.listFormCode) if (stateId !== -1) { - state.states[stateId] = payload + state.states[stateId] = { ...state.states[stateId], ...payload } } else { state.states = [...state.states, payload] } diff --git a/ui/src/views/admin/listForm/edit/FormEdit.tsx b/ui/src/views/admin/listForm/edit/FormEdit.tsx index 0b1f7e0..0d2079d 100644 --- a/ui/src/views/admin/listForm/edit/FormEdit.tsx +++ b/ui/src/views/admin/listForm/edit/FormEdit.tsx @@ -27,7 +27,11 @@ import FormTabSubForm from './FormTabSubForm' import FormFields from './form-fields/FormFields' import { putListForms } from '@/services/admin/list-form.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 { getListFormCustomizations } from '@/services/admin/list-form-customization.service' import { sqlObjectManagerService } from '@/services/sql-query-manager.service' @@ -54,6 +58,7 @@ import FormTabSorting from './FormTabSorting' import FormTabRow from './FormTabRow' import FormTabGantt from './FormTabGantt' import FormTabScheduler from './FormTabScheduler' +import FormTabTodo from './FormTabTodo' import { APP_NAME } from '@/constants/app.constant' import { FormTabWorkflow } from './FormTabWorkflow' @@ -318,6 +323,7 @@ const FormEdit = () => { {visibleTabs.includes('scheduler') && ( {translate('::ListForms.ListFormEdit.TabScheduler')} )} + {visibleTabs.includes('todo') && Todo} {visibleTabs.includes('subForms') && ( {translate('::ListForms.ListFormEdit.SubForms')} )} @@ -431,6 +437,9 @@ const FormEdit = () => { + + + diff --git a/ui/src/views/admin/listForm/edit/FormTabDetails.tsx b/ui/src/views/admin/listForm/edit/FormTabDetails.tsx index 105990a..6f9cb98 100644 --- a/ui/src/views/admin/listForm/edit/FormTabDetails.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabDetails.tsx @@ -410,6 +410,15 @@ function FormTabDetails( component={Checkbox} /> + + + + )} diff --git a/ui/src/views/admin/listForm/edit/FormTabTodo.tsx b/ui/src/views/admin/listForm/edit/FormTabTodo.tsx new file mode 100644 index 0000000..d5b4c07 --- /dev/null +++ b/ui/src/views/admin/listForm/edit/FormTabTodo.tsx @@ -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([]) + 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( + + {translateRef.current('::ListForms.ListFormFieldEdit.TodoFieldsLoadFailed', { + 0: error.toString(), + })} + , + ) + } + }, [listFormCode]) + + useEffect(() => { + loadFields() + }, [loadFields]) + if (!values) return null + + return ( + onSubmit(ListFormEditTabs.TodoForm, formValues, helpers)} + > + {({ isSubmitting, values: formValues }) => ( +
+ +
+ {mappings.map((mapping) => ( + + + {({ field, form }: FieldProps) => ( + option.value === values[mapping.name])} + onChange={(option) => { + form.setFieldValue(field.name, option?.value ?? '') + setValidationError('') + }} + /> + )} + + + ))}
-
- {logs.length === 0 ? ( -
- - - {translate('::ListForms.Wizard.Step4.AllInfoReady') || 'Tüm bilgiler hazır.'} -
- {translate('::ListForms.Wizard.Step4.DeployStartHint')} -
-
- ) : ( -
- {logs.map((log) => ( -
- - - -
- - {log.label} - - {log.detail && ( -
{log.detail}
- )} -
-
- ))} - {isDone && ( -
- 🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')} -
+
+ + + + + + {({ field }: FieldProps) => ( + setFieldValue(field.name, checked)} + /> )} -
- )} + +
-
- + + )} - {/* ── Fixed Footer ─────────────────────────────────────────────── */} -
-
+ {validationError &&

{validationError}

} + +
+
+ -
- -
diff --git a/ui/src/views/admin/listForm/wizard/WizardStep8.tsx b/ui/src/views/admin/listForm/wizard/WizardStep8.tsx new file mode 100644 index 0000000..1f4e224 --- /dev/null +++ b/ui/src/views/admin/listForm/wizard/WizardStep8.tsx @@ -0,0 +1,740 @@ +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 + selectCommandColumns: DatabaseColumnDto[] + groups: WizardGroup[] + subForms: SubFormDto[] + widgets: WidgetEditDto[] + workflow: WorkflowDto + criteria: ListFormWorkflowCriteriaDto[] + translate: (key: string) => string + onBack: () => void + onSubmit: () => Promise +} + +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, +): Omit[] { + 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 + if (status === 'success') return + if (status === 'error') return + return +} + +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 ( +
+ + {open &&
{children}
} +
+ ) +} + +function Row({ label, value }: { label: string; value?: string | number }) { + if (!value && value !== 0) return null + return ( +
+ {label} + + {value} + +
+ ) +} + +// ─── WizardStep8 ────────────────────────────────────────────────────────────── +const WizardStep8 = ({ + values, + wizardName, + selectedColumns, + selectCommandColumns, + groups, + subForms, + widgets, + workflow, + criteria, + translate, + onBack, + onSubmit, +}: WizardStep8Props) => { + const [logs, setLogs] = useState([]) + 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: `Hata: ${err?.message ?? 'Bilinmeyen hata'}`, + 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.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 = criteria.length > 0 ? criteria : (workflow.criteria ?? []) + + return ( +
+ {/* ── Left: Summary ──────────────────────────────────────────── */} +
+
+
+ + + + + + + + + +
+ +
+ + + + + + + + o.value === values.selectCommandType) + ?.label ?? String(values.selectCommandType) + })`} + /> + o.value === values.keyFieldDbSourceType) + ?.label ?? String(values.keyFieldDbSourceType) + })`} + /> +
+
+ +
+
+ {[ + { + 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) => ( +
+
+ {item.value} +
+
{item.label}
+
+ ))} +
+ +
+ {groups.map((g) => ( +
+
+ + {g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`} + +
+ + {g.items.length} {translate('::ListForms.Wizard.Step4.StatField')} + + + {g.colCount} {translate('::App.Listform.ListformField.Column')} + +
+
+
+ {g.items.length === 0 ? ( + + {translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'} + + ) : ( + 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 ( +
+
+ + {item.dataField} + + {meta?.dataType && ( + + {meta.dataType} + + )} +
+
+ + {translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'} + + {isPopupField && ( + + {translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'} + + )} + {isKeyField && ( + + {translate('::ListForms.Wizard.Step4.KeyField')} + + )} + + {item.editorType} + + + span-{item.colSpan} + {item.isRequired && *} + +
+
+ ) + }) + )} +
+
+ ))} + {ungroupedSelectedColumns.length > 0 && ( +
+
+ {translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'} +
+
+ {ungroupedSelectedColumns.map((col) => { + const meta = selectCommandColumns.find((c) => c.columnName === col) + return ( + + {col} + {meta?.dataType && ( + + {meta.dataType} + + )} + + ) + })} +
+
+ )} +
+
+ + {subForms.length > 0 && ( +
+
+ {subForms.map((subForm, index) => ( +
+ + + + + {(subForm.relation || []).length > 0 && ( +
+ Relations +
+ {subForm.relation.map((relation, relationIndex) => ( + + {relation.parentFieldName} : {relation.childFieldName} + + ))} +
+
+ )} +
+ ))} +
+
+ )} + + {widgets.length > 0 && ( +
+
+ {widgets.map((widget, index) => ( +
+ + + + + + + + + + +
+ ))} +
+
+ )} + + {(hasWorkflowFields || workflowItems.length > 0) && ( +
+
+
+ + + + + +
+ {workflowItems.length > 0 && ( +
+ {workflowItems.map((criteria, index) => ( +
+
+ {criteria.title || criteria.kind || `Criteria ${index + 1}`} +
+ {criteria.kind === 'Compare' && ( +
+ {criteria.compareColumn} {criteria.compareOperator}{' '} + {criteria.compareValue} +
+ )} + {(criteria.kind === 'Approval' || criteria.kind === 'Inform') && + criteria.approver && ( +
+ Approver: {formatWorkflowApprovers(criteria.approver)} +
+ )} +
+ ))} +
+ )} +
+
+ )} + + {values.todo && ( +
+
+ + + + + + + + + + + +
+
+ )} +
+ + {/* ── Right: Deploy ──────────────────────────────────────────── */} +
+ {/* Stats */} +
+ {[ + { 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) => ( +
+
+ {s.value} +
+
{s.label}
+
+ ))} +
+ + {/* Log panel — grows to fill remaining height */} +
+
+ + + {translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'} + + {isDone && ( + + {translate('::ListForms.Wizard.Step4.Success') || 'Başarılı'} + + )} + {hasError && ( + + {translate('::ListForms.Wizard.Step4.Error') || 'Hata'} + + )} +
+ +
+ {logs.length === 0 ? ( +
+ + + {translate('::ListForms.Wizard.Step4.AllInfoReady') || 'Tüm bilgiler hazır.'} +
+ {translate('::ListForms.Wizard.Step4.DeployStartHint')} +
+
+ ) : ( +
+ {logs.map((log) => ( +
+ + + +
+ + {log.label} + + {log.detail && ( +
{log.detail}
+ )} +
+
+ ))} + {isDone && ( +
+ 🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')} +
+ )} +
+ )} +
+
+
+ + {/* ── Fixed Footer ─────────────────────────────────────────────── */} +
+
+ +
+ +
+
+
+
+ ) +} + +export default WizardStep8 diff --git a/ui/src/views/developerKit/ComponentEditor.tsx b/ui/src/views/developerKit/ComponentEditor.tsx index 4a65ec9..e1986c4 100644 --- a/ui/src/views/developerKit/ComponentEditor.tsx +++ b/ui/src/views/developerKit/ComponentEditor.tsx @@ -189,8 +189,8 @@ export default ${pascalCaseName}Component;` > {isSubmitting - ? translate('::App.DeveloperKit.ComponentEditor.Saving') - : translate('::App.DeveloperKit.ComponentEditor.Save')} + ? translate('::Saving') + : translate('::Save')}
diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index 7acbb04..832258e 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -270,6 +270,99 @@ const WORKFLOW_COLUMNS: ColumnDefinition[] = [ }, ] +const TODO_COLUMNS: ColumnDefinition[] = [ + { + id: '__TodoId', + columnName: 'Id', + dataType: 'uniqueidentifier', + maxLength: '', + isNullable: false, + defaultValue: 'NEWID()', + description: 'Todo primary key', + }, + { + id: '__TodoTitle', + columnName: 'Title', + dataType: 'nvarchar', + maxLength: '300', + isNullable: false, + defaultValue: '', + description: 'Todo title', + }, + { + id: '__TodoStatus', + columnName: 'Status', + dataType: 'nvarchar', + maxLength: '50', + isNullable: false, + defaultValue: "N'Inbox'", + description: 'Todo board column/status', + }, + { + id: '__TodoDescription', + columnName: 'Description', + dataType: 'nvarchar(MAX)', + maxLength: '', + isNullable: true, + defaultValue: '', + description: 'Todo description', + }, + { + id: '__TodoDueDate', + columnName: 'DueDate', + dataType: 'datetime2', + maxLength: '', + isNullable: true, + defaultValue: '', + description: 'Todo deadline', + }, + { + id: '__TodoTags', + columnName: 'Tags', + dataType: 'nvarchar', + maxLength: '500', + isNullable: true, + defaultValue: '', + description: 'Comma-separated todo tags', + }, + { + id: '__TodoAssignees', + columnName: 'Assignees', + dataType: 'nvarchar', + maxLength: '500', + isNullable: true, + defaultValue: '', + description: 'Comma-separated todo assignees', + }, + { + id: '__TodoPriority', + columnName: 'Priority', + dataType: 'nvarchar', + maxLength: '50', + isNullable: true, + defaultValue: '', + description: 'Todo priority', + }, + { + id: '__TodoCompleted', + columnName: 'Completed', + dataType: 'bit', + maxLength: '', + isNullable: false, + defaultValue: '0', + description: 'Todo completion flag', + }, + { + id: '__TodoSortOrder', + columnName: 'SortOrder', + dataType: 'int', + maxLength: '', + isNullable: false, + defaultValue: '0', + description: 'Todo order inside its board column', + }, +] + // ─── T-SQL Generator ────────────────────────────────────────────────────────── function colToSqlLine(col: ColumnDefinition, addComma = true): string { @@ -1281,6 +1374,35 @@ const SqlTableDesignerDialog = ({ } } + const addTodoColumns = () => { + const existingNames = new Set(columns.map((c) => c.columnName.trim().toLowerCase())) + const toAdd = TODO_COLUMNS.filter((c) => !existingNames.has(c.columnName.toLowerCase())) + + if (toAdd.length > 0) { + setColumns((prev) => { + const nonEmpty = prev.filter((c) => c.columnName.trim() !== '') + return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()] + }) + } + + setIndexes((prev) => { + if (prev.some((index) => index.indexType === 'PrimaryKey')) return prev + + const tableName = settings.tableName || initialTableData?.tableName || 'Table' + return [ + ...prev, + { + id: `auto-todo-pk-${crypto.randomUUID()}`, + indexName: `PK_${tableName}`, + indexType: 'PrimaryKey', + isClustered: false, + columns: [{ columnName: 'Id', order: 'ASC' }], + description: 'Todo primary key (auto)', + }, + ] + }) + } + const importColumnsFromRememberedCreateTable = async () => { let script = '' @@ -1749,6 +1871,9 @@ const SqlTableDesignerDialog = ({ + @@ -2756,7 +2881,7 @@ const SqlTableDesignerDialog = ({ color="indigo-600" className="hover:!bg-indigo-700 disabled:opacity-50 !rounded-lg transition-colors" > - {translate('::App.SqlQueryManager.Save')} + {translate('::Save')} @@ -2792,7 +2917,7 @@ const SqlTableDesignerDialog = ({ // ── Render ───────────────────────────────────────────────────────────────── return ( - + {/* Header */}
diff --git a/ui/src/views/list/CardView.tsx b/ui/src/views/list/CardView.tsx index 39d05ce..8205b91 100644 --- a/ui/src/views/list/CardView.tsx +++ b/ui/src/views/list/CardView.tsx @@ -13,7 +13,7 @@ import { import { getList } from '@/services/form.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' -import { useStoreState } from '@/store' +import { useStoreActions, useStoreState } from '@/store' import DxCardView, { CardViewRef, CardViewTypes, @@ -59,6 +59,9 @@ interface CardViewProps { gridDto?: GridDto } +const cardColumnCountOptions = [1, 2, 3, 4, 5] as const +const defaultCardColumnCount = 3 + const flattenColumns = (columns: any[] = []): any[] => columns.flatMap((column) => (column?.columns?.length ? flattenColumns(column.columns) : [column])) @@ -158,6 +161,11 @@ const getValueByField = (data: Record, field?: string | null) => { return key ? data[key] : undefined } +const getEditingFieldName = (dataField?: string | null) => + String(dataField ?? '') + .split(':')[0] + .trim() + const lookupDisplayValuesEqual = ( left: Record>, right: Record>, @@ -195,11 +203,11 @@ const loadCascadeLookupDisplayValues = async ( .filter((column) => column.colData?.lookupDto?.cascadeParentFields) .map(async (column) => { const lookup = column.colData.lookupDto - const parentFields = lookup.cascadeParentFields + const parentFields: string[] = String(lookup.cascadeParentFields) .split(',') - .map((field: string) => field.trim()) + .map((field) => field.trim()) .filter(Boolean) - const parentValueLists = parentFields.map((field: string) => { + const parentValueLists: string[][] = parentFields.map((field) => { const values = Object.entries(knownValues).find( ([key]) => key.toLowerCase() === field.toLowerCase(), )?.[1] @@ -340,9 +348,7 @@ const deduplicateCardStoreLoads = (store: CustomStore) => { if (cachedCount !== undefined) return Promise.resolve(cachedCount) const pendingCount = pendingTotalCounts.get(countKey) return pendingCount - ? pendingCount.then((count) => - count !== undefined ? count : originalTotalCount(options), - ) + ? pendingCount.then((count) => (count !== undefined ? count : originalTotalCount(options))) : originalTotalCount(options) } } @@ -371,6 +377,15 @@ const CardView = (props: CardViewProps) => { const { translate } = useLocalization() const { checkPermission } = usePermission() const currentUser = useStoreState((state) => state.auth.user) + const savedListState = useStoreState((state) => + state.admin.lists.states.find((item) => item.listFormCode === listFormCode), + ) + const cardColumnCount = savedListState?.cardColumnCount ?? defaultCardColumnCount + const setListState = useStoreActions((actions) => actions.admin.lists.setStates) + const currentCulture = useStoreState( + (state) => state.abpConfig.config?.localization.currentCulture.cultureName, + ) + const localizationTexts = useStoreState((state) => state.abpConfig.texts) const useMobileEditPopup = isMobileViewport() || isTouchLikeDevice() const cardViewRef = useRef() const searchParamsRef = useRef(searchParams) @@ -472,6 +487,16 @@ const CardView = (props: CardViewProps) => { const reload = cardDataSource?.reload() Promise.resolve(reload).finally(() => refreshParentData?.()) }, [refreshParentData]) + const setCardColumnCount = useCallback( + (cardColumnCount: number) => { + setListState({ + listFormCode, + layout: savedListState?.layout ?? 'card', + cardColumnCount, + }) + }, + [listFormCode, savedListState?.layout, setListState], + ) const listFormColumnsRef = useRef({ current: { instance: () => ({ refresh: refreshData }), @@ -498,21 +523,45 @@ const CardView = (props: CardViewProps) => { gridRef: listFormColumnsRef, onShowNote: openNotePanel, }) - const listColumnsCacheRef = useRef<{ gridDto?: GridDto; columns: any[] }>({ columns: [] }) - if (listColumnsCacheRef.current.gridDto !== gridDto) { + const localizationRevisionRef = useRef({ + culture: currentCulture, + texts: localizationTexts, + value: 0, + }) + if ( + localizationRevisionRef.current.culture !== currentCulture || + localizationRevisionRef.current.texts !== localizationTexts + ) { + localizationRevisionRef.current = { + culture: currentCulture, + texts: localizationTexts, + value: localizationRevisionRef.current.value + 1, + } + } + const localizationRevision = localizationRevisionRef.current.value + const listColumnsCacheRef = useRef<{ + gridDto?: GridDto + localizationRevision?: number + columns: any[] + }>({ columns: [] }) + if ( + listColumnsCacheRef.current.gridDto !== gridDto || + listColumnsCacheRef.current.localizationRevision !== localizationRevision + ) { listColumnsCacheRef.current = { gridDto, + localizationRevision, columns: flattenColumns(getBandedColumns()), } } const listColumns = listColumnsCacheRef.current.columns const cardColumnsCacheRef = useRef<{ - gridDto?: GridDto + listColumns?: any[] columns: CardViewTypes.ColumnProperties[] }>({ columns: [] }) - if (cardColumnsCacheRef.current.gridDto !== gridDto) { + if (cardColumnsCacheRef.current.listColumns !== listColumns) { cardColumnsCacheRef.current = { - gridDto, + listColumns, columns: listColumns .filter((column) => column?.type !== 'buttons' && column?.dataField) .map((column) => toCardColumn(column, lookupDisplayValuesRef)), @@ -526,26 +575,33 @@ const CardView = (props: CardViewProps) => { commandButtonsRef.current = commandButtons const renderImageUploadEditor = useCallback((templateData: any) => { - const dataField = + const configuredDataField = templateData?.dataField ?? templateData?.item?.dataField ?? templateData?.editorOptions?.name ?? templateData?.name + const dataField = getEditingFieldName(configuredDataField) const column = listColumnsCacheRef.current.columns.find( - (candidate) => - candidate.dataField?.toLowerCase() === String(dataField ?? '').toLowerCase(), + (candidate) => candidate.dataField?.toLowerCase() === String(dataField ?? '').toLowerCase(), ) const component = templateData?.component const currentFormData = component?.option?.('formData') ?? formDataRef.current ?? {} + const editorValue = templateData?.editorOptions?.value return ( { if (!dataField) return - component?.updateData?.(dataField, value) + if (typeof templateData?.editorOptions?.onValueChanged === 'function') { + templateData.editorOptions.onValueChanged({ value }) + } else { + component?.updateData?.(dataField, value) + } formDataRef.current = { ...currentFormData, [dataField]: value } }} /> @@ -612,9 +668,7 @@ const CardView = (props: CardViewProps) => { ?.split(',') .map((field: string) => field.trim()) .filter(Boolean) - const disabled = parentFields?.some( - (field: string) => !getValueByField(nextData, field), - ) + const disabled = parentFields?.some((field: string) => !getValueByField(nextData, field)) const editor = event.component?.getEditor?.(childField) editor?.option?.({ dataSource: source, disabled: Boolean(disabled), value: null }) @@ -627,13 +681,10 @@ const CardView = (props: CardViewProps) => { [cascadeChildrenByParent], ) - const applyLookupDisplayValues = useCallback( - (values: Record>) => { - if (lookupDisplayValuesEqual(lookupDisplayValuesRef.current, values)) return - lookupDisplayValuesRef.current = values - }, - [], - ) + const applyLookupDisplayValues = useCallback((values: Record>) => { + if (lookupDisplayValuesEqual(lookupDisplayValuesRef.current, values)) return + lookupDisplayValuesRef.current = values + }, []) const getLookupColumns = useCallback( () => @@ -677,9 +728,7 @@ const CardView = (props: CardViewProps) => { Promise.all([ loadLookupDisplayValues(columns), loadCascadeLookupDisplayValues(columns, rows), - ]).then(([values, cascadeValues]) => - applyLookupDisplayValues({ ...values, ...cascadeValues }), - ) + ]).then(([values, cascadeValues]) => applyLookupDisplayValues({ ...values, ...cascadeValues })) }, [applyLookupDisplayValues, getLookupColumns, loadLookupDisplayValues]) const { createSelectDataSource } = useListFormCustomDataSource({ gridRef: cardViewRef }) @@ -809,8 +858,8 @@ const CardView = (props: CardViewProps) => { return { itemType: 'simple', - dataField: item.dataField, - name: item.dataField, + dataField: fieldName, + name: fieldName, colSpan: item.colSpan, label: { text: item.dataField.includes(':') @@ -896,59 +945,56 @@ const CardView = (props: CardViewProps) => { cardViewRef.current?.instance().getDataSource()?.reload() }, [mergedExtraFilter]) - const renderCardHeader = useCallback( - (templateOptions: any, suppliedContainer?: HTMLElement) => { - const templateData = templateOptions?.model ?? templateOptions - const card = templateData?.card ?? templateData - const row = { data: card?.data ?? {}, isEditing: false, key: card?.key } - const templateContainer = templateOptions?.container ?? suppliedContainer - const domContainer = - templateContainer instanceof HTMLElement ? templateContainer : templateContainer?.get?.(0) - domContainer?.replaceChildren() - const actionsElement = document.createElement('div') - actionsElement.className = - 'flex min-h-10 w-full flex-wrap items-center justify-end gap-1 px-2 py-1' + const renderCardHeader = useCallback((templateOptions: any, suppliedContainer?: HTMLElement) => { + const templateData = templateOptions?.model ?? templateOptions + const card = templateData?.card ?? templateData + const row = { data: card?.data ?? {}, isEditing: false, key: card?.key } + const templateContainer = templateOptions?.container ?? suppliedContainer + const domContainer = + templateContainer instanceof HTMLElement ? templateContainer : templateContainer?.get?.(0) + domContainer?.replaceChildren() + const actionsElement = document.createElement('div') + actionsElement.className = + 'flex min-h-10 w-full flex-wrap items-center justify-end gap-1 px-2 py-1' - commandButtonsRef.current.forEach((action: any, index: number) => { - let visible = action.visible ?? true - if (typeof visible === 'function') { - try { - visible = visible({ row, card }) - } catch { - visible = false - } + commandButtonsRef.current.forEach((action: any, index: number) => { + let visible = action.visible ?? true + if (typeof visible === 'function') { + try { + visible = visible({ row, card }) + } catch { + visible = false } - if (!visible) return + } + if (!visible) return - const name = String(action.name ?? action.hint ?? `action-${index}`) - const button = document.createElement('button') - button.type = 'button' - button.className = - 'rounded px-2 py-1 text-xs font-medium hover:bg-gray-100 dark:hover:bg-gray-700' - button.textContent = action.text || action.hint || name - button.title = action.hint ?? action.text ?? name - button.addEventListener('click', (event) => { - event.stopPropagation() - if (name === 'edit') { - cardViewRef.current?.instance().editCard(card.index) - } else if (name === 'delete') { - cardViewRef.current?.instance().deleteCard(card.index) - } else { - action.onClick?.({ - event, - row, - card, - component: cardViewRef.current?.instance(), - }) - } - }) - actionsElement.appendChild(button) + const name = String(action.name ?? action.hint ?? `action-${index}`) + const button = document.createElement('button') + button.type = 'button' + button.className = + 'rounded px-2 py-1 text-xs font-medium hover:bg-gray-100 dark:hover:bg-gray-700' + button.textContent = action.text || action.hint || name + button.title = action.hint ?? action.text ?? name + button.addEventListener('click', (event) => { + event.stopPropagation() + if (name === 'edit') { + cardViewRef.current?.instance().editCard(card.index) + } else if (name === 'delete') { + cardViewRef.current?.instance().deleteCard(card.index) + } else { + action.onClick?.({ + event, + row, + card, + component: cardViewRef.current?.instance(), + }) + } }) + actionsElement.appendChild(button) + }) - return actionsElement - }, - [], - ) + return actionsElement + }, []) const editingOptions = gridDto?.gridOptions.editingOptionDto const selectionOptions = gridDto?.gridOptions.selectionDto @@ -1000,7 +1046,7 @@ const CardView = (props: CardViewProps) => { ) : (
{ height={gridDto.gridOptions.height > 0 ? gridDto.gridOptions.height : undefined} width={gridDto.gridOptions.width || '100%'} cardMinWidth={280} + cardsPerRow={cardColumnCount} + allowColumnReordering={gridDto.gridOptions.columnOptionDto?.allowColumnReordering} + rtlEnabled={gridDto.gridOptions.columnOptionDto?.rtlEnabled} className="list-card-view" hoverStateEnabled={gridDto.gridOptions.columnOptionDto?.hoverStateEnabled} remoteOperations={{ paging: true, filtering: true, sorting: true }} @@ -1016,14 +1065,18 @@ const CardView = (props: CardViewProps) => { onContentReady={onContentReady} onSelectionChanged={onSelectionChanged} onInitNewCard={(event) => { - setFormData((event.data as Record) ?? {}) + const nextFormData = (event.data as Record) ?? {} + formDataRef.current = nextFormData + setFormData(nextFormData) setMode('new') setIsPopupFullScreen( useMobileEditPopup || (editingOptions?.popup?.fullScreen ?? false), ) }} onEditingStart={(event) => { - setFormData((event.data as Record) ?? {}) + const nextFormData = (event.data as Record) ?? {} + formDataRef.current = nextFormData + setFormData(nextFormData) setMode('edit') setIsPopupFullScreen( useMobileEditPopup || (editingOptions?.popup?.fullScreen ?? false), @@ -1137,7 +1190,29 @@ const CardView = (props: CardViewProps) => { ], }} /> - 0}> + + {cardColumnCountOptions.map((columnCount) => { + const selected = cardColumnCount === columnCount + return ( + setCardColumnCount(columnCount), + }} + /> + ) + })} {orderedToolbarData.map((item, index) => ( ))} @@ -1160,8 +1235,20 @@ const CardView = (props: CardViewProps) => { width={gridDto.gridOptions.searchPanelDto.width} placeholder={translate('::App.Search')} /> - - + + { ?.split(',') .map(Number)} showPageSizeSelector={gridDto.gridOptions.pagerOptionDto?.showPageSizeSelector} + showInfo={gridDto.gridOptions.pagerOptionDto?.showInfo} + infoText={gridDto.gridOptions.pagerOptionDto?.infoText} showNavigationButtons={gridDto.gridOptions.pagerOptionDto?.showNavigationButtons} displayMode={gridDto.gridOptions.pagerOptionDto?.displayMode} /> @@ -1182,7 +1271,10 @@ const CardView = (props: CardViewProps) => { enabled={gridDto.gridOptions.columnOptionDto?.columnChooserEnabled} mode={gridDto.gridOptions.columnOptionDto?.columnChooserMode} /> - + {gridDto.gridOptions.subFormsDto?.length > 0 && diff --git a/ui/src/views/list/ChartDrawer.tsx b/ui/src/views/list/ChartDrawer.tsx index e5bc828..399bae5 100644 --- a/ui/src/views/list/ChartDrawer.tsx +++ b/ui/src/views/list/ChartDrawer.tsx @@ -376,7 +376,7 @@ const ChartDrawer = ({
{isSubmitting ? translate('::Saving') - : translate('::App.Platform.ChartDrawer.Save')} + : translate('::Save')}
diff --git a/ui/src/views/list/List.tsx b/ui/src/views/list/List.tsx index 474de86..6f2dd54 100644 --- a/ui/src/views/list/List.tsx +++ b/ui/src/views/list/List.tsx @@ -21,6 +21,7 @@ import { FaTable, FaCalendarAlt, FaProjectDiagram, + FaTasks, } from 'react-icons/fa' /* ======================= @@ -33,6 +34,7 @@ const Tree = React.lazy(() => import('./Tree')) const Chart = React.lazy(() => import('./Chart')) const GanttView = React.lazy(() => import('./GanttView')) const SchedulerView = React.lazy(() => import('./SchedulerView')) +const TodoBoard = React.lazy(() => import('./TodoBoard')) function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): boolean { if (!layout || layout === 'grid') return true @@ -62,6 +64,12 @@ function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): bo dto.gridOptions?.schedulerOptionDto?.startDateExpr && dto.gridOptions?.schedulerOptionDto?.endDateExpr ) + if (layout === 'todo') + return !!( + dto.gridOptions?.layoutDto?.todo && + dto.gridOptions?.todoOptionDto?.titleExpr && + dto.gridOptions?.todoOptionDto?.statusExpr + ) return false } @@ -134,6 +142,7 @@ const List: React.FC = () => { chart: () => import('./Chart'), gantt: () => import('./GanttView'), scheduler: () => import('./SchedulerView'), + todo: () => import('./TodoBoard'), }), [], ) @@ -161,6 +170,17 @@ const List: React.FC = () => { VIEW BUTTONS ======================= */}
+ {gridDto?.gridOptions?.layoutDto.todo && + gridDto?.gridOptions?.todoOptionDto?.titleExpr && + gridDto?.gridOptions?.todoOptionDto?.statusExpr && ( + +
+ {!canEdit && ( + + {translate('::ListForms.TodoBoard.EditRequiresUpdateService')} + + )} + {gridDto.gridOptions.deleteServiceAddress && ( + + )} +
+
+
+
+ {options.titleExpr && ( +
+ + + setDraft((current) => ({ + ...current, + [options.titleExpr!]: event.target.value, + })) + } + /> +
+ )} + {options.descriptionExpr && ( +
+ +