WizardManager düzenlemesi
This commit is contained in:
parent
3e50a28176
commit
a196e84890
15 changed files with 1685 additions and 1465 deletions
|
|
@ -52,45 +52,11 @@ public class ListFormWizardDto
|
||||||
public string KeyFieldName { get; set; }
|
public string KeyFieldName { get; set; }
|
||||||
public DbType KeyFieldDbSourceType { get; set; }
|
public DbType KeyFieldDbSourceType { get; set; }
|
||||||
|
|
||||||
// Tree options (required when DefaultLayout = "tree")
|
public TreeOptionDto TreeOptionDto { get; set; } = new();
|
||||||
public string TreeKeyExpr { get; set; }
|
public GanttOptionDto GanttOptionDto { get; set; } = new();
|
||||||
public string TreeParentIdExpr { get; set; }
|
public SchedulerOptionDto SchedulerOptionDto { get; set; } = new();
|
||||||
public bool TreeAutoExpandAll { get; set; }
|
public TodoOptionDto TodoOptionDto { get; set; } = new();
|
||||||
|
public WorkflowDto WorkflowDto { get; set; } = new();
|
||||||
// Gantt options (required when DefaultLayout = "gantt")
|
|
||||||
public string GanttKeyExpr { get; set; }
|
|
||||||
public string GanttParentIdExpr { get; set; }
|
|
||||||
public bool GanttAutoExpandAll { get; set; }
|
|
||||||
public string GanttTitleExpr { get; set; }
|
|
||||||
public string GanttStartExpr { get; set; }
|
|
||||||
public string GanttEndExpr { get; set; }
|
|
||||||
public string GanttProgressExpr { get; set; }
|
|
||||||
|
|
||||||
// Scheduler options (required when DefaultLayout = "scheduler")
|
|
||||||
public string SchedulerTextExpr { get; set; }
|
|
||||||
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<WizardColumnGroupInputDto> Groups { get; set; } = new();
|
||||||
public List<SubFormDto> SubForms { get; set; } = new();
|
public List<SubFormDto> SubForms { get; set; } = new();
|
||||||
|
|
|
||||||
|
|
@ -264,18 +264,11 @@ public class ListFormWizardAppService(
|
||||||
var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
|
var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
|
||||||
var isDeleted = tableColumns.Contains("IsDeleted");
|
var isDeleted = tableColumns.Contains("IsDeleted");
|
||||||
var isCreated = tableColumns.Contains("CreatorId");
|
var isCreated = tableColumns.Contains("CreatorId");
|
||||||
input.Criteria ??= [];
|
input.WorkflowDto ??= new WorkflowDto();
|
||||||
EnsureUniqueWorkflowCriteriaTitles(input.Criteria);
|
input.WorkflowDto.Criteria ??= [];
|
||||||
NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria);
|
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowDto.Criteria);
|
||||||
var workflow = new WorkflowDto
|
NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowDto.Criteria);
|
||||||
{
|
var workflow = input.WorkflowDto;
|
||||||
ApprovalUserFieldName = input.ApprovalUserFieldName,
|
|
||||||
ApprovalIsFilterUserName = input.ApprovalIsFilterUserName,
|
|
||||||
ApprovalDateFieldName = input.ApprovalDateFieldName,
|
|
||||||
ApprovalStatusFieldName = input.ApprovalStatusFieldName,
|
|
||||||
ApprovalDescriptionFieldName = input.ApprovalDescriptionFieldName,
|
|
||||||
Criteria = input.Criteria,
|
|
||||||
};
|
|
||||||
|
|
||||||
await repoListForm.InsertAsync(new ListForm
|
await repoListForm.InsertAsync(new ListForm
|
||||||
{
|
{
|
||||||
|
|
@ -283,7 +276,7 @@ public class ListFormWizardAppService(
|
||||||
PageSize = 10,
|
PageSize = 10,
|
||||||
ExportJson = WizardConsts.DefaultExportJson,
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
IsSubForm = false,
|
IsSubForm = false,
|
||||||
ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0,
|
ShowNote = input.SubForms.Count > 0 || workflow.Criteria.Count > 0,
|
||||||
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
|
|
@ -304,7 +297,7 @@ public class ListFormWizardAppService(
|
||||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
|
||||||
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
|
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
|
||||||
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
|
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
|
||||||
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Criteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone),
|
SelectionJson = WizardConsts.DefaultSelectionSingleJson(workflow.Criteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone),
|
||||||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
|
|
@ -315,52 +308,23 @@ public class ListFormWizardAppService(
|
||||||
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
||||||
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
|
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
|
||||||
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
|
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
|
||||||
WorkflowJson = HasWorkflow(workflow, input.Criteria) ? JsonSerializer.Serialize(workflow) : null,
|
WorkflowJson = HasWorkflow(workflow) ? JsonSerializer.Serialize(workflow) : null,
|
||||||
TreeOptionJson = (input.Tree || input.DefaultLayout == "tree") && !string.IsNullOrEmpty(input.TreeParentIdExpr)
|
TreeOptionJson = (input.Tree || input.DefaultLayout == "tree") &&
|
||||||
? JsonSerializer.Serialize(new TreeOptionDto
|
!string.IsNullOrEmpty(input.TreeOptionDto?.ParentIdExpr)
|
||||||
{
|
? JsonSerializer.Serialize(input.TreeOptionDto)
|
||||||
KeyExpr = input.TreeKeyExpr,
|
|
||||||
ParentIdExpr = input.TreeParentIdExpr,
|
|
||||||
AutoExpandAll = input.TreeAutoExpandAll,
|
|
||||||
})
|
|
||||||
: null,
|
: null,
|
||||||
GanttOptionJson = (input.Gantt || input.DefaultLayout == "gantt") && !string.IsNullOrEmpty(input.GanttParentIdExpr)
|
GanttOptionJson = (input.Gantt || input.DefaultLayout == "gantt") &&
|
||||||
? JsonSerializer.Serialize(new GanttOptionDto
|
!string.IsNullOrEmpty(input.GanttOptionDto?.ParentIdExpr)
|
||||||
{
|
? JsonSerializer.Serialize(input.GanttOptionDto)
|
||||||
KeyExpr = input.GanttKeyExpr,
|
|
||||||
ParentIdExpr = input.GanttParentIdExpr,
|
|
||||||
AutoExpandAll = input.GanttAutoExpandAll,
|
|
||||||
TitleExpr = input.GanttTitleExpr,
|
|
||||||
StartExpr = input.GanttStartExpr,
|
|
||||||
EndExpr = input.GanttEndExpr,
|
|
||||||
ProgressExpr = input.GanttProgressExpr,
|
|
||||||
})
|
|
||||||
: null,
|
: null,
|
||||||
SchedulerOptionJson = (input.Scheduler || input.DefaultLayout == "scheduler") && !string.IsNullOrEmpty(input.SchedulerTextExpr)
|
SchedulerOptionJson = (input.Scheduler || input.DefaultLayout == "scheduler") &&
|
||||||
? JsonSerializer.Serialize(new SchedulerOptionDto
|
!string.IsNullOrEmpty(input.SchedulerOptionDto?.TextExpr)
|
||||||
{
|
? JsonSerializer.Serialize(input.SchedulerOptionDto)
|
||||||
TextExpr = input.SchedulerTextExpr,
|
|
||||||
StartDateExpr = input.SchedulerStartDateExpr,
|
|
||||||
EndDateExpr = input.SchedulerEndDateExpr,
|
|
||||||
})
|
|
||||||
: null,
|
: null,
|
||||||
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
|
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
|
||||||
!string.IsNullOrEmpty(input.TodoTitleExpr) &&
|
!string.IsNullOrEmpty(input.TodoOptionDto?.TitleExpr) &&
|
||||||
!string.IsNullOrEmpty(input.TodoStatusExpr)
|
!string.IsNullOrEmpty(input.TodoOptionDto?.StatusExpr)
|
||||||
? JsonSerializer.Serialize(new TodoOptionDto
|
? JsonSerializer.Serialize(input.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,
|
: null,
|
||||||
}, autoSave: true);
|
}, autoSave: true);
|
||||||
|
|
||||||
|
|
@ -409,7 +373,7 @@ public class ListFormWizardAppService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var criteria in input.Criteria)
|
foreach (var criteria in workflow.Criteria)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(criteria.Id))
|
if (string.IsNullOrWhiteSpace(criteria.Id))
|
||||||
{
|
{
|
||||||
|
|
@ -443,12 +407,12 @@ public class ListFormWizardAppService(
|
||||||
await SaveWizardSeedFileAsync(input, isDeleted, isCreated, inserted);
|
await SaveWizardSeedFileAsync(input, isDeleted, isCreated, inserted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasWorkflow(WorkflowDto workflow, List<ListFormWorkflowCriteriaDto> criteria)
|
private static bool HasWorkflow(WorkflowDto workflow)
|
||||||
{
|
{
|
||||||
return workflow != null && (
|
return workflow != null && (
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
||||||
criteria.Count > 0
|
workflow.Criteria.Count > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
"Grid": true,
|
"Grid": true,
|
||||||
"Card": true,
|
"Card": true,
|
||||||
"Pivot": true,
|
"Pivot": true,
|
||||||
"Tree": true,
|
|
||||||
"Chart": true,
|
"Chart": true,
|
||||||
"Gantt": true,
|
"Tree": false,
|
||||||
"Scheduler": true,
|
"Gantt": false,
|
||||||
|
"Scheduler": false,
|
||||||
"Todo": true,
|
"Todo": true,
|
||||||
"LanguageTextMenuEn": "Todo",
|
"LanguageTextMenuEn": "Todo",
|
||||||
"LanguageTextMenuTr": "\u0130\u015Fler",
|
"LanguageTextMenuTr": "\u0130\u015Fler",
|
||||||
|
|
@ -39,36 +39,30 @@
|
||||||
"SelectCommand": "Pro_T_Todo",
|
"SelectCommand": "Pro_T_Todo",
|
||||||
"KeyFieldName": "Id",
|
"KeyFieldName": "Id",
|
||||||
"KeyFieldDbSourceType": 9,
|
"KeyFieldDbSourceType": 9,
|
||||||
"TreeKeyExpr": "",
|
"TreeOptionDto": {},
|
||||||
"TreeParentIdExpr": "",
|
"GanttOptionDto": {},
|
||||||
"TreeAutoExpandAll": false,
|
"SchedulerOptionDto": {},
|
||||||
"GanttKeyExpr": "",
|
"TodoOptionDto": {
|
||||||
"GanttParentIdExpr": "",
|
"TitleExpr": "Title",
|
||||||
"GanttAutoExpandAll": false,
|
"StatusExpr": "Status",
|
||||||
"GanttTitleExpr": "",
|
"DescriptionExpr": "Description",
|
||||||
"GanttStartExpr": "",
|
"DueDateExpr": "DueDate",
|
||||||
"GanttEndExpr": "",
|
"TagExpr": "Tags",
|
||||||
"GanttProgressExpr": "",
|
"AssigneeExpr": "Assignees",
|
||||||
"SchedulerTextExpr": "",
|
"PriorityExpr": "Priority",
|
||||||
"SchedulerStartDateExpr": "",
|
"CompletedExpr": "Completed",
|
||||||
"SchedulerEndDateExpr": "",
|
"OrderExpr": "SortOrder",
|
||||||
"TodoTitleExpr": "Title",
|
"StatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
||||||
"TodoStatusExpr": "Status",
|
"AllowDragging": true
|
||||||
"TodoDescriptionExpr": "Description",
|
},
|
||||||
"TodoDueDateExpr": "DueDate",
|
"WorkflowDto": {
|
||||||
"TodoTagExpr": "Tags",
|
"ApprovalUserFieldName": "",
|
||||||
"TodoAssigneeExpr": "Assignees",
|
"ApprovalDateFieldName": "",
|
||||||
"TodoPriorityExpr": "Priority",
|
"ApprovalStatusFieldName": "",
|
||||||
"TodoCompletedExpr": "Completed",
|
"ApprovalDescriptionFieldName": "",
|
||||||
"TodoOrderExpr": "SortOrder",
|
"ApprovalIsFilterUserName": false,
|
||||||
"TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
"Criteria": []
|
||||||
"TodoAllowDragging": true,
|
},
|
||||||
"ApprovalUserFieldName": "",
|
|
||||||
"ApprovalDateFieldName": "",
|
|
||||||
"ApprovalStatusFieldName": "",
|
|
||||||
"ApprovalDescriptionFieldName": "",
|
|
||||||
"ApprovalIsFilterUserName": false,
|
|
||||||
"Criteria": [],
|
|
||||||
"Groups": [
|
"Groups": [
|
||||||
{
|
{
|
||||||
"Caption": "",
|
"Caption": "",
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
"Grid": true,
|
"Grid": true,
|
||||||
"Card": true,
|
"Card": true,
|
||||||
"Pivot": true,
|
"Pivot": true,
|
||||||
"Tree": true,
|
|
||||||
"Chart": true,
|
"Chart": true,
|
||||||
"Gantt": true,
|
"Tree": true,
|
||||||
"Scheduler": true,
|
"Gantt": false,
|
||||||
|
"Scheduler": false,
|
||||||
"Todo": false,
|
"Todo": false,
|
||||||
"LanguageTextMenuEn": "Approval",
|
"LanguageTextMenuEn": "Approval",
|
||||||
"LanguageTextMenuTr": "Onaylama",
|
"LanguageTextMenuTr": "Onaylama",
|
||||||
|
|
@ -39,36 +39,24 @@
|
||||||
"SelectCommand": "Pro_T_Approval",
|
"SelectCommand": "Pro_T_Approval",
|
||||||
"KeyFieldName": "Id",
|
"KeyFieldName": "Id",
|
||||||
"KeyFieldDbSourceType": 9,
|
"KeyFieldDbSourceType": 9,
|
||||||
"TreeKeyExpr": "Title",
|
"TreeOptionDto": {
|
||||||
"TreeParentIdExpr": "ParentTitle",
|
"KeyExpr": "Title",
|
||||||
"TreeAutoExpandAll": true,
|
"ParentIdExpr": "ParentTitle",
|
||||||
"GanttKeyExpr": "",
|
"AutoExpandAll": true
|
||||||
"GanttParentIdExpr": "",
|
},
|
||||||
"GanttAutoExpandAll": false,
|
"GanttOptionDto": {},
|
||||||
"GanttTitleExpr": "",
|
"SchedulerOptionDto": {},
|
||||||
"GanttStartExpr": "",
|
"TodoOptionDto": {
|
||||||
"GanttEndExpr": "",
|
"StatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
||||||
"GanttProgressExpr": "",
|
"AllowDragging": true
|
||||||
"SchedulerTextExpr": "",
|
},
|
||||||
"SchedulerStartDateExpr": "",
|
"WorkflowDto": {
|
||||||
"SchedulerEndDateExpr": "",
|
"ApprovalUserFieldName": "ApprovalUserName",
|
||||||
"TodoTitleExpr": "",
|
"ApprovalDateFieldName": "ApprovalDate",
|
||||||
"TodoStatusExpr": "",
|
"ApprovalStatusFieldName": "ApprovalStatus",
|
||||||
"TodoDescriptionExpr": "",
|
"ApprovalDescriptionFieldName": "ApprovalDescription",
|
||||||
"TodoDueDateExpr": "",
|
"ApprovalIsFilterUserName": false,
|
||||||
"TodoTagExpr": "",
|
"Criteria": [
|
||||||
"TodoAssigneeExpr": "",
|
|
||||||
"TodoPriorityExpr": "",
|
|
||||||
"TodoCompletedExpr": "",
|
|
||||||
"TodoOrderExpr": "",
|
|
||||||
"TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
|
||||||
"TodoAllowDragging": true,
|
|
||||||
"ApprovalUserFieldName": "ApprovalUserName",
|
|
||||||
"ApprovalDateFieldName": "ApprovalDate",
|
|
||||||
"ApprovalStatusFieldName": "ApprovalStatus",
|
|
||||||
"ApprovalDescriptionFieldName": "ApprovalDescription",
|
|
||||||
"ApprovalIsFilterUserName": false,
|
|
||||||
"Criteria": [
|
|
||||||
{
|
{
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
"ListFormCode": "App.Wizard.Approval",
|
||||||
"Kind": "Start",
|
"Kind": "Start",
|
||||||
|
|
@ -141,7 +129,8 @@
|
||||||
"CompareOutcomes": [],
|
"CompareOutcomes": [],
|
||||||
"Id": "App.Wizard.Approval-N004"
|
"Id": "App.Wizard.Approval-N004"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
"Groups": [
|
"Groups": [
|
||||||
{
|
{
|
||||||
"Caption": "",
|
"Caption": "",
|
||||||
|
|
@ -296,4 +285,4 @@
|
||||||
],
|
],
|
||||||
"DataSourceCodes": []
|
"DataSourceCodes": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
"Grid": true,
|
"Grid": true,
|
||||||
"Card": true,
|
"Card": true,
|
||||||
"Pivot": true,
|
"Pivot": true,
|
||||||
"Tree": true,
|
|
||||||
"Chart": true,
|
"Chart": true,
|
||||||
|
"Tree": false,
|
||||||
"Gantt": true,
|
"Gantt": true,
|
||||||
"Scheduler": true,
|
"Scheduler": false,
|
||||||
"Todo": false,
|
"Todo": false,
|
||||||
"LanguageTextMenuEn": "Gantt",
|
"LanguageTextMenuEn": "Gantt",
|
||||||
"LanguageTextMenuTr": "Gantt",
|
"LanguageTextMenuTr": "Gantt",
|
||||||
|
|
@ -39,36 +39,29 @@
|
||||||
"SelectCommand": "Pro_D_Gantt",
|
"SelectCommand": "Pro_D_Gantt",
|
||||||
"KeyFieldName": "Id",
|
"KeyFieldName": "Id",
|
||||||
"KeyFieldDbSourceType": 9,
|
"KeyFieldDbSourceType": 9,
|
||||||
"TreeKeyExpr": "",
|
"TreeOptionDto": {},
|
||||||
"TreeParentIdExpr": "",
|
"GanttOptionDto": {
|
||||||
"TreeAutoExpandAll": false,
|
"KeyExpr": "Id",
|
||||||
"GanttKeyExpr": "Id",
|
"ParentIdExpr": "ParentId",
|
||||||
"GanttParentIdExpr": "ParentId",
|
"AutoExpandAll": true,
|
||||||
"GanttAutoExpandAll": true,
|
"TitleExpr": "Title",
|
||||||
"GanttTitleExpr": "Title",
|
"StartExpr": "StartDate",
|
||||||
"GanttStartExpr": "StartDate",
|
"EndExpr": "EndDate",
|
||||||
"GanttEndExpr": "EndDate",
|
"ProgressExpr": "Progress"
|
||||||
"GanttProgressExpr": "Progress",
|
},
|
||||||
"SchedulerTextExpr": "",
|
"SchedulerOptionDto": {},
|
||||||
"SchedulerStartDateExpr": "",
|
"TodoOptionDto": {
|
||||||
"SchedulerEndDateExpr": "",
|
"StatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
||||||
"TodoTitleExpr": "",
|
"AllowDragging": true
|
||||||
"TodoStatusExpr": "",
|
},
|
||||||
"TodoDescriptionExpr": "",
|
"WorkflowDto": {
|
||||||
"TodoDueDateExpr": "",
|
"ApprovalUserFieldName": "",
|
||||||
"TodoTagExpr": "",
|
"ApprovalDateFieldName": "",
|
||||||
"TodoAssigneeExpr": "",
|
"ApprovalStatusFieldName": "",
|
||||||
"TodoPriorityExpr": "",
|
"ApprovalDescriptionFieldName": "",
|
||||||
"TodoCompletedExpr": "",
|
"ApprovalIsFilterUserName": false,
|
||||||
"TodoOrderExpr": "",
|
"Criteria": []
|
||||||
"TodoStatusOrder": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging",
|
},
|
||||||
"TodoAllowDragging": true,
|
|
||||||
"ApprovalUserFieldName": "",
|
|
||||||
"ApprovalDateFieldName": "",
|
|
||||||
"ApprovalStatusFieldName": "",
|
|
||||||
"ApprovalDescriptionFieldName": "",
|
|
||||||
"ApprovalIsFilterUserName": false,
|
|
||||||
"Criteria": [],
|
|
||||||
"Groups": [
|
"Groups": [
|
||||||
{
|
{
|
||||||
"Caption": "",
|
"Caption": "",
|
||||||
|
|
@ -205,4 +198,4 @@
|
||||||
],
|
],
|
||||||
"DataSourceCodes": []
|
"DataSourceCodes": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,17 +134,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
var isCreated = seedFile.IsCreatedField;
|
var isCreated = seedFile.IsCreatedField;
|
||||||
input.SubForms ??= new List<SubFormDto>();
|
input.SubForms ??= new List<SubFormDto>();
|
||||||
input.Widgets ??= new List<WidgetEditDto>();
|
input.Widgets ??= new List<WidgetEditDto>();
|
||||||
input.Criteria ??= [];
|
input.WorkflowDto ??= new WorkflowDto();
|
||||||
EnsureUniqueWorkflowCriteriaTitles(input.Criteria);
|
input.WorkflowDto.Criteria ??= [];
|
||||||
var workflow = new WorkflowDto
|
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowDto.Criteria);
|
||||||
{
|
var workflow = input.WorkflowDto;
|
||||||
ApprovalUserFieldName = input.ApprovalUserFieldName,
|
|
||||||
ApprovalIsFilterUserName = input.ApprovalIsFilterUserName,
|
|
||||||
ApprovalDateFieldName = input.ApprovalDateFieldName,
|
|
||||||
ApprovalStatusFieldName = input.ApprovalStatusFieldName,
|
|
||||||
ApprovalDescriptionFieldName = input.ApprovalDescriptionFieldName,
|
|
||||||
Criteria = input.Criteria,
|
|
||||||
};
|
|
||||||
|
|
||||||
var wizardName = input.WizardName.Trim();
|
var wizardName = input.WizardName.Trim();
|
||||||
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
||||||
|
|
@ -153,7 +146,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
|
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
|
||||||
? code
|
? code
|
||||||
: input.ListFormCode.Trim();
|
: input.ListFormCode.Trim();
|
||||||
NormalizeWorkflowCriteriaIds(listFormCode, input.Criteria);
|
NormalizeWorkflowCriteriaIds(listFormCode, workflow.Criteria);
|
||||||
var titleLangKey = $"{listFormCode}.Title";
|
var titleLangKey = $"{listFormCode}.Title";
|
||||||
var nameLangKey = code;
|
var nameLangKey = code;
|
||||||
var descLangKey = $"{listFormCode}.Desc";
|
var descLangKey = $"{listFormCode}.Desc";
|
||||||
|
|
@ -386,7 +379,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
PageSize = 10,
|
PageSize = 10,
|
||||||
ExportJson = WizardConsts.DefaultExportJson,
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
IsSubForm = false,
|
IsSubForm = false,
|
||||||
ShowNote = input.SubForms.Count > 0 || input.Criteria.Count > 0,
|
ShowNote = input.SubForms.Count > 0 || workflow.Criteria.Count > 0,
|
||||||
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Card, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler, input.Todo),
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
|
|
@ -407,7 +400,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson(),
|
||||||
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
|
SearchPanelJson = WizardConsts.DefaultSearchPanelJson(),
|
||||||
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
|
GroupPanelJson = WizardConsts.DefaultGroupPanelJson(),
|
||||||
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.Criteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
|
SelectionJson = WizardConsts.DefaultSelectionSingleJson(workflow.Criteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
|
||||||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
|
|
@ -422,24 +415,23 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
||||||
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
|
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
|
||||||
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
|
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
|
||||||
WorkflowJson = HasWorkflow(workflow, input.Criteria) ? JsonSerializer.Serialize(workflow) : null,
|
WorkflowJson = HasWorkflow(workflow) ? JsonSerializer.Serialize(workflow) : null,
|
||||||
|
TreeOptionJson = (input.Tree || input.DefaultLayout == "tree") &&
|
||||||
|
!string.IsNullOrEmpty(input.TreeOptionDto?.ParentIdExpr)
|
||||||
|
? JsonSerializer.Serialize(input.TreeOptionDto)
|
||||||
|
: null,
|
||||||
|
GanttOptionJson = (input.Gantt || input.DefaultLayout == "gantt") &&
|
||||||
|
!string.IsNullOrEmpty(input.GanttOptionDto?.ParentIdExpr)
|
||||||
|
? JsonSerializer.Serialize(input.GanttOptionDto)
|
||||||
|
: null,
|
||||||
|
SchedulerOptionJson = (input.Scheduler || input.DefaultLayout == "scheduler") &&
|
||||||
|
!string.IsNullOrEmpty(input.SchedulerOptionDto?.TextExpr)
|
||||||
|
? JsonSerializer.Serialize(input.SchedulerOptionDto)
|
||||||
|
: null,
|
||||||
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
|
TodoOptionJson = (input.Todo || input.DefaultLayout == "todo") &&
|
||||||
!string.IsNullOrEmpty(input.TodoTitleExpr) &&
|
!string.IsNullOrEmpty(input.TodoOptionDto?.TitleExpr) &&
|
||||||
!string.IsNullOrEmpty(input.TodoStatusExpr)
|
!string.IsNullOrEmpty(input.TodoOptionDto?.StatusExpr)
|
||||||
? JsonSerializer.Serialize(new TodoOptionDto
|
? JsonSerializer.Serialize(input.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,
|
: null,
|
||||||
}, autoSave: true);
|
}, autoSave: true);
|
||||||
|
|
||||||
|
|
@ -493,7 +485,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var criteria in input.Criteria)
|
foreach (var criteria in workflow.Criteria)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(criteria.Id))
|
if (string.IsNullOrWhiteSpace(criteria.Id))
|
||||||
{
|
{
|
||||||
|
|
@ -540,14 +532,14 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
menuParent.ShortName);
|
menuParent.ShortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasWorkflow(WorkflowDto workflow, List<ListFormWorkflowCriteriaDto> criteria)
|
private static bool HasWorkflow(WorkflowDto workflow)
|
||||||
{
|
{
|
||||||
return workflow != null && (
|
return workflow != null && (
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) ||
|
||||||
(criteria?.Count ?? 0) > 0
|
workflow.Criteria.Count > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import {
|
import {
|
||||||
ListFormWorkflowCriteriaDto,
|
GanttOptionDto,
|
||||||
|
SchedulerOptionDto,
|
||||||
SubFormDto,
|
SubFormDto,
|
||||||
|
TodoOptionDto,
|
||||||
|
TreeOptionDto,
|
||||||
WidgetEditDto,
|
WidgetEditDto,
|
||||||
|
WorkflowDto,
|
||||||
SelectCommandTypeEnum,
|
SelectCommandTypeEnum,
|
||||||
} from '@/proxy/form/models'
|
} from '@/proxy/form/models'
|
||||||
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
|
import { ListViewLayoutType } from '@/views/admin/listForm/edit/types'
|
||||||
|
|
@ -66,45 +70,11 @@ export interface ListFormWizardDto {
|
||||||
keyFieldName: string
|
keyFieldName: string
|
||||||
keyFieldDbSourceType: number
|
keyFieldDbSourceType: number
|
||||||
|
|
||||||
// Tree options (required when defaultLayout = 'tree')
|
treeOptionDto: TreeOptionDto
|
||||||
treeKeyExpr?: string
|
ganttOptionDto: GanttOptionDto
|
||||||
treeParentIdExpr?: string
|
schedulerOptionDto: SchedulerOptionDto
|
||||||
treeAutoExpandAll?: boolean
|
todoOptionDto: TodoOptionDto
|
||||||
|
workflowDto: WorkflowDto
|
||||||
// Gantt options (required when defaultLayout = 'gantt')
|
|
||||||
ganttKeyExpr?: string
|
|
||||||
ganttParentIdExpr?: string
|
|
||||||
ganttAutoExpandAll?: boolean
|
|
||||||
ganttTitleExpr?: string
|
|
||||||
ganttStartExpr?: string
|
|
||||||
ganttEndExpr?: string
|
|
||||||
ganttProgressExpr?: string
|
|
||||||
|
|
||||||
// Scheduler options (required when defaultLayout = 'scheduler')
|
|
||||||
schedulerTextExpr?: string
|
|
||||||
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[]
|
groups?: ListFormWizardColumnGroupDto[]
|
||||||
subForms?: SubFormDto[]
|
subForms?: SubFormDto[]
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export const ROUTES_ENUM = {
|
||||||
menuManager: '/admin/menuManager',
|
menuManager: '/admin/menuManager',
|
||||||
listFormManagement: {
|
listFormManagement: {
|
||||||
wizard: '/admin/listform/wizard',
|
wizard: '/admin/listform/wizard',
|
||||||
|
wizardManager: '/admin/listform/wizardManager',
|
||||||
edit: '/admin/listform/edit/:listFormCode',
|
edit: '/admin/listform/edit/:listFormCode',
|
||||||
},
|
},
|
||||||
forumManagement: '/admin/forumManagement',
|
forumManagement: '/admin/forumManagement',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
import { FaFolderOpen } from 'react-icons/fa'
|
||||||
import { getMenus } from '@/services/menu.service'
|
import { getMenus } from '@/services/menu.service'
|
||||||
import { getPermissions } from '@/services/identity.service'
|
import { getPermissions } from '@/services/identity.service'
|
||||||
import { getDataSources } from '@/services/data-source.service'
|
import { getDataSources } from '@/services/data-source.service'
|
||||||
|
|
@ -25,6 +26,9 @@ import WizardStep2 from './WizardStep2'
|
||||||
import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3'
|
import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3'
|
||||||
import WizardStep7 from './WizardStep7'
|
import WizardStep7 from './WizardStep7'
|
||||||
import WizardStep8 from './WizardStep8'
|
import WizardStep8 from './WizardStep8'
|
||||||
|
import WizardStep9 from './WizardStep9'
|
||||||
|
import WizardStep10 from './WizardStep10'
|
||||||
|
import WizardStep11 from './WizardStep11'
|
||||||
import WizardStep4 from './WizardStep4'
|
import WizardStep4 from './WizardStep4'
|
||||||
import WizardStep5 from './WizardStep5'
|
import WizardStep5 from './WizardStep5'
|
||||||
import WizardStep6 from './WizardStep6'
|
import WizardStep6 from './WizardStep6'
|
||||||
|
|
@ -60,22 +64,81 @@ const initialValues: ListFormWizardDto = {
|
||||||
grid: true,
|
grid: true,
|
||||||
card: true,
|
card: true,
|
||||||
pivot: true,
|
pivot: true,
|
||||||
tree: true,
|
|
||||||
chart: true,
|
chart: true,
|
||||||
gantt: true,
|
tree: false,
|
||||||
scheduler: true,
|
gantt: false,
|
||||||
|
scheduler: false,
|
||||||
todo: false,
|
todo: false,
|
||||||
todoTitleExpr: '',
|
treeOptionDto: {
|
||||||
todoStatusExpr: '',
|
keyExpr: '',
|
||||||
todoDescriptionExpr: '',
|
parentIdExpr: '',
|
||||||
todoDueDateExpr: '',
|
hasItemsExpr: '',
|
||||||
todoTagExpr: '',
|
rootValue: '',
|
||||||
todoAssigneeExpr: '',
|
autoExpandAll: false,
|
||||||
todoPriorityExpr: '',
|
recursiveSelection: false,
|
||||||
todoCompletedExpr: '',
|
},
|
||||||
todoOrderExpr: '',
|
ganttOptionDto: {
|
||||||
todoStatusOrder: 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging',
|
keyExpr: '',
|
||||||
todoAllowDragging: true,
|
parentIdExpr: '',
|
||||||
|
hasItemsExpr: '',
|
||||||
|
rootValue: '',
|
||||||
|
autoExpandAll: false,
|
||||||
|
recursiveSelection: false,
|
||||||
|
titleExpr: '',
|
||||||
|
startExpr: '',
|
||||||
|
endExpr: '',
|
||||||
|
progressExpr: '',
|
||||||
|
scaleType: 'weeks',
|
||||||
|
allowEditing: false,
|
||||||
|
allowTaskAdding: false,
|
||||||
|
allowTaskUpdating: false,
|
||||||
|
allowTaskDeleting: false,
|
||||||
|
allowDependencyAdding: false,
|
||||||
|
allowDependencyDeleting: false,
|
||||||
|
allowResourceAdding: false,
|
||||||
|
allowResourceDeleting: false,
|
||||||
|
},
|
||||||
|
schedulerOptionDto: {
|
||||||
|
textExpr: '',
|
||||||
|
startDateExpr: '',
|
||||||
|
endDateExpr: '',
|
||||||
|
allDayExpr: '',
|
||||||
|
recurrenceRuleExpr: '',
|
||||||
|
recurrenceExceptionExpr: '',
|
||||||
|
startDayHour: 8,
|
||||||
|
endDayHour: 18,
|
||||||
|
defaultView: 'week',
|
||||||
|
showAllDayPanel: true,
|
||||||
|
cellDuration: 30,
|
||||||
|
firstDayOfWeek: 1,
|
||||||
|
crossScrollingEnabled: false,
|
||||||
|
allowResizing: false,
|
||||||
|
allowDragging: false,
|
||||||
|
allowAdding: false,
|
||||||
|
allowEditing: false,
|
||||||
|
allowDeleting: false,
|
||||||
|
},
|
||||||
|
todoOptionDto: {
|
||||||
|
titleExpr: '',
|
||||||
|
statusExpr: '',
|
||||||
|
descriptionExpr: '',
|
||||||
|
dueDateExpr: '',
|
||||||
|
tagExpr: '',
|
||||||
|
assigneeExpr: '',
|
||||||
|
priorityExpr: '',
|
||||||
|
completedExpr: '',
|
||||||
|
orderExpr: '',
|
||||||
|
statusOrder: 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging',
|
||||||
|
allowDragging: true,
|
||||||
|
},
|
||||||
|
workflowDto: {
|
||||||
|
approvalUserFieldName: '',
|
||||||
|
approvalDateFieldName: '',
|
||||||
|
approvalStatusFieldName: '',
|
||||||
|
approvalDescriptionFieldName: '',
|
||||||
|
approvalIsFilterUserName: false,
|
||||||
|
criteria: [],
|
||||||
|
},
|
||||||
languageTextMenuEn: '',
|
languageTextMenuEn: '',
|
||||||
languageTextMenuTr: '',
|
languageTextMenuTr: '',
|
||||||
languageTextTitleEn: '',
|
languageTextTitleEn: '',
|
||||||
|
|
@ -95,27 +158,8 @@ const initialValues: ListFormWizardDto = {
|
||||||
selectCommand: '',
|
selectCommand: '',
|
||||||
keyFieldName: '',
|
keyFieldName: '',
|
||||||
keyFieldDbSourceType: DbTypeEnum.Int32,
|
keyFieldDbSourceType: DbTypeEnum.Int32,
|
||||||
treeKeyExpr: '',
|
|
||||||
treeParentIdExpr: '',
|
|
||||||
treeAutoExpandAll: false,
|
|
||||||
ganttKeyExpr: '',
|
|
||||||
ganttParentIdExpr: '',
|
|
||||||
ganttAutoExpandAll: false,
|
|
||||||
ganttTitleExpr: '',
|
|
||||||
ganttStartExpr: '',
|
|
||||||
ganttEndExpr: '',
|
|
||||||
ganttProgressExpr: '',
|
|
||||||
schedulerTextExpr: '',
|
|
||||||
schedulerStartDateExpr: '',
|
|
||||||
schedulerEndDateExpr: '',
|
|
||||||
subForms: [],
|
subForms: [],
|
||||||
widgets: [],
|
widgets: [],
|
||||||
approvalUserFieldName: '',
|
|
||||||
approvalDateFieldName: '',
|
|
||||||
approvalStatusFieldName: '',
|
|
||||||
approvalDescriptionFieldName: '',
|
|
||||||
approvalIsFilterUserName: false,
|
|
||||||
criteria: [],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const step1ValidationSchema = Yup.object().shape({
|
const step1ValidationSchema = Yup.object().shape({
|
||||||
|
|
@ -147,26 +191,7 @@ const step2ValidationSchema = Yup.object().shape({
|
||||||
confirmDelete: Yup.boolean(),
|
confirmDelete: Yup.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const todoValidationSchema = Yup.object().shape({
|
const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSchema)
|
||||||
todoTitleExpr: Yup.string()
|
|
||||||
.trim()
|
|
||||||
.when('todo', {
|
|
||||||
is: true,
|
|
||||||
then: (schema) => schema.required(),
|
|
||||||
otherwise: (schema) => schema.notRequired(),
|
|
||||||
}),
|
|
||||||
todoStatusExpr: Yup.string()
|
|
||||||
.trim()
|
|
||||||
.when('todo', {
|
|
||||||
is: true,
|
|
||||||
then: (schema) => schema.required(),
|
|
||||||
otherwise: (schema) => schema.notRequired(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const listFormValidationSchema = step1ValidationSchema
|
|
||||||
.concat(step2ValidationSchema)
|
|
||||||
.concat(todoValidationSchema)
|
|
||||||
|
|
||||||
// ─── Wizard ───────────────────────────────────────────────────────────────────
|
// ─── Wizard ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -444,19 +469,11 @@ const Wizard = () => {
|
||||||
gantt: w.gantt ?? true,
|
gantt: w.gantt ?? true,
|
||||||
scheduler: w.scheduler ?? true,
|
scheduler: w.scheduler ?? true,
|
||||||
todo: w.todo ?? true,
|
todo: w.todo ?? true,
|
||||||
todoTitleExpr: w.todoTitleExpr ?? '',
|
treeOptionDto: w.treeOptionDto ?? initialValues.treeOptionDto,
|
||||||
todoStatusExpr: w.todoStatusExpr ?? '',
|
ganttOptionDto: w.ganttOptionDto ?? initialValues.ganttOptionDto,
|
||||||
todoDescriptionExpr: w.todoDescriptionExpr ?? '',
|
schedulerOptionDto: w.schedulerOptionDto ?? initialValues.schedulerOptionDto,
|
||||||
todoDueDateExpr: w.todoDueDateExpr ?? '',
|
todoOptionDto: w.todoOptionDto ?? initialValues.todoOptionDto,
|
||||||
todoTagExpr: w.todoTagExpr ?? '',
|
workflowDto: w.workflowDto ?? initialValues.workflowDto,
|
||||||
todoAssigneeExpr: w.todoAssigneeExpr ?? '',
|
|
||||||
todoPriorityExpr: w.todoPriorityExpr ?? '',
|
|
||||||
todoCompletedExpr: w.todoCompletedExpr ?? '',
|
|
||||||
todoOrderExpr: w.todoOrderExpr ?? '',
|
|
||||||
todoStatusOrder:
|
|
||||||
w.todoStatusOrder ??
|
|
||||||
'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging',
|
|
||||||
todoAllowDragging: w.todoAllowDragging ?? true,
|
|
||||||
languageTextMenuEn: w.languageTextMenuEn ?? '',
|
languageTextMenuEn: w.languageTextMenuEn ?? '',
|
||||||
languageTextMenuTr: w.languageTextMenuTr ?? '',
|
languageTextMenuTr: w.languageTextMenuTr ?? '',
|
||||||
languageTextTitleEn: w.languageTextTitleEn ?? '',
|
languageTextTitleEn: w.languageTextTitleEn ?? '',
|
||||||
|
|
@ -476,27 +493,8 @@ const Wizard = () => {
|
||||||
selectCommand: w.selectCommand ?? '',
|
selectCommand: w.selectCommand ?? '',
|
||||||
keyFieldName: w.keyFieldName ?? '',
|
keyFieldName: w.keyFieldName ?? '',
|
||||||
keyFieldDbSourceType: w.keyFieldDbSourceType ?? DbTypeEnum.Int32,
|
keyFieldDbSourceType: w.keyFieldDbSourceType ?? DbTypeEnum.Int32,
|
||||||
treeKeyExpr: w.treeKeyExpr ?? '',
|
|
||||||
treeParentIdExpr: w.treeParentIdExpr ?? '',
|
|
||||||
treeAutoExpandAll: w.treeAutoExpandAll ?? false,
|
|
||||||
ganttKeyExpr: w.ganttKeyExpr ?? '',
|
|
||||||
ganttParentIdExpr: w.ganttParentIdExpr ?? '',
|
|
||||||
ganttAutoExpandAll: w.ganttAutoExpandAll ?? false,
|
|
||||||
ganttTitleExpr: w.ganttTitleExpr ?? '',
|
|
||||||
ganttStartExpr: w.ganttStartExpr ?? '',
|
|
||||||
ganttEndExpr: w.ganttEndExpr ?? '',
|
|
||||||
ganttProgressExpr: w.ganttProgressExpr ?? '',
|
|
||||||
schedulerTextExpr: w.schedulerTextExpr ?? '',
|
|
||||||
schedulerStartDateExpr: w.schedulerStartDateExpr ?? '',
|
|
||||||
schedulerEndDateExpr: w.schedulerEndDateExpr ?? '',
|
|
||||||
subForms: w.subForms ?? [],
|
subForms: w.subForms ?? [],
|
||||||
widgets: w.widgets ?? [],
|
widgets: w.widgets ?? [],
|
||||||
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
|
// Queue column load to run once dbObjects is available
|
||||||
|
|
@ -551,15 +549,8 @@ const Wizard = () => {
|
||||||
|
|
||||||
setSubForms(w.subForms ?? [])
|
setSubForms(w.subForms ?? [])
|
||||||
setWidgets(w.widgets ?? [])
|
setWidgets(w.widgets ?? [])
|
||||||
setWorkflow({
|
setWorkflow(w.workflowDto ?? initialValues.workflowDto)
|
||||||
approvalUserFieldName: w.approvalUserFieldName ?? '',
|
setCriteria(w.workflowDto?.criteria ?? [])
|
||||||
approvalDateFieldName: w.approvalDateFieldName ?? '',
|
|
||||||
approvalStatusFieldName: w.approvalStatusFieldName ?? '',
|
|
||||||
approvalDescriptionFieldName: w.approvalDescriptionFieldName ?? '',
|
|
||||||
approvalIsFilterUserName: w.approvalIsFilterUserName ?? false,
|
|
||||||
criteria: w.criteria ?? [],
|
|
||||||
})
|
|
||||||
setCriteria(w.criteria ?? [])
|
|
||||||
} catch {
|
} catch {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="danger">
|
<Notification type="danger">
|
||||||
|
|
@ -786,12 +777,10 @@ const Wizard = () => {
|
||||||
})),
|
})),
|
||||||
subForms,
|
subForms,
|
||||||
widgets,
|
widgets,
|
||||||
approvalUserFieldName: workflow.approvalUserFieldName,
|
workflowDto: {
|
||||||
approvalDateFieldName: workflow.approvalDateFieldName,
|
...workflow,
|
||||||
approvalStatusFieldName: workflow.approvalStatusFieldName,
|
criteria,
|
||||||
approvalDescriptionFieldName: workflow.approvalDescriptionFieldName,
|
},
|
||||||
approvalIsFilterUserName: workflow.approvalIsFilterUserName,
|
|
||||||
criteria,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ✅ sonra config çek
|
// ✅ sonra config çek
|
||||||
|
|
@ -822,26 +811,6 @@ const Wizard = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mb-6 mt-2">
|
|
||||||
<Steps
|
|
||||||
current={currentStep}
|
|
||||||
className="flex flex-row flex-wrap !justify-start gap-y-2 lg:flex-nowrap lg:!justify-between"
|
|
||||||
>
|
|
||||||
<Steps.Item title={translate('::ListForms.Wizard.MenuInfo') || 'Menu Info'} />
|
|
||||||
<Steps.Item
|
|
||||||
title={translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings'}
|
|
||||||
/>
|
|
||||||
<Steps.Item
|
|
||||||
title={translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields'}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{isLoadingEditData && (
|
{isLoadingEditData && (
|
||||||
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">
|
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">
|
||||||
{translate('::App.Loading') || 'Loading...'}
|
{translate('::App.Loading') || 'Loading...'}
|
||||||
|
|
@ -856,150 +825,268 @@ const Wizard = () => {
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ touched, errors, values }) => (
|
{({ touched, errors, values }) => {
|
||||||
<Form onKeyDown={preventEnterSubmit}>
|
const activeSteps = [
|
||||||
<FormContainer size={currentStep >= 2 ? undefined : 'sm'}>
|
{ id: 0, title: translate('::ListForms.Wizard.MenuInfo') || 'Menu Info' },
|
||||||
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
|
{
|
||||||
{currentStep === 0 && (
|
id: 1,
|
||||||
<WizardStep1
|
title: translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings',
|
||||||
values={values}
|
},
|
||||||
errors={errors}
|
{
|
||||||
touched={touched}
|
id: 2,
|
||||||
wizardName={values.wizardName}
|
title: translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields',
|
||||||
onWizardNameChange={handleWizardNameChange}
|
},
|
||||||
rawMenuItems={rawMenuItems}
|
{
|
||||||
menuTree={menuTree}
|
id: 3,
|
||||||
isLoadingMenu={isLoadingMenu}
|
title: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
|
||||||
onMenuParentChange={handleMenuParentChange}
|
},
|
||||||
onClearMenuParent={() => {
|
{
|
||||||
formikRef.current?.setFieldValue('menuParentCode', '')
|
id: 4,
|
||||||
formikRef.current?.setFieldValue('menuParentShortName', '')
|
title: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
|
||||||
formikRef.current?.setFieldValue('menuParentIcon', '')
|
},
|
||||||
}}
|
{
|
||||||
onMenuCreated={handleMenuCreated}
|
id: 5,
|
||||||
onReloadMenu={getMenuList}
|
title: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
|
||||||
permissionGroupList={permissionGroupList}
|
},
|
||||||
isLoadingPermissionGroup={isLoadingPermissionGroup}
|
...(values.todo
|
||||||
onNext={handleNext}
|
? [
|
||||||
translate={translate}
|
{
|
||||||
/>
|
id: 6,
|
||||||
)}
|
title: translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(values.tree
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: translate('::ListForms.ListFormEdit.TabTree') || 'Tree',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(values.gantt
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: translate('::ListForms.ListFormEdit.TabGantt') || 'Gantt',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(values.scheduler
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
title: translate('::ListForms.ListFormEdit.TabScheduler') || 'Scheduler',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{ id: 10, title: translate('::App.Platform.Deploy') || 'Deploy' },
|
||||||
|
]
|
||||||
|
|
||||||
{/* ─── Step 2: Data Settings ───────────────────────────── */}
|
const goToAdjacentStep = (direction: -1 | 1) => {
|
||||||
{currentStep === 1 && (
|
const currentIndex = activeSteps.findIndex((step) => step.id === currentStep)
|
||||||
<WizardStep2
|
const targetStep = activeSteps[currentIndex + direction]
|
||||||
values={values}
|
if (targetStep) {
|
||||||
errors={errors}
|
setCurrentStep(targetStep.id)
|
||||||
touched={touched}
|
}
|
||||||
isLoadingDataSource={isLoadingDataSource}
|
}
|
||||||
dataSourceList={dataSourceList}
|
|
||||||
isDataSourceNew={isDataSourceNew}
|
|
||||||
onDataSourceSelect={setCurrentDataSource}
|
|
||||||
onDataSourceNewChange={setIsDataSourceNew}
|
|
||||||
dbObjects={dbObjects}
|
|
||||||
isLoadingDbObjects={isLoadingDbObjects}
|
|
||||||
onDbObjectsRefresh={loadDbObjects}
|
|
||||||
selectCommandColumns={selectCommandColumns}
|
|
||||||
isLoadingColumns={isLoadingColumns}
|
|
||||||
selectedColumns={selectedColumns}
|
|
||||||
onLoadColumns={loadColumns}
|
|
||||||
onClearColumns={() => {
|
|
||||||
setSelectCommandColumns([])
|
|
||||||
setSelectedColumns(new Set())
|
|
||||||
}}
|
|
||||||
onToggleColumn={toggleColumn}
|
|
||||||
onToggleAllColumns={(all) => toggleAllColumns(all, values.isTenant)}
|
|
||||||
onTenantChange={handleTenantChange}
|
|
||||||
translate={translate}
|
|
||||||
onBack={handleBack}
|
|
||||||
onNext={handleNext2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ─── Step 3: List Form Fields ───────────────────────────── */}
|
return (
|
||||||
{currentStep === 2 && (
|
<>
|
||||||
<WizardStep3
|
<div className="mb-6 mt-2">
|
||||||
selectedColumns={selectedColumns}
|
<Steps
|
||||||
selectCommandColumns={selectCommandColumns}
|
current={activeSteps.findIndex((step) => step.id === currentStep) + 1}
|
||||||
groups={editingGroups}
|
className="flex flex-row flex-wrap !justify-start gap-y-2 lg:flex-nowrap lg:!justify-between"
|
||||||
onGroupsChange={setEditingGroups}
|
>
|
||||||
dbObjects={dbObjects}
|
<Steps.Item
|
||||||
dsCode={currentDataSource}
|
customIcon={<FaFolderOpen />}
|
||||||
translate={translate}
|
status="pending"
|
||||||
onBack={() => setCurrentStep(1)}
|
title={translate('::App.Listforms.WizardManager') || 'Wizard Manager'}
|
||||||
onNext={() => setCurrentStep(3)}
|
onStepChange={() =>
|
||||||
/>
|
navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizardManager)
|
||||||
)}
|
}
|
||||||
|
/>
|
||||||
|
{activeSteps.map((step) => (
|
||||||
|
<Steps.Item key={step.id} title={step.title} />
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* ─── Step 4: Sub Forms ───────────────────────────── */}
|
<Form onKeyDown={preventEnterSubmit}>
|
||||||
{currentStep === 3 && (
|
<FormContainer size={currentStep >= 2 ? undefined : 'sm'}>
|
||||||
<WizardStep4
|
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
|
||||||
subForms={subForms}
|
{currentStep === 0 && (
|
||||||
selectCommandColumns={selectCommandColumns}
|
<WizardStep1
|
||||||
translate={translate}
|
values={values}
|
||||||
onChange={setSubForms}
|
errors={errors}
|
||||||
onBack={() => setCurrentStep(2)}
|
touched={touched}
|
||||||
onNext={() => setCurrentStep(4)}
|
wizardName={values.wizardName}
|
||||||
/>
|
onWizardNameChange={handleWizardNameChange}
|
||||||
)}
|
rawMenuItems={rawMenuItems}
|
||||||
|
menuTree={menuTree}
|
||||||
|
isLoadingMenu={isLoadingMenu}
|
||||||
|
onMenuParentChange={handleMenuParentChange}
|
||||||
|
onClearMenuParent={() => {
|
||||||
|
formikRef.current?.setFieldValue('menuParentCode', '')
|
||||||
|
formikRef.current?.setFieldValue('menuParentShortName', '')
|
||||||
|
formikRef.current?.setFieldValue('menuParentIcon', '')
|
||||||
|
}}
|
||||||
|
onMenuCreated={handleMenuCreated}
|
||||||
|
onReloadMenu={getMenuList}
|
||||||
|
permissionGroupList={permissionGroupList}
|
||||||
|
isLoadingPermissionGroup={isLoadingPermissionGroup}
|
||||||
|
onNext={handleNext}
|
||||||
|
translate={translate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ─── Step 5: Widgets ───────────────────────────── */}
|
{/* ─── Step 2: Data Settings ───────────────────────────── */}
|
||||||
{currentStep === 4 && (
|
{currentStep === 1 && (
|
||||||
<WizardStep5
|
<WizardStep2
|
||||||
widgets={widgets}
|
values={values}
|
||||||
translate={translate}
|
errors={errors}
|
||||||
onChange={setWidgets}
|
touched={touched}
|
||||||
onBack={() => setCurrentStep(3)}
|
isLoadingDataSource={isLoadingDataSource}
|
||||||
onNext={() => setCurrentStep(5)}
|
dataSourceList={dataSourceList}
|
||||||
/>
|
isDataSourceNew={isDataSourceNew}
|
||||||
)}
|
onDataSourceSelect={setCurrentDataSource}
|
||||||
|
onDataSourceNewChange={setIsDataSourceNew}
|
||||||
|
dbObjects={dbObjects}
|
||||||
|
isLoadingDbObjects={isLoadingDbObjects}
|
||||||
|
onDbObjectsRefresh={loadDbObjects}
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
isLoadingColumns={isLoadingColumns}
|
||||||
|
selectedColumns={selectedColumns}
|
||||||
|
onLoadColumns={loadColumns}
|
||||||
|
onClearColumns={() => {
|
||||||
|
setSelectCommandColumns([])
|
||||||
|
setSelectedColumns(new Set())
|
||||||
|
}}
|
||||||
|
onToggleColumn={toggleColumn}
|
||||||
|
onToggleAllColumns={(all) => toggleAllColumns(all, values.isTenant)}
|
||||||
|
onTenantChange={handleTenantChange}
|
||||||
|
translate={translate}
|
||||||
|
onBack={handleBack}
|
||||||
|
onNext={handleNext2}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ─── Step 6: Workflow ───────────────────────────── */}
|
{/* ─── Step 3: List Form Fields ───────────────────────────── */}
|
||||||
{currentStep === 5 && (
|
{currentStep === 2 && (
|
||||||
<WizardStep6
|
<WizardStep3
|
||||||
listFormCode={values.listFormCode}
|
selectedColumns={selectedColumns}
|
||||||
workflow={workflow}
|
selectCommandColumns={selectCommandColumns}
|
||||||
criteria={criteria}
|
groups={editingGroups}
|
||||||
selectCommandColumns={selectCommandColumns}
|
onGroupsChange={setEditingGroups}
|
||||||
translate={translate}
|
dbObjects={dbObjects}
|
||||||
onWorkflowChange={setWorkflow}
|
dsCode={currentDataSource}
|
||||||
onCriteriaChange={setCriteria}
|
translate={translate}
|
||||||
onBack={() => setCurrentStep(4)}
|
onBack={() => setCurrentStep(1)}
|
||||||
onNext={() => setCurrentStep(6)}
|
onNext={() => setCurrentStep(3)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ─── Step 7: Todo / Kanban ────────────────────── */}
|
{/* ─── Step 4: Sub Forms ───────────────────────────── */}
|
||||||
{currentStep === 6 && (
|
{currentStep === 3 && (
|
||||||
<WizardStep7
|
<WizardStep4
|
||||||
selectedColumns={selectedColumns}
|
subForms={subForms}
|
||||||
selectCommandColumns={selectCommandColumns}
|
selectCommandColumns={selectCommandColumns}
|
||||||
translate={translate}
|
translate={translate}
|
||||||
onBack={() => setCurrentStep(5)}
|
onChange={setSubForms}
|
||||||
onNext={() => setCurrentStep(7)}
|
onBack={() => setCurrentStep(2)}
|
||||||
/>
|
onNext={() => setCurrentStep(4)}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ─── Step 8: Deploy ───────────────────────────── */}
|
{/* ─── Step 5: Widgets ───────────────────────────── */}
|
||||||
{currentStep === 7 && (
|
{currentStep === 4 && (
|
||||||
<WizardStep8
|
<WizardStep5
|
||||||
values={values}
|
widgets={widgets}
|
||||||
wizardName={values.wizardName}
|
translate={translate}
|
||||||
selectedColumns={selectedColumns}
|
onChange={setWidgets}
|
||||||
selectCommandColumns={selectCommandColumns}
|
onBack={() => setCurrentStep(3)}
|
||||||
groups={editingGroups}
|
onNext={() => setCurrentStep(5)}
|
||||||
subForms={subForms}
|
/>
|
||||||
widgets={widgets}
|
)}
|
||||||
workflow={workflow}
|
|
||||||
criteria={criteria}
|
{/* ─── Step 6: Workflow ───────────────────────────── */}
|
||||||
translate={translate}
|
{currentStep === 5 && (
|
||||||
onBack={() => setCurrentStep(6)}
|
<WizardStep6
|
||||||
onSubmit={handleDeploy}
|
listFormCode={values.listFormCode}
|
||||||
/>
|
workflow={workflow}
|
||||||
)}
|
criteria={criteria}
|
||||||
</FormContainer>
|
selectCommandColumns={selectCommandColumns}
|
||||||
</Form>
|
translate={translate}
|
||||||
)}
|
onWorkflowChange={setWorkflow}
|
||||||
|
onCriteriaChange={setCriteria}
|
||||||
|
onBack={() => setCurrentStep(4)}
|
||||||
|
onNext={() => goToAdjacentStep(1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Step 7: Todo / Kanban ────────────────────── */}
|
||||||
|
{currentStep === 6 && values.todo && (
|
||||||
|
<WizardStep7
|
||||||
|
selectedColumns={selectedColumns}
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
translate={translate}
|
||||||
|
onBack={() => goToAdjacentStep(-1)}
|
||||||
|
onNext={() => goToAdjacentStep(1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Step 8: Tree ─────────────────────────────── */}
|
||||||
|
{currentStep === 7 && values.tree && (
|
||||||
|
<WizardStep8
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
translate={translate}
|
||||||
|
onBack={() => goToAdjacentStep(-1)}
|
||||||
|
onNext={() => goToAdjacentStep(1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Step 9: Gantt ────────────────────────────── */}
|
||||||
|
{currentStep === 8 && values.gantt && (
|
||||||
|
<WizardStep9
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
translate={translate}
|
||||||
|
onBack={() => goToAdjacentStep(-1)}
|
||||||
|
onNext={() => goToAdjacentStep(1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Step 10: Scheduler ───────────────────────── */}
|
||||||
|
{currentStep === 9 && values.scheduler && (
|
||||||
|
<WizardStep10
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
translate={translate}
|
||||||
|
onBack={() => goToAdjacentStep(-1)}
|
||||||
|
onNext={() => goToAdjacentStep(1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Step 11: Deploy ──────────────────────────── */}
|
||||||
|
{currentStep === 10 && (
|
||||||
|
<WizardStep11
|
||||||
|
values={values}
|
||||||
|
wizardName={values.wizardName}
|
||||||
|
selectedColumns={selectedColumns}
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
groups={editingGroups}
|
||||||
|
subForms={subForms}
|
||||||
|
widgets={widgets}
|
||||||
|
workflow={workflow}
|
||||||
|
criteria={criteria}
|
||||||
|
translate={translate}
|
||||||
|
onBack={() => goToAdjacentStep(-1)}
|
||||||
|
onSubmit={handleDeploy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormContainer>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</Formik>
|
</Formik>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
163
ui/src/views/admin/listForm/wizard/WizardStep10.tsx
Normal file
163
ui/src/views/admin/listForm/wizard/WizardStep10.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { Button, Card, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
||||||
|
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
import type { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { Field, type FieldProps, useFormikContext } from 'formik'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
|
import { firstDayOfWeekOptions, schedulerViewOptions } from '../edit/options'
|
||||||
|
|
||||||
|
interface WizardStep10Props {
|
||||||
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
|
translate: (key: string) => string
|
||||||
|
onBack: () => void
|
||||||
|
onNext: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const WizardStep10 = ({ selectCommandColumns, translate, onBack, onNext }: WizardStep10Props) => {
|
||||||
|
const { values } = useFormikContext<ListFormWizardDto>()
|
||||||
|
const fieldOptions = useMemo<SelectBoxOption[]>(
|
||||||
|
() =>
|
||||||
|
selectCommandColumns.map((column) => ({
|
||||||
|
value: column.columnName,
|
||||||
|
label: column.columnName,
|
||||||
|
})),
|
||||||
|
[selectCommandColumns],
|
||||||
|
)
|
||||||
|
const option = values.schedulerOptionDto
|
||||||
|
const fieldMappings = [
|
||||||
|
['textExpr', '::ListForms.SchedulerOptions.TextExpr'],
|
||||||
|
['startDateExpr', '::ListForms.SchedulerOptions.StartDateExpr'],
|
||||||
|
['endDateExpr', '::ListForms.SchedulerOptions.EndDateExpr'],
|
||||||
|
['allDayExpr', '::ListForms.SchedulerOptions.AllDayExpr'],
|
||||||
|
['recurrenceRuleExpr', '::ListForms.SchedulerOptions.RecurrenceRuleExpr'],
|
||||||
|
['recurrenceExceptionExpr', '::ListForms.SchedulerOptions.RecurrenceExceptionExpr'],
|
||||||
|
] as const
|
||||||
|
const interactionFields = [
|
||||||
|
['showAllDayPanel', '::ListForms.SchedulerOptions.ShowAllDayPanel'],
|
||||||
|
['crossScrollingEnabled', '::ListForms.SchedulerOptions.CrossScrollingEnabled'],
|
||||||
|
['allowResizing', '::ListForms.SchedulerOptions.AllowResizing'],
|
||||||
|
['allowDragging', '::ListForms.SchedulerOptions.AllowDragging'],
|
||||||
|
['allowAdding', '::ListForms.SchedulerOptions.AllowAdding'],
|
||||||
|
['allowEditing', '::ListForms.ListFormEdit.AllowEditing'],
|
||||||
|
['allowDeleting', '::ListForms.SchedulerOptions.AllowDeleting'],
|
||||||
|
] as const
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pb-20">
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
<Card header={translate('::ListForms.SchedulerOptions.BasicSettings')}>
|
||||||
|
{fieldMappings.slice(0, 4).map(([name, translationKey]) => (
|
||||||
|
<FormItem key={name} label={translate(translationKey)}>
|
||||||
|
<Field name={`schedulerOptionDto.${name}`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable
|
||||||
|
options={fieldOptions}
|
||||||
|
value={fieldOptions.find((fieldOption) => fieldOption.value === option[name])}
|
||||||
|
onChange={(selected) => form.setFieldValue(field.name, selected?.value ?? '')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card header={translate('::ListForms.SchedulerOptions.ViewSettings')}>
|
||||||
|
<FormItem label={translate('::ListForms.SchedulerOptions.DefaultView')}>
|
||||||
|
<Field name="schedulerOptionDto.defaultView">
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={schedulerViewOptions}
|
||||||
|
value={schedulerViewOptions.find(
|
||||||
|
(viewOption) => viewOption.value === option.defaultView,
|
||||||
|
)}
|
||||||
|
onChange={(selected) => form.setFieldValue(field.name, selected?.value ?? 'week')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={translate('::ListForms.SchedulerOptions.FirstDayOfWeek')}>
|
||||||
|
<Field name="schedulerOptionDto.firstDayOfWeek">
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={firstDayOfWeekOptions}
|
||||||
|
value={firstDayOfWeekOptions.find(
|
||||||
|
(dayOption) => dayOption.value === option.firstDayOfWeek,
|
||||||
|
)}
|
||||||
|
onChange={(selected) => form.setFieldValue(field.name, selected?.value ?? 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
{[
|
||||||
|
['startDayHour', 0, 23],
|
||||||
|
['endDayHour', 0, 24],
|
||||||
|
['cellDuration', 5, undefined],
|
||||||
|
].map(([name, min, max]) => (
|
||||||
|
<FormItem
|
||||||
|
key={name}
|
||||||
|
label={translate(
|
||||||
|
`::ListForms.SchedulerOptions.${String(name).charAt(0).toUpperCase()}${String(name).slice(1)}`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
type="number"
|
||||||
|
name={`schedulerOptionDto.${name}`}
|
||||||
|
component={Input}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card header={translate('::ListForms.SchedulerOptions.RecurrenceSettings')}>
|
||||||
|
{fieldMappings.slice(4).map(([name, translationKey]) => (
|
||||||
|
<FormItem key={name} label={translate(translationKey)}>
|
||||||
|
<Field name={`schedulerOptionDto.${name}`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable
|
||||||
|
options={fieldOptions}
|
||||||
|
value={fieldOptions.find((fieldOption) => fieldOption.value === option[name])}
|
||||||
|
onChange={(selected) => form.setFieldValue(field.name, selected?.value ?? '')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card header={translate('::ListForms.SchedulerOptions.InteractionSettings')}>
|
||||||
|
{interactionFields.map(([name, translationKey]) => (
|
||||||
|
<FormItem key={name} label={translate(translationKey)}>
|
||||||
|
<Field name={`schedulerOptionDto.${name}`} component={Checkbox} />
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 type="button" size="sm" icon={<FaArrowLeft />} onClick={onBack}>
|
||||||
|
{translate('::Back')}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" size="sm" variant="solid" icon={<FaArrowRight />} onClick={onNext}>
|
||||||
|
{translate('::Next')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardStep10
|
||||||
779
ui/src/views/admin/listForm/wizard/WizardStep11.tsx
Normal file
779
ui/src/views/admin/listForm/wizard/WizardStep11.tsx
Normal file
|
|
@ -0,0 +1,779 @@
|
||||||
|
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 WizardStep11Props {
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── WizardStep11 ─────────────────────────────────────────────────────────────
|
||||||
|
const WizardStep11 = ({
|
||||||
|
values,
|
||||||
|
wizardName,
|
||||||
|
selectedColumns,
|
||||||
|
selectCommandColumns,
|
||||||
|
groups,
|
||||||
|
subForms,
|
||||||
|
widgets,
|
||||||
|
workflow,
|
||||||
|
criteria,
|
||||||
|
translate,
|
||||||
|
onBack,
|
||||||
|
onSubmit,
|
||||||
|
}: WizardStep11Props) => {
|
||||||
|
const [logs, setLogs] = useState<LogEntry[]>([])
|
||||||
|
const [isDeploying, setIsDeploying] = useState(false)
|
||||||
|
const [isDone, setIsDone] = useState(false)
|
||||||
|
const [hasError, setHasError] = useState(false)
|
||||||
|
|
||||||
|
const steps = buildLogSteps(values, groups, translate)
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
|
||||||
|
const runDeploy = async () => {
|
||||||
|
if (isDeploying) return
|
||||||
|
setIsDeploying(true)
|
||||||
|
setHasError(false)
|
||||||
|
setIsDone(false)
|
||||||
|
|
||||||
|
// Initialize all as pending
|
||||||
|
setLogs(steps.map((s) => ({ ...s, status: 'pending' })))
|
||||||
|
|
||||||
|
const setStatus = (id: number, status: LogStatus) =>
|
||||||
|
setLogs((prev) => prev.map((l) => (l.id === id ? { ...l, status } : l)))
|
||||||
|
|
||||||
|
// Steps 1-7: pre-deploy simulation (fast)
|
||||||
|
for (let i = 0; i < steps.length - 2; i++) {
|
||||||
|
const step = steps[i]
|
||||||
|
setStatus(step.id, 'running')
|
||||||
|
await sleep(300 + Math.random() * 200)
|
||||||
|
setStatus(step.id, 'success')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 8: actual API call
|
||||||
|
const deployStep = steps[steps.length - 2]
|
||||||
|
setStatus(deployStep.id, 'running')
|
||||||
|
try {
|
||||||
|
await onSubmit()
|
||||||
|
setStatus(deployStep.id, 'success')
|
||||||
|
await sleep(200)
|
||||||
|
// Step 9: done
|
||||||
|
const doneStep = steps[steps.length - 1]
|
||||||
|
setStatus(doneStep.id, 'running')
|
||||||
|
await sleep(300)
|
||||||
|
setStatus(doneStep.id, 'success')
|
||||||
|
setIsDone(true)
|
||||||
|
} catch (err: any) {
|
||||||
|
setStatus(deployStep.id, 'error')
|
||||||
|
setLogs((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
id: 999,
|
||||||
|
label: `${translate('::ListForms.Wizard.Step4.Error')}: ${
|
||||||
|
err?.message ?? translate('::App.Platform.Unknown')
|
||||||
|
}`,
|
||||||
|
status: 'error',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
setHasError(true)
|
||||||
|
} finally {
|
||||||
|
setIsDeploying(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
||||||
|
const editingFormFields = groups.flatMap((g) =>
|
||||||
|
g.items
|
||||||
|
.filter((item) => item.includeInEditingForm && item.fieldName !== values.keyFieldName)
|
||||||
|
.map((item) => ({ ...item, groupCaption: g.caption })),
|
||||||
|
)
|
||||||
|
const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.fieldName)))
|
||||||
|
const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col))
|
||||||
|
const hasWorkflowFields = Boolean(
|
||||||
|
workflow.approvalUserFieldName ||
|
||||||
|
workflow.approvalDateFieldName ||
|
||||||
|
workflow.approvalStatusFieldName ||
|
||||||
|
workflow.approvalDescriptionFieldName,
|
||||||
|
)
|
||||||
|
const workflowItems = criteria.length > 0 ? criteria : (workflow.criteria ?? [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-[3fr_2fr] gap-5 pb-24 items-start">
|
||||||
|
{/* ── Left: Summary ──────────────────────────────────────────── */}
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="grid grid-cols-2 gap-3 items-start">
|
||||||
|
<Section title={translate('::ListForms.Wizard.Step4.MenuInfo')}>
|
||||||
|
<Row label={translate('::ListForms.Wizard.Step4.WizardName')} value={wizardName} />
|
||||||
|
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.MenuParent')}
|
||||||
|
value={values.menuParentCode}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.PermissionGroup')}
|
||||||
|
value={values.permissionGroupName}
|
||||||
|
/>
|
||||||
|
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.MenuTr')}
|
||||||
|
value={values.languageTextMenuTr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.MenuEn')}
|
||||||
|
value={values.languageTextMenuEn}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.MenuParentTr')}
|
||||||
|
value={values.languageTextMenuParentTr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.MenuParentEn')}
|
||||||
|
value={values.languageTextMenuParentEn}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title={translate('::ListForms.Wizard.Step4.ListFormSettings')}>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.ListFormCode')}
|
||||||
|
value={values.listFormCode}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.TitleTr')}
|
||||||
|
value={values.languageTextTitleTr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.TitleEn')}
|
||||||
|
value={values.languageTextTitleEn}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.DescTr')}
|
||||||
|
value={values.languageTextDescTr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.DescEn')}
|
||||||
|
value={values.languageTextDescEn}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.DataSource')}
|
||||||
|
value={values.dataSourceCode}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::App.Listform.ListformField.ConnectionString')}
|
||||||
|
value={values.dataSourceConnectionString}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
|
||||||
|
value={`${values.selectCommand} (${
|
||||||
|
selectCommandTypeOptions.find((o: any) => o.value === values.selectCommandType)
|
||||||
|
?.label ?? String(values.selectCommandType)
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.Wizard.Step4.KeyField')}
|
||||||
|
value={`${values.keyFieldName} (${
|
||||||
|
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
|
||||||
|
?.label ?? String(values.keyFieldDbSourceType)
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title={
|
||||||
|
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
|
||||||
|
}
|
||||||
|
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
|
||||||
|
>
|
||||||
|
<div className="mb-3 grid grid-cols-3 gap-2">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.Wizard.Step4.SelectedColumns'),
|
||||||
|
value: selectedColumns.size,
|
||||||
|
className: 'text-indigo-600 dark:text-indigo-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||||
|
value: editingFormFields.length,
|
||||||
|
className: 'text-emerald-600 dark:text-emerald-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.Wizard.Step4.FormGroups'),
|
||||||
|
value: groups.length,
|
||||||
|
className: 'text-gray-700 dark:text-gray-200',
|
||||||
|
},
|
||||||
|
].map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.label}
|
||||||
|
className="rounded-lg border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className={`text-lg font-semibold leading-none ${item.className}`}>
|
||||||
|
{item.value}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-[11px] text-gray-400 truncate">{item.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{groups.map((g) => (
|
||||||
|
<div
|
||||||
|
key={g.id}
|
||||||
|
className="overflow-hidden rounded-lg border border-gray-100 dark:border-gray-800"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3 bg-gray-50 dark:bg-gray-800 px-3 py-2">
|
||||||
|
<span className="text-xs font-semibold text-gray-700 dark:text-gray-200 truncate">
|
||||||
|
{g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-1.5 text-[10px] text-gray-400 shrink-0">
|
||||||
|
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
||||||
|
{g.items.length} {translate('::ListForms.Wizard.Step4.StatField')}
|
||||||
|
</span>
|
||||||
|
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
||||||
|
{g.colCount} {translate('::App.Listform.ListformField.Column')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
|
{g.items.length === 0 ? (
|
||||||
|
<span className="block px-3 py-2 text-xs text-gray-300 italic">
|
||||||
|
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
g.items.map((item) => {
|
||||||
|
const meta = selectCommandColumns.find((c) => c.columnName === item.fieldName)
|
||||||
|
const isKeyField = item.fieldName === values.keyFieldName
|
||||||
|
const isPopupField = item.includeInEditingForm && !isKeyField
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="grid grid-cols-[minmax(150px,1fr)_auto] items-center gap-3 px-3 py-1.5"
|
||||||
|
>
|
||||||
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
|
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400 truncate">
|
||||||
|
{item.fieldName}
|
||||||
|
</span>
|
||||||
|
{meta?.dataType && (
|
||||||
|
<span className="text-[10px] text-gray-400 truncate">
|
||||||
|
{meta.dataType}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap justify-end gap-1">
|
||||||
|
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
|
||||||
|
</span>
|
||||||
|
{isPopupField && (
|
||||||
|
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isKeyField && (
|
||||||
|
<span className="rounded bg-amber-50 dark:bg-amber-900/30 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.KeyField')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
||||||
|
{item.editorType}
|
||||||
|
</span>
|
||||||
|
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
||||||
|
span-{item.colSpan}
|
||||||
|
{item.isRequired && <span className="ml-1 text-red-400">*</span>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{ungroupedSelectedColumns.length > 0 && (
|
||||||
|
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
|
||||||
|
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{ungroupedSelectedColumns.map((col) => {
|
||||||
|
const meta = selectCommandColumns.find((c) => c.columnName === col)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={col}
|
||||||
|
className="inline-flex items-center gap-1 rounded-full border border-amber-200 dark:border-amber-800 bg-white dark:bg-gray-900 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300"
|
||||||
|
>
|
||||||
|
{col}
|
||||||
|
{meta?.dataType && (
|
||||||
|
<span className="text-[10px] text-amber-500 opacity-70">
|
||||||
|
{meta.dataType}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{subForms.length > 0 && (
|
||||||
|
<Section
|
||||||
|
title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'}
|
||||||
|
badge={subForms.length}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{subForms.map((subForm, index) => (
|
||||||
|
<div
|
||||||
|
key={`${subForm.code}-${index}`}
|
||||||
|
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
|
||||||
|
>
|
||||||
|
<Row label="Tab Title" value={subForm.tabTitle} />
|
||||||
|
<Row label="List Form Code" value={subForm.code} />
|
||||||
|
<Row label="Tab Type" value={subForm.tabType} />
|
||||||
|
<Row label="Refresh" value={subForm.isRefresh ? 'Active' : 'Inactive'} />
|
||||||
|
{(subForm.relation || []).length > 0 && (
|
||||||
|
<div className="pt-2">
|
||||||
|
<span className="text-xs text-gray-400">Relations</span>
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1.5">
|
||||||
|
{subForm.relation.map((relation, relationIndex) => (
|
||||||
|
<span
|
||||||
|
key={`${relation.parentFieldName}-${relation.childFieldName}-${relationIndex}`}
|
||||||
|
className="rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs text-gray-600 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
{relation.parentFieldName} : {relation.childFieldName}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{widgets.length > 0 && (
|
||||||
|
<Section
|
||||||
|
title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'}
|
||||||
|
badge={widgets.length}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{widgets.map((widget, index) => (
|
||||||
|
<div
|
||||||
|
key={`${widget.title}-${widget.value}-${index}`}
|
||||||
|
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
|
||||||
|
>
|
||||||
|
<Row label="Title Field" value={widget.title} />
|
||||||
|
<Row label="Value Field" value={widget.value} />
|
||||||
|
<Row label="Color Field" value={widget.color} />
|
||||||
|
<Row label="Icon Field" value={widget.icon} />
|
||||||
|
<Row label="Column Gap" value={widget.colGap} />
|
||||||
|
<Row label="Column Span" value={widget.colSpan} />
|
||||||
|
<Row label="Class Name" value={widget.className} />
|
||||||
|
<Row label="Value Class Name" value={widget.valueClassName} />
|
||||||
|
<Row label="Status" value={widget.isActive ? 'Active' : 'Inactive'} />
|
||||||
|
<Row label="Sql Query" value={widget.sqlQuery} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(hasWorkflowFields || workflowItems.length > 0) && (
|
||||||
|
<Section
|
||||||
|
title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'}
|
||||||
|
badge={workflowItems.length}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Row label="Approval User" value={workflow.approvalUserFieldName} />
|
||||||
|
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
|
||||||
|
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
|
||||||
|
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
|
||||||
|
<Row
|
||||||
|
label="Is Filter User Name?"
|
||||||
|
value={workflow.approvalIsFilterUserName === true ? 'Yes' : 'No'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{workflowItems.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{workflowItems.map((criteria, index) => (
|
||||||
|
<div
|
||||||
|
key={criteria.id || index}
|
||||||
|
className="rounded bg-gray-50 dark:bg-gray-800 px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
|
||||||
|
</div>
|
||||||
|
{criteria.kind === 'Compare' && (
|
||||||
|
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
{criteria.compareColumn} {criteria.compareOperator}{' '}
|
||||||
|
{criteria.compareValue}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
|
||||||
|
criteria.approver && (
|
||||||
|
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
Approver: {formatWorkflowApprovers(criteria.approver)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{values.todo && (
|
||||||
|
<Section title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'}>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoTitleExpr')}
|
||||||
|
value={values.todoOptionDto.titleExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoStatusExpr')}
|
||||||
|
value={values.todoOptionDto.statusExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoDescriptionExpr')}
|
||||||
|
value={values.todoOptionDto.descriptionExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoDueDateExpr')}
|
||||||
|
value={values.todoOptionDto.dueDateExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoTagExpr')}
|
||||||
|
value={values.todoOptionDto.tagExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoAssigneeExpr')}
|
||||||
|
value={values.todoOptionDto.assigneeExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoPriorityExpr')}
|
||||||
|
value={values.todoOptionDto.priorityExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoCompletedExpr')}
|
||||||
|
value={values.todoOptionDto.completedExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoOrderExpr')}
|
||||||
|
value={values.todoOptionDto.orderExpr}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}
|
||||||
|
value={values.todoOptionDto.statusOrder}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}
|
||||||
|
value={translate(
|
||||||
|
values.todoOptionDto.allowDragging
|
||||||
|
? '::App.Status.Active'
|
||||||
|
: '::App.Status.Inactive',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Right: Deploy ──────────────────────────────────────────── */}
|
||||||
|
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-8 gap-2">
|
||||||
|
{[
|
||||||
|
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
||||||
|
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||||
|
value: editingFormFields.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::App.Listform.ListformField.Column'),
|
||||||
|
value: selectedColumns.size,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
|
||||||
|
value: subForms.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
|
||||||
|
value: widgets.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
|
||||||
|
value: workflowItems.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.ListFormEdit.TabTodo') || 'Todo',
|
||||||
|
value: values.todo ? 1 : 0,
|
||||||
|
},
|
||||||
|
].map((s) => (
|
||||||
|
<div
|
||||||
|
key={s.label}
|
||||||
|
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3 text-center shadow-sm"
|
||||||
|
>
|
||||||
|
<div className="text-2xl font-bold text-indigo-600 dark:text-indigo-400">
|
||||||
|
{s.value}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Log panel — grows to fill remaining height */}
|
||||||
|
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1 min-h-0">
|
||||||
|
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0">
|
||||||
|
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
|
||||||
|
<FaRocket className="text-indigo-400 text-xs" />
|
||||||
|
{translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'}
|
||||||
|
</span>
|
||||||
|
{isDone && (
|
||||||
|
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
|
||||||
|
<FaCheckCircle /> {translate('::ListForms.Wizard.Step4.Success')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{hasError && (
|
||||||
|
<span className="text-xs text-red-500 font-semibold flex items-center gap-1">
|
||||||
|
<FaExclamationCircle /> {translate('::ListForms.Wizard.Step4.Error')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto p-4 bg-[#0d1117] dark:bg-black font-mono min-h-[360px]">
|
||||||
|
{logs.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full gap-3 py-10 select-none">
|
||||||
|
<FaRocket className="text-gray-700 text-3xl" />
|
||||||
|
<span className="text-xs text-gray-600 italic text-center">
|
||||||
|
{translate('::ListForms.Wizard.Step4.AllInfoReady')}
|
||||||
|
<br />
|
||||||
|
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{logs.map((log) => (
|
||||||
|
<div key={log.id} className="flex items-start gap-2.5">
|
||||||
|
<span className="mt-0.5 shrink-0">
|
||||||
|
<LogIcon status={log.status} />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
className={`text-xs leading-relaxed ${
|
||||||
|
log.status === 'success'
|
||||||
|
? 'text-emerald-400'
|
||||||
|
: log.status === 'error'
|
||||||
|
? 'text-red-400'
|
||||||
|
: log.status === 'running'
|
||||||
|
? 'text-yellow-300'
|
||||||
|
: 'text-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{log.label}
|
||||||
|
</span>
|
||||||
|
{log.detail && (
|
||||||
|
<div className="text-[10px] text-gray-600 mt-0.5">{log.detail}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{isDone && (
|
||||||
|
<div className="mt-4 rounded-lg border border-emerald-800 bg-emerald-950/40 px-4 py-2.5 text-xs text-emerald-400 text-center font-semibold">
|
||||||
|
🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Fixed Footer ─────────────────────────────────────────────── */}
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||||
|
<div className="flex items-center gap-3 w-full">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
type="button"
|
||||||
|
icon={<FaArrowLeft />}
|
||||||
|
disabled={isDeploying}
|
||||||
|
onClick={onBack}
|
||||||
|
>
|
||||||
|
{translate('::Back') || 'Back'}
|
||||||
|
</Button>
|
||||||
|
<div className="flex-1 flex items-center justify-end">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="solid"
|
||||||
|
type="button"
|
||||||
|
icon={<FaRocket />}
|
||||||
|
loading={isDeploying}
|
||||||
|
disabled={isDeploying || isDone}
|
||||||
|
onClick={runDeploy}
|
||||||
|
>
|
||||||
|
{isDeploying
|
||||||
|
? translate('::ListForms.Wizard.Step4.Log.Deploying')
|
||||||
|
: isDone
|
||||||
|
? `✓ ${translate('::ListForms.Wizard.Step4.Log.Completed')}`
|
||||||
|
: translate('::ListForms.Wizard.Step4.DeployAndSave')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardStep11
|
||||||
|
|
@ -6,7 +6,12 @@ import { Field, FieldProps, FormikErrors, FormikTouched, useFormikContext } from
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import CreatableSelect from 'react-select/creatable'
|
import CreatableSelect from 'react-select/creatable'
|
||||||
import { FaArrowLeft, FaArrowRight, FaPlus, FaTable } from 'react-icons/fa'
|
import { FaArrowLeft, FaArrowRight, FaPlus, FaTable } from 'react-icons/fa'
|
||||||
import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options'
|
import {
|
||||||
|
dbSourceTypeOptions,
|
||||||
|
listFormDefaultLayoutOptions,
|
||||||
|
selectCommandTypeOptions,
|
||||||
|
sqlDataTypeToDbType,
|
||||||
|
} from '../edit/options'
|
||||||
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
import SqlTableDesignerDialog from '@/views/developerKit/SqlTableDesignerDialog'
|
import SqlTableDesignerDialog from '@/views/developerKit/SqlTableDesignerDialog'
|
||||||
|
|
||||||
|
|
@ -265,44 +270,44 @@ const WizardStep2 = ({
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Select
|
<Select
|
||||||
field={field}
|
field={field}
|
||||||
form={form}
|
form={form}
|
||||||
isClearable
|
isClearable
|
||||||
isLoading={isLoadingDbObjects}
|
isLoading={isLoadingDbObjects}
|
||||||
options={grouped}
|
options={grouped}
|
||||||
placeholder={
|
placeholder={
|
||||||
isLoadingDbObjects
|
isLoadingDbObjects
|
||||||
? translate('::App.Loading')
|
? translate('::App.Loading')
|
||||||
: translate('::ListForms.Wizard.Step2.SelectCommand') ||
|
: translate('::ListForms.Wizard.Step2.SelectCommand') ||
|
||||||
'Tablo/View/SP seç veya SQL yaz…'
|
'Tablo/View/SP seç veya SQL yaz…'
|
||||||
}
|
}
|
||||||
value={field.value ? { label: field.value, value: field.value } : null}
|
value={field.value ? { label: field.value, value: field.value } : null}
|
||||||
onChange={(option: any) => {
|
onChange={(option: any) => {
|
||||||
if (!option) {
|
if (!option) {
|
||||||
form.setFieldValue(field.name, '')
|
form.setFieldValue(field.name, '')
|
||||||
onClearColumns()
|
onClearColumns()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
form.setFieldValue(field.name, option.value)
|
form.setFieldValue(field.name, option.value)
|
||||||
const type = option.__isNew__
|
const type = option.__isNew__
|
||||||
? SelectCommandTypeEnum.Query
|
? SelectCommandTypeEnum.Query
|
||||||
: (option.__type ?? SelectCommandTypeEnum.Query)
|
: (option.__type ?? SelectCommandTypeEnum.Query)
|
||||||
form.setFieldValue('selectCommandType', type)
|
form.setFieldValue('selectCommandType', type)
|
||||||
form.setFieldValue('keyFieldName', '')
|
form.setFieldValue('keyFieldName', '')
|
||||||
form.setFieldTouched('keyFieldName', false)
|
form.setFieldTouched('keyFieldName', false)
|
||||||
if (!option.__isNew__ && option.__schema != null && option.__rawName) {
|
if (!option.__isNew__ && option.__schema != null && option.__rawName) {
|
||||||
onLoadColumns(values.dataSourceCode, option.__schema, option.__rawName)
|
onLoadColumns(values.dataSourceCode, option.__schema, option.__rawName)
|
||||||
} else {
|
} else {
|
||||||
onClearColumns()
|
onClearColumns()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onCreateOption={(inputValue: string) => {
|
onCreateOption={(inputValue: string) => {
|
||||||
form.setFieldValue(field.name, inputValue)
|
form.setFieldValue(field.name, inputValue)
|
||||||
form.setFieldValue('selectCommandType', SelectCommandTypeEnum.Query)
|
form.setFieldValue('selectCommandType', SelectCommandTypeEnum.Query)
|
||||||
form.setFieldValue('keyFieldName', '')
|
form.setFieldValue('keyFieldName', '')
|
||||||
form.setFieldTouched('keyFieldName', false)
|
form.setFieldTouched('keyFieldName', false)
|
||||||
onClearColumns()
|
onClearColumns()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -473,7 +478,7 @@ const WizardStep2 = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
<div className="grid grid-cols-1 gap-x-6">
|
||||||
<div className="flex flex-wrap gap-6">
|
<div className="flex flex-wrap gap-6">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
|
||||||
|
|
@ -501,7 +506,7 @@ const WizardStep2 = ({
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-6">
|
<div className="grid grid-cols-8 gap-x-4">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
|
||||||
invalid={errors?.grid && touched?.grid}
|
invalid={errors?.grid && touched?.grid}
|
||||||
|
|
@ -616,116 +621,6 @@ const WizardStep2 = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Tree options (full-width, shown when defaultLayout = 'tree') ─── */}
|
|
||||||
{values.defaultLayout === 'tree' && (
|
|
||||||
<div className="flex flex-wrap gap-6 mt-2 p-4 rounded-lg border border-indigo-200 dark:border-indigo-800 bg-indigo-50 dark:bg-indigo-900/20">
|
|
||||||
<h6 className="w-full text-sm font-semibold text-indigo-700 dark:text-indigo-300 mb-1">
|
|
||||||
{translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout') || 'Tree Ayarları'}
|
|
||||||
</h6>
|
|
||||||
<FormItem
|
|
||||||
label={translate('::App.Listform.ListformField.KeyFieldName')}
|
|
||||||
className="min-w-[180px]"
|
|
||||||
>
|
|
||||||
<Field name="treeKeyExpr">
|
|
||||||
{({ field, form }: FieldProps) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable
|
|
||||||
options={selectCommandColumns.map((c) => ({ label: c.columnName, value: c.columnName }))}
|
|
||||||
value={values.treeKeyExpr ? { label: values.treeKeyExpr, value: values.treeKeyExpr } : null}
|
|
||||||
onChange={(opt: any) => form.setFieldValue(field.name, opt?.value ?? '')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem
|
|
||||||
label={translate('::ListForms.ListFormEdit.ParentIdExpr')}
|
|
||||||
className="min-w-[180px]"
|
|
||||||
>
|
|
||||||
<Field name="treeParentIdExpr">
|
|
||||||
{({ field, form }: FieldProps) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable
|
|
||||||
options={selectCommandColumns.map((c) => ({ label: c.columnName, value: c.columnName }))}
|
|
||||||
value={values.treeParentIdExpr ? { label: values.treeParentIdExpr, value: values.treeParentIdExpr } : null}
|
|
||||||
onChange={(opt: any) => form.setFieldValue(field.name, opt?.value ?? '')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem label={translate('::ListForms.ListFormEdit.AutoExpandAll')}>
|
|
||||||
<Field name="treeAutoExpandAll" component={Checkbox} />
|
|
||||||
</FormItem>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── Gantt options (full-width, shown when defaultLayout = 'gantt') ─── */}
|
|
||||||
{values.defaultLayout === 'gantt' && (
|
|
||||||
<div className="flex flex-wrap gap-6 mt-2 p-4 rounded-lg border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/20">
|
|
||||||
<h6 className="w-full text-sm font-semibold text-amber-700 dark:text-amber-300 mb-1">
|
|
||||||
{translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout') || 'Gantt Ayarları'}
|
|
||||||
</h6>
|
|
||||||
{[
|
|
||||||
{ name: 'ganttKeyExpr', label: translate('::App.Listform.ListformField.KeyFieldName') },
|
|
||||||
{ name: 'ganttParentIdExpr', label: translate('::ListForms.ListFormEdit.ParentIdExpr') },
|
|
||||||
{ name: 'ganttTitleExpr', label: translate('::ListForms.ListFormEdit.TitleExpr') },
|
|
||||||
{ name: 'ganttStartExpr', label: translate('::ListForms.ListFormEdit.StartExpr') },
|
|
||||||
{ name: 'ganttEndExpr', label: translate('::ListForms.ListFormEdit.EndExpr') },
|
|
||||||
{ name: 'ganttProgressExpr', label: translate('::ListForms.ListFormEdit.ProgressExpr') },
|
|
||||||
].map(({ name, label }) => (
|
|
||||||
<FormItem key={name} label={label} className="min-w-[180px]">
|
|
||||||
<Field name={name}>
|
|
||||||
{({ field, form }: FieldProps) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable
|
|
||||||
options={selectCommandColumns.map((c) => ({ label: c.columnName, value: c.columnName }))}
|
|
||||||
value={(values as any)[name] ? { label: (values as any)[name], value: (values as any)[name] } : null}
|
|
||||||
onChange={(opt: any) => form.setFieldValue(field.name, opt?.value ?? '')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormItem>
|
|
||||||
))}
|
|
||||||
<FormItem label={translate('::ListForms.ListFormEdit.AutoExpandAll')}>
|
|
||||||
<Field name="ganttAutoExpandAll" component={Checkbox} />
|
|
||||||
</FormItem>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── Scheduler options (full-width, shown when defaultLayout = 'scheduler') ─── */}
|
|
||||||
{values.defaultLayout === 'scheduler' && (
|
|
||||||
<div className="flex flex-wrap gap-6 mt-2 p-4 rounded-lg border border-teal-200 dark:border-teal-800 bg-teal-50 dark:bg-teal-900/20">
|
|
||||||
<h6 className="w-full text-sm font-semibold text-teal-700 dark:text-teal-300 mb-1">
|
|
||||||
{translate('::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout') || 'Scheduler Ayarları'}
|
|
||||||
</h6>
|
|
||||||
{[
|
|
||||||
{ name: 'schedulerTextExpr', label: translate('::ListForms.SchedulerOptions.TextExpr') },
|
|
||||||
{ name: 'schedulerStartDateExpr', label: translate('::ListForms.SchedulerOptions.StartDateExpr') },
|
|
||||||
{ name: 'schedulerEndDateExpr', label: translate('::ListForms.SchedulerOptions.EndDateExpr') },
|
|
||||||
].map(({ name, label }) => (
|
|
||||||
<FormItem key={name} label={label} className="min-w-[180px]">
|
|
||||||
<Field name={name}>
|
|
||||||
{({ field, form }: FieldProps) => (
|
|
||||||
<Select
|
|
||||||
field={field}
|
|
||||||
form={form}
|
|
||||||
isClearable
|
|
||||||
options={selectCommandColumns.map((c) => ({ label: c.columnName, value: c.columnName }))}
|
|
||||||
value={(values as any)[name] ? { label: (values as any)[name], value: (values as any)[name] } : null}
|
|
||||||
onChange={(opt: any) => form.setFieldValue(field.name, opt?.value ?? '')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormItem>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
label={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
||||||
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
|
import type { TodoOptionDto } from '@/proxy/form/models'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import type { SelectBoxOption } from '@/types/shared'
|
import type { SelectBoxOption } from '@/types/shared'
|
||||||
import { Field, type FieldProps, useFormikContext } from 'formik'
|
import { Field, type FieldProps, useFormikContext } from 'formik'
|
||||||
|
|
@ -15,51 +16,42 @@ interface WizardStep7Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappings: Array<{
|
const mappings: Array<{
|
||||||
name:
|
name: keyof TodoOptionDto
|
||||||
| 'todoTitleExpr'
|
|
||||||
| 'todoStatusExpr'
|
|
||||||
| 'todoDescriptionExpr'
|
|
||||||
| 'todoDueDateExpr'
|
|
||||||
| 'todoTagExpr'
|
|
||||||
| 'todoAssigneeExpr'
|
|
||||||
| 'todoPriorityExpr'
|
|
||||||
| 'todoCompletedExpr'
|
|
||||||
| 'todoOrderExpr'
|
|
||||||
translationKey: string
|
translationKey: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
name: 'todoTitleExpr',
|
name: 'titleExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoTitleExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoTitleExpr',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'todoStatusExpr',
|
name: 'statusExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoStatusExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoStatusExpr',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'todoDescriptionExpr',
|
name: 'descriptionExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoDescriptionExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoDescriptionExpr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'todoDueDateExpr',
|
name: 'dueDateExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoDueDateExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoDueDateExpr',
|
||||||
},
|
},
|
||||||
{ name: 'todoTagExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoTagExpr' },
|
{ name: 'tagExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoTagExpr' },
|
||||||
{
|
{
|
||||||
name: 'todoAssigneeExpr',
|
name: 'assigneeExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoAssigneeExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoAssigneeExpr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'todoPriorityExpr',
|
name: 'priorityExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoPriorityExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoPriorityExpr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'todoCompletedExpr',
|
name: 'completedExpr',
|
||||||
translationKey: '::ListForms.ListFormFieldEdit.TodoCompletedExpr',
|
translationKey: '::ListForms.ListFormFieldEdit.TodoCompletedExpr',
|
||||||
},
|
},
|
||||||
{ name: 'todoOrderExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoOrderExpr' },
|
{ name: 'orderExpr', translationKey: '::ListForms.ListFormFieldEdit.TodoOrderExpr' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const WizardStep7 = ({
|
const WizardStep7 = ({
|
||||||
|
|
@ -69,8 +61,7 @@ const WizardStep7 = ({
|
||||||
onBack,
|
onBack,
|
||||||
onNext,
|
onNext,
|
||||||
}: WizardStep7Props) => {
|
}: WizardStep7Props) => {
|
||||||
const { errors, touched, values, setFieldTouched, setFieldValue, validateForm } =
|
const { values, setFieldValue } = useFormikContext<ListFormWizardDto>()
|
||||||
useFormikContext<ListFormWizardDto>()
|
|
||||||
const [validationError, setValidationError] = useState('')
|
const [validationError, setValidationError] = useState('')
|
||||||
const fieldOptions = useMemo<SelectBoxOption[]>(
|
const fieldOptions = useMemo<SelectBoxOption[]>(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -80,14 +71,11 @@ const WizardStep7 = ({
|
||||||
[selectCommandColumns, selectedColumns],
|
[selectCommandColumns, selectedColumns],
|
||||||
)
|
)
|
||||||
|
|
||||||
const continueToDeploy = async () => {
|
const continueToDeploy = () => {
|
||||||
await Promise.all([
|
if (
|
||||||
setFieldTouched('todoTitleExpr', true, false),
|
values.todo &&
|
||||||
setFieldTouched('todoStatusExpr', true, false),
|
(!values.todoOptionDto.titleExpr?.trim() || !values.todoOptionDto.statusExpr?.trim())
|
||||||
])
|
) {
|
||||||
const validationErrors = await validateForm()
|
|
||||||
|
|
||||||
if (validationErrors.todoTitleExpr || validationErrors.todoStatusExpr) {
|
|
||||||
setValidationError(translate('::ListForms.ListFormFieldEdit.TodoRequiredFields'))
|
setValidationError(translate('::ListForms.ListFormFieldEdit.TodoRequiredFields'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -105,17 +93,19 @@ const WizardStep7 = ({
|
||||||
<FormItem
|
<FormItem
|
||||||
key={mapping.name}
|
key={mapping.name}
|
||||||
label={`${translate(mapping.translationKey)}${isRequired ? ' *' : ''}`}
|
label={`${translate(mapping.translationKey)}${isRequired ? ' *' : ''}`}
|
||||||
invalid={Boolean(touched[mapping.name] && errors[mapping.name])}
|
invalid={Boolean(validationError && mapping.required)}
|
||||||
errorMessage={touched[mapping.name] ? errors[mapping.name] : undefined}
|
errorMessage={mapping.required ? validationError : undefined}
|
||||||
>
|
>
|
||||||
<Field name={mapping.name}>
|
<Field name={`todoOptionDto.${mapping.name}`}>
|
||||||
{({ field, form }: FieldProps) => (
|
{({ field, form }: FieldProps) => (
|
||||||
<Select
|
<Select
|
||||||
field={field}
|
field={field}
|
||||||
form={form}
|
form={form}
|
||||||
isClearable={!isRequired}
|
isClearable={!isRequired}
|
||||||
options={fieldOptions}
|
options={fieldOptions}
|
||||||
value={fieldOptions.find((option) => option.value === values[mapping.name])}
|
value={fieldOptions.find(
|
||||||
|
(option) => option.value === values.todoOptionDto[mapping.name],
|
||||||
|
)}
|
||||||
onChange={(option) => {
|
onChange={(option) => {
|
||||||
form.setFieldValue(field.name, option?.value ?? '')
|
form.setFieldValue(field.name, option?.value ?? '')
|
||||||
setValidationError('')
|
setValidationError('')
|
||||||
|
|
@ -131,13 +121,13 @@ const WizardStep7 = ({
|
||||||
<div className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
|
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}>
|
||||||
<Field
|
<Field
|
||||||
name="todoStatusOrder"
|
name="todoOptionDto.statusOrder"
|
||||||
component={Input}
|
component={Input}
|
||||||
placeholder={translate('::ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder')}
|
placeholder={translate('::ListForms.ListFormFieldEdit.TodoStatusOrderPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}>
|
<FormItem label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}>
|
||||||
<Field name="todoAllowDragging">
|
<Field name="todoOptionDto.allowDragging">
|
||||||
{({ field }: FieldProps<boolean>) => (
|
{({ field }: FieldProps<boolean>) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={Boolean(field.value)}
|
checked={Boolean(field.value)}
|
||||||
|
|
|
||||||
|
|
@ -1,773 +1,84 @@
|
||||||
import { Button } from '@/components/ui'
|
import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
||||||
|
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import React, { useState } from 'react'
|
import type { SelectBoxOption } from '@/types/shared'
|
||||||
import {
|
import { Field, type FieldProps, useFormikContext } from 'formik'
|
||||||
FaArrowLeft,
|
import { useMemo } from 'react'
|
||||||
FaCheckCircle,
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
FaChevronDown,
|
|
||||||
FaChevronRight,
|
|
||||||
FaCircle,
|
|
||||||
FaExclamationCircle,
|
|
||||||
FaRocket,
|
|
||||||
FaSpinner,
|
|
||||||
} from 'react-icons/fa'
|
|
||||||
import { WizardGroup } from './WizardStep3'
|
|
||||||
import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options'
|
|
||||||
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
|
||||||
import {
|
|
||||||
ListFormWorkflowCriteriaDto,
|
|
||||||
SubFormDto,
|
|
||||||
WidgetEditDto,
|
|
||||||
WorkflowDto,
|
|
||||||
} from '@/proxy/form/models'
|
|
||||||
import { formatWorkflowApprovers } from '@/utils/workflow/workflowHelpers'
|
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
interface WizardStep8Props {
|
||||||
|
|
||||||
export interface WizardStep8Props {
|
|
||||||
values: ListFormWizardDto
|
|
||||||
wizardName: string
|
|
||||||
selectedColumns: Set<string>
|
|
||||||
selectCommandColumns: DatabaseColumnDto[]
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
groups: WizardGroup[]
|
|
||||||
subForms: SubFormDto[]
|
|
||||||
widgets: WidgetEditDto[]
|
|
||||||
workflow: WorkflowDto
|
|
||||||
criteria: ListFormWorkflowCriteriaDto[]
|
|
||||||
translate: (key: string) => string
|
translate: (key: string) => string
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onSubmit: () => Promise<void>
|
onNext: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogStatus = 'pending' | 'running' | 'success' | 'error'
|
const WizardStep8 = ({ selectCommandColumns, translate, onBack, onNext }: WizardStep8Props) => {
|
||||||
|
const { values } = useFormikContext<ListFormWizardDto>()
|
||||||
|
const fieldOptions = useMemo<SelectBoxOption[]>(
|
||||||
|
() =>
|
||||||
|
selectCommandColumns.map((column) => ({
|
||||||
|
value: column.columnName,
|
||||||
|
label: column.columnName,
|
||||||
|
})),
|
||||||
|
[selectCommandColumns],
|
||||||
|
)
|
||||||
|
|
||||||
interface LogEntry {
|
const selectFields = [
|
||||||
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,
|
name: 'treeOptionDto.keyExpr',
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.CreatingMenu', { 0: values.menuCode }),
|
value: values.treeOptionDto.keyExpr,
|
||||||
detail: `Parent: ${values.menuParentCode}`,
|
label: translate('::App.Listform.ListformField.KeyFieldName'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
name: 'treeOptionDto.parentIdExpr',
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.SavingLanguageTexts'),
|
value: values.treeOptionDto.parentIdExpr,
|
||||||
detail: `EN: ${values.languageTextMenuEn} / TR: ${values.languageTextMenuTr}`,
|
label: translate('::ListForms.ListFormEdit.ParentIdExpr'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.ConfiguringPermission', {
|
|
||||||
0: values.permissionGroupName,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.ConnectingDataSource', {
|
|
||||||
0: values.dataSourceCode,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.CreatingListForm', { 0: values.listFormCode }),
|
|
||||||
detail: `Key: ${values.keyFieldName}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
label: translate('::ListForms.Wizard.Step4.Log.SavingFormGroups', {
|
|
||||||
0: groups.length,
|
|
||||||
1: totalFields,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{ id: 8, label: translate('::ListForms.Wizard.Step4.Log.Deploying') },
|
|
||||||
{ id: 9, label: translate('::ListForms.Wizard.Step4.Log.Completed') },
|
|
||||||
]
|
]
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Mini-components ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function LogIcon({ status }: { status: LogStatus }) {
|
|
||||||
if (status === 'running') return <FaSpinner className="text-indigo-500 animate-spin shrink-0" />
|
|
||||||
if (status === 'success') return <FaCheckCircle className="text-emerald-500 shrink-0" />
|
|
||||||
if (status === 'error') return <FaExclamationCircle className="text-red-500 shrink-0" />
|
|
||||||
return <FaCircle className="text-gray-300 dark:text-gray-600 shrink-0 text-[8px] mt-1" />
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SectionProps {
|
|
||||||
title: string
|
|
||||||
badge?: string | number
|
|
||||||
children: React.ReactNode
|
|
||||||
defaultOpen?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function Section({ title, badge, children, defaultOpen = true }: SectionProps) {
|
|
||||||
const [open, setOpen] = useState(defaultOpen)
|
|
||||||
return (
|
|
||||||
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="plain"
|
|
||||||
shape="none"
|
|
||||||
className="w-full !h-auto !items-center !justify-between !rounded-none !bg-gray-50 !px-4 !py-2.5 transition-colors hover:!bg-gray-100 dark:!bg-gray-800 dark:hover:!bg-gray-700"
|
|
||||||
onClick={() => setOpen((v) => !v)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{open ? (
|
|
||||||
<FaChevronDown className="text-gray-400 text-xs" />
|
|
||||||
) : (
|
|
||||||
<FaChevronRight className="text-gray-400 text-xs" />
|
|
||||||
)}
|
|
||||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">{title}</span>
|
|
||||||
</div>
|
|
||||||
{badge !== undefined && (
|
|
||||||
<span className="text-xs bg-indigo-100 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded-full font-medium">
|
|
||||||
{badge}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{open && <div className="px-4 py-3 bg-white dark:bg-gray-900">{children}</div>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Row({ label, value }: { label: string; value?: string | number }) {
|
|
||||||
if (!value && value !== 0) return null
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2 py-1 border-b border-gray-100 dark:border-gray-800 last:border-0">
|
|
||||||
<span className="text-xs text-gray-400 w-40 shrink-0">{label}</span>
|
|
||||||
<span className="text-xs text-gray-700 dark:text-gray-200 font-medium break-all">
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── WizardStep8 ──────────────────────────────────────────────────────────────
|
|
||||||
const WizardStep8 = ({
|
|
||||||
values,
|
|
||||||
wizardName,
|
|
||||||
selectedColumns,
|
|
||||||
selectCommandColumns,
|
|
||||||
groups,
|
|
||||||
subForms,
|
|
||||||
widgets,
|
|
||||||
workflow,
|
|
||||||
criteria,
|
|
||||||
translate,
|
|
||||||
onBack,
|
|
||||||
onSubmit,
|
|
||||||
}: WizardStep8Props) => {
|
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([])
|
|
||||||
const [isDeploying, setIsDeploying] = useState(false)
|
|
||||||
const [isDone, setIsDone] = useState(false)
|
|
||||||
const [hasError, setHasError] = useState(false)
|
|
||||||
|
|
||||||
const steps = buildLogSteps(values, groups, translate)
|
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
||||||
|
|
||||||
const runDeploy = async () => {
|
|
||||||
if (isDeploying) return
|
|
||||||
setIsDeploying(true)
|
|
||||||
setHasError(false)
|
|
||||||
setIsDone(false)
|
|
||||||
|
|
||||||
// Initialize all as pending
|
|
||||||
setLogs(steps.map((s) => ({ ...s, status: 'pending' })))
|
|
||||||
|
|
||||||
const setStatus = (id: number, status: LogStatus) =>
|
|
||||||
setLogs((prev) => prev.map((l) => (l.id === id ? { ...l, status } : l)))
|
|
||||||
|
|
||||||
// Steps 1-7: pre-deploy simulation (fast)
|
|
||||||
for (let i = 0; i < steps.length - 2; i++) {
|
|
||||||
const step = steps[i]
|
|
||||||
setStatus(step.id, 'running')
|
|
||||||
await sleep(300 + Math.random() * 200)
|
|
||||||
setStatus(step.id, 'success')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 8: actual API call
|
|
||||||
const deployStep = steps[steps.length - 2]
|
|
||||||
setStatus(deployStep.id, 'running')
|
|
||||||
try {
|
|
||||||
await onSubmit()
|
|
||||||
setStatus(deployStep.id, 'success')
|
|
||||||
await sleep(200)
|
|
||||||
// Step 9: done
|
|
||||||
const doneStep = steps[steps.length - 1]
|
|
||||||
setStatus(doneStep.id, 'running')
|
|
||||||
await sleep(300)
|
|
||||||
setStatus(doneStep.id, 'success')
|
|
||||||
setIsDone(true)
|
|
||||||
} catch (err: any) {
|
|
||||||
setStatus(deployStep.id, 'error')
|
|
||||||
setLogs((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
id: 999,
|
|
||||||
label: `${translate('::ListForms.Wizard.Step4.Error')}: ${
|
|
||||||
err?.message ?? translate('::App.Platform.Unknown')
|
|
||||||
}`,
|
|
||||||
status: 'error',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
setHasError(true)
|
|
||||||
} finally {
|
|
||||||
setIsDeploying(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
|
||||||
const editingFormFields = groups.flatMap((g) =>
|
|
||||||
g.items
|
|
||||||
.filter((item) => item.includeInEditingForm && item.fieldName !== values.keyFieldName)
|
|
||||||
.map((item) => ({ ...item, groupCaption: g.caption })),
|
|
||||||
)
|
|
||||||
const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.fieldName)))
|
|
||||||
const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col))
|
|
||||||
const hasWorkflowFields = Boolean(
|
|
||||||
workflow.approvalUserFieldName ||
|
|
||||||
workflow.approvalDateFieldName ||
|
|
||||||
workflow.approvalStatusFieldName ||
|
|
||||||
workflow.approvalDescriptionFieldName,
|
|
||||||
)
|
|
||||||
const workflowItems = criteria.length > 0 ? criteria : (workflow.criteria ?? [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-[3fr_2fr] gap-5 pb-24 items-start">
|
<div className="pb-20">
|
||||||
{/* ── Left: Summary ──────────────────────────────────────────── */}
|
<div className="grid grid-cols-1 gap-x-6 md:grid-cols-2">
|
||||||
<div className="flex flex-col gap-3">
|
{selectFields.map((item) => (
|
||||||
<div className="grid grid-cols-2 gap-3 items-start">
|
<FormItem key={item.name} label={item.label}>
|
||||||
<Section title={translate('::ListForms.Wizard.Step4.MenuInfo')}>
|
<Field name={item.name}>
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.WizardName')} value={wizardName} />
|
{({ field, form }: FieldProps) => (
|
||||||
<Row label={translate('::ListForms.Wizard.Step4.MenuCode')} value={values.menuCode} />
|
<Select
|
||||||
<Row
|
field={field}
|
||||||
label={translate('::ListForms.Wizard.Step4.MenuParent')}
|
form={form}
|
||||||
value={values.menuParentCode}
|
isClearable
|
||||||
/>
|
options={fieldOptions}
|
||||||
<Row
|
value={fieldOptions.find((option) => option.value === item.value)}
|
||||||
label={translate('::ListForms.Wizard.Step4.PermissionGroup')}
|
onChange={(option) => form.setFieldValue(field.name, option?.value ?? '')}
|
||||||
value={values.permissionGroupName}
|
|
||||||
/>
|
|
||||||
<Row label={translate('::App.Listform.ListformField.Icon')} value={values.menuIcon} />
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.MenuTr')}
|
|
||||||
value={values.languageTextMenuTr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.MenuEn')}
|
|
||||||
value={values.languageTextMenuEn}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.MenuParentTr')}
|
|
||||||
value={values.languageTextMenuParentTr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.MenuParentEn')}
|
|
||||||
value={values.languageTextMenuParentEn}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section title={translate('::ListForms.Wizard.Step4.ListFormSettings')}>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.ListFormCode')}
|
|
||||||
value={values.listFormCode}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.TitleTr')}
|
|
||||||
value={values.languageTextTitleTr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.TitleEn')}
|
|
||||||
value={values.languageTextTitleEn}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.DescTr')}
|
|
||||||
value={values.languageTextDescTr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.DescEn')}
|
|
||||||
value={values.languageTextDescEn}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.DataSource')}
|
|
||||||
value={values.dataSourceCode}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::App.Listform.ListformField.ConnectionString')}
|
|
||||||
value={values.dataSourceConnectionString}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
|
|
||||||
value={`${values.selectCommand} (${
|
|
||||||
selectCommandTypeOptions.find((o: any) => o.value === values.selectCommandType)
|
|
||||||
?.label ?? String(values.selectCommandType)
|
|
||||||
})`}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.KeyField')}
|
|
||||||
value={`${values.keyFieldName} (${
|
|
||||||
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
|
|
||||||
?.label ?? String(values.keyFieldDbSourceType)
|
|
||||||
})`}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Section
|
|
||||||
title={
|
|
||||||
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
|
|
||||||
}
|
|
||||||
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
|
|
||||||
>
|
|
||||||
<div className="mb-3 grid grid-cols-3 gap-2">
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.Wizard.Step4.SelectedColumns'),
|
|
||||||
value: selectedColumns.size,
|
|
||||||
className: 'text-indigo-600 dark:text-indigo-400',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
|
||||||
value: editingFormFields.length,
|
|
||||||
className: 'text-emerald-600 dark:text-emerald-400',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.Wizard.Step4.FormGroups'),
|
|
||||||
value: groups.length,
|
|
||||||
className: 'text-gray-700 dark:text-gray-200',
|
|
||||||
},
|
|
||||||
].map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.label}
|
|
||||||
className="rounded-lg border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 px-3 py-2"
|
|
||||||
>
|
|
||||||
<div className={`text-lg font-semibold leading-none ${item.className}`}>
|
|
||||||
{item.value}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-[11px] text-gray-400 truncate">{item.label}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{groups.map((g) => (
|
|
||||||
<div
|
|
||||||
key={g.id}
|
|
||||||
className="overflow-hidden rounded-lg border border-gray-100 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-3 bg-gray-50 dark:bg-gray-800 px-3 py-2">
|
|
||||||
<span className="text-xs font-semibold text-gray-700 dark:text-gray-200 truncate">
|
|
||||||
{g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
|
||||||
</span>
|
|
||||||
<div className="flex items-center gap-1.5 text-[10px] text-gray-400 shrink-0">
|
|
||||||
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
|
||||||
{g.items.length} {translate('::ListForms.Wizard.Step4.StatField')}
|
|
||||||
</span>
|
|
||||||
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
|
||||||
{g.colCount} {translate('::App.Listform.ListformField.Column')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
|
||||||
{g.items.length === 0 ? (
|
|
||||||
<span className="block px-3 py-2 text-xs text-gray-300 italic">
|
|
||||||
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
g.items.map((item) => {
|
|
||||||
const meta = selectCommandColumns.find((c) => c.columnName === item.fieldName)
|
|
||||||
const isKeyField = item.fieldName === values.keyFieldName
|
|
||||||
const isPopupField = item.includeInEditingForm && !isKeyField
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="grid grid-cols-[minmax(150px,1fr)_auto] items-center gap-3 px-3 py-1.5"
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
|
||||||
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400 truncate">
|
|
||||||
{item.fieldName}
|
|
||||||
</span>
|
|
||||||
{meta?.dataType && (
|
|
||||||
<span className="text-[10px] text-gray-400 truncate">
|
|
||||||
{meta.dataType}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap justify-end gap-1">
|
|
||||||
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
|
|
||||||
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
|
|
||||||
</span>
|
|
||||||
{isPopupField && (
|
|
||||||
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
|
|
||||||
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{isKeyField && (
|
|
||||||
<span className="rounded bg-amber-50 dark:bg-amber-900/30 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300">
|
|
||||||
{translate('::ListForms.Wizard.Step4.KeyField')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
|
||||||
{item.editorType}
|
|
||||||
</span>
|
|
||||||
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
|
||||||
span-{item.colSpan}
|
|
||||||
{item.isRequired && <span className="ml-1 text-red-400">*</span>}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{ungroupedSelectedColumns.length > 0 && (
|
|
||||||
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
|
|
||||||
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
|
|
||||||
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-1.5">
|
|
||||||
{ungroupedSelectedColumns.map((col) => {
|
|
||||||
const meta = selectCommandColumns.find((c) => c.columnName === col)
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={col}
|
|
||||||
className="inline-flex items-center gap-1 rounded-full border border-amber-200 dark:border-amber-800 bg-white dark:bg-gray-900 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300"
|
|
||||||
>
|
|
||||||
{col}
|
|
||||||
{meta?.dataType && (
|
|
||||||
<span className="text-[10px] text-amber-500 opacity-70">
|
|
||||||
{meta.dataType}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
{subForms.length > 0 && (
|
|
||||||
<Section
|
|
||||||
title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'}
|
|
||||||
badge={subForms.length}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{subForms.map((subForm, index) => (
|
|
||||||
<div
|
|
||||||
key={`${subForm.code}-${index}`}
|
|
||||||
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
|
|
||||||
>
|
|
||||||
<Row label="Tab Title" value={subForm.tabTitle} />
|
|
||||||
<Row label="List Form Code" value={subForm.code} />
|
|
||||||
<Row label="Tab Type" value={subForm.tabType} />
|
|
||||||
<Row label="Refresh" value={subForm.isRefresh ? 'Active' : 'Inactive'} />
|
|
||||||
{(subForm.relation || []).length > 0 && (
|
|
||||||
<div className="pt-2">
|
|
||||||
<span className="text-xs text-gray-400">Relations</span>
|
|
||||||
<div className="mt-1 flex flex-wrap gap-1.5">
|
|
||||||
{subForm.relation.map((relation, relationIndex) => (
|
|
||||||
<span
|
|
||||||
key={`${relation.parentFieldName}-${relation.childFieldName}-${relationIndex}`}
|
|
||||||
className="rounded bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs text-gray-600 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
{relation.parentFieldName} : {relation.childFieldName}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{widgets.length > 0 && (
|
|
||||||
<Section
|
|
||||||
title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'}
|
|
||||||
badge={widgets.length}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{widgets.map((widget, index) => (
|
|
||||||
<div
|
|
||||||
key={`${widget.title}-${widget.value}-${index}`}
|
|
||||||
className="rounded-lg border border-gray-100 dark:border-gray-800 px-3 py-2"
|
|
||||||
>
|
|
||||||
<Row label="Title Field" value={widget.title} />
|
|
||||||
<Row label="Value Field" value={widget.value} />
|
|
||||||
<Row label="Color Field" value={widget.color} />
|
|
||||||
<Row label="Icon Field" value={widget.icon} />
|
|
||||||
<Row label="Column Gap" value={widget.colGap} />
|
|
||||||
<Row label="Column Span" value={widget.colSpan} />
|
|
||||||
<Row label="Class Name" value={widget.className} />
|
|
||||||
<Row label="Value Class Name" value={widget.valueClassName} />
|
|
||||||
<Row label="Status" value={widget.isActive ? 'Active' : 'Inactive'} />
|
|
||||||
<Row label="Sql Query" value={widget.sqlQuery} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(hasWorkflowFields || workflowItems.length > 0) && (
|
|
||||||
<Section
|
|
||||||
title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'}
|
|
||||||
badge={workflowItems.length}
|
|
||||||
>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<Row label="Approval User" value={workflow.approvalUserFieldName} />
|
|
||||||
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
|
|
||||||
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
|
|
||||||
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
|
|
||||||
<Row
|
|
||||||
label="Is Filter User Name?"
|
|
||||||
value={workflow.approvalIsFilterUserName === true ? 'Yes' : 'No'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{workflowItems.length > 0 && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{workflowItems.map((criteria, index) => (
|
|
||||||
<div
|
|
||||||
key={criteria.id || index}
|
|
||||||
className="rounded bg-gray-50 dark:bg-gray-800 px-3 py-2"
|
|
||||||
>
|
|
||||||
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
|
||||||
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
|
|
||||||
</div>
|
|
||||||
{criteria.kind === 'Compare' && (
|
|
||||||
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
|
||||||
{criteria.compareColumn} {criteria.compareOperator}{' '}
|
|
||||||
{criteria.compareValue}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
|
|
||||||
criteria.approver && (
|
|
||||||
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
|
||||||
Approver: {formatWorkflowApprovers(criteria.approver)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Field>
|
||||||
</Section>
|
</FormItem>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
{values.todo && (
|
<FormItem label={translate('::ListForms.ListFormEdit.HasItemsExpr')}>
|
||||||
<Section title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'}>
|
<Field name="treeOptionDto.hasItemsExpr" component={Input} />
|
||||||
<div className="grid grid-cols-2 gap-x-4">
|
</FormItem>
|
||||||
<Row
|
<FormItem label={translate('::ListForms.ListFormEdit.RootValue')}>
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoTitleExpr')}
|
<Field name="treeOptionDto.rootValue" component={Input} />
|
||||||
value={values.todoTitleExpr}
|
</FormItem>
|
||||||
/>
|
<FormItem label={translate('::ListForms.ListFormEdit.AutoExpandAll')}>
|
||||||
<Row
|
<Field name="treeOptionDto.autoExpandAll" component={Checkbox} />
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoStatusExpr')}
|
</FormItem>
|
||||||
value={values.todoStatusExpr}
|
<FormItem label={translate('::ListForms.ListFormEdit.RecursiveSelection')}>
|
||||||
/>
|
<Field name="treeOptionDto.recursiveSelection" component={Checkbox} />
|
||||||
<Row
|
</FormItem>
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoDescriptionExpr')}
|
|
||||||
value={values.todoDescriptionExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoDueDateExpr')}
|
|
||||||
value={values.todoDueDateExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoTagExpr')}
|
|
||||||
value={values.todoTagExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoAssigneeExpr')}
|
|
||||||
value={values.todoAssigneeExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoPriorityExpr')}
|
|
||||||
value={values.todoPriorityExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoCompletedExpr')}
|
|
||||||
value={values.todoCompletedExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoOrderExpr')}
|
|
||||||
value={values.todoOrderExpr}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoStatusOrder')}
|
|
||||||
value={values.todoStatusOrder}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.ListFormFieldEdit.TodoAllowDragging')}
|
|
||||||
value={translate(
|
|
||||||
values.todoAllowDragging ? '::App.Status.Active' : '::App.Status.Inactive',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Right: Deploy ──────────────────────────────────────────── */}
|
<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="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
|
<div className="flex w-full items-center justify-between gap-3">
|
||||||
{/* Stats */}
|
<Button type="button" size="sm" icon={<FaArrowLeft />} onClick={onBack}>
|
||||||
<div className="grid grid-cols-8 gap-2">
|
{translate('::Back')}
|
||||||
{[
|
</Button>
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
<Button type="button" size="sm" variant="solid" icon={<FaArrowRight />} onClick={onNext}>
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
{translate('::Next')}
|
||||||
{
|
|
||||||
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
|
||||||
value: editingFormFields.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::App.Listform.ListformField.Column'),
|
|
||||||
value: selectedColumns.size,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
|
|
||||||
value: subForms.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
|
|
||||||
value: widgets.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
|
|
||||||
value: workflowItems.length,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: translate('::ListForms.ListFormEdit.TabTodo') || 'Todo',
|
|
||||||
value: values.todo ? 1 : 0,
|
|
||||||
},
|
|
||||||
].map((s) => (
|
|
||||||
<div
|
|
||||||
key={s.label}
|
|
||||||
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3 text-center shadow-sm"
|
|
||||||
>
|
|
||||||
<div className="text-2xl font-bold text-indigo-600 dark:text-indigo-400">
|
|
||||||
{s.value}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Log panel — grows to fill remaining height */}
|
|
||||||
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1 min-h-0">
|
|
||||||
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0">
|
|
||||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
|
|
||||||
<FaRocket className="text-indigo-400 text-xs" />
|
|
||||||
{translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'}
|
|
||||||
</span>
|
|
||||||
{isDone && (
|
|
||||||
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
|
|
||||||
<FaCheckCircle /> {translate('::ListForms.Wizard.Step4.Success')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{hasError && (
|
|
||||||
<span className="text-xs text-red-500 font-semibold flex items-center gap-1">
|
|
||||||
<FaExclamationCircle /> {translate('::ListForms.Wizard.Step4.Error')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-4 bg-[#0d1117] dark:bg-black font-mono min-h-[360px]">
|
|
||||||
{logs.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-3 py-10 select-none">
|
|
||||||
<FaRocket className="text-gray-700 text-3xl" />
|
|
||||||
<span className="text-xs text-gray-600 italic text-center">
|
|
||||||
{translate('::ListForms.Wizard.Step4.AllInfoReady')}
|
|
||||||
<br />
|
|
||||||
{translate('::ListForms.Wizard.Step4.DeployStartHint')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{logs.map((log) => (
|
|
||||||
<div key={log.id} className="flex items-start gap-2.5">
|
|
||||||
<span className="mt-0.5 shrink-0">
|
|
||||||
<LogIcon status={log.status} />
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className={`text-xs leading-relaxed ${
|
|
||||||
log.status === 'success'
|
|
||||||
? 'text-emerald-400'
|
|
||||||
: log.status === 'error'
|
|
||||||
? 'text-red-400'
|
|
||||||
: log.status === 'running'
|
|
||||||
? 'text-yellow-300'
|
|
||||||
: 'text-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{log.label}
|
|
||||||
</span>
|
|
||||||
{log.detail && (
|
|
||||||
<div className="text-[10px] text-gray-600 mt-0.5">{log.detail}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{isDone && (
|
|
||||||
<div className="mt-4 rounded-lg border border-emerald-800 bg-emerald-950/40 px-4 py-2.5 text-xs text-emerald-400 text-center font-semibold">
|
|
||||||
🎉 {translate('::ListForms.Wizard.Step4.DeploySuccess')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ── Fixed Footer ─────────────────────────────────────────────── */}
|
|
||||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
|
||||||
<div className="flex items-center gap-3 w-full">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="default"
|
|
||||||
type="button"
|
|
||||||
icon={<FaArrowLeft />}
|
|
||||||
disabled={isDeploying}
|
|
||||||
onClick={onBack}
|
|
||||||
>
|
|
||||||
{translate('::Back') || 'Back'}
|
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex-1 flex items-center justify-end">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="solid"
|
|
||||||
type="button"
|
|
||||||
icon={<FaRocket />}
|
|
||||||
loading={isDeploying}
|
|
||||||
disabled={isDeploying || isDone}
|
|
||||||
onClick={runDeploy}
|
|
||||||
>
|
|
||||||
{isDeploying
|
|
||||||
? translate('::ListForms.Wizard.Step4.Log.Deploying')
|
|
||||||
: isDone
|
|
||||||
? `✓ ${translate('::ListForms.Wizard.Step4.Log.Completed')}`
|
|
||||||
: translate('::ListForms.Wizard.Step4.DeployAndSave')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
126
ui/src/views/admin/listForm/wizard/WizardStep9.tsx
Normal file
126
ui/src/views/admin/listForm/wizard/WizardStep9.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { Button, Card, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
||||||
|
import type { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
import type { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { Field, type FieldProps, useFormikContext } from 'formik'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
|
import { gantScaleOptions } from '../edit/options'
|
||||||
|
|
||||||
|
interface WizardStep9Props {
|
||||||
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
|
translate: (key: string) => string
|
||||||
|
onBack: () => void
|
||||||
|
onNext: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const WizardStep9 = ({ selectCommandColumns, translate, onBack, onNext }: WizardStep9Props) => {
|
||||||
|
const { values } = useFormikContext<ListFormWizardDto>()
|
||||||
|
const fieldOptions = useMemo<SelectBoxOption[]>(
|
||||||
|
() =>
|
||||||
|
selectCommandColumns.map((column) => ({
|
||||||
|
value: column.columnName,
|
||||||
|
label: column.columnName,
|
||||||
|
})),
|
||||||
|
[selectCommandColumns],
|
||||||
|
)
|
||||||
|
const option = values.ganttOptionDto
|
||||||
|
const selectFields = [
|
||||||
|
['keyExpr', translate('::App.Listform.ListformField.KeyFieldName')],
|
||||||
|
['parentIdExpr', translate('::ListForms.ListFormEdit.ParentIdExpr')],
|
||||||
|
['titleExpr', translate('::ListForms.ListFormEdit.TitleExpr')],
|
||||||
|
['startExpr', translate('::ListForms.ListFormEdit.StartExpr')],
|
||||||
|
['endExpr', translate('::ListForms.ListFormEdit.EndExpr')],
|
||||||
|
['progressExpr', translate('::ListForms.ListFormEdit.ProgressExpr')],
|
||||||
|
] as const
|
||||||
|
const permissionFields = [
|
||||||
|
['allowEditing', '::ListForms.ListFormEdit.AllowEditing'],
|
||||||
|
['allowTaskAdding', '::ListForms.ListFormEdit.AllowTaskAdding'],
|
||||||
|
['allowTaskUpdating', '::ListForms.ListFormEdit.AllowTaskUpdating'],
|
||||||
|
['allowTaskDeleting', '::ListForms.ListFormEdit.AllowTaskDeleting'],
|
||||||
|
['allowDependencyAdding', '::ListForms.ListFormEdit.AllowDependencyAdding'],
|
||||||
|
['allowDependencyDeleting', '::ListForms.ListFormEdit.AllowDependencyDeleting'],
|
||||||
|
['allowResourceAdding', '::ListForms.ListFormEdit.AllowResourceAdding'],
|
||||||
|
['allowResourceDeleting', '::ListForms.ListFormEdit.AllowResourceDeleting'],
|
||||||
|
] as const
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pb-20">
|
||||||
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||||
|
<Card header={translate('::ListForms.SchedulerOptions.BasicSettings')}>
|
||||||
|
<div className="grid grid-cols-1 gap-x-5 md:grid-cols-2">
|
||||||
|
{selectFields.map(([name, label]) => (
|
||||||
|
<FormItem key={name} label={label}>
|
||||||
|
<Field name={`ganttOptionDto.${name}`}>
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable
|
||||||
|
options={fieldOptions}
|
||||||
|
value={fieldOptions.find((fieldOption) => fieldOption.value === option[name])}
|
||||||
|
onChange={(selected) => form.setFieldValue(field.name, selected?.value ?? '')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
<FormItem label={translate('::ListForms.ListFormEdit.HasItemsExpr')}>
|
||||||
|
<Field name="ganttOptionDto.hasItemsExpr" component={Input} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={translate('::ListForms.ListFormEdit.RootValue')}>
|
||||||
|
<Field name="ganttOptionDto.rootValue" component={Input} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={translate('::ListForms.ListFormEdit.ScaleType')}>
|
||||||
|
<Field name="ganttOptionDto.scaleType">
|
||||||
|
{({ field, form }: FieldProps) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable
|
||||||
|
options={gantScaleOptions}
|
||||||
|
value={gantScaleOptions.find(
|
||||||
|
(scaleOption) => scaleOption.value === option.scaleType,
|
||||||
|
)}
|
||||||
|
onChange={(selected) =>
|
||||||
|
form.setFieldValue(field.name, selected?.value ?? 'weeks')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={translate('::ListForms.ListFormEdit.AutoExpandAll')}>
|
||||||
|
<Field name="ganttOptionDto.autoExpandAll" component={Checkbox} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={translate('::ListForms.ListFormEdit.RecursiveSelection')}>
|
||||||
|
<Field name="ganttOptionDto.recursiveSelection" component={Checkbox} />
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card header={translate('::ListForms.ListFormEdit.AllowEditing')}>
|
||||||
|
<div className="grid grid-cols-1 gap-x-5 md:grid-cols-2">
|
||||||
|
{permissionFields.map(([name, translationKey]) => (
|
||||||
|
<FormItem key={name} label={translate(translationKey)}>
|
||||||
|
<Field name={`ganttOptionDto.${name}`} component={Checkbox} />
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 type="button" size="sm" icon={<FaArrowLeft />} onClick={onBack}>
|
||||||
|
{translate('::Back')}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" size="sm" variant="solid" icon={<FaArrowRight />} onClick={onNext}>
|
||||||
|
{translate('::Next')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardStep9
|
||||||
Loading…
Reference in a new issue