Todo komponenti
This commit is contained in:
parent
412ad236cc
commit
24e92fcb82
45 changed files with 3696 additions and 905 deletions
|
|
@ -190,6 +190,19 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
|
|||
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]
|
||||
public string PagerOptionJson { get; set; }
|
||||
public GridPagerOptionDto PagerOptionDto
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
|
||||
|
||||
public List<WizardColumnGroupInputDto> Groups { get; set; } = new();
|
||||
public List<SubFormDto> SubForms { get; set; } = new();
|
||||
public List<WidgetEditDto> Widgets { get; set; } = new();
|
||||
public WorkflowDto Workflow { get; set; } = new();
|
||||
public List<ListFormWorkflowCriteriaDto> WorkflowCriteria { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
|
|
@ -132,14 +132,17 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
var isCreated = seedFile.IsCreatedField;
|
||||
input.SubForms ??= new List<SubFormDto>();
|
||||
input.Widgets ??= new List<WidgetEditDto>();
|
||||
input.Workflow ??= new WorkflowDto();
|
||||
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public class ListForm : Entity<Guid>
|
|||
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
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
|||
if (listform.WorkflowJson.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
var workflow = JsonSerializer.Deserialize<Workflow>(listform.WorkflowJson);
|
||||
if (workflow != null && workflow.IsFilterUserName)
|
||||
if (workflow != null && workflow.ApprovalIsFilterUserName)
|
||||
{
|
||||
if (whereParts.Any())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<object> GetAtomicValues()
|
||||
{
|
||||
yield return ApprovalUserFieldName;
|
||||
yield return IsFilterUserName;
|
||||
yield return ApprovalDateFieldName;
|
||||
yield return ApprovalStatusFieldName;
|
||||
yield return ApprovalDescriptionFieldName;
|
||||
yield return ApprovalIsFilterUserName;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Sozsoft.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20260713103339_Initial")]
|
||||
[Migration("20260716120523_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -3264,6 +3264,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<string>("TitleJson")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TodoOptionJson")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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");
|
||||
});
|
||||
|
|
@ -1392,6 +1392,7 @@ namespace Sozsoft.Platform.Migrations
|
|||
TreeOptionJson = 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),
|
||||
TodoOptionJson = table.Column<string>(type: "text", nullable: true),
|
||||
FilterRowJson = table.Column<string>(type: "text", nullable: true),
|
||||
RowJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
HeaderFilterJson = table.Column<string>(type: "text", nullable: true),
|
||||
|
|
@ -3261,6 +3261,9 @@ namespace Sozsoft.Platform.Migrations
|
|||
b.Property<string>("TitleJson")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TodoOptionJson")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("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");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,4 +33,5 @@ export interface ListFormJsonRowDto {
|
|||
export interface ListState {
|
||||
listFormCode: string
|
||||
layout: ListViewLayoutType
|
||||
cardColumnCount?: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, string[]> = {
|
|||
'tree',
|
||||
'gantt',
|
||||
'scheduler',
|
||||
'todo',
|
||||
'pager',
|
||||
'state',
|
||||
'extrafilter',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<string> {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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') && (
|
||||
<TabNav value="scheduler">{translate('::ListForms.ListFormEdit.TabScheduler')}</TabNav>
|
||||
)}
|
||||
{visibleTabs.includes('todo') && <TabNav value="todo">Todo</TabNav>}
|
||||
{visibleTabs.includes('subForms') && (
|
||||
<TabNav value="subForms">{translate('::ListForms.ListFormEdit.SubForms')}</TabNav>
|
||||
)}
|
||||
|
|
@ -431,6 +437,9 @@ const FormEdit = () => {
|
|||
<TabContent value="scheduler" className="px-2">
|
||||
<FormTabScheduler onSubmit={onSubmit} />
|
||||
</TabContent>
|
||||
<TabContent value="todo" className="px-2">
|
||||
<FormTabTodo onSubmit={onSubmit} />
|
||||
</TabContent>
|
||||
<TabContent value="pager" className="px-2">
|
||||
<FormTabPager onSubmit={onSubmit} />
|
||||
</TabContent>
|
||||
|
|
|
|||
|
|
@ -410,6 +410,15 @@ function FormTabDetails(
|
|||
component={Checkbox}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="Todo Layout">
|
||||
<Field
|
||||
className="w-20"
|
||||
autoComplete="off"
|
||||
name="layoutDto.todo"
|
||||
component={Checkbox}
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
137
ui/src/views/admin/listForm/edit/FormTabTodo.tsx
Normal file
137
ui/src/views/admin/listForm/edit/FormTabTodo.tsx
Normal 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
|
||||
|
|
@ -314,10 +314,10 @@ export function FormTabWorkflow(
|
|||
const schema = object().shape({
|
||||
workflowDto: object().shape({
|
||||
approvalUserFieldName: string().required(),
|
||||
isFilterUserName: bool(),
|
||||
approvalStatusFieldName: string().required(),
|
||||
approvalDateFieldName: string(),
|
||||
approvalDescriptionFieldName: string(),
|
||||
approvalIsFilterUserName: bool(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
@ -442,16 +442,16 @@ export function FormTabWorkflow(
|
|||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.Workflow.IsFilterUserName')}
|
||||
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName')}
|
||||
invalid={
|
||||
!!(
|
||||
errors.workflowDto?.isFilterUserName &&
|
||||
touched.workflowDto?.isFilterUserName
|
||||
errors.workflowDto?.approvalIsFilterUserName &&
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ export const listFormDefaultLayoutOptions = [
|
|||
{ value: 'tree', label: 'Tree' },
|
||||
{ value: 'gantt', label: 'Gantt' },
|
||||
{ value: 'scheduler', label: 'Scheduler' },
|
||||
{ value: 'todo', label: 'Todo' },
|
||||
]
|
||||
|
||||
export const listFormAlignmentOptions = [
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export type ListViewLayoutType =
|
|||
| 'chart'
|
||||
| 'gantt'
|
||||
| 'scheduler'
|
||||
| 'todo'
|
||||
|
||||
export const layoutTypes = {
|
||||
grid: 'Grid',
|
||||
|
|
@ -17,4 +18,5 @@ export const layoutTypes = {
|
|||
chart: 'Chart',
|
||||
gantt: 'Gantt',
|
||||
scheduler: 'Scheduler',
|
||||
todo: 'Todo',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import WizardStep1, {
|
|||
import WizardStep2 from './WizardStep2'
|
||||
import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3'
|
||||
import WizardStep7 from './WizardStep7'
|
||||
import WizardStep8 from './WizardStep8'
|
||||
import WizardStep4 from './WizardStep4'
|
||||
import WizardStep5 from './WizardStep5'
|
||||
import WizardStep6 from './WizardStep6'
|
||||
|
|
@ -57,11 +58,24 @@ const initialValues: ListFormWizardDto = {
|
|||
allowDetail: false,
|
||||
defaultLayout: 'grid',
|
||||
grid: true,
|
||||
card: true,
|
||||
pivot: true,
|
||||
tree: true,
|
||||
chart: true,
|
||||
gantt: true,
|
||||
scheduler: true,
|
||||
todo: true,
|
||||
todoTitleExpr: '',
|
||||
todoStatusExpr: '',
|
||||
todoDescriptionExpr: '',
|
||||
todoDueDateExpr: '',
|
||||
todoTagExpr: '',
|
||||
todoAssigneeExpr: '',
|
||||
todoPriorityExpr: '',
|
||||
todoCompletedExpr: '',
|
||||
todoOrderExpr: '',
|
||||
todoStatusOrder: 'Inbox, Today, This Week, This Month, Later, Done',
|
||||
todoAllowDragging: true,
|
||||
languageTextMenuEn: '',
|
||||
languageTextMenuTr: '',
|
||||
languageTextTitleEn: '',
|
||||
|
|
@ -95,15 +109,12 @@ const initialValues: ListFormWizardDto = {
|
|||
schedulerEndDateExpr: '',
|
||||
subForms: [],
|
||||
widgets: [],
|
||||
workflow: {
|
||||
approvalUserFieldName: '',
|
||||
isFilterUserName: false,
|
||||
approvalDateFieldName: '',
|
||||
approvalStatusFieldName: '',
|
||||
approvalDescriptionFieldName: '',
|
||||
criteria: [],
|
||||
},
|
||||
workflowCriteria: [],
|
||||
approvalUserFieldName: '',
|
||||
approvalDateFieldName: '',
|
||||
approvalStatusFieldName: '',
|
||||
approvalDescriptionFieldName: '',
|
||||
approvalIsFilterUserName: false,
|
||||
criteria: [],
|
||||
}
|
||||
|
||||
const step1ValidationSchema = Yup.object().shape({
|
||||
|
|
@ -225,13 +236,13 @@ const Wizard = () => {
|
|||
const [widgets, setWidgets] = useState<WidgetEditDto[]>([])
|
||||
const [workflow, setWorkflow] = useState<WorkflowDto>({
|
||||
approvalUserFieldName: '',
|
||||
isFilterUserName: false,
|
||||
approvalDateFieldName: '',
|
||||
approvalStatusFieldName: '',
|
||||
approvalDescriptionFieldName: '',
|
||||
approvalIsFilterUserName: false,
|
||||
criteria: [],
|
||||
})
|
||||
const [workflowCriteria, setWorkflowCriteria] = useState<ListFormWorkflowCriteriaDto[]>([])
|
||||
const [criteria, setCriteria] = useState<ListFormWorkflowCriteriaDto[]>([])
|
||||
|
||||
// Audit columns that should not be selected by default
|
||||
const AUDIT_COLUMNS = new Set([
|
||||
|
|
@ -406,11 +417,24 @@ const Wizard = () => {
|
|||
allowDetail: w.allowDetail ?? false,
|
||||
defaultLayout: w.defaultLayout ?? 'grid',
|
||||
grid: w.grid ?? true,
|
||||
card: w.card ?? true,
|
||||
pivot: w.pivot ?? true,
|
||||
tree: w.tree ?? true,
|
||||
chart: w.chart ?? true,
|
||||
gantt: w.gantt ?? 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 ?? 'Inbox, Today, This Week, This Month, Later, Done',
|
||||
todoAllowDragging: w.todoAllowDragging ?? true,
|
||||
languageTextMenuEn: w.languageTextMenuEn ?? '',
|
||||
languageTextMenuTr: w.languageTextMenuTr ?? '',
|
||||
languageTextTitleEn: w.languageTextTitleEn ?? '',
|
||||
|
|
@ -444,15 +468,12 @@ const Wizard = () => {
|
|||
schedulerEndDateExpr: w.schedulerEndDateExpr ?? '',
|
||||
subForms: w.subForms ?? [],
|
||||
widgets: w.widgets ?? [],
|
||||
workflow: w.workflow ?? {
|
||||
approvalUserFieldName: '',
|
||||
isFilterUserName: false,
|
||||
approvalDateFieldName: '',
|
||||
approvalStatusFieldName: '',
|
||||
approvalDescriptionFieldName: '',
|
||||
criteria: [],
|
||||
},
|
||||
workflowCriteria: w.workflowCriteria ?? w.workflow?.criteria ?? [],
|
||||
approvalUserFieldName: w.approvalUserFieldName ?? '',
|
||||
approvalDateFieldName: w.approvalDateFieldName ?? '',
|
||||
approvalStatusFieldName: w.approvalStatusFieldName ?? '',
|
||||
approvalDescriptionFieldName: w.approvalDescriptionFieldName ?? '',
|
||||
approvalIsFilterUserName: w.approvalIsFilterUserName ?? false,
|
||||
criteria: w.criteria ?? [],
|
||||
})
|
||||
|
||||
// Queue column load to run once dbObjects is available
|
||||
|
|
@ -507,17 +528,15 @@ const Wizard = () => {
|
|||
|
||||
setSubForms(w.subForms ?? [])
|
||||
setWidgets(w.widgets ?? [])
|
||||
setWorkflow(
|
||||
w.workflow ?? {
|
||||
approvalUserFieldName: '',
|
||||
isFilterUserName: false,
|
||||
approvalDateFieldName: '',
|
||||
approvalStatusFieldName: '',
|
||||
approvalDescriptionFieldName: '',
|
||||
criteria: [],
|
||||
},
|
||||
)
|
||||
setWorkflowCriteria(w.workflowCriteria ?? w.workflow?.criteria ?? [])
|
||||
setWorkflow({
|
||||
approvalUserFieldName: w.approvalUserFieldName ?? '',
|
||||
approvalDateFieldName: w.approvalDateFieldName ?? '',
|
||||
approvalStatusFieldName: w.approvalStatusFieldName ?? '',
|
||||
approvalDescriptionFieldName: w.approvalDescriptionFieldName ?? '',
|
||||
approvalIsFilterUserName: w.approvalIsFilterUserName ?? false,
|
||||
criteria: w.criteria ?? [],
|
||||
})
|
||||
setCriteria(w.criteria ?? [])
|
||||
} catch {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
|
|
@ -730,11 +749,12 @@ const Wizard = () => {
|
|||
})),
|
||||
subForms,
|
||||
widgets,
|
||||
workflow: {
|
||||
...workflow,
|
||||
criteria: workflowCriteria,
|
||||
},
|
||||
workflowCriteria,
|
||||
approvalUserFieldName: workflow.approvalUserFieldName,
|
||||
approvalDateFieldName: workflow.approvalDateFieldName,
|
||||
approvalStatusFieldName: workflow.approvalStatusFieldName,
|
||||
approvalDescriptionFieldName: workflow.approvalDescriptionFieldName,
|
||||
approvalIsFilterUserName: workflow.approvalIsFilterUserName,
|
||||
criteria,
|
||||
})
|
||||
|
||||
// ✅ sonra config çek
|
||||
|
|
@ -780,6 +800,7 @@ const Wizard = () => {
|
|||
<Steps.Item title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'} />
|
||||
<Steps.Item title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'} />
|
||||
<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>
|
||||
</div>
|
||||
|
|
@ -900,19 +921,30 @@ const Wizard = () => {
|
|||
<WizardStep6
|
||||
listFormCode={values.listFormCode}
|
||||
workflow={workflow}
|
||||
criteria={workflowCriteria}
|
||||
criteria={criteria}
|
||||
selectCommandColumns={selectCommandColumns}
|
||||
translate={translate}
|
||||
onWorkflowChange={setWorkflow}
|
||||
onCriteriaChange={setWorkflowCriteria}
|
||||
onCriteriaChange={setCriteria}
|
||||
onBack={() => setCurrentStep(4)}
|
||||
onNext={() => setCurrentStep(6)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ─── Step 7: Deploy ───────────────────────────── */}
|
||||
{/* ─── Step 7: Todo / Kanban ────────────────────── */}
|
||||
{currentStep === 6 && (
|
||||
<WizardStep7
|
||||
selectedColumns={selectedColumns}
|
||||
selectCommandColumns={selectCommandColumns}
|
||||
translate={translate}
|
||||
onBack={() => setCurrentStep(5)}
|
||||
onNext={() => setCurrentStep(7)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ─── Step 8: Deploy ───────────────────────────── */}
|
||||
{currentStep === 7 && (
|
||||
<WizardStep8
|
||||
values={values}
|
||||
wizardName={values.wizardName}
|
||||
selectedColumns={selectedColumns}
|
||||
|
|
@ -921,9 +953,9 @@ const Wizard = () => {
|
|||
subForms={subForms}
|
||||
widgets={widgets}
|
||||
workflow={workflow}
|
||||
workflowCriteria={workflowCriteria}
|
||||
criteria={criteria}
|
||||
translate={translate}
|
||||
onBack={() => setCurrentStep(5)}
|
||||
onBack={() => setCurrentStep(6)}
|
||||
onSubmit={handleDeploy}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -599,6 +599,20 @@ const WizardStep2 = ({
|
|||
component={Checkbox}
|
||||
/>
|
||||
</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>
|
||||
|
||||
|
|
|
|||
|
|
@ -306,18 +306,18 @@ function WizardStep6({
|
|||
</FormItem>
|
||||
))}
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.Workflow.IsFilterUserName')}
|
||||
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalIsFilterUserName')}
|
||||
>
|
||||
<Field name="isFilterUserName">
|
||||
<Field name="approvalIsFilterUserName">
|
||||
{({ field, form }: FieldProps<boolean>) => (
|
||||
<Checkbox
|
||||
name={field.name}
|
||||
checked={Boolean(values.isFilterUserName)}
|
||||
checked={Boolean(values.approvalIsFilterUserName)}
|
||||
onChange={(checked) => {
|
||||
form.setFieldValue(field.name, checked)
|
||||
onWorkflowChange({
|
||||
...values,
|
||||
isFilterUserName: checked,
|
||||
approvalIsFilterUserName: checked,
|
||||
criteria,
|
||||
})
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,714 +1,151 @@
|
|||
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 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'
|
||||
import type { SelectBoxOption } from '@/types/shared'
|
||||
import { Field, type FieldProps, useFormikContext } from 'formik'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { FaArrowLeft, FaArrowRight, FaColumns } from 'react-icons/fa'
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface WizardStep7Props {
|
||||
values: ListFormWizardDto
|
||||
wizardName: string
|
||||
interface WizardStep7Props {
|
||||
selectedColumns: Set<string>
|
||||
selectCommandColumns: DatabaseColumnDto[]
|
||||
groups: WizardGroup[]
|
||||
subForms: SubFormDto[]
|
||||
widgets: WidgetEditDto[]
|
||||
workflow: WorkflowDto
|
||||
workflowCriteria: ListFormWorkflowCriteriaDto[]
|
||||
translate: (key: string) => string
|
||||
onBack: () => void
|
||||
onSubmit: () => Promise<void>
|
||||
onNext: () => void
|
||||
}
|
||||
|
||||
type LogStatus = 'pending' | 'running' | 'success' | 'error'
|
||||
|
||||
interface LogEntry {
|
||||
id: number
|
||||
const mappings: Array<{
|
||||
name:
|
||||
| 'todoTitleExpr'
|
||||
| 'todoStatusExpr'
|
||||
| 'todoDescriptionExpr'
|
||||
| 'todoDueDateExpr'
|
||||
| 'todoTagExpr'
|
||||
| 'todoAssigneeExpr'
|
||||
| 'todoPriorityExpr'
|
||||
| 'todoCompletedExpr'
|
||||
| 'todoOrderExpr'
|
||||
label: string
|
||||
status: LogStatus
|
||||
detail?: string
|
||||
}
|
||||
required?: boolean
|
||||
}> = [
|
||||
{ name: 'todoTitleExpr', label: 'Başlık alanı', required: true },
|
||||
{ name: 'todoStatusExpr', label: 'Kanban / durum alanı', required: true },
|
||||
{ name: 'todoDescriptionExpr', label: 'Açıklama alanı' },
|
||||
{ name: 'todoDueDateExpr', label: 'Termin tarihi alanı' },
|
||||
{ name: 'todoTagExpr', label: 'Etiket alanı' },
|
||||
{ name: 'todoAssigneeExpr', label: 'Atanan kişi alanı' },
|
||||
{ name: 'todoPriorityExpr', label: 'Öncelik alanı' },
|
||||
{ name: 'todoCompletedExpr', label: 'Tamamlandı alanı' },
|
||||
{ name: 'todoOrderExpr', label: 'Kart sıra alanı' },
|
||||
]
|
||||
|
||||
// ─── 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 = ({
|
||||
values,
|
||||
wizardName,
|
||||
selectedColumns,
|
||||
selectCommandColumns,
|
||||
groups,
|
||||
subForms,
|
||||
widgets,
|
||||
workflow,
|
||||
workflowCriteria,
|
||||
translate,
|
||||
onBack,
|
||||
onSubmit,
|
||||
onNext,
|
||||
}: WizardStep7Props) => {
|
||||
const [logs, setLogs] = useState<LogEntry[]>([])
|
||||
const [isDeploying, setIsDeploying] = useState(false)
|
||||
const [isDone, setIsDone] = useState(false)
|
||||
const [hasError, setHasError] = useState(false)
|
||||
const { values, setFieldValue } = useFormikContext<ListFormWizardDto>()
|
||||
const [validationError, setValidationError] = useState('')
|
||||
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 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 continueToDeploy = () => {
|
||||
if (values.todo && (!values.todoTitleExpr || !values.todoStatusExpr)) {
|
||||
setValidationError('Başlık alanı ve Kanban / durum alanı zorunludur.')
|
||||
return
|
||||
}
|
||||
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 (
|
||||
<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 className="pb-20">
|
||||
<div className="mb-5 rounded-xl border border-indigo-200 bg-indigo-50/60 p-4 dark:border-indigo-900 dark:bg-indigo-950/20">
|
||||
<div className="flex items-center gap-2 font-semibold text-indigo-700 dark:text-indigo-300">
|
||||
<FaColumns /> Todo / Kanban ayrıntıları
|
||||
</div>
|
||||
|
||||
<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>
|
||||
)}
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Liste alanlarını Todo kartının başlık, durum ve diğer özellikleriyle eşleştirin.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* ── 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">
|
||||
{s.value}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
{!values.todo ? (
|
||||
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-700 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200">
|
||||
Todo görünümü liste ayarlarında kapalı. Bu adımı atlayabilir veya önceki adımlardan Todo
|
||||
görünümünü etkinleştirebilirsiniz.
|
||||
</div>
|
||||
|
||||
{/* 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') || '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 className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{mappings.map((mapping) => (
|
||||
<FormItem
|
||||
key={mapping.name}
|
||||
label={`${mapping.label}${mapping.required ? ' *' : ''}`}
|
||||
>
|
||||
<Field name={mapping.name}>
|
||||
{({ field, form }: FieldProps) => (
|
||||
<Select
|
||||
field={field}
|
||||
form={form}
|
||||
isClearable={!mapping.required}
|
||||
options={fieldOptions}
|
||||
value={fieldOptions.find((option) => option.value === values[mapping.name])}
|
||||
onChange={(option) => {
|
||||
form.setFieldValue(field.name, option?.value ?? '')
|
||||
setValidationError('')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="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 className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<FormItem label="Durum sırası (virgülle ayırın)">
|
||||
<Field
|
||||
name="todoStatusOrder"
|
||||
component={Input}
|
||||
placeholder="Inbox, Today, This Week, This Month, Later, Done"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Kartları kolonlar arasında sürüklemeye izin ver">
|
||||
<Field name="todoAllowDragging">
|
||||
{({ field }: FieldProps<boolean>) => (
|
||||
<Checkbox
|
||||
checked={Boolean(field.value)}
|
||||
onChange={(checked) => setFieldValue(field.name, checked)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
</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">
|
||||
{validationError && <p className="mt-4 text-sm text-red-500">{validationError}</p>}
|
||||
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 flex h-16 items-center border-t border-gray-200 bg-white px-6 dark:border-gray-700 dark:bg-gray-900">
|
||||
<div className="flex w-full items-center justify-between gap-3">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Geri'}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
variant="solid"
|
||||
type="button"
|
||||
icon={<FaArrowLeft />}
|
||||
onClick={onBack}
|
||||
disabled={isDeploying}
|
||||
icon={<FaArrowRight />}
|
||||
onClick={continueToDeploy}
|
||||
>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Next') || 'Deploy adımına geç'}
|
||||
</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>
|
||||
|
|
|
|||
740
ui/src/views/admin/listForm/wizard/WizardStep8.tsx
Normal file
740
ui/src/views/admin/listForm/wizard/WizardStep8.tsx
Normal file
|
|
@ -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<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: `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 (
|
||||
<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.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.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="Başlık alanı" value={values.todoTitleExpr} />
|
||||
<Row label="Kanban / durum alanı" value={values.todoStatusExpr} />
|
||||
<Row label="Açıklama alanı" value={values.todoDescriptionExpr} />
|
||||
<Row label="Termin tarihi alanı" value={values.todoDueDateExpr} />
|
||||
<Row label="Etiket alanı" value={values.todoTagExpr} />
|
||||
<Row label="Atanan kişi alanı" value={values.todoAssigneeExpr} />
|
||||
<Row label="Öncelik alanı" value={values.todoPriorityExpr} />
|
||||
<Row label="Tamamlandı alanı" value={values.todoCompletedExpr} />
|
||||
<Row label="Kart sıra alanı" value={values.todoOrderExpr} />
|
||||
<Row label="Durum sırası" value={values.todoStatusOrder} />
|
||||
<Row label="Sürükle-bırak" value={values.todoAllowDragging ? 'Aktif' : 'Pasif'} />
|
||||
</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') || '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>
|
||||
)}
|
||||
</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
|
||||
|
|
@ -189,8 +189,8 @@ export default ${pascalCaseName}Component;`
|
|||
>
|
||||
<FaRegSave className="w-4 h-4" />
|
||||
{isSubmitting
|
||||
? translate('::App.DeveloperKit.ComponentEditor.Saving')
|
||||
: translate('::App.DeveloperKit.ComponentEditor.Save')}
|
||||
? translate('::Saving')
|
||||
: translate('::Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
<Button size="xs" variant="solid" color="indigo-600" onClick={addWorkflowColumns}>
|
||||
{translate('::App.SqlQueryManager.AddWorkflowColumns')}
|
||||
</Button>
|
||||
<Button size="xs" variant="solid" color="cyan-600" onClick={addTodoColumns}>
|
||||
Add Todo Columns
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="solid"
|
||||
|
|
@ -2406,7 +2531,7 @@ const SqlTableDesignerDialog = ({
|
|||
color="indigo-600"
|
||||
className="hover:!bg-indigo-700 disabled:opacity-50 !rounded-lg transition-colors"
|
||||
>
|
||||
{translate('::App.SqlQueryManager.Save')}
|
||||
{translate('::Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2792,7 +2917,7 @@ const SqlTableDesignerDialog = ({
|
|||
// ── Render ─────────────────────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={1100}>
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={1200}>
|
||||
<Dialog.Header className="flex flex-col gap-2 -mt-5 p-2 ">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -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<string, any>, field?: string | null) => {
|
|||
return key ? data[key] : undefined
|
||||
}
|
||||
|
||||
const getEditingFieldName = (dataField?: string | null) =>
|
||||
String(dataField ?? '')
|
||||
.split(':')[0]
|
||||
.trim()
|
||||
|
||||
const lookupDisplayValuesEqual = (
|
||||
left: Record<string, Map<string, string>>,
|
||||
right: Record<string, Map<string, string>>,
|
||||
|
|
@ -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<any, any>) => {
|
|||
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<CardViewRef>()
|
||||
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 (
|
||||
<ImageUploadEditorComponent
|
||||
{...templateData}
|
||||
column={column}
|
||||
value={getValueByField(currentFormData, dataField)}
|
||||
value={
|
||||
editorValue !== undefined ? editorValue : getValueByField(currentFormData, dataField)
|
||||
}
|
||||
setValue={(value: any) => {
|
||||
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<string, Map<string, string>>) => {
|
||||
if (lookupDisplayValuesEqual(lookupDisplayValuesRef.current, values)) return
|
||||
lookupDisplayValuesRef.current = values
|
||||
},
|
||||
[],
|
||||
)
|
||||
const applyLookupDisplayValues = useCallback((values: Record<string, Map<string, string>>) => {
|
||||
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) => {
|
|||
) : (
|
||||
<div className="p-1">
|
||||
<DxCardView
|
||||
key={`CardView-${listFormCode}`}
|
||||
key={`CardView-${listFormCode}-${localizationRevision}`}
|
||||
ref={cardViewRef as any}
|
||||
fieldHintEnabled
|
||||
id={`CardView-${listFormCode}`}
|
||||
|
|
@ -1009,6 +1055,9 @@ 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<string, any>) ?? {})
|
||||
const nextFormData = (event.data as Record<string, any>) ?? {}
|
||||
formDataRef.current = nextFormData
|
||||
setFormData(nextFormData)
|
||||
setMode('new')
|
||||
setIsPopupFullScreen(
|
||||
useMobileEditPopup || (editingOptions?.popup?.fullScreen ?? false),
|
||||
)
|
||||
}}
|
||||
onEditingStart={(event) => {
|
||||
setFormData((event.data as Record<string, any>) ?? {})
|
||||
const nextFormData = (event.data as Record<string, any>) ?? {}
|
||||
formDataRef.current = nextFormData
|
||||
setFormData(nextFormData)
|
||||
setMode('edit')
|
||||
setIsPopupFullScreen(
|
||||
useMobileEditPopup || (editingOptions?.popup?.fullScreen ?? false),
|
||||
|
|
@ -1137,7 +1190,29 @@ const CardView = (props: CardViewProps) => {
|
|||
],
|
||||
}}
|
||||
/>
|
||||
<Toolbar visible={orderedToolbarData.length > 0}>
|
||||
<Toolbar visible>
|
||||
{cardColumnCountOptions.map((columnCount) => {
|
||||
const selected = cardColumnCount === columnCount
|
||||
return (
|
||||
<ToolbarItem
|
||||
key={`card-column-count-${columnCount}`}
|
||||
location="before"
|
||||
locateInMenu="never"
|
||||
widget="dxButton"
|
||||
options={{
|
||||
text: String(columnCount),
|
||||
hint: String(columnCount),
|
||||
type: selected ? 'default' : 'normal',
|
||||
stylingMode: selected ? 'contained' : 'text',
|
||||
elementAttr: {
|
||||
'aria-label': String(columnCount),
|
||||
'aria-pressed': selected,
|
||||
},
|
||||
onClick: () => setCardColumnCount(columnCount),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{orderedToolbarData.map((item, index) => (
|
||||
<ToolbarItem key={`${item.name ?? 'toolbar'}-${index}`} {...(item as any)} />
|
||||
))}
|
||||
|
|
@ -1160,8 +1235,20 @@ const CardView = (props: CardViewProps) => {
|
|||
width={gridDto.gridOptions.searchPanelDto.width}
|
||||
placeholder={translate('::App.Search')}
|
||||
/>
|
||||
<FilterPanel visible={gridDto.gridOptions.filterPanelDto.visible} />
|
||||
<HeaderFilter visible={gridDto.gridOptions.headerFilterDto.visible} />
|
||||
<FilterPanel
|
||||
visible={gridDto.gridOptions.filterPanelDto.visible}
|
||||
filterEnabled={gridDto.gridOptions.filterPanelDto.filterEnabled}
|
||||
texts={gridDto.gridOptions.filterPanelDto.texts}
|
||||
/>
|
||||
<HeaderFilter
|
||||
visible={gridDto.gridOptions.headerFilterDto.visible}
|
||||
width={gridDto.gridOptions.headerFilterDto.width}
|
||||
height={gridDto.gridOptions.headerFilterDto.height}
|
||||
search={{
|
||||
enabled: gridDto.gridOptions.headerFilterDto.allowSearch,
|
||||
timeout: gridDto.gridOptions.headerFilterDto.searchTimeout,
|
||||
}}
|
||||
/>
|
||||
<Selection
|
||||
mode={selectionOptions?.mode}
|
||||
allowSelectAll={selectionOptions?.allowSelectAll}
|
||||
|
|
@ -1175,6 +1262,8 @@ const CardView = (props: CardViewProps) => {
|
|||
?.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}
|
||||
/>
|
||||
<LoadPanel visible={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled !== false} />
|
||||
<LoadPanel
|
||||
visible={gridDto.gridOptions.pagerOptionDto?.loadPanelEnabled !== false}
|
||||
message={gridDto.gridOptions.pagerOptionDto?.loadPanelText}
|
||||
/>
|
||||
</DxCardView>
|
||||
|
||||
{gridDto.gridOptions.subFormsDto?.length > 0 &&
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ const ChartDrawer = ({
|
|||
<div className="flex items-center justify-center gap-2">
|
||||
{isSubmitting
|
||||
? translate('::Saving')
|
||||
: translate('::App.Platform.ChartDrawer.Save')}
|
||||
: translate('::Save')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
======================= */}
|
||||
<div className="flex gap-1 ml-auto">
|
||||
{gridDto?.gridOptions?.layoutDto.todo &&
|
||||
gridDto?.gridOptions?.todoOptionDto?.titleExpr &&
|
||||
gridDto?.gridOptions?.todoOptionDto?.statusExpr && (
|
||||
<Button
|
||||
size="sm"
|
||||
icon={<FaTasks />}
|
||||
variant={viewMode === 'todo' ? 'solid' : 'default'}
|
||||
onClick={() => setLayout('todo')}
|
||||
onMouseEnter={() => preload.todo()}
|
||||
/>
|
||||
)}
|
||||
{gridDto?.gridOptions?.layoutDto.scheduler &&
|
||||
gridDto?.gridOptions?.schedulerOptionDto?.textExpr &&
|
||||
gridDto?.gridOptions?.schedulerOptionDto?.startDateExpr && (
|
||||
|
|
@ -295,6 +315,15 @@ const List: React.FC = () => {
|
|||
/>
|
||||
)}
|
||||
|
||||
{viewMode === 'todo' && (
|
||||
<TodoBoard
|
||||
listFormCode={listFormCode}
|
||||
searchParams={searchParams}
|
||||
isSubForm={false}
|
||||
gridDto={gridDto}
|
||||
/>
|
||||
)}
|
||||
|
||||
{viewMode === 'chart' && (
|
||||
<Chart
|
||||
id={gridDto?.gridOptions.id as string}
|
||||
|
|
|
|||
1364
ui/src/views/list/TodoBoard.tsx
Normal file
1364
ui/src/views/list/TodoBoard.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,7 @@ type PendingSave = {
|
|||
type UseListFormStateStoringParams = {
|
||||
listFormCode: string
|
||||
storageKey?: string | null
|
||||
filterPrefix: 'list' | 'tree' | 'pivot'
|
||||
filterPrefix: 'list' | 'tree' | 'pivot' | 'todo'
|
||||
skipSaveRef?: MutableRefObject<boolean>
|
||||
showSaveToast?: boolean
|
||||
saveDebounceMs?: number
|
||||
|
|
@ -151,7 +151,14 @@ export const useListFormStateStoring = ({
|
|||
|
||||
return pendingSaveRef.current.promise
|
||||
},
|
||||
[filterName, listFormCode, saveDebounceMs, showSaveErrorToast, showSaveSuccessToast, skipSaveRef],
|
||||
[
|
||||
filterName,
|
||||
listFormCode,
|
||||
saveDebounceMs,
|
||||
showSaveErrorToast,
|
||||
showSaveSuccessToast,
|
||||
skipSaveRef,
|
||||
],
|
||||
)
|
||||
|
||||
const customLoadState = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ const DesignerDrawer: React.FC<DesignerDrawerProps> = ({
|
|||
|
||||
<div className="flex items-center justify-between border-t border-slate-200 dark:border-gray-700 px-5 py-4">
|
||||
<Button variant="solid" block onClick={onSave}>
|
||||
{translate('::Public.designer.save')}
|
||||
{translate('::Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue