LisformWorkflow çalışması
This commit is contained in:
parent
7b0f4acced
commit
73cb479e50
22 changed files with 1058 additions and 753 deletions
|
|
@ -1,9 +1,8 @@
|
||||||
using System;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
|
||||||
|
|
||||||
public class WorkflowDto
|
public class WorkflowDto
|
||||||
{
|
{
|
||||||
public string ApprovalFieldName { get; set; }
|
public string ApprovalFieldName { get; set; }
|
||||||
public DateTime ApprovalDateFieldName { get; set; }
|
public string ApprovalDateFieldName { get; set; }
|
||||||
|
public string ApprovalStatusFieldName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
|
|
||||||
public class CreateUpdateListFormWorkflowCriteriaDto
|
public class CreateUpdateListFormWorkflowCriteriaDto
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public string Kind { get; set; }
|
public string Kind { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ public interface IListFormWorkflowAppService : IApplicationService
|
||||||
{
|
{
|
||||||
Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null);
|
||||||
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
||||||
Task DeleteCriteriaAsync(Guid id);
|
Task DeleteCriteriaAsync(string id);
|
||||||
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
|
|
||||||
public class ListFormWorkflowCriteriaDto : AuditedEntityDto<Guid>
|
public class ListFormWorkflowCriteriaDto : AuditedEntityDto<string>
|
||||||
{
|
{
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public string Kind { get; set; }
|
public string Kind { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,12 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService
|
public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService
|
||||||
{
|
{
|
||||||
private const string DefaultListFormCode = "workflow";
|
private const string DefaultListFormCode = "workflow";
|
||||||
|
private const string CriteriaIdPrefix = "N";
|
||||||
|
private const int CriteriaIdPadding = 3;
|
||||||
|
|
||||||
private readonly IRepository<ListFormWorkflow, Guid> criteriaRepository;
|
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
|
||||||
|
|
||||||
public ListFormWorkflowAppService(IRepository<ListFormWorkflow, Guid> criteriaRepository)
|
public ListFormWorkflowAppService(IRepository<ListFormWorkflow, string> criteriaRepository)
|
||||||
{
|
{
|
||||||
this.criteriaRepository = criteriaRepository;
|
this.criteriaRepository = criteriaRepository;
|
||||||
}
|
}
|
||||||
|
|
@ -43,10 +45,10 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
public async Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input)
|
public async Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input)
|
||||||
{
|
{
|
||||||
var code = NormalizeListFormCode(input.ListFormCode);
|
var code = NormalizeListFormCode(input.ListFormCode);
|
||||||
var isNew = !input.Id.HasValue || input.Id.Value == Guid.Empty;
|
var isNew = input.Id.IsNullOrWhiteSpace();
|
||||||
var criteria = isNew
|
var criteria = isNew
|
||||||
? new ListFormWorkflow(GuidGenerator.Create())
|
? new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
|
||||||
: await criteriaRepository.GetAsync(input.Id.Value);
|
: await criteriaRepository.GetAsync(input.Id);
|
||||||
|
|
||||||
if (!isNew && criteria.ListFormCode != code)
|
if (!isNew && criteria.ListFormCode != code)
|
||||||
{
|
{
|
||||||
|
|
@ -89,7 +91,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("criteria/{id}")]
|
[HttpDelete("criteria/{id}")]
|
||||||
public async Task DeleteCriteriaAsync(Guid id)
|
public async Task DeleteCriteriaAsync(string id)
|
||||||
{
|
{
|
||||||
var criteria = await criteriaRepository.GetAsync(id);
|
var criteria = await criteriaRepository.GetAsync(id);
|
||||||
await criteriaRepository.DeleteAsync(criteria, autoSave: true);
|
await criteriaRepository.DeleteAsync(criteria, autoSave: true);
|
||||||
|
|
@ -97,7 +99,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
var remaining = await criteriaRepository.GetListAsync(x => x.ListFormCode == criteria.ListFormCode);
|
var remaining = await criteriaRepository.GetListAsync(x => x.ListFormCode == criteria.ListFormCode);
|
||||||
foreach (var item in remaining)
|
foreach (var item in remaining)
|
||||||
{
|
{
|
||||||
var changed = ClearDeletedTarget(item, id.ToString());
|
var changed = ClearDeletedTarget(item, id);
|
||||||
if (changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
await criteriaRepository.UpdateAsync(item, autoSave: true);
|
await criteriaRepository.UpdateAsync(item, autoSave: true);
|
||||||
|
|
@ -117,30 +119,30 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
|
|
||||||
var start = await CreateCriteriaAsync(code, "Start", "İş Akışı Başlat", 80, 150);
|
var start = await CreateCriteriaAsync(code, "Start", "İş Akışı Başlat", 80, 150);
|
||||||
var compare = await CreateCriteriaAsync(code, "Compare", "Tutar kontrolü", 330, 130);
|
var compare = await CreateCriteriaAsync(code, "Compare", "Tutar kontrolü", 330, 130);
|
||||||
var approval = await CreateCriteriaAsync(code, "Approval", "Yönetici Onayı", 590, 60, "ayse.yilmaz");
|
var approval = await CreateCriteriaAsync(code, "Approval", "Yönetici Onayı", 590, 60, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
||||||
var inform = await CreateCriteriaAsync(code, "Inform", "Muhasebe Bilgilendirme", 590, 230, "muhasebe");
|
var inform = await CreateCriteriaAsync(code, "Inform", "Muhasebe Bilgilendirme", 590, 230, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
||||||
var end = await CreateCriteriaAsync(code, "End", "Akışı Bitir", 850, 150);
|
var end = await CreateCriteriaAsync(code, "End", "Akışı Bitir", 850, 150);
|
||||||
|
|
||||||
start.NextOnStart = compare.Id.ToString();
|
start.NextOnStart = compare.Id;
|
||||||
compare.NextOnTrue = approval.Id.ToString();
|
compare.NextOnTrue = approval.Id;
|
||||||
compare.NextOnFalse = inform.Id.ToString();
|
compare.NextOnFalse = inform.Id;
|
||||||
compare.CompareOutcomesJson = SerializeCompareOutcomes([
|
compare.CompareOutcomesJson = SerializeCompareOutcomes([
|
||||||
new CompareOutcomeDto
|
new CompareOutcomeDto
|
||||||
{
|
{
|
||||||
Label = "Onay gerekir",
|
Label = "Onay gerekir",
|
||||||
TargetId = approval.Id.ToString(),
|
TargetId = approval.Id,
|
||||||
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = ">", CompareValue = 5000 }]
|
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = ">", CompareValue = 5000 }]
|
||||||
},
|
},
|
||||||
new CompareOutcomeDto
|
new CompareOutcomeDto
|
||||||
{
|
{
|
||||||
Label = "Bilgilendir",
|
Label = "Bilgilendir",
|
||||||
TargetId = inform.Id.ToString(),
|
TargetId = inform.Id,
|
||||||
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = "<=", CompareValue = 5000 }]
|
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = "<=", CompareValue = 5000 }]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
approval.NextOnApprove = inform.Id.ToString();
|
approval.NextOnApprove = inform.Id;
|
||||||
approval.NextOnReject = end.Id.ToString();
|
approval.NextOnReject = end.Id;
|
||||||
inform.NextOnStart = end.Id.ToString();
|
inform.NextOnStart = end.Id;
|
||||||
|
|
||||||
await criteriaRepository.UpdateAsync(start, autoSave: true);
|
await criteriaRepository.UpdateAsync(start, autoSave: true);
|
||||||
await criteriaRepository.UpdateAsync(compare, autoSave: true);
|
await criteriaRepository.UpdateAsync(compare, autoSave: true);
|
||||||
|
|
@ -158,7 +160,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
int positionY,
|
int positionY,
|
||||||
string approver = "")
|
string approver = "")
|
||||||
{
|
{
|
||||||
var criteria = new ListFormWorkflow(GuidGenerator.Create())
|
var criteria = new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
|
||||||
{
|
{
|
||||||
ListFormCode = listFormCode,
|
ListFormCode = listFormCode,
|
||||||
Kind = kind,
|
Kind = kind,
|
||||||
|
|
@ -181,6 +183,43 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
return criteria;
|
return criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateNextCriteriaIdAsync()
|
||||||
|
{
|
||||||
|
var criteria = await criteriaRepository.GetListAsync();
|
||||||
|
var maxNumber = criteria
|
||||||
|
.Select(x => TryParseCriteriaIdNumber(x.Id))
|
||||||
|
.DefaultIfEmpty(0)
|
||||||
|
.Max();
|
||||||
|
|
||||||
|
var nextNumber = maxNumber + 1;
|
||||||
|
var nextId = FormatCriteriaId(nextNumber);
|
||||||
|
var existingIds = criteria.Select(x => x.Id).ToHashSet();
|
||||||
|
|
||||||
|
while (existingIds.Contains(nextId))
|
||||||
|
{
|
||||||
|
nextNumber++;
|
||||||
|
nextId = FormatCriteriaId(nextNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int TryParseCriteriaIdNumber(string id)
|
||||||
|
{
|
||||||
|
if (id.IsNullOrWhiteSpace() ||
|
||||||
|
!id.StartsWith(CriteriaIdPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.TryParse(id[CriteriaIdPrefix.Length..], out var number) ? number : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatCriteriaId(int number)
|
||||||
|
{
|
||||||
|
return $"{CriteriaIdPrefix}{number.ToString().PadLeft(CriteriaIdPadding, '0')}";
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ClearDeletedTarget(ListFormWorkflow criteria, string deletedId)
|
private static bool ClearDeletedTarget(ListFormWorkflow criteria, string deletedId)
|
||||||
{
|
{
|
||||||
var changed = false;
|
var changed = false;
|
||||||
|
|
|
||||||
|
|
@ -18673,6 +18673,78 @@
|
||||||
"key": "SuccessfullySaved",
|
"key": "SuccessfullySaved",
|
||||||
"en": "Successfully Saved",
|
"en": "Successfully Saved",
|
||||||
"tr": "Başarıyla Kaydedildi"
|
"tr": "Başarıyla Kaydedildi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.Criteria",
|
||||||
|
"en": "Criteria",
|
||||||
|
"tr": "Kriterler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaStart",
|
||||||
|
"en": "Start",
|
||||||
|
"tr": "Başlat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaCompare",
|
||||||
|
"en": "Compare",
|
||||||
|
"tr": "Karşılaştır"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaApproval",
|
||||||
|
"en": "Approval",
|
||||||
|
"tr": "Onaylanacak Kişi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaInform",
|
||||||
|
"en": "Information",
|
||||||
|
"tr": "Bilgilendirilecek Kişi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaEnd",
|
||||||
|
"en": "Finish",
|
||||||
|
"tr": "Akışı Bitir"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaTitleRule",
|
||||||
|
"en": "Title / Rule",
|
||||||
|
"tr": "Başlık / Kural"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.CriteriaConnections",
|
||||||
|
"en": "Connections",
|
||||||
|
"tr": "Bağlantılar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.WorkflowNoCriteria",
|
||||||
|
"en": "No criteria defined for the selected workflow.",
|
||||||
|
"tr": "Seçili iş akışı için açıklama kaydı yok."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalFieldName",
|
||||||
|
"en": "Approval Field Name",
|
||||||
|
"tr": "Onay Alanı Adı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalDateFieldName",
|
||||||
|
"en": "Approval Date Field Name",
|
||||||
|
"tr": "Onay Tarihi Alanı Adı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalStatusFieldName",
|
||||||
|
"en": "Approval Status Field Name",
|
||||||
|
"tr": "Onay Durumu Alanı Adı"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -3,13 +3,13 @@ using Volo.Abp.Domain.Entities;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
public class ListFormWorkflow : Entity<Guid>
|
public class ListFormWorkflow : Entity<string>
|
||||||
{
|
{
|
||||||
protected ListFormWorkflow()
|
protected ListFormWorkflow()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListFormWorkflow(Guid id) : base(id)
|
public ListFormWorkflow(string id) : base(id)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -487,6 +487,7 @@ public class PlatformDbContext :
|
||||||
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListFormWorkflow)), Prefix.DbSchema);
|
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListFormWorkflow)), Prefix.DbSchema);
|
||||||
b.ConfigureByConvention();
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
|
b.Property(x => x.Id).HasMaxLength(50);
|
||||||
b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64);
|
b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64);
|
||||||
b.Property(x => x.Kind).IsRequired().HasMaxLength(50);
|
b.Property(x => x.Kind).IsRequired().HasMaxLength(50);
|
||||||
b.Property(x => x.Title).IsRequired().HasMaxLength(250);
|
b.Property(x => x.Title).IsRequired().HasMaxLength(250);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260522200739_Initial")]
|
[Migration("20260523104659_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -3414,8 +3414,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<string>("Approver")
|
b.Property<string>("Approver")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
|
@ -1393,7 +1393,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
name: "Sas_H_ListFormWorkflow",
|
name: "Sas_H_ListFormWorkflow",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||||
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
||||||
|
|
@ -3411,8 +3411,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<string>("Approver")
|
b.Property<string>("Approver")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export const ListFormEditTabs = {
|
||||||
StateForm: 'state',
|
StateForm: 'state',
|
||||||
SubForm: 'subForm',
|
SubForm: 'subForm',
|
||||||
Widget: 'widget',
|
Widget: 'widget',
|
||||||
|
Workflow: 'workflow',
|
||||||
Fields: 'fields',
|
Fields: 'fields',
|
||||||
Customization: 'customization',
|
Customization: 'customization',
|
||||||
ExtraFilter: 'extraFilter',
|
ExtraFilter: 'extraFilter',
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,7 @@ export interface GridOptionsEditDto extends GridOptionsDto, Record<string, any>
|
||||||
formFieldsDefaultValueDto: FieldsDefaultValueDto[]
|
formFieldsDefaultValueDto: FieldsDefaultValueDto[]
|
||||||
widgetsJson?: string
|
widgetsJson?: string
|
||||||
widgetsDto: WidgetEditDto[]
|
widgetsDto: WidgetEditDto[]
|
||||||
|
workflowDto: WorkflowDto
|
||||||
extraFilterEditDto: ExtraFilterEditDto[]
|
extraFilterEditDto: ExtraFilterEditDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -905,6 +906,12 @@ export interface WidgetEditDto {
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WorkflowDto {
|
||||||
|
approvalFieldName: string
|
||||||
|
approvalDateFieldName: string
|
||||||
|
approvalStatusFieldName: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface LayoutDto {
|
export interface LayoutDto {
|
||||||
grid: boolean
|
grid: boolean
|
||||||
pivot: boolean
|
pivot: boolean
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,6 @@ export const operatorOptions = [">", ">=", "<", "<=", "=", "!="].map(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const columnOptions = ["Tutar", "Id"].map((value) => ({
|
|
||||||
value,
|
|
||||||
label: value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const kindIcon: Record<string, any> = {
|
export const kindIcon: Record<string, any> = {
|
||||||
Start: FiPlay as any,
|
Start: FiPlay as any,
|
||||||
Compare: FiGitBranch as any,
|
Compare: FiGitBranch as any,
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCrit
|
||||||
nextOnApprove: '',
|
nextOnApprove: '',
|
||||||
nextOnReject: '',
|
nextOnReject: '',
|
||||||
compareOutcomes:
|
compareOutcomes:
|
||||||
kind === 'Compare' ? [emptyCompareOutcome('Durum 1'), emptyCompareOutcome('Durum 2')] : [],
|
kind === 'Compare' ? [emptyCompareOutcome1('>5000'), emptyCompareOutcome2('<=5000')] : [],
|
||||||
positionX: 32,
|
positionX: 32,
|
||||||
positionY: 150,
|
positionY: 150,
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +375,7 @@ export function defaultTitle(kind: string) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyCompareOutcome(label = 'Durum'): CompareOutcomeDto {
|
export function emptyCompareOutcome1(label = 'Durum'): CompareOutcomeDto {
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
targetId: '',
|
targetId: '',
|
||||||
|
|
@ -383,6 +383,14 @@ export function emptyCompareOutcome(label = 'Durum'): CompareOutcomeDto {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emptyCompareOutcome2(label = 'Durum'): CompareOutcomeDto {
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
targetId: '',
|
||||||
|
conditions: [{ compareColumn: 'Tutar', compareOperator: '<=', compareValue: 5000 }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function toCompareOutcomeForm(
|
export function toCompareOutcomeForm(
|
||||||
outcome: Partial<CompareOutcomeDto> &
|
outcome: Partial<CompareOutcomeDto> &
|
||||||
Partial<WorkflowConditionDto> & {
|
Partial<WorkflowConditionDto> & {
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,11 @@ import FormFields from './form-fields/FormFields'
|
||||||
import { putListForms } from '@/services/admin/list-form.service'
|
import { putListForms } from '@/services/admin/list-form.service'
|
||||||
import { getRoles, getUsers } from '@/services/identity.service'
|
import { getRoles, getUsers } from '@/services/identity.service'
|
||||||
import { GridOptionsEditDto, ListFormCustomizationDto } from '@/proxy/form/models'
|
import { GridOptionsEditDto, ListFormCustomizationDto } from '@/proxy/form/models'
|
||||||
|
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
||||||
import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models'
|
import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models'
|
||||||
import { getListFormCustomizations } from '@/services/admin/list-form-customization.service'
|
import { getListFormCustomizations } from '@/services/admin/list-form-customization.service'
|
||||||
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import FormTabWidgets from './FormTabWidgets'
|
import FormTabWidgets from './FormTabWidgets'
|
||||||
|
|
@ -71,6 +74,8 @@ const FormEdit = () => {
|
||||||
const [langOptions, setLangOptions] = useState<SelectBoxOption[]>([])
|
const [langOptions, setLangOptions] = useState<SelectBoxOption[]>([])
|
||||||
const [roleList, setRoleList] = useState<SelectBoxOption[]>([])
|
const [roleList, setRoleList] = useState<SelectBoxOption[]>([])
|
||||||
const [userList, setUserList] = useState<SelectBoxOption[]>([])
|
const [userList, setUserList] = useState<SelectBoxOption[]>([])
|
||||||
|
const [workflowColumns, setWorkflowColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const [isLoadingWorkflowColumns, setIsLoadingWorkflowColumns] = useState(false)
|
||||||
|
|
||||||
const languages: LanguageInfo[] | undefined = useStoreState(
|
const languages: LanguageInfo[] | undefined = useStoreState(
|
||||||
(state) => state.abpConfig.config?.localization.languages,
|
(state) => state.abpConfig.config?.localization.languages,
|
||||||
|
|
@ -141,6 +146,51 @@ const FormEdit = () => {
|
||||||
refreshData()
|
refreshData()
|
||||||
}, [listFormCode])
|
}, [listFormCode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadWorkflowColumns = async () => {
|
||||||
|
const dataSourceCode = listFormValues?.dataSourceCode
|
||||||
|
const selectCommand = listFormValues?.selectCommand
|
||||||
|
|
||||||
|
if (!dataSourceCode || !selectCommand) {
|
||||||
|
setWorkflowColumns([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingWorkflowColumns(true)
|
||||||
|
try {
|
||||||
|
const objectsResponse = await sqlObjectManagerService.getAllObjects(dataSourceCode)
|
||||||
|
const objects = objectsResponse.data
|
||||||
|
const objectInfo = findSelectCommandObject(
|
||||||
|
objects,
|
||||||
|
selectCommand,
|
||||||
|
listFormValues.selectCommandType,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!objectInfo) {
|
||||||
|
setWorkflowColumns([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnsResponse = await sqlObjectManagerService.getTableColumns(
|
||||||
|
dataSourceCode,
|
||||||
|
objectInfo.schemaName,
|
||||||
|
objectInfo.objectName,
|
||||||
|
)
|
||||||
|
setWorkflowColumns(columnsResponse.data ?? [])
|
||||||
|
} catch {
|
||||||
|
setWorkflowColumns([])
|
||||||
|
} finally {
|
||||||
|
setIsLoadingWorkflowColumns(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWorkflowColumns()
|
||||||
|
}, [
|
||||||
|
listFormValues?.dataSourceCode,
|
||||||
|
listFormValues?.selectCommand,
|
||||||
|
listFormValues?.selectCommandType,
|
||||||
|
])
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
editType: string,
|
editType: string,
|
||||||
values: GridOptionsEditDto,
|
values: GridOptionsEditDto,
|
||||||
|
|
@ -189,9 +239,12 @@ const FormEdit = () => {
|
||||||
|
|
||||||
{/* SAĞ TARAF */}
|
{/* SAĞ TARAF */}
|
||||||
{listFormValues.isTenant && (
|
{listFormValues.isTenant && (
|
||||||
<Badge className='font-semibold' content="Bu bir MULTI TENANT form'dur, veri kaybı olmaması için, sorgularda TENANTID
|
<Badge
|
||||||
parametresini kullanmayı unutmayınız." innerClass="p-1 bg-red-50 text-red-500">
|
className="font-semibold"
|
||||||
</Badge>
|
content="Bu bir MULTI TENANT form'dur, veri kaybı olmaması için, sorgularda TENANTID
|
||||||
|
parametresini kullanmayı unutmayınız."
|
||||||
|
innerClass="p-1 bg-red-50 text-red-500"
|
||||||
|
></Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Tabs defaultValue="details" variant="underline">
|
<Tabs defaultValue="details" variant="underline">
|
||||||
|
|
@ -392,7 +445,13 @@ const FormEdit = () => {
|
||||||
<FormTabWidgets listFormCode={listFormCode} />
|
<FormTabWidgets listFormCode={listFormCode} />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="workflow" className="px-2">
|
<TabContent value="workflow" className="px-2">
|
||||||
<FormTabWorkflow listFormCode={listFormCode} />
|
<FormTabWorkflow
|
||||||
|
listFormCode={listFormCode}
|
||||||
|
userList={userList}
|
||||||
|
selectCommandColumns={workflowColumns}
|
||||||
|
isLoadingSelectCommandColumns={isLoadingWorkflowColumns}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value="fields" className="px-2">
|
<TabContent value="fields" className="px-2">
|
||||||
<FormFields
|
<FormFields
|
||||||
|
|
@ -451,4 +510,48 @@ const FormEdit = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSelectCommandObject(
|
||||||
|
objects: SqlObjectExplorerDto | undefined,
|
||||||
|
selectCommand: string,
|
||||||
|
selectCommandType?: SelectCommandTypeEnum,
|
||||||
|
) {
|
||||||
|
if (!objects || !selectCommand || selectCommandType === SelectCommandTypeEnum.Query) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectCommandType === SelectCommandTypeEnum.Table) {
|
||||||
|
const table = objects.tables.find((item) => item.tableName === selectCommand)
|
||||||
|
return table ? { schemaName: table.schemaName, objectName: table.tableName } : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectCommandType === SelectCommandTypeEnum.View) {
|
||||||
|
const view = objects.views.find((item) => item.objectName === selectCommand)
|
||||||
|
return view ? { schemaName: view.schemaName, objectName: view.objectName } : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectCommandType === SelectCommandTypeEnum.TableValuedFunction) {
|
||||||
|
const fn = objects.functions.find((item) => item.objectName === selectCommand)
|
||||||
|
return fn ? { schemaName: fn.schemaName, objectName: fn.objectName } : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectCommandType === SelectCommandTypeEnum.StoredProcedure) {
|
||||||
|
const sp = objects.storedProcedures.find((item) => item.objectName === selectCommand)
|
||||||
|
return sp ? { schemaName: sp.schemaName, objectName: sp.objectName } : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = objects.tables.find((item) => item.tableName === selectCommand)
|
||||||
|
if (table) return { schemaName: table.schemaName, objectName: table.tableName }
|
||||||
|
|
||||||
|
const view = objects.views.find((item) => item.objectName === selectCommand)
|
||||||
|
if (view) return { schemaName: view.schemaName, objectName: view.objectName }
|
||||||
|
|
||||||
|
const fn = objects.functions.find((item) => item.objectName === selectCommand)
|
||||||
|
if (fn) return { schemaName: fn.schemaName, objectName: fn.objectName }
|
||||||
|
|
||||||
|
const sp = objects.storedProcedures.find((item) => item.objectName === selectCommand)
|
||||||
|
if (sp) return { schemaName: sp.schemaName, objectName: sp.objectName }
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export default FormEdit
|
export default FormEdit
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,16 @@ import {
|
||||||
type WorkflowCriteriaForm,
|
type WorkflowCriteriaForm,
|
||||||
} from '@/utils/workflow/workflowHelpers'
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
|
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
import { DashboardShell } from '../workflow/DashboardShell'
|
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
|
||||||
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||||
|
import { Button, Card, FormContainer, FormItem, Input, Select } from '@/components/ui'
|
||||||
|
import { ListFormEditTabs } from '@/proxy/admin/list-form/options'
|
||||||
|
import { object, string } from 'yup'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
|
import { FormEditProps } from './FormEdit'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
|
||||||
type PendingLink = {
|
type PendingLink = {
|
||||||
sourceId: string
|
sourceId: string
|
||||||
|
|
@ -25,18 +34,33 @@ type DragEndEvent = {
|
||||||
delta: { x: number; y: number }
|
delta: { x: number; y: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
export function FormTabWorkflow(
|
||||||
|
props: FormEditProps & {
|
||||||
|
listFormCode: string
|
||||||
|
userList: SelectBoxOption[]
|
||||||
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
|
isLoadingSelectCommandColumns: boolean
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const columnOptions: SelectBoxOption[] = props.selectCommandColumns.length
|
||||||
|
? props.selectCommandColumns.map((column) => ({
|
||||||
|
value: column.columnName,
|
||||||
|
label: `${column.columnName} (${column.dataType})`,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
|
||||||
const [criteria, setCriteria] = useState<WorkflowCriteriaDto[]>([])
|
const [criteria, setCriteria] = useState<WorkflowCriteriaDto[]>([])
|
||||||
const [selectedId, setSelectedId] = useState('')
|
const [selectedId, setSelectedId] = useState('')
|
||||||
const [pendingLink, setPendingLink] = useState<PendingLink>(null)
|
const [pendingLink, setPendingLink] = useState<PendingLink>(null)
|
||||||
const [criteriaForm, setCriteriaForm] = useState<WorkflowCriteriaForm>(
|
const [criteriaForm, setCriteriaForm] = useState<WorkflowCriteriaForm>(
|
||||||
emptyCriteria('Start', listFormCode),
|
emptyCriteria('Start', props.listFormCode),
|
||||||
)
|
)
|
||||||
const [dragPreview, setDragPreview] = useState<DragPreview>(null)
|
const [dragPreview, setDragPreview] = useState<DragPreview>(null)
|
||||||
const [canvasZoom, setCanvasZoom] = useState(1)
|
const [canvasZoom, setCanvasZoom] = useState(1)
|
||||||
const [designerTab, setDesignerTab] = useState('flow')
|
const [designerTab, setDesignerTab] = useState('flow')
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
const canvasRef = useRef<HTMLDivElement | null>(null)
|
const canvasRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const currentCriteria = useMemo(() => criteria, [criteria])
|
const currentCriteria = useMemo(() => criteria, [criteria])
|
||||||
|
|
||||||
|
|
@ -46,10 +70,10 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const loadState = useCallback(async () => {
|
const loadState = useCallback(async () => {
|
||||||
const data = await workflowService.getState(listFormCode)
|
const data = await workflowService.getState(props.listFormCode)
|
||||||
setCriteria(data.criteria)
|
setCriteria(data.criteria)
|
||||||
return data
|
return data
|
||||||
}, [listFormCode])
|
}, [props.listFormCode])
|
||||||
|
|
||||||
const runAction = useCallback(
|
const runAction = useCallback(
|
||||||
async (action: () => Promise<unknown>) => {
|
async (action: () => Promise<unknown>) => {
|
||||||
|
|
@ -72,9 +96,9 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
if (selectedCriteria) {
|
if (selectedCriteria) {
|
||||||
setCriteriaForm(toCriteriaForm(selectedCriteria))
|
setCriteriaForm(toCriteriaForm(selectedCriteria))
|
||||||
} else {
|
} else {
|
||||||
setCriteriaForm(emptyCriteria('Start', listFormCode))
|
setCriteriaForm(emptyCriteria('Start', props.listFormCode))
|
||||||
}
|
}
|
||||||
}, [listFormCode, selectedCriteria])
|
}, [props.listFormCode, selectedCriteria])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedId) return
|
if (!selectedId) return
|
||||||
|
|
@ -90,7 +114,7 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria({
|
await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(criteriaForm),
|
...normalizeCriteria(criteriaForm),
|
||||||
listFormCode,
|
listFormCode: props.listFormCode,
|
||||||
})
|
})
|
||||||
setSelectedId('')
|
setSelectedId('')
|
||||||
})
|
})
|
||||||
|
|
@ -100,8 +124,8 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
setDesignerTab('flow')
|
setDesignerTab('flow')
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
const saved = await workflowService.saveCriteria({
|
const saved = await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(emptyCriteria(kind, listFormCode)),
|
...normalizeCriteria(emptyCriteria(kind, props.listFormCode)),
|
||||||
listFormCode,
|
listFormCode: props.listFormCode,
|
||||||
positionX: 80 + (currentCriteria.length % 5) * 230,
|
positionX: 80 + (currentCriteria.length % 5) * 230,
|
||||||
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
||||||
})
|
})
|
||||||
|
|
@ -145,12 +169,15 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
await workflowService.saveCriteria({
|
||||||
|
...normalizeCriteria(next),
|
||||||
|
listFormCode: props.listFormCode,
|
||||||
|
})
|
||||||
setPendingLink(null)
|
setPendingLink(null)
|
||||||
setSelectedId(sourceId)
|
setSelectedId(sourceId)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[busy, currentCriteria, listFormCode, runAction],
|
[busy, currentCriteria, props.listFormCode, runAction],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -190,7 +217,10 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
await workflowService.saveCriteria({
|
||||||
|
...normalizeCriteria(next),
|
||||||
|
listFormCode: props.listFormCode,
|
||||||
|
})
|
||||||
setSelectedId(next.id)
|
setSelectedId(next.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +245,10 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
|
|
||||||
setPendingLink(null)
|
setPendingLink(null)
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
await workflowService.saveCriteria({
|
||||||
|
...normalizeCriteria(next),
|
||||||
|
listFormCode: props.listFormCode,
|
||||||
|
})
|
||||||
setSelectedId('')
|
setSelectedId('')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +267,7 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
|
|
||||||
await workflowService.saveCriteria({
|
await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(item),
|
...normalizeCriteria(item),
|
||||||
listFormCode,
|
listFormCode: props.listFormCode,
|
||||||
positionX: position.x,
|
positionX: position.x,
|
||||||
positionY: position.y,
|
positionY: position.y,
|
||||||
})
|
})
|
||||||
|
|
@ -262,8 +295,113 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
setSelectedId(sourceId)
|
setSelectedId(sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const schema = object().shape({
|
||||||
|
approvalFieldName: string(),
|
||||||
|
approvalDateFieldName: string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const initialValues = useStoreState((s) => s.admin.lists.values)
|
||||||
|
if (!initialValues) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardShell
|
<div>
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={schema}
|
||||||
|
onSubmit={async (values, formikHelpers) => {
|
||||||
|
await props.onSubmit(ListFormEditTabs.Workflow, values, formikHelpers)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ touched, errors, values, isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<FormContainer size="sm">
|
||||||
|
<Card className="my-2">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalFieldName')}
|
||||||
|
invalid={
|
||||||
|
errors.workflowDto?.approvalFieldName &&
|
||||||
|
touched.workflowDto?.approvalFieldName
|
||||||
|
}
|
||||||
|
errorMessage={errors.workflowDto?.approvalFieldName}
|
||||||
|
>
|
||||||
|
<Field type="text" name="workflowDto.approvalFieldName">
|
||||||
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={columnOptions}
|
||||||
|
isClearable={true}
|
||||||
|
value={columnOptions.filter(
|
||||||
|
(option) => option.value === values.workflowDto.approvalFieldName,
|
||||||
|
)}
|
||||||
|
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalDateFieldName')}
|
||||||
|
invalid={
|
||||||
|
errors.workflowDto?.approvalDateFieldName &&
|
||||||
|
touched.workflowDto?.approvalDateFieldName
|
||||||
|
}
|
||||||
|
errorMessage={errors.workflowDto?.approvalDateFieldName}
|
||||||
|
>
|
||||||
|
<Field type="text" name="workflowDto.approvalDateFieldName">
|
||||||
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={columnOptions}
|
||||||
|
isClearable={true}
|
||||||
|
value={columnOptions.filter(
|
||||||
|
(option) => option.value === values.workflowDto.approvalDateFieldName,
|
||||||
|
)}
|
||||||
|
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalStatusFieldName')}
|
||||||
|
invalid={
|
||||||
|
errors.workflowDto?.approvalStatusFieldName &&
|
||||||
|
touched.workflowDto?.approvalStatusFieldName
|
||||||
|
}
|
||||||
|
errorMessage={errors.workflowDto?.approvalStatusFieldName}
|
||||||
|
>
|
||||||
|
<Field type="text" name="workflowDto.approvalStatusFieldName">
|
||||||
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={columnOptions}
|
||||||
|
isClearable={true}
|
||||||
|
value={columnOptions.filter(
|
||||||
|
(option) => option.value === values.workflowDto.approvalStatusFieldName,
|
||||||
|
)}
|
||||||
|
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button block variant="solid" loading={isSubmitting}>
|
||||||
|
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</FormContainer>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
|
||||||
|
<WorkflowDesigner
|
||||||
busy={busy}
|
busy={busy}
|
||||||
canvasRef={canvasRef}
|
canvasRef={canvasRef}
|
||||||
canvasZoom={canvasZoom}
|
canvasZoom={canvasZoom}
|
||||||
|
|
@ -272,28 +410,34 @@ export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
designerTab={designerTab}
|
designerTab={designerTab}
|
||||||
dragPreview={dragPreview}
|
dragPreview={dragPreview}
|
||||||
pendingLink={pendingLink}
|
pendingLink={pendingLink}
|
||||||
selectedId={selectedId}
|
selectedCriteriaId={selectedId}
|
||||||
|
userList={props.userList}
|
||||||
|
selectCommandColumns={props.selectCommandColumns}
|
||||||
|
isLoadingSelectCommandColumns={props.isLoadingSelectCommandColumns}
|
||||||
onAddCriteria={addCriteria}
|
onAddCriteria={addCriteria}
|
||||||
onBeginLink={beginLink}
|
onBeginLink={beginLink}
|
||||||
onChangeCriteriaForm={setCriteriaForm}
|
onChangeCriteriaForm={setCriteriaForm}
|
||||||
onClearCanvasSelection={clearCanvasSelection}
|
onClearSelection={clearCanvasSelection}
|
||||||
onConnectNodes={connectNodes}
|
onConnect={connectNodes}
|
||||||
onDeleteSelectedCriteria={deleteSelectedCriteria}
|
onDeleteCriteria={deleteSelectedCriteria}
|
||||||
onDisconnectLink={disconnectLink}
|
onDeleteLink={disconnectLink}
|
||||||
onDragMove={(event: DragEndEvent | null) =>
|
onDragMove={(event: DragEndEvent | null) =>
|
||||||
setDragPreview(event ? { id: event.active.id, delta: event.delta } : null)
|
setDragPreview(event ? { id: event.active.id, delta: event.delta } : null)
|
||||||
}
|
}
|
||||||
onFitFlowLayout={fitFlowLayout}
|
onFitLayout={fitFlowLayout}
|
||||||
onOpenCriteriaDetails={openCriteriaDetails}
|
onOpenDetails={openCriteriaDetails}
|
||||||
onResetDemo={() => runAction(() => workflowService.resetDemo(listFormCode))}
|
onResetDemo={() => runAction(() => workflowService.resetDemo(props.listFormCode))}
|
||||||
onSaveCriteria={saveCriteria}
|
onSaveCriteria={saveCriteria}
|
||||||
onSelectCriteria={setSelectedId}
|
onSelectCriteria={setSelectedId}
|
||||||
onSetDesignerTab={setDesignerTab}
|
onSetDesignerTab={setDesignerTab}
|
||||||
onUpdateNodePosition={updateNodePosition}
|
onUpdateNodePosition={updateNodePosition}
|
||||||
onZoomIn={() => setCanvasZoom((current) => Math.min(1.5, Number((current + 0.1).toFixed(2))))}
|
onZoomIn={() =>
|
||||||
|
setCanvasZoom((current) => Math.min(1.5, Number((current + 0.1).toFixed(2))))
|
||||||
|
}
|
||||||
onZoomOut={() =>
|
onZoomOut={() =>
|
||||||
setCanvasZoom((current) => Math.max(0.6, Number((current - 0.1).toFixed(2))))
|
setCanvasZoom((current) => Math.max(0.6, Number((current - 0.1).toFixed(2))))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,484 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { FiSave, FiTrash2 } from 'react-icons/fi'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import {
|
|
||||||
columnOptions,
|
|
||||||
kindIcon,
|
|
||||||
kindOptions,
|
|
||||||
operatorOptions,
|
|
||||||
} from '@/utils/workflow/workflowConstants'
|
|
||||||
import {
|
|
||||||
compareOutcomeRuleText,
|
|
||||||
criteriaSummary,
|
|
||||||
emptyCompareOutcome,
|
|
||||||
targetTitle,
|
|
||||||
} from '@/utils/workflow/workflowHelpers'
|
|
||||||
import type { CompareOutcomeDto, WorkflowCriteriaDto } from '@/services/workflow.service'
|
|
||||||
|
|
||||||
const SaveIcon = FiSave as any
|
|
||||||
const TrashIcon = FiTrash2 as any
|
|
||||||
|
|
||||||
const tableButtonClass =
|
|
||||||
'inline-flex min-h-8 items-center justify-center gap-1.5 rounded-md border px-2.5 py-1 text-[13px] font-medium leading-none transition-colors disabled:cursor-not-allowed disabled:opacity-50'
|
|
||||||
|
|
||||||
type CriteriaTableProps = {
|
|
||||||
criteria: WorkflowCriteriaDto[]
|
|
||||||
selectedId: string
|
|
||||||
form: any
|
|
||||||
busy: boolean
|
|
||||||
onSelect: (id: string) => void
|
|
||||||
onChange: (form: any) => void
|
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
|
||||||
onDelete: (id: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CriteriaTable({
|
|
||||||
criteria,
|
|
||||||
selectedId,
|
|
||||||
form,
|
|
||||||
busy,
|
|
||||||
onSelect,
|
|
||||||
onChange,
|
|
||||||
onSubmit,
|
|
||||||
onDelete,
|
|
||||||
}: CriteriaTableProps) {
|
|
||||||
const setField = (name: string, value: unknown) => onChange({ ...form, [name]: value })
|
|
||||||
const targetOptions = [
|
|
||||||
{ value: '', label: 'Bağlantı yok' },
|
|
||||||
...criteria
|
|
||||||
.filter((item) => item.id !== form.id)
|
|
||||||
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
|
||||||
]
|
|
||||||
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
|
||||||
const next = [...(form.compareOutcomes || [])]
|
|
||||||
next[index] = { ...next[index], ...patch }
|
|
||||||
setField('compareOutcomes', next)
|
|
||||||
}
|
|
||||||
const updateCompareCondition = (
|
|
||||||
outcomeIndex: number,
|
|
||||||
conditionIndex: number,
|
|
||||||
patch: Record<string, unknown>,
|
|
||||||
) => {
|
|
||||||
const next = [...(form.compareOutcomes || [])]
|
|
||||||
const conditions = [...(next[outcomeIndex]?.conditions || [])]
|
|
||||||
conditions[conditionIndex] = { ...conditions[conditionIndex], ...patch }
|
|
||||||
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
|
||||||
setField('compareOutcomes', next)
|
|
||||||
}
|
|
||||||
const addCompareCondition = (outcomeIndex: number) => {
|
|
||||||
const next = [...(form.compareOutcomes || [])]
|
|
||||||
next[outcomeIndex] = {
|
|
||||||
...next[outcomeIndex],
|
|
||||||
conditions: [
|
|
||||||
...(next[outcomeIndex]?.conditions || []),
|
|
||||||
{ compareColumn: 'Tutar', compareOperator: '>', compareValue: 0 },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
setField('compareOutcomes', next)
|
|
||||||
}
|
|
||||||
const removeCompareCondition = (outcomeIndex: number, conditionIndex: number) => {
|
|
||||||
const next = [...(form.compareOutcomes || [])]
|
|
||||||
const conditions = (next[outcomeIndex]?.conditions || []).filter(
|
|
||||||
(_: unknown, index: number) => index !== conditionIndex,
|
|
||||||
)
|
|
||||||
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
|
||||||
setField('compareOutcomes', next)
|
|
||||||
}
|
|
||||||
const removeCompareOutcome = (index: number) => {
|
|
||||||
setField(
|
|
||||||
'compareOutcomes',
|
|
||||||
(form.compareOutcomes || []).filter((_: unknown, itemIndex: number) => itemIndex !== index),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const targetSelect = (
|
|
||||||
value: string | null | undefined,
|
|
||||||
onSelectTarget: (value: string) => void,
|
|
||||||
required = false,
|
|
||||||
) => (
|
|
||||||
<select
|
|
||||||
required={required}
|
|
||||||
value={value || ''}
|
|
||||||
onChange={(event) => onSelectTarget(event.target.value)}
|
|
||||||
>
|
|
||||||
{targetOptions.map((option) => (
|
|
||||||
<option key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)
|
|
||||||
const toggleRow = (id: string) => onSelect(id === selectedId ? '' : id)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="min-w-0 rounded-lg">
|
|
||||||
<form className="block" onSubmit={onSubmit}>
|
|
||||||
<div className="overflow-auto rounded-md border border-gray-200 text-sm">
|
|
||||||
<div className="min-w-[920px]">
|
|
||||||
<div className="grid grid-cols-[minmax(110px,0.8fr)_120px_minmax(220px,1.4fr)_minmax(220px,1.2fr)_110px] bg-slate-50 font-semibold text-slate-700">
|
|
||||||
<div className="px-4 py-3">Id</div>
|
|
||||||
<div className="px-4 py-3">Tip</div>
|
|
||||||
<div className="px-4 py-3">Başlık / Kural</div>
|
|
||||||
<div className="px-4 py-3">Bağlantılar</div>
|
|
||||||
<div className="px-4 py-3">İşlem</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{criteria.length === 0 && (
|
|
||||||
<div className="border-t border-gray-100 px-4 py-6 text-center text-slate-500">
|
|
||||||
Seçili iş akışı için açıklama kaydı yok.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{criteria.map((item) => {
|
|
||||||
const isSelected = item.id === selectedId
|
|
||||||
const connectionSummary = criteriaConnectionSummary(item, criteria)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={item.id}>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'grid cursor-pointer grid-cols-[minmax(110px,0.8fr)_120px_minmax(220px,1.4fr)_minmax(220px,1.2fr)_110px] border-t border-gray-100',
|
|
||||||
{
|
|
||||||
'bg-blue-50': isSelected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onClick={() => toggleRow(item.id)}
|
|
||||||
>
|
|
||||||
<div className="min-w-0 px-4 py-3">
|
|
||||||
<strong className="break-words">{item.id}</strong>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-3">
|
|
||||||
{kindOptions.find((option) => option.value === item.kind)?.label}
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0 break-words px-4 py-3">
|
|
||||||
{criteriaSummaryContent(item)}
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0 break-words px-4 py-3">{connectionSummary}</div>
|
|
||||||
<div className="px-4 py-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
|
||||||
)}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
toggleRow(item.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isSelected ? 'Kapat' : 'Düzenle'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isSelected && (
|
|
||||||
<div className="grid gap-3.5 border-t border-gray-100 bg-slate-50 p-3.5">
|
|
||||||
<div className="grid grid-cols-3 gap-2.5 max-[720px]:grid-cols-1">
|
|
||||||
<Field label="Tip" required>
|
|
||||||
<select
|
|
||||||
value={form.kind}
|
|
||||||
onChange={(event) => setField('kind', event.target.value)}
|
|
||||||
>
|
|
||||||
{kindOptions.map((option) => (
|
|
||||||
<option key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</Field>
|
|
||||||
<Field label="Başlık" required>
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
value={form.title}
|
|
||||||
onChange={(event) => setField('title', event.target.value)}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
label="Onaylayacak Kişi"
|
|
||||||
required={form.kind === 'Approval' || form.kind === 'Inform'}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
required={form.kind === 'Approval' || form.kind === 'Inform'}
|
|
||||||
value={form.approver}
|
|
||||||
onChange={(event) => setField('approver', event.target.value)}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
{(form.kind === 'Start' || form.kind === 'Inform') && (
|
|
||||||
<Field label="Sonraki adım" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnStart,
|
|
||||||
(value) => setField('nextOnStart', value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{form.kind === 'Approval' && (
|
|
||||||
<>
|
|
||||||
<Field label="Onay adımı" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnApprove,
|
|
||||||
(value) => setField('nextOnApprove', value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field label="Red adımı" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnReject,
|
|
||||||
(value) => setField('nextOnReject', value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{form.kind === 'Compare' && (
|
|
||||||
<div className="grid gap-2.5 rounded-lg border border-gray-200 bg-slate-50 p-2.5">
|
|
||||||
<div className="flex items-center justify-between gap-2 text-[13px] font-bold text-slate-700">
|
|
||||||
<span>Karşılaştırma durumları</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
|
||||||
)}
|
|
||||||
disabled={(form.compareOutcomes || []).length >= 4}
|
|
||||||
onClick={() =>
|
|
||||||
setField('compareOutcomes', [
|
|
||||||
...(form.compareOutcomes || []),
|
|
||||||
emptyCompareOutcome(
|
|
||||||
`Durum ${(form.compareOutcomes || []).length + 1}`,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Ekle
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{(form.compareOutcomes || []).map(
|
|
||||||
(outcome: CompareOutcomeDto, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="grid gap-2 border-t border-gray-200 pt-2 first:border-t-0 first:pt-0"
|
|
||||||
>
|
|
||||||
<div className="grid grid-cols-[minmax(130px,0.8fr)_minmax(200px,1.4fr)_auto] items-center gap-2 max-[720px]:grid-cols-1">
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
value={outcome.label}
|
|
||||||
aria-label="Durum adı zorunlu"
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareOutcome(index, {
|
|
||||||
label: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{targetSelect(
|
|
||||||
outcome.targetId,
|
|
||||||
(targetId) =>
|
|
||||||
updateCompareOutcome(index, {
|
|
||||||
targetId,
|
|
||||||
}),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
|
||||||
)}
|
|
||||||
disabled={(form.compareOutcomes || []).length <= 2}
|
|
||||||
onClick={() => removeCompareOutcome(index)}
|
|
||||||
>
|
|
||||||
Sil
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-2.5">
|
|
||||||
{(outcome.conditions || []).map((condition, conditionIndex) => (
|
|
||||||
<div
|
|
||||||
key={conditionIndex}
|
|
||||||
className="grid grid-cols-[minmax(100px,0.7fr)_82px_minmax(110px,0.8fr)_auto] items-center gap-1.5 max-[720px]:grid-cols-1"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
value={condition.compareColumn}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareCondition(index, conditionIndex, {
|
|
||||||
column: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{columnOptions.map((option) => (
|
|
||||||
<option key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<select
|
|
||||||
value={condition.compareOperator}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareCondition(index, conditionIndex, {
|
|
||||||
operator: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{operatorOptions.map((option) => (
|
|
||||||
<option key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
value={condition.compareValue}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareCondition(index, conditionIndex, {
|
|
||||||
compareValue: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
|
||||||
)}
|
|
||||||
disabled={(outcome.conditions || []).length <= 1}
|
|
||||||
onClick={() =>
|
|
||||||
removeCompareCondition(index, conditionIndex)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Koşulu sil
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
|
||||||
)}
|
|
||||||
onClick={() => addCompareCondition(index)}
|
|
||||||
>
|
|
||||||
Koşul ekle
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'border-blue-600 bg-blue-600 text-white',
|
|
||||||
)}
|
|
||||||
disabled={busy}
|
|
||||||
>
|
|
||||||
<SaveIcon />
|
|
||||||
Kaydet
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
tableButtonClass,
|
|
||||||
'border-red-600 bg-red-600 text-white',
|
|
||||||
)}
|
|
||||||
disabled={busy || !form.id}
|
|
||||||
onClick={() => onDelete(form.id)}
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
|
||||||
Sil
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Field({
|
|
||||||
label,
|
|
||||||
children,
|
|
||||||
required = false,
|
|
||||||
}: {
|
|
||||||
label: string
|
|
||||||
children: React.ReactNode
|
|
||||||
required?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="grid gap-1.5 text-[12px] text-slate-700">
|
|
||||||
<span>
|
|
||||||
{label}
|
|
||||||
{required && <span className="font-bold text-red-600"> *</span>}
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
|
||||||
if (item.kind === 'Compare') {
|
|
||||||
const outcomes = item.compareOutcomes || []
|
|
||||||
if (!outcomes.length) return '-'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className="m-0 grid gap-1">
|
|
||||||
{outcomes.map((outcome, index: number) => (
|
|
||||||
<li key={`${outcome.label || 'outcome'}-${index}`}>
|
|
||||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
|
||||||
{compareOutcomeRuleText(outcome)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return criteriaSummary(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: WorkflowCriteriaDto[]) {
|
|
||||||
if (item.kind === 'Compare') {
|
|
||||||
const outcomes = item.compareOutcomes || []
|
|
||||||
if (!outcomes.length) return '-'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className="m-0 grid gap-1">
|
|
||||||
{outcomes.map((outcome, index: number) => (
|
|
||||||
<li key={`${outcome.label || 'target'}-${index}`}>
|
|
||||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
|
||||||
{targetTitle(criteria, outcome.targetId)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.kind === 'Approval') {
|
|
||||||
return (
|
|
||||||
<ul className="m-0 grid gap-1">
|
|
||||||
<li>
|
|
||||||
<strong>Onay:</strong> {targetTitle(criteria, item.nextOnApprove)}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Red:</strong> {targetTitle(criteria, item.nextOnReject)}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.kind === 'Start' || item.kind === 'Inform') {
|
|
||||||
return targetTitle(criteria, item.nextOnStart)
|
|
||||||
}
|
|
||||||
|
|
||||||
return '-'
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
import { WorkflowDesigner } from './WorkflowDesigner'
|
|
||||||
import { useEffect, useState, type FormEvent, type RefObject } from 'react'
|
|
||||||
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
|
||||||
import { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
|
||||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
|
||||||
|
|
||||||
type DashboardShellProps = {
|
|
||||||
busy: boolean
|
|
||||||
canvasRef: RefObject<HTMLDivElement>
|
|
||||||
canvasZoom: number
|
|
||||||
criteriaForm: any
|
|
||||||
currentCriteria: WorkflowCriteriaDto[]
|
|
||||||
designerTab: string
|
|
||||||
dragPreview: any
|
|
||||||
pendingLink: any
|
|
||||||
selectedId: string
|
|
||||||
onAddCriteria: (kind: string) => void
|
|
||||||
onBeginLink: (sourceId: string, outcome: string) => void
|
|
||||||
onChangeCriteriaForm: (form: any) => void
|
|
||||||
onClearCanvasSelection: () => void
|
|
||||||
onConnectNodes: (sourceId: string, outcome: string, targetId: string) => void
|
|
||||||
onDeleteSelectedCriteria: (criteriaId?: string) => void
|
|
||||||
onDisconnectLink: (sourceId: string, outcome: string) => void
|
|
||||||
onDragMove: (event: any) => void
|
|
||||||
onFitFlowLayout: () => void
|
|
||||||
onOpenCriteriaDetails: (id: string) => void
|
|
||||||
onResetDemo: () => void
|
|
||||||
onSaveCriteria: (event: FormEvent<HTMLFormElement>) => void
|
|
||||||
onSelectCriteria: (id: string) => void
|
|
||||||
onSetDesignerTab: (tab: string) => void
|
|
||||||
onUpdateNodePosition: (event: any) => void
|
|
||||||
onZoomIn: () => void
|
|
||||||
onZoomOut: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DashboardShell({
|
|
||||||
busy,
|
|
||||||
canvasRef,
|
|
||||||
canvasZoom,
|
|
||||||
criteriaForm,
|
|
||||||
currentCriteria,
|
|
||||||
designerTab,
|
|
||||||
dragPreview,
|
|
||||||
pendingLink,
|
|
||||||
selectedId,
|
|
||||||
onAddCriteria,
|
|
||||||
onBeginLink,
|
|
||||||
onChangeCriteriaForm,
|
|
||||||
onClearCanvasSelection,
|
|
||||||
onConnectNodes,
|
|
||||||
onDeleteSelectedCriteria,
|
|
||||||
onDisconnectLink,
|
|
||||||
onDragMove,
|
|
||||||
onFitFlowLayout,
|
|
||||||
onOpenCriteriaDetails,
|
|
||||||
onResetDemo,
|
|
||||||
onSaveCriteria,
|
|
||||||
onSelectCriteria,
|
|
||||||
onSetDesignerTab,
|
|
||||||
onUpdateNodePosition,
|
|
||||||
onZoomIn,
|
|
||||||
onZoomOut,
|
|
||||||
}: DashboardShellProps) {
|
|
||||||
const [selectCommandColumns, setSelectCommandColumns] = useState<DatabaseColumnDto[]>([])
|
|
||||||
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
|
|
||||||
|
|
||||||
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
|
||||||
if (!dsCode || !name) {
|
|
||||||
setSelectCommandColumns([])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setIsLoadingColumns(true)
|
|
||||||
try {
|
|
||||||
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
|
||||||
setSelectCommandColumns(res.data ?? [])
|
|
||||||
} catch {
|
|
||||||
setSelectCommandColumns([])
|
|
||||||
} finally {
|
|
||||||
setIsLoadingColumns(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen">
|
|
||||||
<main className="grid">
|
|
||||||
<WorkflowDesigner
|
|
||||||
busy={busy}
|
|
||||||
canvasRef={canvasRef}
|
|
||||||
canvasZoom={canvasZoom}
|
|
||||||
criteriaForm={criteriaForm}
|
|
||||||
currentCriteria={currentCriteria}
|
|
||||||
designerTab={designerTab}
|
|
||||||
dragPreview={dragPreview}
|
|
||||||
pendingLink={pendingLink}
|
|
||||||
selectedCriteriaId={selectedId}
|
|
||||||
onAddCriteria={onAddCriteria}
|
|
||||||
onBeginLink={onBeginLink}
|
|
||||||
onChangeCriteriaForm={onChangeCriteriaForm}
|
|
||||||
onClearSelection={onClearCanvasSelection}
|
|
||||||
onConnect={onConnectNodes}
|
|
||||||
onDeleteCriteria={onDeleteSelectedCriteria}
|
|
||||||
onDeleteLink={onDisconnectLink}
|
|
||||||
onDragMove={onDragMove}
|
|
||||||
onFitLayout={onFitFlowLayout}
|
|
||||||
onOpenDetails={onOpenCriteriaDetails}
|
|
||||||
onResetDemo={onResetDemo}
|
|
||||||
onSaveCriteria={onSaveCriteria}
|
|
||||||
onSelectCriteria={onSelectCriteria}
|
|
||||||
onSetDesignerTab={onSetDesignerTab}
|
|
||||||
onUpdateNodePosition={onUpdateNodePosition}
|
|
||||||
onZoomIn={onZoomIn}
|
|
||||||
onZoomOut={onZoomOut}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '@/utils/workflow/workflowHelpers'
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import type { KeyboardEvent, MouseEvent, RefObject } from 'react'
|
import type { KeyboardEvent, MouseEvent, RefObject } from 'react'
|
||||||
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
type PendingLink = {
|
type PendingLink = {
|
||||||
sourceId: string
|
sourceId: string
|
||||||
|
|
@ -36,7 +37,7 @@ type FlowNodeProps = {
|
||||||
onBeginLink: (sourceId: string, outcome: string) => void
|
onBeginLink: (sourceId: string, outcome: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FlowCanvas({
|
export function WorkflowCanvas({
|
||||||
currentCriteria,
|
currentCriteria,
|
||||||
dragPreview,
|
dragPreview,
|
||||||
zoom,
|
zoom,
|
||||||
|
|
@ -126,7 +127,6 @@ export function FlowCanvas({
|
||||||
)}
|
)}
|
||||||
{currentCriteria.length === 0 && (
|
{currentCriteria.length === 0 && (
|
||||||
<div className="sticky left-[18px] top-[18px] z-30 inline-grid max-w-[360px] gap-1 rounded-lg border border-[#cfd6e2] bg-white/95 p-3.5 text-[#475467] shadow-lg">
|
<div className="sticky left-[18px] top-[18px] z-30 inline-grid max-w-[360px] gap-1 rounded-lg border border-[#cfd6e2] bg-white/95 p-3.5 text-[#475467] shadow-lg">
|
||||||
<strong className="text-slate-700">Boş canvas</strong>
|
|
||||||
<span>
|
<span>
|
||||||
Üstteki butonlardan adım ekleyin, sonra çıkış etiketleriyle bağlantıları kurun.
|
Üstteki butonlardan adım ekleyin, sonra çıkış etiketleriyle bağlantıları kurun.
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -286,6 +286,7 @@ function FlowNode({
|
||||||
top: item.positionY,
|
top: item.positionY,
|
||||||
transform: CSS.Translate.toString(transform),
|
transform: CSS.Translate.toString(transform),
|
||||||
}
|
}
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
@ -334,7 +335,7 @@ function FlowNode({
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
{kindOptions.find((option) => option.value === item.kind)?.label}
|
{translate('::' + kindOptions.find((option) => option.value === item.kind)?.value)}
|
||||||
</span>
|
</span>
|
||||||
<strong className="break-words text-sm leading-tight [overflow-wrap:anywhere]">
|
<strong className="break-words text-sm leading-tight [overflow-wrap:anywhere]">
|
||||||
{item.title}
|
{item.title}
|
||||||
512
ui/src/views/admin/listForm/workflow/WorkflowCriteria.tsx
Normal file
512
ui/src/views/admin/listForm/workflow/WorkflowCriteria.tsx
Normal file
|
|
@ -0,0 +1,512 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { FaEdit, FaTrash } from 'react-icons/fa'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { Button, Dialog, FormContainer, FormItem, Input, Select, Table } from '@/components/ui'
|
||||||
|
import TBody from '@/components/ui/Table/TBody'
|
||||||
|
import THead from '@/components/ui/Table/THead'
|
||||||
|
import Td from '@/components/ui/Table/Td'
|
||||||
|
import Th from '@/components/ui/Table/Th'
|
||||||
|
import Tr from '@/components/ui/Table/Tr'
|
||||||
|
import { kindOptions, operatorOptions } from '@/utils/workflow/workflowConstants'
|
||||||
|
import {
|
||||||
|
compareOutcomeRuleText,
|
||||||
|
criteriaSummary,
|
||||||
|
emptyCompareOutcome1,
|
||||||
|
emptyCompareOutcome2,
|
||||||
|
targetTitle,
|
||||||
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
|
import type { CompareOutcomeDto, WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
|
||||||
|
type WorkflowCriteriaProps = {
|
||||||
|
criteria: WorkflowCriteriaDto[]
|
||||||
|
selectedId: string
|
||||||
|
formValues: any
|
||||||
|
busy: boolean
|
||||||
|
userList: SelectBoxOption[]
|
||||||
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
|
isLoadingSelectCommandColumns: boolean
|
||||||
|
onSelect: (id: string) => void
|
||||||
|
onChange: (form: any) => void
|
||||||
|
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
|
onDelete: (id: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorkflowCriteria({
|
||||||
|
criteria,
|
||||||
|
selectedId,
|
||||||
|
formValues,
|
||||||
|
busy,
|
||||||
|
userList,
|
||||||
|
selectCommandColumns,
|
||||||
|
isLoadingSelectCommandColumns,
|
||||||
|
onSelect,
|
||||||
|
onChange,
|
||||||
|
onSubmit,
|
||||||
|
onDelete,
|
||||||
|
}: WorkflowCriteriaProps) {
|
||||||
|
const setField = (name: string, value: unknown) => onChange({ ...formValues, [name]: value })
|
||||||
|
const targetOptions = [
|
||||||
|
{ value: '', label: 'Bağlantı yok' },
|
||||||
|
...criteria
|
||||||
|
.filter((item) => item.id !== formValues.id)
|
||||||
|
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
||||||
|
]
|
||||||
|
const compareColumnOptions = selectCommandColumns.length
|
||||||
|
? selectCommandColumns.map((column) => ({
|
||||||
|
value: column.columnName,
|
||||||
|
label: `${column.columnName} (${column.dataType})`,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
const defaultCompareColumn = compareColumnOptions[0]?.value ?? 'Tutar'
|
||||||
|
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
||||||
|
const next = [...(formValues.compareOutcomes || [])]
|
||||||
|
next[index] = { ...next[index], ...patch }
|
||||||
|
setField('compareOutcomes', next)
|
||||||
|
}
|
||||||
|
const updateCompareCondition = (
|
||||||
|
outcomeIndex: number,
|
||||||
|
conditionIndex: number,
|
||||||
|
patch: Record<string, unknown>,
|
||||||
|
) => {
|
||||||
|
const next = [...(formValues.compareOutcomes || [])]
|
||||||
|
const conditions = [...(next[outcomeIndex]?.conditions || [])]
|
||||||
|
conditions[conditionIndex] = { ...conditions[conditionIndex], ...patch }
|
||||||
|
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
||||||
|
setField('compareOutcomes', next)
|
||||||
|
}
|
||||||
|
const addCompareCondition = (outcomeIndex: number) => {
|
||||||
|
const next = [...(formValues.compareOutcomes || [])]
|
||||||
|
next[outcomeIndex] = {
|
||||||
|
...next[outcomeIndex],
|
||||||
|
conditions: [
|
||||||
|
...(next[outcomeIndex]?.conditions || []),
|
||||||
|
{ compareColumn: defaultCompareColumn, compareOperator: '>', compareValue: 0 },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
setField('compareOutcomes', next)
|
||||||
|
}
|
||||||
|
const removeCompareCondition = (outcomeIndex: number, conditionIndex: number) => {
|
||||||
|
const next = [...(formValues.compareOutcomes || [])]
|
||||||
|
const conditions = (next[outcomeIndex]?.conditions || []).filter(
|
||||||
|
(_: unknown, index: number) => index !== conditionIndex,
|
||||||
|
)
|
||||||
|
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
||||||
|
setField('compareOutcomes', next)
|
||||||
|
}
|
||||||
|
const removeCompareOutcome = (index: number) => {
|
||||||
|
setField(
|
||||||
|
'compareOutcomes',
|
||||||
|
(formValues.compareOutcomes || []).filter(
|
||||||
|
(_: unknown, itemIndex: number) => itemIndex !== index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const targetSelect = (
|
||||||
|
value: string | null | undefined,
|
||||||
|
onSelectTarget: (value: string) => void,
|
||||||
|
required = false,
|
||||||
|
className = '',
|
||||||
|
) => (
|
||||||
|
<SelectField
|
||||||
|
required={required}
|
||||||
|
options={targetOptions}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={onSelectTarget}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const closeDialog = () => onSelect('')
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table compact>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="text-center w-2/12">{translate('::Operation')}</Th>
|
||||||
|
<Th>{translate('::App.Platform.ID')}</Th>
|
||||||
|
<Th>{translate('::App.Platform.Type')}</Th>
|
||||||
|
<Th>{translate('::ListForms.ListFormEdit.Workflow.CriteriaTitleRule')}</Th>
|
||||||
|
<Th>{translate('::ListForms.ListFormEdit.Workflow.CriteriaConnections')}</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{criteria.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={5} className="text-center">
|
||||||
|
{translate('::ListForms.ListFormEdit.WorkflowNoCriteria')}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{criteria.map((item) => {
|
||||||
|
const isSelected = item.id === selectedId
|
||||||
|
const connectionSummary = criteriaConnectionSummary(item, criteria)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={item.id} className={classNames(isSelected && 'bg-blue-50')}>
|
||||||
|
<Td>
|
||||||
|
<div className="flex-wrap inline-flex xl:flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
shape="circle"
|
||||||
|
variant="plain"
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
title="Edit"
|
||||||
|
icon={<FaEdit />}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
onSelect(item.id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
shape="circle"
|
||||||
|
variant="plain"
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
title="Delete"
|
||||||
|
icon={<FaTrash />}
|
||||||
|
disabled={busy}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
onDelete(item.id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<strong className="break-words">{item.id}</strong>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{translate(
|
||||||
|
'::' + kindOptions.find((option) => option.value === item.kind)?.value,
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td className="min-w-[220px] break-words">{criteriaSummaryContent(item)}</Td>
|
||||||
|
<Td className="min-w-[220px] break-words">{connectionSummary}</Td>
|
||||||
|
</Tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<Dialog isOpen={!!selectedId} onClose={closeDialog} onRequestClose={closeDialog} width="lg">
|
||||||
|
<form onSubmit={onSubmit} className="flex flex-1 flex-col min-h-0">
|
||||||
|
<Dialog.Body className="flex flex-col">
|
||||||
|
<div className="flex-1 min-h-0 overflow-y-auto pr-1">
|
||||||
|
<FormContainer size="sm">
|
||||||
|
<div className="grid grid-cols-2 gap-1">
|
||||||
|
<FormItem label="Tip" asterisk>
|
||||||
|
<SelectField
|
||||||
|
required
|
||||||
|
options={kindOptions}
|
||||||
|
value={formValues.kind}
|
||||||
|
onChange={(value) => setField('kind', value)}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Başlık" asterisk>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
value={formValues.title}
|
||||||
|
onChange={(event) => setField('title', event.target.value)}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
label="Onaylayacak Kişi"
|
||||||
|
asterisk={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
||||||
|
>
|
||||||
|
<SelectField
|
||||||
|
required
|
||||||
|
options={userList}
|
||||||
|
value={formValues.approver}
|
||||||
|
onChange={(value) => setField('approver', value)}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
|
||||||
|
<FormItem label="Sonraki adım" asterisk>
|
||||||
|
{targetSelect(
|
||||||
|
formValues.nextOnStart,
|
||||||
|
(value) => setField('nextOnStart', value),
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formValues.kind === 'Approval' && (
|
||||||
|
<>
|
||||||
|
<FormItem label="Onay adımı" asterisk>
|
||||||
|
{targetSelect(
|
||||||
|
formValues.nextOnApprove,
|
||||||
|
(value) => setField('nextOnApprove', value),
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Red adımı" asterisk>
|
||||||
|
{targetSelect(
|
||||||
|
formValues.nextOnReject,
|
||||||
|
(value) => setField('nextOnReject', value),
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formValues.kind === 'Compare' && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="mb-3 flex items-center justify-between gap-2">
|
||||||
|
<h6>
|
||||||
|
Karşılaştırma durumları
|
||||||
|
{isLoadingSelectCommandColumns && (
|
||||||
|
<span className="ml-2 text-xs font-normal text-gray-400">
|
||||||
|
Sütunlar yükleniyor...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</h6>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
disabled={(formValues.compareOutcomes || []).length >= 4}
|
||||||
|
onClick={() =>
|
||||||
|
setField('compareOutcomes', [
|
||||||
|
...(formValues.compareOutcomes || []),
|
||||||
|
emptyCompareOutcome1(
|
||||||
|
`Durum ${(formValues.compareOutcomes || []).length + 1}`,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Karşılaştırma Ekle
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
{(formValues.compareOutcomes || []).map(
|
||||||
|
(outcome: CompareOutcomeDto, index: number) => (
|
||||||
|
<div key={index} className="rounded border border-gray-200 p-3">
|
||||||
|
<div className="flex flex-col-11 items-center gap-2 mb-2">
|
||||||
|
<strong className="flex-[5]">Durum {index + 1}</strong>
|
||||||
|
<strong className="flex-[5]">Bağlantı</strong>
|
||||||
|
<span className="flex-1" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col-11 items-center gap-2">
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
value={outcome.label}
|
||||||
|
aria-label="Durum adı zorunlu"
|
||||||
|
onChange={(event) =>
|
||||||
|
updateCompareOutcome(index, {
|
||||||
|
label: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="flex-[5]"
|
||||||
|
/>
|
||||||
|
{targetSelect(
|
||||||
|
outcome.targetId,
|
||||||
|
(targetId) =>
|
||||||
|
updateCompareOutcome(index, {
|
||||||
|
targetId,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
'flex-[5]',
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="plain"
|
||||||
|
className="flex-1"
|
||||||
|
disabled={(formValues.compareOutcomes || []).length <= 2}
|
||||||
|
onClick={() => removeCompareOutcome(index)}
|
||||||
|
>
|
||||||
|
Sil
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 grid gap-2">
|
||||||
|
{(outcome.conditions || []).map((condition, conditionIndex) => (
|
||||||
|
<div
|
||||||
|
key={conditionIndex}
|
||||||
|
className="flex flex-col-12 items-center gap-2"
|
||||||
|
>
|
||||||
|
<SelectField
|
||||||
|
options={compareColumnOptions}
|
||||||
|
value={condition.compareColumn}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateCompareCondition(index, conditionIndex, {
|
||||||
|
compareColumn: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="flex-[4]"
|
||||||
|
/>
|
||||||
|
<SelectField
|
||||||
|
options={operatorOptions}
|
||||||
|
value={condition.compareOperator}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateCompareCondition(index, conditionIndex, {
|
||||||
|
compareOperator: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="flex-[3]"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
value={condition.compareValue}
|
||||||
|
onChange={(event) =>
|
||||||
|
updateCompareCondition(index, conditionIndex, {
|
||||||
|
compareValue: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="flex-[3]"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="plain"
|
||||||
|
disabled={(outcome.conditions || []).length <= 1}
|
||||||
|
onClick={() => removeCompareCondition(index, conditionIndex)}
|
||||||
|
className="flex-[1]"
|
||||||
|
>
|
||||||
|
Sil
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => addCompareCondition(index)}
|
||||||
|
className="flex-[1]"
|
||||||
|
>
|
||||||
|
Ekle
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormContainer>
|
||||||
|
</div>
|
||||||
|
</Dialog.Body>
|
||||||
|
|
||||||
|
<Dialog.Footer className="flex justify-end gap-2 border-t pt-3 mt-1">
|
||||||
|
<Button type="button" variant="plain" disabled={busy} onClick={closeDialog}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="plain"
|
||||||
|
disabled={busy || !formValues.id}
|
||||||
|
onClick={() => onDelete(formValues.id)}
|
||||||
|
>
|
||||||
|
Sil
|
||||||
|
</Button>
|
||||||
|
<Button variant="solid" loading={busy} type="submit">
|
||||||
|
Kaydet
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectField({
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
required = false,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
options: SelectBoxOption[]
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
required?: boolean
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const selectedOption = options.find((option) => option.value === value) ?? null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Select
|
||||||
|
options={options}
|
||||||
|
value={selectedOption}
|
||||||
|
onChange={(option: any) => onChange(option?.value ?? '')}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
{required && (
|
||||||
|
<input
|
||||||
|
tabIndex={-1}
|
||||||
|
className="pointer-events-none h-0 w-0 opacity-0"
|
||||||
|
required
|
||||||
|
value={value}
|
||||||
|
onChange={() => undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
||||||
|
if (item.kind === 'Compare') {
|
||||||
|
const outcomes = item.compareOutcomes || []
|
||||||
|
if (!outcomes.length) return '-'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="m-0 grid gap-1">
|
||||||
|
{outcomes.map((outcome, index: number) => (
|
||||||
|
<li key={`${outcome.label || 'outcome'}-${index}`}>
|
||||||
|
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||||
|
{compareOutcomeRuleText(outcome)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return criteriaSummary(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: WorkflowCriteriaDto[]) {
|
||||||
|
if (item.kind === 'Compare') {
|
||||||
|
const outcomes = item.compareOutcomes || []
|
||||||
|
if (!outcomes.length) return '-'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="m-0 grid gap-1">
|
||||||
|
{outcomes.map((outcome, index: number) => (
|
||||||
|
<li key={`${outcome.label || 'target'}-${index}`}>
|
||||||
|
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||||
|
{targetTitle(criteria, outcome.targetId)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.kind === 'Approval') {
|
||||||
|
return (
|
||||||
|
<ul className="m-0 grid gap-1">
|
||||||
|
<li>
|
||||||
|
<strong>Onay:</strong> {targetTitle(criteria, item.nextOnApprove)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Red:</strong> {targetTitle(criteria, item.nextOnReject)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.kind === 'Start' || item.kind === 'Inform') {
|
||||||
|
return targetTitle(criteria, item.nextOnStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,13 @@ import { DndContext } from '@dnd-kit/core'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { FiMaximize2, FiRefreshCw, FiZoomIn, FiZoomOut } from 'react-icons/fi'
|
import { FiMaximize2, FiRefreshCw, FiZoomIn, FiZoomOut } from 'react-icons/fi'
|
||||||
import { kindIcon, kindOptions } from '@/utils/workflow/workflowConstants'
|
import { kindIcon, kindOptions } from '@/utils/workflow/workflowConstants'
|
||||||
import { CriteriaTable } from './CriteriaTable'
|
import { WorkflowCriteria } from './WorkflowCriteria'
|
||||||
import { FlowCanvas } from './FlowCanvas'
|
import { WorkflowCanvas } from './WorkflowCanvas'
|
||||||
import type { FormEvent, RefObject } from 'react'
|
import type { FormEvent, RefObject } from 'react'
|
||||||
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
|
||||||
type WorkflowDesignerProps = {
|
type WorkflowDesignerProps = {
|
||||||
busy: boolean
|
busy: boolean
|
||||||
|
|
@ -17,6 +20,9 @@ type WorkflowDesignerProps = {
|
||||||
dragPreview: any
|
dragPreview: any
|
||||||
pendingLink: any
|
pendingLink: any
|
||||||
selectedCriteriaId: string
|
selectedCriteriaId: string
|
||||||
|
userList: SelectBoxOption[]
|
||||||
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
|
isLoadingSelectCommandColumns: boolean
|
||||||
onAddCriteria: (kind: string) => void
|
onAddCriteria: (kind: string) => void
|
||||||
onBeginLink: (sourceId: string, outcome: string) => void
|
onBeginLink: (sourceId: string, outcome: string) => void
|
||||||
onChangeCriteriaForm: (form: any) => void
|
onChangeCriteriaForm: (form: any) => void
|
||||||
|
|
@ -54,6 +60,9 @@ export function WorkflowDesigner({
|
||||||
dragPreview,
|
dragPreview,
|
||||||
pendingLink,
|
pendingLink,
|
||||||
selectedCriteriaId,
|
selectedCriteriaId,
|
||||||
|
userList,
|
||||||
|
selectCommandColumns,
|
||||||
|
isLoadingSelectCommandColumns,
|
||||||
onAddCriteria,
|
onAddCriteria,
|
||||||
onBeginLink,
|
onBeginLink,
|
||||||
onChangeCriteriaForm,
|
onChangeCriteriaForm,
|
||||||
|
|
@ -72,7 +81,11 @@ export function WorkflowDesigner({
|
||||||
onZoomIn,
|
onZoomIn,
|
||||||
onZoomOut,
|
onZoomOut,
|
||||||
}: WorkflowDesignerProps) {
|
}: WorkflowDesignerProps) {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="min-h-screen">
|
||||||
|
<main className="grid">
|
||||||
<section className="relative min-w-0 rounded-lg border border-gray-200 bg-white p-4 max-[1080px]:pr-4">
|
<section className="relative min-w-0 rounded-lg border border-gray-200 bg-white p-4 max-[1080px]:pr-4">
|
||||||
<div className="mb-3.5 flex items-center justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
<div className="mb-3.5 flex items-center justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
||||||
<DesignerTabs activeTab={designerTab} onChange={onSetDesignerTab} />
|
<DesignerTabs activeTab={designerTab} onChange={onSetDesignerTab} />
|
||||||
|
|
@ -98,7 +111,7 @@ export function WorkflowDesigner({
|
||||||
onDragCancel={() => onDragMove(null)}
|
onDragCancel={() => onDragMove(null)}
|
||||||
onDragEnd={onUpdateNodePosition}
|
onDragEnd={onUpdateNodePosition}
|
||||||
>
|
>
|
||||||
<FlowCanvas
|
<WorkflowCanvas
|
||||||
currentCriteria={currentCriteria}
|
currentCriteria={currentCriteria}
|
||||||
dragPreview={dragPreview}
|
dragPreview={dragPreview}
|
||||||
zoom={canvasZoom}
|
zoom={canvasZoom}
|
||||||
|
|
@ -118,11 +131,14 @@ export function WorkflowDesigner({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{designerTab === 'criteria' && (
|
{designerTab === 'criteria' && (
|
||||||
<CriteriaTable
|
<WorkflowCriteria
|
||||||
criteria={currentCriteria}
|
criteria={currentCriteria}
|
||||||
selectedId={selectedCriteriaId}
|
selectedId={selectedCriteriaId}
|
||||||
form={criteriaForm}
|
formValues={criteriaForm}
|
||||||
busy={busy}
|
busy={busy}
|
||||||
|
userList={userList}
|
||||||
|
selectCommandColumns={selectCommandColumns}
|
||||||
|
isLoadingSelectCommandColumns={isLoadingSelectCommandColumns}
|
||||||
onSelect={onSelectCriteria}
|
onSelect={onSelectCriteria}
|
||||||
onChange={onChangeCriteriaForm}
|
onChange={onChangeCriteriaForm}
|
||||||
onSubmit={onSaveCriteria}
|
onSubmit={onSaveCriteria}
|
||||||
|
|
@ -130,6 +146,8 @@ export function WorkflowDesigner({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,9 +170,11 @@ function DesignerToolbar({
|
||||||
onZoomIn: () => void
|
onZoomIn: () => void
|
||||||
onZoomOut: () => void
|
onZoomOut: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap justify-end gap-2">
|
||||||
<button
|
{/* <button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(designerButtonClass, 'border-gray-300 bg-white text-slate-700')}
|
className={classNames(designerButtonClass, 'border-gray-300 bg-white text-slate-700')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
|
|
@ -163,7 +183,7 @@ function DesignerToolbar({
|
||||||
>
|
>
|
||||||
<FiRefreshCw />
|
<FiRefreshCw />
|
||||||
Demo
|
Demo
|
||||||
</button>
|
</button> */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(designerButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
className={classNames(designerButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
||||||
|
|
@ -204,7 +224,7 @@ function DesignerToolbar({
|
||||||
onClick={() => onAddCriteria(option.value)}
|
onClick={() => onAddCriteria(option.value)}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
{option.label}
|
{translate(`::ListForms.ListFormEdit.Workflow.Criteria${option.value}`)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
@ -219,6 +239,8 @@ function DesignerTabs({
|
||||||
activeTab: string
|
activeTab: string
|
||||||
onChange: (tab: string) => void
|
onChange: (tab: string) => void
|
||||||
}) {
|
}) {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inline-flex gap-1 rounded-lg" role="tablist" aria-label="Akış tasarımı">
|
<div className="inline-flex gap-1 rounded-lg" role="tablist" aria-label="Akış tasarımı">
|
||||||
<button
|
<button
|
||||||
|
|
@ -232,7 +254,7 @@ function DesignerTabs({
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange('flow')}
|
onClick={() => onChange('flow')}
|
||||||
>
|
>
|
||||||
Akış
|
{translate('::ListForms.ListFormEdit.TabWorkflow')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -245,7 +267,7 @@ function DesignerTabs({
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange('criteria')}
|
onClick={() => onChange('criteria')}
|
||||||
>
|
>
|
||||||
Adımlar
|
{translate('::ListForms.ListFormEdit.Workflow.Criteria')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue