Note ve AuditLog kısmı düzenlendi.
This commit is contained in:
parent
4943f78f89
commit
203160fce0
13 changed files with 756 additions and 190 deletions
|
|
@ -15,6 +15,8 @@ public class AuditLogDto : EntityDto<Guid>
|
||||||
public string ApplicationName { get; set; }
|
public string ApplicationName { get; set; }
|
||||||
public Guid? UserId { get; protected set; }
|
public Guid? UserId { get; protected set; }
|
||||||
public string UserName { get; protected set; }
|
public string UserName { get; protected set; }
|
||||||
|
public Guid? TenantId { get; protected set; }
|
||||||
|
public string TenantName { get; protected set; }
|
||||||
public DateTime ExecutionTime { get; protected set; }
|
public DateTime ExecutionTime { get; protected set; }
|
||||||
public int ExecutionDuration { get; protected set; }
|
public int ExecutionDuration { get; protected set; }
|
||||||
public string ClientIpAddress { get; protected set; }
|
public string ClientIpAddress { get; protected set; }
|
||||||
|
|
|
||||||
|
|
@ -306,7 +306,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
};
|
};
|
||||||
|
|
||||||
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
||||||
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, fields, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
||||||
|
|
||||||
// Performans: Dictionary ile hızlı lookup
|
// Performans: Dictionary ile hızlı lookup
|
||||||
var columnFormatsDict = result.ColumnFormats.ToDictionary(c => c.FieldName, c => c);
|
var columnFormatsDict = result.ColumnFormats.ToDictionary(c => c.FieldName, c => c);
|
||||||
|
|
|
||||||
|
|
@ -1008,12 +1008,30 @@
|
||||||
"en": "Cancel",
|
"en": "Cancel",
|
||||||
"tr": "İptal"
|
"tr": "İptal"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "Download",
|
||||||
|
"en": "Download",
|
||||||
|
"tr": "İndir"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "Insert",
|
||||||
|
"en": "Insert",
|
||||||
|
"tr": "Ekle"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "Delete",
|
"key": "Delete",
|
||||||
"en": "Delete",
|
"en": "Delete",
|
||||||
"tr": "Sil"
|
"tr": "Sil"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "Operation",
|
||||||
|
"en": "Operation",
|
||||||
|
"tr": "İşlem"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Platform.HangfireLogin",
|
"key": "App.Platform.HangfireLogin",
|
||||||
|
|
@ -3270,6 +3288,90 @@
|
||||||
"en": "Refresh",
|
"en": "Refresh",
|
||||||
"tr": "Tazele"
|
"tr": "Tazele"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.AddNote",
|
||||||
|
"en": "Add Note",
|
||||||
|
"tr": "Not Ekle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NotesPanel.DownloadFailed",
|
||||||
|
"en": "Download Failed",
|
||||||
|
"tr": "İndirme Başarısız"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Upload.MaxSize2Mb",
|
||||||
|
"en": "Maximum file size is 2MB",
|
||||||
|
"tr": "Maksimum dosya boyutu 2MB'dir"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NotesPanel.ClosePanel",
|
||||||
|
"en": "Close Panel",
|
||||||
|
"tr": "Paneli Kapat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Subject",
|
||||||
|
"en": "Enter a short title...",
|
||||||
|
"tr": "Kısa bir başlık girin..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Content",
|
||||||
|
"en": "Enter note content...",
|
||||||
|
"tr": "Notunuzu buraya yazın..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NotesPanel.OpenPanel",
|
||||||
|
"en": "Open Panel",
|
||||||
|
"tr": "Paneli Aç"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.AuditLogs.FetchFailed",
|
||||||
|
"en": "Fetch Failed",
|
||||||
|
"tr": "Veri Getirme Başarısız"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.Notes",
|
||||||
|
"en": "Notes",
|
||||||
|
"tr": "Notlar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.Notes.Empty",
|
||||||
|
"en": "No notes yet",
|
||||||
|
"tr": "Henüz hiçbir not bulunmuyor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Type.Note",
|
||||||
|
"en": "Note",
|
||||||
|
"tr": "Not"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Type.Message",
|
||||||
|
"en": "Message",
|
||||||
|
"tr": "Mesaj"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.NoteModal.Type.Activity",
|
||||||
|
"en": "Activity",
|
||||||
|
"tr": "Aktivite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.AuditLogs.Empty",
|
||||||
|
"en": "No audit logs yet",
|
||||||
|
"tr": "Henüz hiçbir audit logu bulunmuyor"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.ListForm.DeleteFilter",
|
"key": "ListForms.ListForm.DeleteFilter",
|
||||||
|
|
|
||||||
|
|
@ -1722,7 +1722,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Sector)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Sector)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
{
|
{
|
||||||
|
|
@ -1815,7 +1815,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.WorkHour)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.WorkHour)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ public static class ListFormSeeder_DefaultJsons
|
||||||
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid, string newId = "@NEWID") => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
||||||
{
|
{
|
||||||
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
new() { FieldName = "CreationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
new() { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
new() { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
|
||||||
new() { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
|
new() { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
|
||||||
new() { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
new() { FieldName = "Id", FieldDbType = dbType, Value = newId, CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
||||||
|
|
|
||||||
|
|
@ -1478,6 +1478,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Currency)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Currency)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 350, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1485,14 +1486,13 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new() {
|
new() {
|
||||||
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items =
|
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items =
|
||||||
[
|
[
|
||||||
|
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order = 2, DataField = "Symbol", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 2, DataField = "Symbol", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||||
new EditingFormItemDto { Order = 3, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 3, DataField = "Rate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxNumberBox },
|
||||||
new EditingFormItemDto { Order = 4, DataField = "Rate", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxNumberBox },
|
new EditingFormItemDto { Order = 4, DataField = "IsActive", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }
|
||||||
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2 = EditorTypes.dxCheckBox }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#region Currency Fields
|
#region Currency Fields
|
||||||
|
|
@ -1623,6 +1623,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.CountryGroup)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.CountryGroup)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1634,7 +1635,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#region CountryGroup Fields
|
#region CountryGroup Fields
|
||||||
|
|
@ -1713,6 +1713,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Country)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Country)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -1730,7 +1731,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "Currency", FieldDbType = DbType.String, Value = "TRY", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "Currency", FieldDbType = DbType.String, Value = "TRY", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
})
|
})
|
||||||
|
|
@ -2210,6 +2210,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.UomCategory)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.UomCategory)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, false, false, true),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, false, false, true),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
|
|
@ -2221,7 +2222,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
SubFormsJson = JsonSerializer.Serialize(new List<dynamic>() {
|
SubFormsJson = JsonSerializer.Serialize(new List<dynamic>() {
|
||||||
new {
|
new {
|
||||||
TabType = ListFormTabTypeEnum.List,
|
TabType = ListFormTabTypeEnum.List,
|
||||||
|
|
@ -2316,6 +2316,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Uom)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Uom)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
|
|
@ -2328,7 +2329,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox },
|
new EditingFormItemDto { Order = 5, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox },
|
||||||
]}
|
]}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
}),
|
}),
|
||||||
|
|
@ -2520,7 +2520,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillType)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillType)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false, true),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false, true),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
|
||||||
{
|
{
|
||||||
|
|
@ -2638,7 +2638,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillLevel)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.SkillLevel)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=
|
||||||
|
|
@ -2784,7 +2784,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Skill)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Skill)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(DbType.String),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(AppCodes.Definitions.SkillLevel, 600, 300, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(AppCodes.Definitions.SkillLevel, 600, 300, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
||||||
|
|
@ -5115,7 +5115,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}),
|
}),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.MenuGroup)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.MenuGroup)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
});
|
});
|
||||||
|
|
||||||
#region MenuGroup Fields
|
#region MenuGroup Fields
|
||||||
|
|
@ -5217,7 +5217,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
new EditingFormItemDto { Order = 12, DataField = "ShortName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order = 12, DataField = "ShortName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
|
||||||
]}
|
]}
|
||||||
}),
|
}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Code"),
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "IsDisabled", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "IsDisabled", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
})
|
})
|
||||||
|
|
@ -5992,7 +5992,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
}),
|
}),
|
||||||
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.PaymentMethod)),
|
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.PaymentMethod)),
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
new() { FieldName = "Commission", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }
|
new() { FieldName = "Commission", FieldDbType = DbType.Decimal, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ public class ListFormManager : PlatformDomainService, IListFormManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFormField icerisinde olmayan (select sorgusu ile alinmayan) deger almasi gereken zorunlu alanlar
|
// ListFormField icerisinde olmayan (select sorgusu ile alinmayan) deger almasi gereken zorunlu alanlar
|
||||||
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, op, keys, queryParameters);
|
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, listFormFields, op, keys, queryParameters, inputParams);
|
||||||
|
|
||||||
if (PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
if (PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,11 @@ public interface IDefaultValueManager
|
||||||
{
|
{
|
||||||
Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormFields,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
object[] keys = null,
|
object[] keys = null,
|
||||||
Dictionary<string, StringValues> queryParameters = null);
|
Dictionary<string, StringValues> queryParameters = null,
|
||||||
|
dynamic inputParams = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
|
|
@ -39,9 +41,11 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
|
|
||||||
public async Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
public async Task<Dictionary<string, object>> GenerateDefaultValuesAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
List<ListFormField> listFormFields,
|
||||||
OperationEnum op,
|
OperationEnum op,
|
||||||
object[] keys = null,
|
object[] keys = null,
|
||||||
Dictionary<string, StringValues> queryParameters = null)
|
Dictionary<string, StringValues> queryParameters = null,
|
||||||
|
dynamic inputParams = null)
|
||||||
{
|
{
|
||||||
var fields = new Dictionary<string, object>();
|
var fields = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
@ -60,69 +64,87 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var field in JsonSerializer.Deserialize<List<FieldsDefaultValue>>(defaultFieldsJson))
|
foreach (var defaultField in JsonSerializer.Deserialize<List<FieldsDefaultValue>>(defaultFieldsJson))
|
||||||
{
|
{
|
||||||
object value = null;
|
object value = null;
|
||||||
switch (field.CustomValueType)
|
switch (defaultField.CustomValueType)
|
||||||
{
|
{
|
||||||
case FieldCustomValueTypeEnum.CustomKey:
|
case FieldCustomValueTypeEnum.CustomKey:
|
||||||
if (field.Value == PlatformConsts.DefaultValues.UserId)
|
if (defaultField.Value == PlatformConsts.DefaultValues.UserId)
|
||||||
value = CurrentUser.Id;
|
value = CurrentUser.Id;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.UserName)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.UserName)
|
||||||
value = CurrentUser.Name;
|
value = CurrentUser.Name;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Roles)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Roles)
|
||||||
value = CurrentUser.Roles; //.JoinAsString("','");
|
value = CurrentUser.Roles; //.JoinAsString("','");
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Date)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Date)
|
||||||
value = Clock.Now.Date;
|
value = Clock.Now.Date;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Now)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Now)
|
||||||
value = Clock.Now;
|
value = Clock.Now;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Day)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Day)
|
||||||
value = Clock.Now.Day;
|
value = Clock.Now.Day;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Month)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Month)
|
||||||
value = Clock.Now.Month;
|
value = Clock.Now.Month;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Year)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Year)
|
||||||
value = Clock.Now.Year;
|
value = Clock.Now.Year;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Id)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Id)
|
||||||
value = keys?.FirstOrDefault();
|
value = keys?.FirstOrDefault();
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.NewId)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.NewId)
|
||||||
value = Guid.NewGuid();
|
value = Guid.NewGuid();
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
||||||
value = keys;
|
value = keys;
|
||||||
else if (field.Value == PlatformConsts.DefaultValues.TenantId)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.TenantId)
|
||||||
value = CurrentTenant.Id;
|
value = CurrentTenant.Id;
|
||||||
else
|
else
|
||||||
value = field.Value;
|
{
|
||||||
|
if ((object)inputParams != null)
|
||||||
|
{
|
||||||
|
foreach (var item in JsonSerializer.Deserialize<Dictionary<string, object>>(inputParams))
|
||||||
|
{
|
||||||
|
var field = listFormFields.FirstOrDefault(c => c.FieldName == item.Key);
|
||||||
|
if (field == null
|
||||||
|
|| (op == OperationEnum.Insert && !field.CanCreate)
|
||||||
|
|| (op == OperationEnum.Update && !field.CanUpdate)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = QueryHelper.GetFormattedValue(field.SourceDbType, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//value = field.Value;
|
||||||
//TODO: artirilabilir
|
//TODO: artirilabilir
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.QueryParams:
|
case FieldCustomValueTypeEnum.QueryParams:
|
||||||
if (queryParameters != null)
|
if (queryParameters != null)
|
||||||
{
|
{
|
||||||
value = queryParameters.GetValueOrDefault(field.Value);
|
value = queryParameters.GetValueOrDefault(defaultField.Value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.DbQuery:
|
case FieldCustomValueTypeEnum.DbQuery:
|
||||||
if (!string.IsNullOrWhiteSpace(field.SqlQuery))
|
if (!string.IsNullOrWhiteSpace(defaultField.SqlQuery))
|
||||||
{
|
{
|
||||||
var dynamicDataManager = LazyServiceProvider.LazyGetRequiredService<IDynamicDataManager>();
|
var dynamicDataManager = LazyServiceProvider.LazyGetRequiredService<IDynamicDataManager>();
|
||||||
var (dynamicDataRepository, connectionString, _) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (dynamicDataRepository, connectionString, _) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
value = await dynamicDataRepository.ExecuteScalarAsync<object>(field.SqlQuery, connectionString);
|
value = await dynamicDataRepository.ExecuteScalarAsync<object>(defaultField.SqlQuery, connectionString);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FieldCustomValueTypeEnum.Value:
|
case FieldCustomValueTypeEnum.Value:
|
||||||
default:
|
default:
|
||||||
value = field.Value;
|
value = defaultField.Value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
|
if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
|
||||||
{
|
{
|
||||||
var formattedValue = QueryHelper.GetFormattedValue(field.FieldDbType, value);
|
var formattedValue = QueryHelper.GetFormattedValue(defaultField.FieldDbType, value);
|
||||||
if (fields.ContainsKey(field.FieldName))
|
if (fields.ContainsKey(defaultField.FieldName))
|
||||||
{
|
{
|
||||||
fields[field.FieldName] = formattedValue;
|
fields[defaultField.FieldName] = formattedValue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fields.Add(field.FieldName, formattedValue);
|
fields.Add(defaultField.FieldName, formattedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ export interface AuditLogDto {
|
||||||
applicationName: string
|
applicationName: string
|
||||||
userId?: string
|
userId?: string
|
||||||
userName?: string
|
userName?: string
|
||||||
|
tenantId?: string
|
||||||
|
tenantName?: string
|
||||||
executionTime: string
|
executionTime: string
|
||||||
executionDuration: number
|
executionDuration: number
|
||||||
clientIpAddress?: string
|
clientIpAddress?: string
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
UserClaimModel,
|
UserClaimModel,
|
||||||
UserInfoViewModel,
|
UserInfoViewModel,
|
||||||
} from '@/proxy/admin/models'
|
} from '@/proxy/admin/models'
|
||||||
import { ListResultDto } from '../proxy'
|
import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '../proxy'
|
||||||
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
|
@ -74,6 +74,13 @@ export const getAuditLogs = (id: string) =>
|
||||||
url: `/api/app/audit-log/${id}`,
|
url: `/api/app/audit-log/${id}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getAuditLogList = (input: PagedAndSortedResultRequestDto) =>
|
||||||
|
apiService.fetchData<PagedResultDto<AuditLogDto>, PagedAndSortedResultRequestDto>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/audit-log',
|
||||||
|
params: input,
|
||||||
|
})
|
||||||
|
|
||||||
export const postClaimUser = (input: UserClaimModel) =>
|
export const postClaimUser = (input: UserClaimModel) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,62 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { FaStickyNote, FaEnvelope, FaTrash, FaDownload, FaClock, FaPaperclip } from 'react-icons/fa'
|
import {
|
||||||
import { Avatar, Button } from '@/components/ui'
|
FaStickyNote,
|
||||||
|
FaEnvelope,
|
||||||
|
FaPlus,
|
||||||
|
FaTrash,
|
||||||
|
FaDownload,
|
||||||
|
FaClock,
|
||||||
|
FaPaperclip,
|
||||||
|
FaHistory,
|
||||||
|
FaSyncAlt,
|
||||||
|
} from 'react-icons/fa'
|
||||||
|
import { Avatar, Badge, Button, Spinner, Tabs } from '@/components/ui'
|
||||||
|
import TabContent from '@/components/ui/Tabs/TabContent'
|
||||||
|
import TabList from '@/components/ui/Tabs/TabList'
|
||||||
|
import TabNav from '@/components/ui/Tabs/TabNav'
|
||||||
import { NoteDto } from '@/proxy/note/models'
|
import { NoteDto } from '@/proxy/note/models'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
|
import apiService from '@/services/api.service'
|
||||||
|
import { PagedResultDto } from '@/proxy'
|
||||||
|
import { AuditLogActionDto, AuditLogDto } from '@/proxy/auditLog/audit-log'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface NoteListProps {
|
interface NoteListProps {
|
||||||
notes: NoteDto[]
|
notes: NoteDto[]
|
||||||
|
entityName: string
|
||||||
|
entityId: string
|
||||||
|
onAddNote?: () => void
|
||||||
onDeleteNote?: (noteId: string) => void
|
onDeleteNote?: (noteId: string) => void
|
||||||
onDownloadFile?: (fileData: any) => void
|
onDownloadFile?: (fileData: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NoteList: React.FC<NoteListProps> = ({
|
export const NoteList: React.FC<NoteListProps> = ({
|
||||||
notes,
|
notes,
|
||||||
|
entityName,
|
||||||
|
entityId,
|
||||||
|
onAddNote,
|
||||||
onDeleteNote,
|
onDeleteNote,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
}) => {
|
}) => {
|
||||||
const user = useStoreState((state) => state.auth.user)
|
const user = useStoreState((state) => state.auth.user)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState<'notes' | 'audit'>('notes')
|
||||||
|
const [auditLoading, setAuditLoading] = useState(false)
|
||||||
|
const [auditError, setAuditError] = useState<string | null>(null)
|
||||||
|
const [auditItems, setAuditItems] = useState<
|
||||||
|
Array<{
|
||||||
|
log: AuditLogDto
|
||||||
|
matchedActions: Array<{
|
||||||
|
action: AuditLogActionDto
|
||||||
|
input: any
|
||||||
|
operation: string
|
||||||
|
rowLabel?: string
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
>([])
|
||||||
|
const [auditLoadedKey, setAuditLoadedKey] = useState<string | null>(null)
|
||||||
|
|
||||||
const getNoteStyle = (type: string) => {
|
const getNoteStyle = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -38,103 +78,511 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notes.length === 0)
|
const entityIdNormalized = useMemo(
|
||||||
return (
|
() =>
|
||||||
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
String(entityId ?? '')
|
||||||
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
.trim()
|
||||||
<p className="text-sm">Henüz hiçbir not bulunmuyor</p>
|
.toLowerCase(),
|
||||||
</div>
|
[entityId],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const listFormCodeNormalized = useMemo(
|
||||||
|
() =>
|
||||||
|
String(entityName ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase(),
|
||||||
|
[entityName],
|
||||||
|
)
|
||||||
|
|
||||||
|
const normalize = (value: unknown) =>
|
||||||
|
String(value ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
|
||||||
|
const tryParseJson = (text: string) => {
|
||||||
|
if (!text) return null
|
||||||
|
try {
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch {
|
||||||
|
// sometimes parameters are a JSON string containing JSON
|
||||||
|
try {
|
||||||
|
const unwrapped = JSON.parse(`"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`)
|
||||||
|
return JSON.parse(unwrapped)
|
||||||
|
} catch {
|
||||||
|
// last resort: try to parse substring that looks like JSON
|
||||||
|
const first = text.indexOf('{')
|
||||||
|
const last = text.lastIndexOf('}')
|
||||||
|
if (first >= 0 && last > first) {
|
||||||
|
const slice = text.slice(first, last + 1)
|
||||||
|
try {
|
||||||
|
return JSON.parse(slice)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getListFormInputFromAction = (action: AuditLogActionDto) => {
|
||||||
|
const parametersText = String(action?.parameters ?? '').trim()
|
||||||
|
if (!parametersText) return null
|
||||||
|
const parsed = tryParseJson(parametersText)
|
||||||
|
const input = parsed?.input
|
||||||
|
if (!input || typeof input !== 'object') return null
|
||||||
|
if (!('listFormCode' in input)) return null
|
||||||
|
return input as any
|
||||||
|
}
|
||||||
|
|
||||||
|
const getKeysFromInput = (input: any): string[] => {
|
||||||
|
if (!input) return []
|
||||||
|
if (!Array.isArray(input.keys)) return []
|
||||||
|
return input.keys.filter((k: any) => k !== null && k !== undefined).map((k: any) => String(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOperationFromInput = (
|
||||||
|
input: any,
|
||||||
|
action: AuditLogActionDto,
|
||||||
|
): 'insert' | 'update' | 'delete' | 'unknown' => {
|
||||||
|
const keys = getKeysFromInput(input)
|
||||||
|
const hasKeys = keys.length > 0
|
||||||
|
const data = input?.data
|
||||||
|
const hasData = !!data && typeof data === 'object' && Object.keys(data).length > 0
|
||||||
|
|
||||||
|
if (!hasKeys && hasData) return 'insert'
|
||||||
|
if (hasKeys && hasData) return 'update'
|
||||||
|
if (hasKeys && !hasData) return 'delete'
|
||||||
|
|
||||||
|
const method = normalize(action?.methodName)
|
||||||
|
if (method.includes('create') || method.includes('insert') || method.includes('add'))
|
||||||
|
return 'insert'
|
||||||
|
if (method.includes('update') || method.includes('edit')) return 'update'
|
||||||
|
if (method.includes('delete') || method.includes('remove')) return 'delete'
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
const findMatchingValueInData = (
|
||||||
|
data: any,
|
||||||
|
targetNormalized: string,
|
||||||
|
depth = 0,
|
||||||
|
visited?: Set<any>,
|
||||||
|
): { value: any; path: string } | null => {
|
||||||
|
if (!targetNormalized) return null
|
||||||
|
if (data === null || data === undefined) return null
|
||||||
|
if (depth > 6) return null
|
||||||
|
|
||||||
|
const type = typeof data
|
||||||
|
if (type === 'string' || type === 'number' || type === 'boolean') {
|
||||||
|
return normalize(data) === targetNormalized ? { value: data, path: '' } : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const hit = findMatchingValueInData(data[i], targetNormalized, depth + 1, visited)
|
||||||
|
if (hit) return { value: hit.value, path: `[${i}]${hit.path ? '.' + hit.path : ''}` }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'object') {
|
||||||
|
const visit = visited ?? new Set<any>()
|
||||||
|
if (visit.has(data)) return null
|
||||||
|
visit.add(data)
|
||||||
|
|
||||||
|
const entries = Object.entries(data as Record<string, any>)
|
||||||
|
const preferredKeys = ['id', 'Id', 'name', 'Name', 'code', 'Code']
|
||||||
|
const orderedEntries = [
|
||||||
|
...preferredKeys
|
||||||
|
.filter((k) => Object.prototype.hasOwnProperty.call(data, k))
|
||||||
|
.map((k) => [k, (data as any)[k]] as const),
|
||||||
|
...entries.filter(([k]) => !preferredKeys.includes(k)),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const [k, v] of orderedEntries) {
|
||||||
|
const hit = findMatchingValueInData(v, targetNormalized, depth + 1, visit)
|
||||||
|
if (hit) {
|
||||||
|
const childPath = hit.path ? '.' + hit.path : ''
|
||||||
|
return { value: hit.value, path: `${k}${childPath}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRowLabelIfMatches = (input: any): string | null => {
|
||||||
|
if (!entityIdNormalized) return null
|
||||||
|
const inputFormCode = normalize(input?.listFormCode)
|
||||||
|
if (!inputFormCode || inputFormCode !== listFormCodeNormalized) return null
|
||||||
|
|
||||||
|
const keys = getKeysFromInput(input)
|
||||||
|
.map((k) => normalize(k))
|
||||||
|
.filter(Boolean)
|
||||||
|
if (keys.length > 0) {
|
||||||
|
// update/delete should match by keys
|
||||||
|
if (keys.includes(entityIdNormalized)) {
|
||||||
|
return getKeysFromInput(input).join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some entities may use a different PK than the visible row key; allow strict match via input.data too.
|
||||||
|
const data = input?.data
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
|
if (hit) {
|
||||||
|
const nameValue = (data as any)?.Name ?? (data as any)?.name
|
||||||
|
return nameValue ? String(nameValue) : String(hit.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert: keys is null, match by scanning input.data for entity id/name/code/etc.
|
||||||
|
const data = input?.data
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
|
if (!hit) return null
|
||||||
|
|
||||||
|
// Prefer showing data.Name if present; otherwise show matched value
|
||||||
|
const nameValue = (data as any)?.Name ?? (data as any)?.name
|
||||||
|
if (nameValue) return String(nameValue)
|
||||||
|
return String(hit.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMatchedActions = (log: AuditLogDto) => {
|
||||||
|
const actions = log.actions ?? []
|
||||||
|
const matched: Array<{
|
||||||
|
action: AuditLogActionDto
|
||||||
|
input: any
|
||||||
|
operation: string
|
||||||
|
rowLabel?: string
|
||||||
|
}> = []
|
||||||
|
for (const action of actions) {
|
||||||
|
const input = getListFormInputFromAction(action)
|
||||||
|
if (!input) continue
|
||||||
|
const rowLabel = getRowLabelIfMatches(input)
|
||||||
|
if (rowLabel == null) continue
|
||||||
|
const operation = getOperationFromInput(input, action)
|
||||||
|
matched.push({ action, input, operation, rowLabel: String(rowLabel) })
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadAuditLogs = async () => {
|
||||||
|
setAuditLoading(true)
|
||||||
|
setAuditError(null)
|
||||||
|
try {
|
||||||
|
const response = await apiService.fetchData<PagedResultDto<AuditLogDto>>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/audit-log',
|
||||||
|
params: {
|
||||||
|
skipCount: 0,
|
||||||
|
maxResultCount: 200,
|
||||||
|
sorting: 'ExecutionTime DESC',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = response.data?.items ?? []
|
||||||
|
const filtered = items
|
||||||
|
.map((log) => ({ log, matchedActions: buildMatchedActions(log) }))
|
||||||
|
.filter((x) => x.matchedActions.length > 0)
|
||||||
|
|
||||||
|
setAuditItems(filtered)
|
||||||
|
setAuditLoadedKey(`${listFormCodeNormalized}|${entityIdNormalized}`)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch audit logs:', e)
|
||||||
|
setAuditError(translate('::App.AuditLogs.FetchFailed'))
|
||||||
|
setAuditItems([])
|
||||||
|
setAuditLoadedKey(`${listFormCodeNormalized}|${entityIdNormalized}`)
|
||||||
|
} finally {
|
||||||
|
setAuditLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentTab !== 'audit') return
|
||||||
|
if (!listFormCodeNormalized && !entityIdNormalized) return
|
||||||
|
const key = `${listFormCodeNormalized}|${entityIdNormalized}`
|
||||||
|
if (auditLoadedKey === key) return
|
||||||
|
loadAuditLogs()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentTab, listFormCodeNormalized, entityIdNormalized])
|
||||||
|
|
||||||
|
const getStatusBadge = (statusCode?: number) => {
|
||||||
|
if (!statusCode) return <Badge className="bg-gray-500" content="?" />
|
||||||
|
if (statusCode >= 200 && statusCode < 300)
|
||||||
|
return <Badge className="bg-green-500" content={statusCode} />
|
||||||
|
if (statusCode >= 400 && statusCode < 500)
|
||||||
|
return <Badge className="bg-yellow-500" content={statusCode} />
|
||||||
|
if (statusCode >= 500) return <Badge className="bg-red-500" content={statusCode} />
|
||||||
|
return <Badge className="bg-blue-500" content={statusCode} />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="space-y-5 ml-5">
|
<Tabs
|
||||||
{notes.map((note, index) => {
|
value={currentTab}
|
||||||
const files = note.filesJson ? JSON.parse(note.filesJson) : []
|
onChange={(val) => setCurrentTab(val as 'notes' | 'audit')}
|
||||||
const creationDate = note.creationTime ? new Date(note.creationTime) : null
|
variant="pill"
|
||||||
const { icon, border } = getNoteStyle(note.type)
|
>
|
||||||
|
<TabList className="mb-4 bg-gray-50 p-1 rounded-lg">
|
||||||
|
<TabNav value="notes">
|
||||||
|
{translate('::ListForms.ListForm.Notes')}
|
||||||
|
<Badge className="ml-2 bg-blue-500" content={`${notes?.length ?? 0}`} />
|
||||||
|
</TabNav>
|
||||||
|
<TabNav value="audit">
|
||||||
|
{translate('::App.AuditLogs')}
|
||||||
|
<Badge className="ml-2 bg-purple-500" content={`${auditItems?.length ?? 0}`} />
|
||||||
|
</TabNav>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
return (
|
<TabContent value="notes">
|
||||||
<div
|
<div className="flex items-center justify-end mb-2">
|
||||||
key={note.id || index}
|
<Button
|
||||||
className={`relative bg-white border-l-4 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 ${border}`}
|
variant="solid"
|
||||||
|
size="xs"
|
||||||
|
type="button"
|
||||||
|
onClick={onAddNote}
|
||||||
|
disabled={!onAddNote}
|
||||||
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
{/* Timeline Düğmesi */}
|
<FaPlus className="mr-1" /> {translate('::ListForms.ListForm.AddNote')}
|
||||||
<div className="absolute -left-7 top-4 bg-white rounded-full border-2 border-gray-300 p-2">
|
</Button>
|
||||||
{icon}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
{(notes?.length ?? 0) === 0 ? (
|
||||||
{/* Header */}
|
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
||||||
<div className="flex justify-between items-start">
|
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
||||||
<div>
|
<p className="text-sm">{translate('::ListForms.ListForm.Notes.Empty')}</p>
|
||||||
<div className="flex items-center gap-1 text-sm font-semibold text-gray-800">
|
|
||||||
<Avatar
|
|
||||||
size={25}
|
|
||||||
shape="circle"
|
|
||||||
src={AVATAR_URL(note.creatorId, note.tenantId)}
|
|
||||||
/>
|
|
||||||
{note.createUserName}
|
|
||||||
</div>
|
|
||||||
{creationDate && (
|
|
||||||
<div className="flex items-center gap-1 text-xs text-gray-400 mt-1 ml-1">
|
|
||||||
<FaClock /> {creationDate.toLocaleString()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sil butonu */}
|
|
||||||
{user?.id === note.creatorId && (
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => onDeleteNote?.(note.id as string)}
|
|
||||||
title="Sil"
|
|
||||||
className="text-red-400 hover:text-red-600"
|
|
||||||
>
|
|
||||||
<FaTrash />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Body */}
|
|
||||||
<div className="mt-3 ml-1">
|
|
||||||
{note.subject && (
|
|
||||||
<h4 className="text-sm font-bold text-gray-900 mb-1">{note.subject}</h4>
|
|
||||||
)}
|
|
||||||
{note.content && (
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: note.content }} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Files */}
|
|
||||||
{files.length > 0 && (
|
|
||||||
<div className="mt-3 flex flex-wrap gap-2">
|
|
||||||
{files.map((file: any, index: number) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="group flex items-center gap-2 bg-gray-50 border border-gray-200 px-2 py-1 rounded-md text-xs text-gray-600 hover:bg-gray-100 transition"
|
|
||||||
>
|
|
||||||
<FaPaperclip className="text-blue-500" />
|
|
||||||
<span className="truncate max-w-[150px]">{file.FileName}</span>
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => onDownloadFile?.(file)}
|
|
||||||
title="İndir"
|
|
||||||
className="text-blue-500 hover:text-blue-700 ml-1"
|
|
||||||
>
|
|
||||||
<FaDownload />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
) : (
|
||||||
})}
|
<div className="space-y-5 ml-5">
|
||||||
</div>
|
{notes.map((note, index) => {
|
||||||
|
const files = note.filesJson ? JSON.parse(note.filesJson) : []
|
||||||
|
const creationDate = note.creationTime ? new Date(note.creationTime) : null
|
||||||
|
const { icon, border } = getNoteStyle(note.type)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={note.id || index}
|
||||||
|
className={`relative bg-white border-l-4 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 ${border}`}
|
||||||
|
>
|
||||||
|
{/* Timeline Düğmesi */}
|
||||||
|
<div className="absolute -left-7 top-4 bg-white rounded-full border-2 border-gray-300 p-2">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-1 text-sm font-semibold text-gray-800">
|
||||||
|
<Avatar
|
||||||
|
size={25}
|
||||||
|
shape="circle"
|
||||||
|
src={AVATAR_URL(note.creatorId, note.tenantId)}
|
||||||
|
/>
|
||||||
|
{note.createUserName}
|
||||||
|
</div>
|
||||||
|
{creationDate && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-gray-400 mt-1 ml-1">
|
||||||
|
<FaClock /> {creationDate.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sil butonu */}
|
||||||
|
{user?.id === note.creatorId && (
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => onDeleteNote?.(note.id as string)}
|
||||||
|
title={translate('::Delete')}
|
||||||
|
className="text-red-400 hover:text-red-600"
|
||||||
|
>
|
||||||
|
<FaTrash />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="mt-3 ml-1">
|
||||||
|
{note.subject && (
|
||||||
|
<h4 className="text-sm font-bold text-gray-900 mb-1">{note.subject}</h4>
|
||||||
|
)}
|
||||||
|
{note.content && <div dangerouslySetInnerHTML={{ __html: note.content }} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Files */}
|
||||||
|
{files.length > 0 && (
|
||||||
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
|
{files.map((file: any, index: number) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="group flex items-center gap-2 bg-gray-50 border border-gray-200 px-2 py-1 rounded-md text-xs text-gray-600 hover:bg-gray-100 transition"
|
||||||
|
>
|
||||||
|
<FaPaperclip className="text-blue-500" />
|
||||||
|
<span className="truncate max-w-[150px]">{file.FileName}</span>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => onDownloadFile?.(file)}
|
||||||
|
title={translate('::Download')}
|
||||||
|
className="text-blue-500 hover:text-blue-700 ml-1"
|
||||||
|
>
|
||||||
|
<FaDownload />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TabContent>
|
||||||
|
|
||||||
|
<TabContent value="audit">
|
||||||
|
<div className="flex items-center justify-end mb-2">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="default"
|
||||||
|
type="button"
|
||||||
|
onClick={loadAuditLogs}
|
||||||
|
disabled={auditLoading}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<FaSyncAlt className="mr-1" /> {translate('::ListForms.ListForm.Refresh')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{auditLoading ? (
|
||||||
|
<div className="flex items-center justify-center py-10">
|
||||||
|
<Spinner size={32} />
|
||||||
|
</div>
|
||||||
|
) : auditError ? (
|
||||||
|
<div className="text-sm text-red-600">{auditError}</div>
|
||||||
|
) : (auditItems?.length ?? 0) === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
||||||
|
<FaHistory className="text-4xl mb-2 opacity-50" />
|
||||||
|
<p className="text-sm">{translate('::ListForms.ListForm.AuditLogs.Empty')}</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4 ml-5">
|
||||||
|
{auditItems.map(({ log, matchedActions }) => {
|
||||||
|
const execTime = log.executionTime ? new Date(log.executionTime) : null
|
||||||
|
const changeCount = log.entityChangeCount ?? log.entityChanges?.length ?? 0
|
||||||
|
|
||||||
|
const primaryMatch = matchedActions?.[0]
|
||||||
|
const op = (primaryMatch?.operation ?? 'unknown') as string
|
||||||
|
const opLabel =
|
||||||
|
op === 'insert'
|
||||||
|
? translate('::Insert')
|
||||||
|
: op === 'update'
|
||||||
|
? translate('::Update')
|
||||||
|
: op === 'delete'
|
||||||
|
? translate('::Delete')
|
||||||
|
: translate('::Operation')
|
||||||
|
const opClass =
|
||||||
|
op === 'insert'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: op === 'update'
|
||||||
|
? 'bg-yellow-500'
|
||||||
|
: op === 'delete'
|
||||||
|
? 'bg-red-500'
|
||||||
|
: 'bg-gray-500'
|
||||||
|
|
||||||
|
const rowLabel = primaryMatch?.rowLabel ?? ''
|
||||||
|
|
||||||
|
const propertyChanges = (log.entityChanges ?? []).flatMap(
|
||||||
|
(c) => c?.propertyChanges ?? [],
|
||||||
|
)
|
||||||
|
const changeLines: string[] = []
|
||||||
|
if (propertyChanges.length > 0) {
|
||||||
|
for (const pc of propertyChanges) {
|
||||||
|
changeLines.push(
|
||||||
|
`${pc.propertyName}: ${(pc.originalValue ?? '') || '∅'} → ${(pc.newValue ?? '') || '∅'}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
primaryMatch?.input?.data &&
|
||||||
|
typeof primaryMatch.input.data === 'object'
|
||||||
|
) {
|
||||||
|
const entries = Object.entries(primaryMatch.input.data as Record<string, any>)
|
||||||
|
for (const [k, v] of entries) {
|
||||||
|
changeLines.push(`${k}: ${String(v)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={log.id}
|
||||||
|
className="relative bg-white border-l-4 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 border-purple-400"
|
||||||
|
>
|
||||||
|
<div className="absolute -left-7 top-4 bg-white rounded-full border-2 border-gray-300 p-2">
|
||||||
|
<FaHistory className="text-purple-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="flex items-center gap-1 text-sm font-semibold text-gray-800">
|
||||||
|
<Avatar
|
||||||
|
size={25}
|
||||||
|
shape="circle"
|
||||||
|
src={AVATAR_URL(log.userId, log.tenantId)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{log.userName}
|
||||||
|
</div>
|
||||||
|
{execTime && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-gray-400 mt-1">
|
||||||
|
<FaClock /> {execTime.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
||||||
|
<Badge className={opClass} content={opLabel} />
|
||||||
|
{rowLabel ? <Badge className="bg-gray-600" content={rowLabel} /> : null}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 mt-1 truncate">
|
||||||
|
{(log.httpMethod || 'HTTP') + ' ' + (log.url || '')}
|
||||||
|
</div>
|
||||||
|
{changeLines.length > 0 && (
|
||||||
|
<div className="mt-2 text-xs text-gray-600 space-y-1">
|
||||||
|
{changeLines.slice(0, 8).map((line, idx) => (
|
||||||
|
<div key={idx} className="truncate">
|
||||||
|
{line}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{changeLines.length > 8 && (
|
||||||
|
<div className="text-gray-400">
|
||||||
|
+{changeLines.length - 8} değişiklik daha
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
|
{getStatusBadge(log.httpStatusCode)}
|
||||||
|
<Badge className="bg-purple-500" content={`${changeCount}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TabContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,12 @@ import {
|
||||||
headerOptions,
|
headerOptions,
|
||||||
} from '@/proxy/reports/data'
|
} from '@/proxy/reports/data'
|
||||||
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
|
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
type: Yup.string().required('Not tipi zorunludur'),
|
type: Yup.string().required(),
|
||||||
subject: Yup.string().required('Konu zorunludur'),
|
subject: Yup.string().required(),
|
||||||
content: Yup.string().required('İçerik zorunludur'),
|
content: Yup.string().required(),
|
||||||
})
|
})
|
||||||
|
|
||||||
interface NoteModalProps {
|
interface NoteModalProps {
|
||||||
|
|
@ -37,11 +38,11 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
const [fileList, setFileList] = useState<File[]>([])
|
const [fileList, setFileList] = useState<File[]>([])
|
||||||
|
const { translate } = useLocalization()
|
||||||
const types = [
|
const types = [
|
||||||
{ value: 'note', label: 'Not' },
|
{ value: 'note', label: translate('::ListForms.ListForm.NoteModal.Type.Note') },
|
||||||
{ value: 'message', label: 'Mesaj' },
|
{ value: 'message', label: translate('::ListForms.ListForm.NoteModal.Type.Message') },
|
||||||
{ value: 'activity', label: 'Aktivite' },
|
{ value: 'activity', label: translate('::ListForms.ListForm.NoteModal.Type.Activity') },
|
||||||
]
|
]
|
||||||
|
|
||||||
const handleSave = async (values: any) => {
|
const handleSave = async (values: any) => {
|
||||||
|
|
@ -69,7 +70,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
const beforeUpload = (files: FileList | null) => {
|
const beforeUpload = (files: FileList | null) => {
|
||||||
if (!files) return true
|
if (!files) return true
|
||||||
for (const f of Array.from(files)) {
|
for (const f of Array.from(files)) {
|
||||||
if (f.size > 2 * 1024 * 1024) return 'En fazla 2MB dosya yükleyebilirsiniz'
|
if (f.size > 2 * 1024 * 1024) return translate('::ListForms.ListForm.NoteModal.Upload.MaxSize2Mb')
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +88,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
<div className="p-2 bg-purple-100 rounded-full">
|
<div className="p-2 bg-purple-100 rounded-full">
|
||||||
<FaPlus className="text-purple-600 text-lg" />
|
<FaPlus className="text-purple-600 text-lg" />
|
||||||
</div>
|
</div>
|
||||||
Not / Aktivite Ekle
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -104,11 +105,11 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
invalid={!!(errors.type && touched.type)}
|
invalid={!!(errors.type && touched.type)}
|
||||||
errorMessage={errors.type}
|
errorMessage={errors.type}
|
||||||
>
|
>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-2">
|
||||||
{types.map((t) => (
|
{types.map((t) => (
|
||||||
<label
|
<label
|
||||||
key={t.value}
|
key={t.value}
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-md cursor-pointer transition-all duration-200 ${
|
className={`flex items-center gap-2 px-2 py-1 rounded-md cursor-pointer transition-all duration-200 ${
|
||||||
values.type === t.value
|
values.type === t.value
|
||||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||||
: 'border-gray-300 hover:border-purple-400'
|
: 'border-gray-300 hover:border-purple-400'
|
||||||
|
|
@ -128,6 +129,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
{/* KONUSU */}
|
{/* KONUSU */}
|
||||||
<FormItem
|
<FormItem
|
||||||
label="Konu"
|
label="Konu"
|
||||||
|
asterisk
|
||||||
invalid={!!(errors.subject && touched.subject)}
|
invalid={!!(errors.subject && touched.subject)}
|
||||||
errorMessage={errors.subject}
|
errorMessage={errors.subject}
|
||||||
>
|
>
|
||||||
|
|
@ -135,7 +137,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
type="text"
|
type="text"
|
||||||
name="subject"
|
name="subject"
|
||||||
as={Input}
|
as={Input}
|
||||||
placeholder="Kısa bir başlık girin..."
|
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -152,7 +154,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onValueChanged={(e) => setFieldValue('content', e.value)}
|
onValueChanged={(e) => setFieldValue('content', e.value)}
|
||||||
height={220}
|
height={220}
|
||||||
placeholder="Notunuzu buraya yazın..."
|
placeholder={translate('::ListForms.ListForm.NoteModal.Content')}
|
||||||
>
|
>
|
||||||
<MediaResizing enabled={true} />
|
<MediaResizing enabled={true} />
|
||||||
<ImageUpload fileUploadMode="base64" />
|
<ImageUpload fileUploadMode="base64" />
|
||||||
|
|
@ -200,7 +202,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{/* DOSYA YÜKLEME */}
|
{/* DOSYA YÜKLEME */}
|
||||||
<FormItem label="Dosya Ekle">
|
<FormItem>
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
||||||
<Upload
|
<Upload
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
|
@ -223,7 +225,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
variant="twoTone"
|
variant="twoTone"
|
||||||
className="flex items-center justify-center mx-auto"
|
className="flex items-center justify-center mx-auto"
|
||||||
>
|
>
|
||||||
Dosya Yükle
|
{translate('::App.Listforms.ImportManager.UploadFile')}
|
||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
|
||||||
|
|
@ -261,7 +263,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
{/* ALT BUTONLAR */}
|
{/* ALT BUTONLAR */}
|
||||||
<div className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
<div className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
||||||
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
||||||
İptal
|
{translate('::Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
|
@ -270,7 +272,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
|
||||||
disabled={isSubmitting || !values.subject || !values.type}
|
disabled={isSubmitting || !values.subject || !values.type}
|
||||||
className="px-6 flex items-center gap-2"
|
className="px-6 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{uploading ? 'Yükleniyor...' : 'Ekle'}
|
{uploading ? translate('::App.Loading') : translate('::ListForms.Wizard.Add')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,12 @@ import { Button, Badge } from '@/components/ui'
|
||||||
import {
|
import {
|
||||||
FaChevronLeft,
|
FaChevronLeft,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaPlus,
|
|
||||||
FaTimes,
|
FaTimes,
|
||||||
FaGripVertical,
|
FaGripVertical,
|
||||||
FaChevronUp,
|
|
||||||
FaChevronDown,
|
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { noteService } from '@/services/note.service'
|
import { noteService } from '@/services/note.service'
|
||||||
import { NoteDto } from '@/proxy/note/models'
|
import { NoteDto } from '@/proxy/note/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface NotePanelProps {
|
interface NotePanelProps {
|
||||||
entityName: string
|
entityName: string
|
||||||
|
|
@ -34,6 +32,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
const [dragStart, setDragStart] = useState({ y: 0, startTop: 0 })
|
const [dragStart, setDragStart] = useState({ y: 0, startTop: 0 })
|
||||||
const buttonRef = useRef<HTMLDivElement>(null)
|
const buttonRef = useRef<HTMLDivElement>(null)
|
||||||
const [showEntityInfo, setShowEntityInfo] = useState(false)
|
const [showEntityInfo, setShowEntityInfo] = useState(false)
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
// Fetch activities
|
// Fetch activities
|
||||||
const fetchActivities = async () => {
|
const fetchActivities = async () => {
|
||||||
|
|
@ -58,12 +57,12 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
fileData.FileType,
|
fileData.FileType,
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Dosya indirilemedi', err)
|
console.error(translate('::ListForms.ListForm.NotesPanel.DownloadFailed'), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteActivity = async (activityId: string) => {
|
const handleDeleteActivity = async (activityId: string) => {
|
||||||
if (!confirm('Bu aktiviteyi silmek istediğinize emin misiniz?')) return
|
if (!confirm(translate('::DeleteConfirmation'))) return
|
||||||
try {
|
try {
|
||||||
await noteService.delete(activityId)
|
await noteService.delete(activityId)
|
||||||
setActivities((prev) => prev.filter((a) => a.id !== activityId))
|
setActivities((prev) => prev.filter((a) => a.id !== activityId))
|
||||||
|
|
@ -136,7 +135,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onToggle()
|
onToggle()
|
||||||
}}
|
}}
|
||||||
title={isVisible ? 'Paneli kapat' : 'Paneli aç'}
|
title={isVisible ? translate('::ListForms.ListForm.NotesPanel.ClosePanel') : translate('::ListForms.ListForm.NotesPanel.OpenPanel')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
||||||
|
|
@ -161,19 +160,14 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
<div className="p-4 border-b border-gray-200 bg-gray-50">
|
<div className="p-4 border-b border-gray-200 bg-gray-50">
|
||||||
{/* Üst Satır: Başlık, Kayıt Bilgisi Toggle ve Kapat Butonu */}
|
{/* Üst Satır: Başlık, Kayıt Bilgisi Toggle ve Kapat Butonu */}
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h3 className="text-lg font-semibold text-gray-800">Notlar</h3>
|
<div className="flex items-center gap-1 text-sm text-gray-700">
|
||||||
|
<span className="font-medium">{entityName}</span>
|
||||||
|
<code className="bg-gray-100 px-2 rounded text-gray-800 text-xs font-mono">
|
||||||
|
<Badge className="bg-blue-100 text-blue-600" content={entityId} />
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* 👇 Kayıt Bilgisi Aç/Kapa Butonu */}
|
|
||||||
<button
|
|
||||||
onClick={() => setShowEntityInfo((prev) => !prev)}
|
|
||||||
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 cursor-pointer select-none"
|
|
||||||
title="Kayıt Bilgisi"
|
|
||||||
>
|
|
||||||
{showEntityInfo ? <FaChevronUp /> : <FaChevronDown />}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Kapat Butonu */}
|
|
||||||
<Button variant="plain" size="xs" onClick={onToggle} title="Paneli kapat">
|
<Button variant="plain" size="xs" onClick={onToggle} title="Paneli kapat">
|
||||||
<FaTimes />
|
<FaTimes />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -186,30 +180,17 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
showEntityInfo ? 'max-h-20 mt-2 opacity-100' : 'max-h-0 opacity-0'
|
showEntityInfo ? 'max-h-20 mt-2 opacity-100' : 'max-h-0 opacity-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1 text-sm text-gray-700">
|
|
||||||
<span className="font-medium">{entityName}</span>
|
|
||||||
<code className="bg-gray-100 px-2 rounded text-gray-800 text-xs font-mono">
|
|
||||||
<Badge className="bg-blue-100 text-blue-600" content={entityId} />
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Alt buton: Not Ekle */}
|
|
||||||
<div className="flex gap-2 mt-3">
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => setShowAddModal(true)}
|
|
||||||
className="flex justify-center items-center py-4 w-full"
|
|
||||||
>
|
|
||||||
<FaPlus className="mr-1" /> Not Ekle
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<NoteList
|
<NoteList
|
||||||
notes={activities}
|
notes={activities}
|
||||||
|
entityName={entityName}
|
||||||
|
entityId={entityId}
|
||||||
|
onAddNote={() => setShowAddModal(true)}
|
||||||
onDeleteNote={handleDeleteActivity}
|
onDeleteNote={handleDeleteActivity}
|
||||||
onDownloadFile={handleDownloadFile}
|
onDownloadFile={handleDownloadFile}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue