Activity kaydı ve Blob kaydı
This commit is contained in:
parent
5e4726c9be
commit
6f91454ae8
32 changed files with 441 additions and 2006 deletions
|
|
@ -1,16 +1,18 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Content;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ActivityDto : FullAuditedEntityDto<Guid>
|
||||
public class ActivityDto : EntityDto<Guid>
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Content { get; set; }
|
||||
public string RecipientUserName { get; set; }
|
||||
public string FilesJson { get; set; }
|
||||
|
||||
protected ActivityDto() { }
|
||||
public IRemoteStreamContent[] Files { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ActivityFileDto : FullAuditedEntityDto<Guid>
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public string FileType { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public Guid ActivityItemId { get; set; }
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ActivityItemDto : FullAuditedEntityDto<Guid>, IMultiTenant
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public Guid? RecipientUserId { get; set; }
|
||||
public string RecipientUserName { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Content { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public Guid ActivityId { get; set; }
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Kurs.Platform.BlobStoring;
|
||||
using Kurs.Platform.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.BlobStoring;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Kurs.Platform.DataSources;
|
||||
namespace Kurs.Platform.Activities;
|
||||
|
||||
[Authorize]
|
||||
public class ActivityAppService : CrudAppService<
|
||||
|
|
@ -14,9 +18,28 @@ public class ActivityAppService : CrudAppService<
|
|||
Guid,
|
||||
PagedAndSortedResultRequestDto>
|
||||
{
|
||||
public ActivityAppService(
|
||||
IRepository<Activity, Guid> repo) : base(repo)
|
||||
{
|
||||
private readonly IBlobContainer<ActivityBlobContainer> ActivityBlobContainer;
|
||||
|
||||
public ActivityAppService(
|
||||
IRepository<Activity, Guid> repo,
|
||||
IBlobContainer<ActivityBlobContainer> activityBlobContainer
|
||||
) : base(repo)
|
||||
{
|
||||
ActivityBlobContainer = activityBlobContainer;
|
||||
|
||||
// CreatePolicyName = $"{AppCodes.Listforms.Listform}.Create";
|
||||
// UpdatePolicyName = $"{AppCodes.Listforms.Listform}.Update";
|
||||
// DeletePolicyName = $"{AppCodes.Listforms.Listform}.Delete";
|
||||
|
||||
// bool canAccess = await authManager.CanAccess(listFormCode, AuthorizationTypeEnum.Update);
|
||||
// if (!canAccess)
|
||||
// {
|
||||
// throw new UserFriendlyException(L[AppErrorCodes.NoAuth]);
|
||||
// }
|
||||
}
|
||||
|
||||
public override Task<ActivityDto> CreateAsync([FromForm] ActivityDto input)
|
||||
{
|
||||
return base.CreateAsync(input);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
using AutoMapper;
|
||||
using Kurs.Platform.Entities;
|
||||
|
||||
namespace Kurs.Platform.DataSources;
|
||||
namespace Kurs.Platform.Activities;
|
||||
|
||||
public class ActivityAutoMapperProfile : Profile
|
||||
{
|
||||
public ActivityAutoMapperProfile()
|
||||
{
|
||||
CreateMap<Activity, ActivityDto>();
|
||||
CreateMap<ActivityItem, ActivityItemDto>();
|
||||
CreateMap<ActivityFile, ActivityFileDto>();
|
||||
// Map from Activity to ActivityDto
|
||||
CreateMap<Activity, ActivityDto>()
|
||||
.ForMember(dest => dest.FilesJson, opt => opt.MapFrom(src => src.FilesJson))
|
||||
.ForMember(dest => dest.Files, opt => opt.Ignore()); // Ignore Files property as we handle it manually
|
||||
|
||||
// Mapping from ActivityDto to Activity
|
||||
CreateMap<ActivityDto, Activity>()
|
||||
.ForMember(dest => dest.TenantId, opt => opt.MapFrom(src => src.TenantId))
|
||||
.ForMember(dest => dest.EntityName, opt => opt.MapFrom(src => src.EntityName))
|
||||
.ForMember(dest => dest.EntityId, opt => opt.MapFrom(src => src.EntityId))
|
||||
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type))
|
||||
.ForMember(dest => dest.Subject, opt => opt.MapFrom(src => src.Subject))
|
||||
.ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content))
|
||||
// Handling the FilesJson field (as a JSON string)
|
||||
.ForMember(dest => dest.FilesJson, opt => opt.MapFrom(src => src.FilesJson)); // Adjust the mapping as needed
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using Kurs.Platform.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Kurs.Platform.DataSources;
|
||||
|
||||
[Authorize]
|
||||
public class ActivityFileAppService : CrudAppService<
|
||||
ActivityFile,
|
||||
ActivityFileDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto>
|
||||
{
|
||||
public ActivityFileAppService(
|
||||
IRepository<ActivityFile, Guid> repo) : base(repo)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using Kurs.Platform.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Kurs.Platform.DataSources;
|
||||
|
||||
[Authorize]
|
||||
public class ActivityItemAppService : CrudAppService<
|
||||
ActivityItem,
|
||||
ActivityItemDto,
|
||||
Guid,
|
||||
PagedAndSortedResultRequestDto>
|
||||
{
|
||||
public ActivityItemAppService(
|
||||
IRepository<ActivityItem, Guid> repo) : base(repo)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -5785,6 +5785,12 @@
|
|||
"en": "Export",
|
||||
"tr": "Dışa Ver"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "ListForms.ListFormEdit.PermissionsImport",
|
||||
"en": "Import",
|
||||
"tr": "İçe Aktar"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "ListForms.ListFormEdit.CommandColumns",
|
||||
|
|
@ -15256,7 +15262,7 @@
|
|||
"Order": 1,
|
||||
"Url": "/admin/list/list-bank",
|
||||
"Icon": "FcMoneyTransfer",
|
||||
"RequiredPermissionName": "App.Definitions.Bank",
|
||||
"RequiredPermissionName": "App.Accounting.Bank",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1428,6 +1428,7 @@ public static class PlatformConsts
|
|||
{
|
||||
public const string AvatarContainer = "Avatar";
|
||||
public const string ImportContainer = "Import";
|
||||
public const string ActivityContainer = "Activity";
|
||||
}
|
||||
|
||||
public static readonly ReadOnlyCollection<LanguageInfo> Languages = new(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
using Volo.Abp.BlobStoring;
|
||||
|
||||
namespace Kurs.Platform.BlobStoring;
|
||||
|
||||
[BlobContainerName(PlatformConsts.BlobContainers.ActivityContainer)]
|
||||
public class ActivityBlobContainer
|
||||
{
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
|
|
@ -8,24 +7,11 @@ namespace Kurs.Platform.Entities;
|
|||
public class Activity : FullAuditedEntity<Guid>, IMultiTenant
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Content { get; set; }
|
||||
public Guid? RecipientUserId { get; set; }
|
||||
public string RecipientUserName { get; set; }
|
||||
|
||||
public ICollection<ActivityItem> ActivityItems { get; set; }
|
||||
|
||||
protected Activity() { }
|
||||
|
||||
public Activity(Guid id, string type, string subject, string content, string recipientUserName = null)
|
||||
: base(id)
|
||||
{
|
||||
Type = type;
|
||||
Subject = subject;
|
||||
Content = content;
|
||||
RecipientUserName = recipientUserName;
|
||||
ActivityItems = new List<ActivityItem>();
|
||||
}
|
||||
|
||||
public string FilesJson { get; set; }
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ActivityFile : FullAuditedEntity<Guid>, IMultiTenant
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public string FileType { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public Guid ActivityItemId { get; set; }
|
||||
public ActivityItem ActivityItem { get; set; }
|
||||
|
||||
protected ActivityFile() { }
|
||||
|
||||
public ActivityFile(
|
||||
Guid id,
|
||||
Guid activityItemId,
|
||||
string entityName,
|
||||
string entityId,
|
||||
string fileName,
|
||||
long fileSize,
|
||||
string fileType,
|
||||
string filePath)
|
||||
: base(id)
|
||||
{
|
||||
ActivityItemId = activityItemId;
|
||||
EntityName = entityName;
|
||||
EntityId = entityId;
|
||||
FileName = fileName;
|
||||
FileSize = fileSize;
|
||||
FileType = fileType;
|
||||
FilePath = filePath;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ActivityItem : FullAuditedEntity<Guid>, IMultiTenant
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
public string Type { get; set; }
|
||||
public string EntityName { get; set; }
|
||||
public string EntityId { get; set; }
|
||||
public Guid? RecipientUserId { get; set; }
|
||||
public string RecipientUserName { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Content { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public Guid ActivityId { get; set; }
|
||||
public Activity Activity { get; set; }
|
||||
|
||||
public ICollection<ActivityFile> AttachedFiles { get; set; }
|
||||
|
||||
protected ActivityItem() { }
|
||||
|
||||
public ActivityItem(
|
||||
Guid id,
|
||||
Guid activityId,
|
||||
string type,
|
||||
string entityName,
|
||||
string entityId,
|
||||
string subject,
|
||||
string content,
|
||||
Guid? recipientUserId = null,
|
||||
string recipientUserName = null)
|
||||
: base(id)
|
||||
{
|
||||
ActivityId = activityId;
|
||||
Type = type;
|
||||
EntityName = entityName;
|
||||
EntityId = entityId;
|
||||
Subject = subject;
|
||||
Content = content;
|
||||
RecipientUserId = recipientUserId;
|
||||
RecipientUserName = recipientUserName;
|
||||
AttachedFiles = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -64,8 +64,6 @@ public class PlatformDbContext :
|
|||
public DbSet<ReportGenerated> ReportGenerated { get; set; }
|
||||
public DbSet<ReportCategory> ReportCategories { get; set; }
|
||||
public DbSet<Activity> Activities { get; set; }
|
||||
public DbSet<ActivityItem> ActivityItems { get; set; }
|
||||
public DbSet<ActivityFile> ActivityFiles { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Definitions from the modules
|
||||
|
|
@ -1559,65 +1557,9 @@ public class PlatformDbContext :
|
|||
b.ToTable(Prefix.DbTableDefault + nameof(Activity), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Subject).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.Content).IsRequired().HasMaxLength(2000);
|
||||
b.Property(x => x.RecipientUserName).HasMaxLength(100);
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
|
||||
b.HasIndex(x => x.Type);
|
||||
b.HasIndex(x => x.CreatorId);
|
||||
b.HasIndex(x => x.CreationTime);
|
||||
});
|
||||
|
||||
builder.Entity<ActivityItem>(b =>
|
||||
{
|
||||
b.ToTable(Prefix.DbTableDefault + nameof(ActivityItem), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.Property(x => x.EntityName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.EntityId).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Subject).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.Content).IsRequired().HasMaxLength(2000);
|
||||
b.Property(x => x.RecipientUserName).HasMaxLength(100);
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
|
||||
// Foreign key relationship
|
||||
b.HasOne(ai => ai.Activity)
|
||||
.WithMany(a => a.ActivityItems)
|
||||
.HasForeignKey(ai => ai.ActivityId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => x.ActivityId);
|
||||
b.HasIndex(x => x.EntityName);
|
||||
b.HasIndex(x => x.EntityId);
|
||||
b.HasIndex(x => x.Type);
|
||||
b.HasIndex(x => x.RecipientUserId);
|
||||
b.HasIndex(x => x.CreatorId);
|
||||
});
|
||||
|
||||
builder.Entity<ActivityFile>(b =>
|
||||
{
|
||||
b.ToTable(Prefix.DbTableDefault + nameof(ActivityFile), Prefix.DbSchema);
|
||||
b.ConfigureByConvention();
|
||||
|
||||
b.Property(x => x.EntityName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.EntityId).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.FileName).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.FileType).IsRequired().HasMaxLength(50);
|
||||
b.Property(x => x.FilePath).IsRequired().HasMaxLength(512);
|
||||
b.Property(x => x.FileSize).IsRequired();
|
||||
|
||||
// Foreign key relationship
|
||||
b.HasOne(af => af.ActivityItem)
|
||||
.WithMany(ai => ai.AttachedFiles)
|
||||
.HasForeignKey(af => af.ActivityItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => x.ActivityItemId);
|
||||
b.HasIndex(x => x.EntityName);
|
||||
b.HasIndex(x => x.EntityId);
|
||||
b.HasIndex(x => x.FileName);
|
||||
b.HasIndex(x => x.CreatorId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Kurs.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20251013134213_Initial")]
|
||||
[Migration("20251013214306_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -651,6 +651,15 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FilesJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
|
@ -665,10 +674,6 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("RecipientUserName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
|
|
@ -678,194 +683,15 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreationTime");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("PActivity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ActivityItemId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("FileType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ActivityItemId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.HasIndex("EntityName");
|
||||
|
||||
b.HasIndex("FileName");
|
||||
|
||||
b.ToTable("PActivityFile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ActivityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<Guid?>("RecipientUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("RecipientUserName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ActivityId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.HasIndex("EntityName");
|
||||
|
||||
b.HasIndex("RecipientUserId");
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("PActivityItem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -8860,28 +8686,6 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("NotificationRule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityFile", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.ActivityItem", "ActivityItem")
|
||||
.WithMany("AttachedFiles")
|
||||
.HasForeignKey("ActivityItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ActivityItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Activity", "Activity")
|
||||
.WithMany("ActivityItems")
|
||||
.HasForeignKey("ActivityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Activity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ApiEndpoint", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.CustomEntity", "Entity")
|
||||
|
|
@ -9380,16 +9184,6 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Activity", b =>
|
||||
{
|
||||
b.Navigation("ActivityItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.Navigation("AttachedFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.BlogCategory", b =>
|
||||
{
|
||||
b.Navigation("Posts");
|
||||
|
|
@ -1214,10 +1214,12 @@ namespace Kurs.Platform.Migrations
|
|||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Type = table.Column<int>(type: "int", nullable: false),
|
||||
EntityName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
EntityId = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Type = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Subject = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
Content = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
|
||||
RecipientUserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
FilesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
|
|
@ -2612,39 +2614,6 @@ namespace Kurs.Platform.Migrations
|
|||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PActivityItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Type = table.Column<int>(type: "int", nullable: false),
|
||||
EntityName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
EntityId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
RecipientUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
RecipientUserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Subject = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
Content = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
|
||||
ActivityId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PActivityItem", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PActivityItem_PActivity_ActivityId",
|
||||
column: x => x.ActivityId,
|
||||
principalTable: "PActivity",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PBackgroundWorker_MailQueue",
|
||||
columns: table => new
|
||||
|
|
@ -3348,38 +3317,6 @@ namespace Kurs.Platform.Migrations
|
|||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PActivityFile",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
EntityId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
FileName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
FileSize = table.Column<long>(type: "bigint", nullable: false),
|
||||
FileType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
FilePath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||
ActivityItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PActivityFile", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PActivityFile_PActivityItem_ActivityItemId",
|
||||
column: x => x.ActivityItemId,
|
||||
principalTable: "PActivityItem",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PBackgroundWorker_MailQueueEvents",
|
||||
columns: table => new
|
||||
|
|
@ -3919,76 +3856,6 @@ namespace Kurs.Platform.Migrations
|
|||
table: "OpenIddictTokens",
|
||||
column: "ReferenceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivity_CreationTime",
|
||||
table: "PActivity",
|
||||
column: "CreationTime");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivity_CreatorId",
|
||||
table: "PActivity",
|
||||
column: "CreatorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivity_Type",
|
||||
table: "PActivity",
|
||||
column: "Type");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityFile_ActivityItemId",
|
||||
table: "PActivityFile",
|
||||
column: "ActivityItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityFile_CreatorId",
|
||||
table: "PActivityFile",
|
||||
column: "CreatorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityFile_EntityId",
|
||||
table: "PActivityFile",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityFile_EntityName",
|
||||
table: "PActivityFile",
|
||||
column: "EntityName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityFile_FileName",
|
||||
table: "PActivityFile",
|
||||
column: "FileName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_ActivityId",
|
||||
table: "PActivityItem",
|
||||
column: "ActivityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_CreatorId",
|
||||
table: "PActivityItem",
|
||||
column: "CreatorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_EntityId",
|
||||
table: "PActivityItem",
|
||||
column: "EntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_EntityName",
|
||||
table: "PActivityItem",
|
||||
column: "EntityName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_RecipientUserId",
|
||||
table: "PActivityItem",
|
||||
column: "RecipientUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PActivityItem_Type",
|
||||
table: "PActivityItem",
|
||||
column: "Type");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PApiEndpoint_EntityId",
|
||||
table: "PApiEndpoint",
|
||||
|
|
@ -4378,7 +4245,7 @@ namespace Kurs.Platform.Migrations
|
|||
name: "OpenIddictTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PActivityFile");
|
||||
name: "PActivity");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PAiBot");
|
||||
|
|
@ -4533,9 +4400,6 @@ namespace Kurs.Platform.Migrations
|
|||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictAuthorizations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PActivityItem");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PBackgroundWorker_MailQueue");
|
||||
|
||||
|
|
@ -4581,9 +4445,6 @@ namespace Kurs.Platform.Migrations
|
|||
migrationBuilder.DropTable(
|
||||
name: "OpenIddictApplications");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PActivity");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PBackgroundWorker_MailQueueTableFormat");
|
||||
|
||||
|
|
@ -648,6 +648,15 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("FilesJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
|
|
@ -662,10 +671,6 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<string>("RecipientUserName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
|
|
@ -675,194 +680,15 @@ namespace Kurs.Platform.Migrations
|
|||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreationTime");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("PActivity", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ActivityItemId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("FileType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ActivityItemId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.HasIndex("EntityName");
|
||||
|
||||
b.HasIndex("FileName");
|
||||
|
||||
b.ToTable("PActivityFile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ActivityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("CreationTime");
|
||||
|
||||
b.Property<Guid?>("CreatorId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("CreatorId");
|
||||
|
||||
b.Property<Guid?>("DeleterId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("DeleterId");
|
||||
|
||||
b.Property<DateTime?>("DeletionTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("DeletionTime");
|
||||
|
||||
b.Property<string>("EntityId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EntityName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("IsDeleted");
|
||||
|
||||
b.Property<DateTime?>("LastModificationTime")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("LastModificationTime");
|
||||
|
||||
b.Property<Guid?>("LastModifierId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("LastModifierId");
|
||||
|
||||
b.Property<Guid?>("RecipientUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("RecipientUserName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<Guid?>("TenantId")
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasColumnName("TenantId");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ActivityId");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("EntityId");
|
||||
|
||||
b.HasIndex("EntityName");
|
||||
|
||||
b.HasIndex("RecipientUserId");
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("PActivityItem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -8857,28 +8683,6 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("NotificationRule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityFile", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.ActivityItem", "ActivityItem")
|
||||
.WithMany("AttachedFiles")
|
||||
.HasForeignKey("ActivityItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ActivityItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Activity", "Activity")
|
||||
.WithMany("ActivityItems")
|
||||
.HasForeignKey("ActivityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Activity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ApiEndpoint", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.CustomEntity", "Entity")
|
||||
|
|
@ -9377,16 +9181,6 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.Activity", b =>
|
||||
{
|
||||
b.Navigation("ActivityItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ActivityItem", b =>
|
||||
{
|
||||
b.Navigation("AttachedFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.BlogCategory", b =>
|
||||
{
|
||||
b.Navigation("Posts");
|
||||
|
|
|
|||
|
|
@ -340,6 +340,14 @@ public class PlatformHttpApiHostModule : AbpModule
|
|||
fileSystem.BasePath = configuration["App:ImportPath"];
|
||||
});
|
||||
});
|
||||
|
||||
options.Containers.Configure<ActivityBlobContainer>(container =>
|
||||
{
|
||||
container.UseFileSystem(fileSystem =>
|
||||
{
|
||||
fileSystem.BasePath = configuration["App:ActivityPath"];
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"AttachmentsPath": "/etc/api/mail-queue/attachments",
|
||||
"CdnPath": "/etc/api/cdn",
|
||||
"ImportPath": "/etc/api/import",
|
||||
"ActivityPath": "/etc/api/activity",
|
||||
"BaseDomain": "sozsoft.com"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"AttachmentsPath": "/etc/api/mail-queue/attachments",
|
||||
"CdnPath": "/etc/api/cdn",
|
||||
"ImportPath": "/etc/api/import",
|
||||
"ActivityPath": "/etc/api/activity",
|
||||
"BaseDomain": "sozsoft.com"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"AttachmentsPath": "C:\\Private\\Projects\\sozsoft\\configs\\mail-queue\\attachments",
|
||||
"CdnPath": "C:\\Private\\Projects\\sozsoft\\configs\\docker\\data\\cdn",
|
||||
"ImportPath": "C:\\Private\\Projects\\sozsoft\\configs\\docker\\data\\import",
|
||||
"ActivityPath": "C:\\Private\\Projects\\sozsoft\\configs\\docker\\data\\activity",
|
||||
"Version": "1.0.1"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,11 @@
|
|||
import { FullAuditedEntityDto } from "../abp";
|
||||
import { AuditedEntityDto } from "../abp";
|
||||
|
||||
export interface ActivityDto extends FullAuditedEntityDto<string> {
|
||||
export interface ActivityDto extends AuditedEntityDto {
|
||||
tenantId?: string;
|
||||
type?: string;
|
||||
subject?: string;
|
||||
content?: string;
|
||||
recipientUserName?: string;
|
||||
entityName: string;
|
||||
entityId: string;
|
||||
type: string;
|
||||
subject: string;
|
||||
content: string;
|
||||
files: File[];
|
||||
}
|
||||
|
||||
export interface ActivityFileDto extends FullAuditedEntityDto<string> {
|
||||
tenantId?: string;
|
||||
entityName?: string;
|
||||
entityId?: string;
|
||||
fileName?: string;
|
||||
fileSize?: number;
|
||||
fileType?: string;
|
||||
filePath?: string;
|
||||
activityItemId: string;
|
||||
}
|
||||
|
||||
export interface ActivityItemDto extends FullAuditedEntityDto<string> {
|
||||
tenantId?: string;
|
||||
type?: string;
|
||||
entityName?: string;
|
||||
entityId?: string;
|
||||
recipientUserId?: string;
|
||||
recipientUserName?: string;
|
||||
subject?: string;
|
||||
content?: string;
|
||||
activityId: string;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
export interface Activity {
|
||||
id: string
|
||||
type: 'note' | 'message'
|
||||
subject: string
|
||||
content: string
|
||||
recipientUserName?: string
|
||||
creatorId: string
|
||||
creationTime: Date
|
||||
data?: ActivityItem
|
||||
}
|
||||
|
||||
export interface ActivityItem {
|
||||
id?: string
|
||||
type: 'note' | 'message'
|
||||
entityName: string
|
||||
entityId: string
|
||||
recipientUserId?: string
|
||||
recipientUserName?: string
|
||||
subject: string
|
||||
content: string
|
||||
creatorId?: string
|
||||
creationTime?: Date
|
||||
attachedFiles?: ActivityFile[]
|
||||
}
|
||||
|
||||
export interface ActivityFile {
|
||||
id?: string
|
||||
entityName: string
|
||||
entityId: string
|
||||
fileName: string
|
||||
fileSize: number
|
||||
fileType: string
|
||||
filePath: string
|
||||
creatorId?: string
|
||||
creationTime?: Date
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import { ActivityDto, ActivityItemDto, ActivityFileDto } from '@/proxy/activity/models'
|
||||
import { PagedResultDto } from '@/proxy'
|
||||
import { ActivityDto } from '@/proxy/activity/models'
|
||||
import apiService from '@/services/api.service'
|
||||
import { AxiosError } from 'axios'
|
||||
|
||||
class ActivityService {
|
||||
async getList(params?: any): Promise<ActivityDto[]> {
|
||||
const response = await apiService.fetchData<ActivityDto[]>({
|
||||
async getList(params?: any): Promise<PagedResultDto<ActivityDto>> {
|
||||
const response = await apiService.fetchData<PagedResultDto<ActivityDto>>({
|
||||
url: '/api/app/activity',
|
||||
method: 'GET',
|
||||
params,
|
||||
|
|
@ -19,13 +21,45 @@ class ActivityService {
|
|||
return response.data
|
||||
}
|
||||
|
||||
async create(data: ActivityDto): Promise<ActivityDto> {
|
||||
const response = await apiService.fetchData<ActivityDto>({
|
||||
url: '/api/app/activity',
|
||||
method: 'POST',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
async create(data: ActivityDto): Promise<any> {
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('entityName', data.entityName)
|
||||
formData.append('entityId', data.entityId)
|
||||
formData.append('type', data.type)
|
||||
formData.append('subject', data.subject)
|
||||
formData.append('content', data.content)
|
||||
|
||||
// Ensure files are actual File objects, not blob URLs
|
||||
if (data.files && data.files.length > 0) {
|
||||
for (const file of data.files) {
|
||||
// Ensure file is a File object (in case it's a Blob URL, you need to get the actual file)
|
||||
if (file instanceof File) {
|
||||
formData.append('files', file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('FormData:', formData)
|
||||
|
||||
// Make the POST request
|
||||
const response = await apiService.fetchData({
|
||||
url: '/api/app/activity',
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
// Don't manually set the Content-Type header; let the browser handle it
|
||||
})
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
console.error('Error creating activity:', error.response?.data)
|
||||
return error.response?.data
|
||||
} else {
|
||||
console.error('Unexpected error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: string, data: ActivityDto): Promise<ActivityDto> {
|
||||
|
|
@ -43,92 +77,6 @@ class ActivityService {
|
|||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
// 🔹 ActivityItem kayıtları ---------------------------------------------------
|
||||
async getItems(activityId?: string): Promise<ActivityItemDto[]> {
|
||||
const response = await apiService.fetchData<ActivityItemDto[]>({
|
||||
url: '/api/app/activity-item',
|
||||
method: 'GET',
|
||||
params: { activityId },
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getItem(id: string): Promise<ActivityItemDto> {
|
||||
const response = await apiService.fetchData<ActivityItemDto>({
|
||||
url: `/api/app/activity-item/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async createItem(data: ActivityItemDto): Promise<ActivityItemDto> {
|
||||
const response = await apiService.fetchData<ActivityItemDto>({
|
||||
url: '/api/app/activity-item',
|
||||
method: 'POST',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async updateItem(id: string, data: ActivityItemDto): Promise<ActivityItemDto> {
|
||||
const response = await apiService.fetchData<ActivityItemDto>({
|
||||
url: `/api/app/activity-item/${id}`,
|
||||
method: 'PUT',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async deleteItem(id: string): Promise<void> {
|
||||
await apiService.fetchData({
|
||||
url: `/api/app/activity-item/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
// 🔹 ActivityFile kayıtları ---------------------------------------------------
|
||||
async getFiles(activityItemId?: string): Promise<ActivityFileDto[]> {
|
||||
const response = await apiService.fetchData<ActivityFileDto[]>({
|
||||
url: '/api/app/activity-file',
|
||||
method: 'GET',
|
||||
params: { activityItemId },
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getFile(id: string): Promise<ActivityFileDto> {
|
||||
const response = await apiService.fetchData<ActivityFileDto>({
|
||||
url: `/api/app/activity-file/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async uploadFile(data: ActivityFileDto): Promise<ActivityFileDto> {
|
||||
const response = await apiService.fetchData<ActivityFileDto>({
|
||||
url: '/api/app/activity-file',
|
||||
method: 'POST',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async updateFile(id: string, data: ActivityFileDto): Promise<ActivityFileDto> {
|
||||
const response = await apiService.fetchData<ActivityFileDto>({
|
||||
url: `/api/app/activity-file/${id}`,
|
||||
method: 'PUT',
|
||||
data: data as any,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
async deleteFile(id: string): Promise<void> {
|
||||
await apiService.fetchData({
|
||||
url: `/api/app/activity-file/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const activityService = new ActivityService()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const schema = object().shape({
|
|||
u: string(),
|
||||
d: string(),
|
||||
e: string(),
|
||||
i: string(),
|
||||
})
|
||||
|
||||
function FormTabPermissions(props: FormEditProps) {
|
||||
|
|
@ -183,6 +184,31 @@ function FormTabPermissions(props: FormEditProps) {
|
|||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.PermissionsImport')}
|
||||
invalid={errors.permissionDto?.i && touched.permissionDto?.i}
|
||||
errorMessage={errors.permissionDto?.i}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="permissionDto.i"
|
||||
placeholder={translate('::ListForms.ListFormEdit.PermissionsImport')}
|
||||
>
|
||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||
<Select
|
||||
field={field}
|
||||
form={form}
|
||||
isClearable={true}
|
||||
options={permissions}
|
||||
value={permissions?.filter(
|
||||
(option) => option.value === values.permissionDto.i,
|
||||
)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
|
||||
<Button block variant="solid" loading={isSubmitting} type="submit">
|
||||
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||
|
|
|
|||
|
|
@ -9,31 +9,19 @@ import {
|
|||
FaClock,
|
||||
} from 'react-icons/fa'
|
||||
import { Button } from '@/components/ui'
|
||||
import { Activity } from '@/proxy/formActivity/models'
|
||||
import { ActivityDto } from '@/proxy/activity/models'
|
||||
|
||||
interface ActivityListProps {
|
||||
activities: Activity[]
|
||||
activities: ActivityDto[]
|
||||
onDeleteActivity?: (activityId: string) => void
|
||||
onDeleteFile?: (fileId: string) => void
|
||||
onDownloadFile?: (fileData: any) => void
|
||||
}
|
||||
|
||||
export const ActivityList: React.FC<ActivityListProps> = ({
|
||||
activities,
|
||||
onDeleteActivity,
|
||||
onDeleteFile,
|
||||
onDownloadFile,
|
||||
}) => {
|
||||
const formatDate = (date: Date) => {
|
||||
return new Intl.DateTimeFormat('tr-TR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(new Date(date))
|
||||
}
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'note':
|
||||
|
|
@ -45,9 +33,9 @@ export const ActivityList: React.FC<ActivityListProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
const handleDelete = (activity: Activity) => {
|
||||
onDeleteActivity?.(activity.id)
|
||||
}
|
||||
// const handleDelete = (activity: ActivityDto) => {
|
||||
// onDeleteActivity?.(activity)
|
||||
// }
|
||||
|
||||
const handleDownloadFile = (fileData: any) => {
|
||||
onDownloadFile?.(fileData)
|
||||
|
|
@ -76,26 +64,20 @@ export const ActivityList: React.FC<ActivityListProps> = ({
|
|||
<div className="flex items-center gap-1 font-semibold mb-1">
|
||||
<FaUser className="text-xs" />
|
||||
<span>{activity.creatorId}</span>
|
||||
|
||||
{activity.recipientUserName && (
|
||||
<>
|
||||
<span>→</span>
|
||||
<FaUser className="text-xs" />
|
||||
<span>{activity.recipientUserName}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{activity.subject && (
|
||||
<p className="text-sm font-medium text-gray-800 mb-1 break-words">{activity.subject}</p>
|
||||
<p className="text-sm font-medium text-gray-800 mb-1 break-words">
|
||||
{activity.subject}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
{activity.content && (
|
||||
<p className="text-sm text-gray-700 mb-2 break-words">{activity.content}</p>
|
||||
)}
|
||||
|
||||
{/* Note tipinde dosyaları göster */}
|
||||
{activity.type === 'note' && activity.data && (activity.data as any).attachedFiles?.length > 0 && (
|
||||
{/* {activity.type === 'note' && activity.data && (activity.data as any).attachedFiles?.length > 0 && (
|
||||
<div className="mt-2 mb-2">
|
||||
{((activity.data as any).attachedFiles || []).map((file: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between p-2 bg-gray-50 rounded border">
|
||||
|
|
@ -116,19 +98,19 @@ export const ActivityList: React.FC<ActivityListProps> = ({
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1 text-xs text-gray-500">
|
||||
<FaClock />
|
||||
{formatDate(activity.creationTime)}
|
||||
{activity.creationTime?.toLocaleString()}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="plain"
|
||||
size="xs"
|
||||
onClick={() => handleDelete(activity)}
|
||||
// onClick={() => handleDelete(activity)}
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="text-red-500" />
|
||||
|
|
|
|||
191
ui/src/views/form/FormActivityPanel/ActivityModal.tsx
Normal file
191
ui/src/views/form/FormActivityPanel/ActivityModal.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Button, Input, Dialog, Select, FormContainer, FormItem, Upload } from '@/components/ui'
|
||||
import { FaFileUpload, FaStickyNote, FaPlus, FaTrash, FaPaperclip } from 'react-icons/fa'
|
||||
import { Field, Form, Formik } from 'formik'
|
||||
import { SelectBoxOption } from '@/shared/types'
|
||||
import * as Yup from 'yup'
|
||||
import { activityService } from '@/services/activity.service'
|
||||
|
||||
// Validation schema
|
||||
const validationSchema = Yup.object({
|
||||
type: Yup.string().required('Not tipi zorunludur'),
|
||||
subject: Yup.string().required('Konu zorunludur'),
|
||||
content: Yup.string().required('İçerik zorunludur'),
|
||||
})
|
||||
|
||||
interface AddContentModalProps {
|
||||
entityName: string
|
||||
entityId: string
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const AddContentModal: React.FC<AddContentModalProps> = ({
|
||||
entityName,
|
||||
entityId,
|
||||
isOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [files, setFiles] = useState<string[] | undefined>()
|
||||
|
||||
const types: SelectBoxOption[] = [
|
||||
{ value: 'note', label: 'Not' },
|
||||
{ value: 'message', label: 'Mesaj' },
|
||||
{ value: 'activity', label: 'Aktivite' },
|
||||
]
|
||||
|
||||
// onSaveContent güncelleme
|
||||
const handleSave = async (values: any) => {
|
||||
setUploading(true)
|
||||
try {
|
||||
// API çağrısı
|
||||
await activityService.create({ ...values, entityName, entityId, files })
|
||||
|
||||
//onClose() // Modal'ı kapat
|
||||
} catch (error) {
|
||||
console.error('Save failed:', error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const beforeUpload = (files: FileList | null, fileList: File[]) => {
|
||||
let valid: string | boolean = true
|
||||
|
||||
const maxFileSize = 2000000
|
||||
|
||||
if (fileList.length >= 1) {
|
||||
return `Sadece bir dosya seçebilirsiniz`
|
||||
}
|
||||
|
||||
if (files) {
|
||||
for (const f of files) {
|
||||
if (f.size >= maxFileSize) {
|
||||
valid = 'En fazla 2mb dosya yükleyebilirsiniz'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
const onChooseImage = async (file: File[]) => {
|
||||
if (file.length === 0) {
|
||||
setFiles(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
setFiles(file.map((f) => URL.createObjectURL(f)))
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={onClose}>
|
||||
<div className="p-2 w-full mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||
<div className="p-2 bg-purple-100 rounded-full">
|
||||
<FaPlus className="text-purple-600 text-lg" />
|
||||
</div>
|
||||
Not Ekle
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
type: 'note',
|
||||
subject: '',
|
||||
content: '',
|
||||
}}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSave}
|
||||
>
|
||||
{({ values, touched, errors, setFieldValue, isSubmitting }) => (
|
||||
<Form>
|
||||
<FormContainer size="sm">
|
||||
{/* Not Tipi */}
|
||||
<FormItem
|
||||
label="Not Tipi"
|
||||
invalid={!!(errors.type && touched.type)}
|
||||
errorMessage={errors.type}
|
||||
>
|
||||
<Field name="type">
|
||||
{({ field }: any) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={types.find((t) => t.value === values.type)}
|
||||
onChange={(selected: any) => setFieldValue('type', selected.value)}
|
||||
options={types}
|
||||
isSearchable={false}
|
||||
isClearable
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
|
||||
{/* Konu */}
|
||||
<FormItem
|
||||
label="Konu"
|
||||
invalid={!!(errors.subject && touched.subject)}
|
||||
errorMessage={errors.subject}
|
||||
>
|
||||
<Field type="text" name="subject" component={Input} />
|
||||
</FormItem>
|
||||
|
||||
{/* Not İçeriği */}
|
||||
<FormItem
|
||||
label="Not İçeriği"
|
||||
invalid={!!(errors.content && touched.content)}
|
||||
errorMessage={errors.content}
|
||||
>
|
||||
<Field
|
||||
name="content"
|
||||
render={({ field }: any) => (
|
||||
<textarea
|
||||
{...field}
|
||||
placeholder="Notunuzu buraya yazın..."
|
||||
className="w-full p-2 border border-gray-300 rounded-md resize-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
rows={4}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
{/* Dosya Yükleme */}
|
||||
<FormItem label="Dosya Ekle">
|
||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
||||
<Upload
|
||||
className="cursor-pointer"
|
||||
showList={false}
|
||||
multiple={false}
|
||||
uploadLimit={1}
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={onChooseImage}
|
||||
>
|
||||
<Button icon={<FaFileUpload />} type="button"></Button>
|
||||
</Upload>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormContainer>
|
||||
|
||||
<div className="mt-4 flex justify-between items-center pt-4 border-t border-gray-200">
|
||||
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
||||
İptal
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="md"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !values.subject || !values.type}
|
||||
className="px-6 flex items-center gap-2"
|
||||
>
|
||||
{uploading ? 'Yükleniyor...' : 'Ekle'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Button, Input, Dialog, Select } from '@/components/ui'
|
||||
import { FaFileUpload, FaEnvelope, FaStickyNote, FaUsers, FaTimes, FaPlus, FaTrash, FaPaperclip } from 'react-icons/fa'
|
||||
import { ActivityFile } from '@/proxy/formActivity/models'
|
||||
import { getUsers } from '@/services/identity.service'
|
||||
import { IdentityUserDto } from '@/proxy/admin/models'
|
||||
|
||||
// Birleştirilmiş Not ve Dosya Ekleme Modal'ı
|
||||
interface AddContentModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSaveContent: (subject: string, content: string, files: File[]) => Promise<any>
|
||||
}
|
||||
|
||||
export const AddContentModal: React.FC<AddContentModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSaveContent
|
||||
}) => {
|
||||
const [subject, setSubject] = useState('')
|
||||
const [content, setContent] = useState('')
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
const handleFileSelect = (files: FileList | null) => {
|
||||
if (files && files.length > 0) {
|
||||
const newFiles = Array.from(files)
|
||||
setSelectedFiles(prev => [...prev, ...newFiles])
|
||||
}
|
||||
}
|
||||
|
||||
const removeFile = (index: number) => {
|
||||
setSelectedFiles(prev => prev.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (content.trim() || selectedFiles.length > 0) {
|
||||
setUploading(true)
|
||||
try {
|
||||
await onSaveContent(subject.trim(), content.trim(), selectedFiles)
|
||||
resetForm()
|
||||
onClose()
|
||||
} catch (error) {
|
||||
console.error('Save failed:', error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
setContent('')
|
||||
setSelectedFiles([])
|
||||
setUploading(false)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
resetForm()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const isFormValid = content.trim() || selectedFiles.length > 0
|
||||
const totalFileSize = selectedFiles.reduce((total, file) => total + file.size, 0)
|
||||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose}>
|
||||
<div className="p-6 w-full mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||
<div className="p-2 bg-purple-100 rounded-full">
|
||||
<FaPlus className="text-purple-600 text-lg" />
|
||||
</div>
|
||||
Not Ekle
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 mb-2">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Konu <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={subject}
|
||||
onChange={(e) => setSubject(e.target.value)}
|
||||
placeholder="Mesajın konusunu girin..."
|
||||
className="w-full"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Not Alanı */}
|
||||
<div>
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-gray-700 mb-2">
|
||||
<FaStickyNote className="text-yellow-500" />
|
||||
Not İçeriği <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder="Notunuzu buraya yazın... (İsteğe bağlı)"
|
||||
className="w-full h-32 p-4 border border-gray-300 rounded-md resize-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-colors duration-200"
|
||||
style={{ fontSize: '14px', lineHeight: '1.5' }}
|
||||
/>
|
||||
<div className="mt-1 text-xs text-gray-500 text-right">
|
||||
{content.length} karakter
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dosya Yükleme Alanı */}
|
||||
<div>
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-gray-700 mb-2">
|
||||
<FaPaperclip className="text-blue-500" />
|
||||
Dosya Ekle
|
||||
</label>
|
||||
|
||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={(e) => handleFileSelect(e.target.files)}
|
||||
className="hidden"
|
||||
id="file-upload"
|
||||
disabled={uploading}
|
||||
/>
|
||||
<label
|
||||
htmlFor="file-upload"
|
||||
className="cursor-pointer flex flex-col items-center gap-2"
|
||||
>
|
||||
<FaFileUpload className="text-2xl text-gray-400" />
|
||||
<div className="text-sm text-gray-600">
|
||||
<span className="font-medium text-purple-600 hover:text-purple-700">
|
||||
Dosya seçmek için tıklayın
|
||||
</span>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Birden fazla dosya seçebilirsiniz
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Seçili Dosyalar */}
|
||||
{selectedFiles.length > 0 && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
Seçili Dosyalar ({selectedFiles.length})
|
||||
</div>
|
||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
||||
{selectedFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-md">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm truncate">{file.name}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{(file.size / 1024).toFixed(2)} KB • {file.type || 'Bilinmeyen tür'}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="plain"
|
||||
size="xs"
|
||||
onClick={() => removeFile(index)}
|
||||
className="ml-2 text-red-500 hover:text-red-700"
|
||||
disabled={uploading}
|
||||
>
|
||||
<FaTrash />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Toplam boyut: {(totalFileSize / 1024).toFixed(2)} KB
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center pt-4 border-t border-gray-200">
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="default"
|
||||
size="md"
|
||||
onClick={handleClose}
|
||||
disabled={uploading}
|
||||
className="px-6"
|
||||
>
|
||||
İptal
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="md"
|
||||
onClick={handleSave}
|
||||
disabled={!isFormValid || uploading}
|
||||
className="px-6 flex items-center gap-2"
|
||||
>
|
||||
{uploading ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||
Yükleniyor...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaPlus />
|
||||
Ekle
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
// Eski modallar geriye dönük uyumluluk için korundu
|
||||
interface AddNoteModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSave: (content: string) => void
|
||||
}
|
||||
|
||||
export const AddNoteModal: React.FC<AddNoteModalProps> = ({ isOpen, onClose, onSave }) => {
|
||||
return (
|
||||
<AddContentModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSaveContent={async (content: string) => {
|
||||
if (content) {
|
||||
onSave(content)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface AddFileModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onUpload: (file: File) => Promise<ActivityFile>
|
||||
}
|
||||
|
||||
export const AddFileModal: React.FC<AddFileModalProps> = ({ isOpen, onClose, onUpload }) => {
|
||||
return (
|
||||
<AddContentModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSaveContent={async (subject: string, content: string, files: File[]) => {
|
||||
for (const file of files) {
|
||||
await onUpload(file)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface UserOption {
|
||||
value: string
|
||||
label: string
|
||||
email?: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
interface SendMessageModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSend: (recipients: UserOption[], subject: string, content: string) => void
|
||||
}
|
||||
|
||||
interface MessageTemplate {
|
||||
value: string
|
||||
label: string
|
||||
subject?: string
|
||||
content?: string
|
||||
}
|
||||
|
||||
const MESSAGE_TEMPLATES: MessageTemplate[] = [
|
||||
{ value: '', label: 'Şablon seçin...' },
|
||||
{ value: 'follow_up', label: 'Takip Mesajı', subject: 'Form Durumu', content: 'Merhaba,\n\nForm ile ilgili güncelleme bekliyoruz. Lütfen en kısa sürede dönüş yapabilir misiniz?\n\nTeşekkürler.' },
|
||||
{ value: 'approval', label: 'Onay Talebi', subject: 'Form İncelemesi', content: 'Sayın yetkili,\n\nForm incelemenizi ve onayınızı rica ediyoruz.\n\nSaygılarımla.' },
|
||||
{ value: 'rejection', label: 'Red Bildirimi', subject: 'Form Red Edildi', content: 'Merhaba,\n\nForm talebiniz aşağıdaki nedenlerle red edilmiştir:\n\n- [Nedeni buraya yazın]\n\nDüzeltme sonrası tekrar başvurabilirsiniz.' },
|
||||
{ value: 'completion', label: 'Tamamlanma Bildirimi', subject: 'Form Tamamlandı', content: 'Tebrikler!\n\nForm işleminiz başarıyla tamamlanmıştır.\n\nİyi günler.' }
|
||||
]
|
||||
|
||||
export const SendMessageModal: React.FC<SendMessageModalProps> = ({ isOpen, onClose, onSend }) => {
|
||||
const [selectedUsers, setSelectedUsers] = useState<UserOption[]>([])
|
||||
const [subject, setSubject] = useState('')
|
||||
const [content, setContent] = useState('')
|
||||
const [users, setUsers] = useState<UserOption[]>([])
|
||||
const [isLoadingUsers, setIsLoadingUsers] = useState(false)
|
||||
const [error, setError] = useState<string>('')
|
||||
const [selectedTemplate, setSelectedTemplate] = useState('')
|
||||
|
||||
// Load users when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadUsers()
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const loadUsers = async () => {
|
||||
setIsLoadingUsers(true)
|
||||
setError('')
|
||||
try {
|
||||
const response = await getUsers(0, 100) // Get first 100 users
|
||||
const userOptions: UserOption[] = (response.data.items || [])
|
||||
.filter((user: IdentityUserDto) => user.isActive && user.userName)
|
||||
.map((user: IdentityUserDto) => ({
|
||||
value: user.id || '',
|
||||
label: `${user.name} ${user.surname}` || '',
|
||||
email: user.email,
|
||||
isActive: user.isActive
|
||||
}))
|
||||
|
||||
setUsers(userOptions)
|
||||
} catch (err) {
|
||||
console.error('Failed to load users:', err)
|
||||
setError('Kullanıcılar yüklenirken hata oluştu')
|
||||
} finally {
|
||||
setIsLoadingUsers(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSend = () => {
|
||||
if (selectedUsers.length > 0 && subject.trim() && content.trim()) {
|
||||
onSend(selectedUsers, subject.trim(), content.trim())
|
||||
resetForm()
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const handleTemplateChange = (templateValue: string) => {
|
||||
setSelectedTemplate(templateValue)
|
||||
const template = MESSAGE_TEMPLATES.find(t => t.value === templateValue)
|
||||
if (template && template.value && template.subject && template.content) {
|
||||
setSubject(template.subject)
|
||||
setContent(template.content)
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
setSelectedUsers([])
|
||||
setSubject('')
|
||||
setContent('')
|
||||
setError('')
|
||||
setSelectedTemplate('')
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
resetForm()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const isFormValid = selectedUsers.length > 0 && subject.trim() && content.trim()
|
||||
|
||||
const customStyles = {
|
||||
control: (provided: any) => ({
|
||||
...provided,
|
||||
minHeight: '42px',
|
||||
borderRadius: '6px',
|
||||
borderColor: '#d1d5db',
|
||||
'&:hover': {
|
||||
borderColor: '#9ca3af',
|
||||
},
|
||||
'&:focus-within': {
|
||||
borderColor: '#3b82f6',
|
||||
boxShadow: '0 0 0 2px rgba(59, 130, 246, 0.1)',
|
||||
},
|
||||
}),
|
||||
multiValue: (provided: any) => ({
|
||||
...provided,
|
||||
backgroundColor: '#e0e7ff',
|
||||
borderRadius: '4px',
|
||||
}),
|
||||
multiValueLabel: (provided: any) => ({
|
||||
...provided,
|
||||
color: '#3730a3',
|
||||
fontSize: '14px',
|
||||
}),
|
||||
multiValueRemove: (provided: any) => ({
|
||||
...provided,
|
||||
color: '#6b7280',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f87171',
|
||||
color: 'white',
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose}>
|
||||
<div className="p-6 w-full mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||
<div className="p-2 bg-green-100 rounded-full">
|
||||
<FaEnvelope className="text-green-600 text-lg" />
|
||||
</div>
|
||||
Yeni Mesaj Gönder
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-5 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Mesaj Şablonu
|
||||
</label>
|
||||
<Select
|
||||
value={MESSAGE_TEMPLATES.find(t => t.value === selectedTemplate)}
|
||||
onChange={(selected) => {
|
||||
const template = selected as MessageTemplate
|
||||
handleTemplateChange(template?.value || '')
|
||||
}}
|
||||
options={MESSAGE_TEMPLATES}
|
||||
placeholder="İsteğe bağlı: Hazır şablon seçin..."
|
||||
isSearchable={false}
|
||||
isClearable
|
||||
styles={{
|
||||
control: (provided: any) => ({
|
||||
...provided,
|
||||
minHeight: '38px',
|
||||
borderRadius: '6px',
|
||||
borderColor: '#e5e7eb',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-gray-700 mb-2">
|
||||
<FaUsers className="text-blue-500" />
|
||||
Alıcılar <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Select
|
||||
isMulti
|
||||
isLoading={isLoadingUsers}
|
||||
value={selectedUsers}
|
||||
onChange={(selected) => setSelectedUsers(selected as UserOption[])}
|
||||
options={users}
|
||||
placeholder={isLoadingUsers ? "Kullanıcılar yükleniyor..." : "Kullanıcıları seçin..."}
|
||||
noOptionsMessage={() => "Kullanıcı bulunamadı"}
|
||||
loadingMessage={() => "Kullanıcılar yükleniyor..."}
|
||||
isSearchable
|
||||
styles={customStyles}
|
||||
closeMenuOnSelect={false}
|
||||
hideSelectedOptions={false}
|
||||
formatOptionLabel={(option: UserOption) => (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{option.label}</div>
|
||||
</div>
|
||||
<div className={`w-2 h-2 rounded-full ${option.isActive ? 'bg-green-400' : 'bg-gray-300'}`} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{selectedUsers.length > 0 && (
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<div className="text-sm text-gray-600">
|
||||
{selectedUsers.length} kullanıcı seçildi
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedUsers([])}
|
||||
className="text-xs text-gray-500 hover:text-red-500 underline"
|
||||
>
|
||||
Tümünü Kaldır
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Konu <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={subject}
|
||||
onChange={(e) => setSubject(e.target.value)}
|
||||
placeholder="Mesajın konusunu girin..."
|
||||
className="w-full"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Mesaj İçeriği <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder="Mesajınızı buraya yazın..."
|
||||
className="w-full h-40 p-4 border border-gray-300 rounded-md resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors duration-200"
|
||||
style={{ fontSize: '14px', lineHeight: '1.5' }}
|
||||
/>
|
||||
<div className="mt-1 text-xs text-gray-500 text-right">
|
||||
{content.length} karakter
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center pt-4 border-t border-gray-200">
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="default"
|
||||
size="md"
|
||||
onClick={handleClose}
|
||||
className="px-6"
|
||||
>
|
||||
İptal
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="md"
|
||||
onClick={handleSend}
|
||||
disabled={!isFormValid}
|
||||
className="px-6 flex items-center gap-2"
|
||||
>
|
||||
<FaEnvelope />
|
||||
Gönder
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,16 +1,10 @@
|
|||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { useFormActivity } from './useFormActivity'
|
||||
import { AddContentModal, SendMessageModal } from './ActivityModals'
|
||||
import { AddContentModal } from './ActivityModal'
|
||||
import { ActivityList } from './ActivityList'
|
||||
import { Button, Badge } from '@/components/ui'
|
||||
import {
|
||||
FaChevronLeft,
|
||||
FaChevronRight,
|
||||
FaPlus,
|
||||
FaEnvelope,
|
||||
FaTimes,
|
||||
FaGripVertical,
|
||||
} from 'react-icons/fa'
|
||||
import { FaChevronLeft, FaChevronRight, FaPlus, FaTimes, FaGripVertical } from 'react-icons/fa'
|
||||
import { activityService } from '@/services/activity.service'
|
||||
import { ActivityDto } from '@/proxy/activity/models'
|
||||
|
||||
interface ActivityPanelProps {
|
||||
entityName: string
|
||||
|
|
@ -19,30 +13,33 @@ interface ActivityPanelProps {
|
|||
onToggle: () => void
|
||||
}
|
||||
|
||||
export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
||||
export const ActivityPanel: React.FC<ActivityPanelProps> = ({
|
||||
entityName,
|
||||
entityId,
|
||||
isVisible,
|
||||
onToggle,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'activities' | 'notes' | 'messages'>('activities')
|
||||
const [showAddContent, setShowAddContent] = useState(false)
|
||||
const [showSendMessage, setShowSendMessage] = useState(false)
|
||||
|
||||
// Draggable button state
|
||||
const [buttonPosition, setButtonPosition] = useState({ top: '50%' })
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [dragStart, setDragStart] = useState({ y: 0, startTop: 0 })
|
||||
const [activities, setActivities] = useState<ActivityDto[]>([])
|
||||
const buttonRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
activities,
|
||||
loading,
|
||||
deleteActivity,
|
||||
deleteFile,
|
||||
addNote,
|
||||
sendMessage,
|
||||
} = useFormActivity(entityName, entityId)
|
||||
useEffect(() => {
|
||||
const fetchActivities = async () => {
|
||||
// Simulate API call to fetch activities
|
||||
// Gerçek implementasyonda API'dan veri çekilecek
|
||||
const fetchedActivities = await activityService.getList()
|
||||
if (fetchedActivities && fetchedActivities.items) {
|
||||
setActivities(fetchedActivities.items)
|
||||
}
|
||||
}
|
||||
|
||||
fetchActivities()
|
||||
}, [isVisible])
|
||||
|
||||
const handleDownloadFile = (fileData: any) => {
|
||||
// Simulate file download - gerçek implementasyonda API'dan dosya indirme yapılacak
|
||||
|
|
@ -53,8 +50,6 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
}
|
||||
|
||||
const getTotalCount = () => activities.length
|
||||
const getNoteCount = () => activities.filter(a => a.type === 'note').length
|
||||
const getMessageCount = () => activities.filter(a => a.type === 'message').length
|
||||
|
||||
// Mouse drag handlers
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
|
|
@ -144,6 +139,23 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
return null
|
||||
}
|
||||
|
||||
// // Example onSaveContent function
|
||||
// const onSaveContent = async (
|
||||
// entityName: string,
|
||||
// entityId: string,
|
||||
// type: string,
|
||||
// subject: string,
|
||||
// content: string,
|
||||
// files: File[],
|
||||
// ) => {
|
||||
// try {
|
||||
// await activityService.create({ entityName, entityId, type, subject, content, files } as any)
|
||||
// } catch (error) {
|
||||
// console.error('Error saving content:', error)
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Toggle Button - Draggable */}
|
||||
|
|
@ -171,7 +183,7 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
shape='none'
|
||||
shape="none"
|
||||
className="!rounded-l-full !rounded-r-none"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -189,10 +201,7 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
|
||||
{/* Overlay - Click outside to close */}
|
||||
{isVisible && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-25 z-20"
|
||||
onClick={onToggle}
|
||||
/>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25 z-20" onClick={onToggle} />
|
||||
)}
|
||||
|
||||
{/* Activity Panel */}
|
||||
|
|
@ -216,7 +225,7 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
<div className="text-sm text-gray-600 mb-3 flex items-center gap-2">
|
||||
<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} />
|
||||
<Badge className="bg-blue-100 text-blue-600" content={entityId} />
|
||||
</code>
|
||||
</div>
|
||||
|
||||
|
|
@ -231,105 +240,26 @@ export const FormActivityPanel: React.FC<ActivityPanelProps> = ({
|
|||
<FaPlus className="mr-1" />
|
||||
Not Ekle
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="xs"
|
||||
onClick={() => setShowSendMessage(true)}
|
||||
className="flex justify-center items-center py-4 w-full"
|
||||
>
|
||||
<FaEnvelope className="mr-1" />
|
||||
Mesaj Gönder
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-gray-200">
|
||||
<button
|
||||
onClick={() => setActiveTab('activities')}
|
||||
className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === 'activities'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Tümü ({getTotalCount()})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('notes')}
|
||||
className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === 'notes'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Notlar ({getNoteCount()})
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setActiveTab('messages')}
|
||||
className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === 'messages'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Mesajlar ({getMessageCount()})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span className="ml-2 text-gray-600">Yükleniyor...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{activeTab === 'activities' && (
|
||||
<ActivityList
|
||||
activities={activities}
|
||||
onDeleteActivity={deleteActivity}
|
||||
onDeleteFile={deleteFile}
|
||||
onDownloadFile={handleDownloadFile}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'notes' && (
|
||||
<ActivityList
|
||||
activities={activities.filter((a) => a.type === 'note')}
|
||||
onDeleteActivity={deleteActivity}
|
||||
onDeleteFile={deleteFile}
|
||||
onDownloadFile={handleDownloadFile}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'messages' && (
|
||||
<ActivityList
|
||||
activities={activities.filter((a) => a.type === 'message')}
|
||||
onDeleteActivity={deleteActivity}
|
||||
onDeleteFile={deleteFile}
|
||||
onDownloadFile={handleDownloadFile}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<ActivityList
|
||||
activities={activities}
|
||||
// onDeleteActivity={deleteActivity}
|
||||
onDownloadFile={handleDownloadFile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<AddContentModal
|
||||
entityName={entityName}
|
||||
entityId={entityId}
|
||||
isOpen={showAddContent}
|
||||
onClose={() => setShowAddContent(false)}
|
||||
onSaveContent={addNote}
|
||||
/>
|
||||
|
||||
<SendMessageModal
|
||||
isOpen={showSendMessage}
|
||||
onClose={() => setShowSendMessage(false)}
|
||||
onSend={sendMessage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
import { ActivityItem, ActivityFile, Activity } from '@/proxy/formActivity/models'
|
||||
import { ActivityDto, ActivityItemDto, ActivityFileDto } from '@/proxy/activity/models'
|
||||
import { activityService } from '@/services/activity.service'
|
||||
import { useStoreState } from '@/store/store'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export const useFormActivity = (entityName: string, entityId: string) => {
|
||||
const [activityItems, setActivityItems] = useState<ActivityItem[]>([])
|
||||
const [files, setFiles] = useState<ActivityFile[]>([])
|
||||
const [activities, setActivities] = useState<Activity[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
|
||||
// Load data from API on component mount
|
||||
useEffect(() => {
|
||||
if (!entityName || !entityId) return
|
||||
loadActivitiesFromAPI()
|
||||
}, [entityName, entityId])
|
||||
|
||||
const loadActivitiesFromAPI = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// Tüm ActivityItem'ları al
|
||||
const items = await activityService.getItems()
|
||||
|
||||
// EntityName ve EntityId ile filtrele
|
||||
const filteredItems = items.filter(
|
||||
item => item.entityName === entityName && item.entityId === entityId
|
||||
)
|
||||
|
||||
// DTO'dan ActivityItem'a dönüştür
|
||||
const convertedItems: ActivityItem[] = filteredItems.map(dto => ({
|
||||
id: dto.id,
|
||||
type: (dto.type as 'note' | 'message') || 'note',
|
||||
entityName: dto.entityName || entityName,
|
||||
entityId: dto.entityId || entityId,
|
||||
recipientUserId: dto.recipientUserId,
|
||||
recipientUserName: dto.recipientUserName,
|
||||
subject: dto.subject || '',
|
||||
content: dto.content || '',
|
||||
creatorId: dto.creatorId || user.id,
|
||||
creationTime: dto.creationTime ? new Date(dto.creationTime) : new Date(),
|
||||
attachedFiles: []
|
||||
}))
|
||||
|
||||
// Her ActivityItem için dosyaları yükle
|
||||
for (const item of convertedItems) {
|
||||
if (item.id) {
|
||||
try {
|
||||
const itemFiles = await activityService.getFiles(item.id)
|
||||
const convertedFiles: ActivityFile[] = itemFiles.map(fileDto => ({
|
||||
id: fileDto.id,
|
||||
entityName: fileDto.entityName || entityName,
|
||||
entityId: fileDto.entityId || entityId,
|
||||
fileName: fileDto.fileName || '',
|
||||
fileSize: fileDto.fileSize || 0,
|
||||
fileType: fileDto.fileType || '',
|
||||
filePath: fileDto.filePath || '',
|
||||
creatorId: fileDto.creatorId || user.id,
|
||||
creationTime: fileDto.creationTime ? new Date(fileDto.creationTime) : new Date(),
|
||||
}))
|
||||
item.attachedFiles = convertedFiles
|
||||
setFiles(prev => [...prev, ...convertedFiles])
|
||||
} catch (error) {
|
||||
console.error('Failed to load files for activity item:', item.id, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setActivityItems(convertedItems)
|
||||
} catch (error) {
|
||||
console.error('Failed to load activities from API:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Update activities when data changes
|
||||
useEffect(() => {
|
||||
const allActivities: Activity[] = []
|
||||
|
||||
// Convert all activity items to activities
|
||||
activityItems.forEach((item) => {
|
||||
allActivities.push({
|
||||
id: item.id || `${item.type}_${Date.now()}`,
|
||||
type: item.type,
|
||||
subject: item.subject || '',
|
||||
content: item.content,
|
||||
recipientUserName: item.recipientUserName,
|
||||
creationTime: item.creationTime || new Date(),
|
||||
creatorId: item.creatorId || 'Bilinmeyen',
|
||||
data: item,
|
||||
})
|
||||
})
|
||||
|
||||
// Sort by timestamp (newest first)
|
||||
allActivities.sort(
|
||||
(a, b) => new Date(b.creationTime).getTime() - new Date(a.creationTime).getTime(),
|
||||
)
|
||||
setActivities(allActivities)
|
||||
}, [activityItems, files])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const addNote = async (subject: string, content: string, files: File[]) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// Önce Activity oluştur
|
||||
const activityDto: Partial<ActivityDto> = {
|
||||
type: 'note',
|
||||
subject: subject || 'Not',
|
||||
content: content || (files.length > 0 ? `${files.length} dosya eklendi` : ''),
|
||||
}
|
||||
|
||||
const createdActivity = await activityService.create(activityDto as ActivityDto)
|
||||
|
||||
// ActivityItem oluştur
|
||||
const activityItemDto: Partial<ActivityItemDto> = {
|
||||
activityId: createdActivity.id,
|
||||
type: 'note',
|
||||
entityName,
|
||||
entityId,
|
||||
subject: subject || 'Not',
|
||||
content: content || (files.length > 0 ? `${files.length} dosya eklendi` : ''),
|
||||
}
|
||||
|
||||
const createdActivityItem = await activityService.createItem(activityItemDto as ActivityItemDto)
|
||||
|
||||
// Dosyaları yükle
|
||||
const uploadedFiles: ActivityFile[] = []
|
||||
for (const file of files) {
|
||||
try {
|
||||
const activityFileDto: Partial<ActivityFileDto> = {
|
||||
activityItemId: createdActivityItem.id,
|
||||
entityName,
|
||||
entityId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
fileType: file.type,
|
||||
filePath: `uploads/${entityName}/${entityId}/${file.name}`,
|
||||
}
|
||||
|
||||
const createdFile = await activityService.uploadFile(activityFileDto as ActivityFileDto)
|
||||
|
||||
const newFile: ActivityFile = {
|
||||
id: createdFile.id,
|
||||
entityName: createdFile.entityName || entityName,
|
||||
entityId: createdFile.entityId || entityId,
|
||||
fileName: createdFile.fileName || file.name,
|
||||
fileSize: createdFile.fileSize || file.size,
|
||||
fileType: createdFile.fileType || file.type,
|
||||
filePath: createdFile.filePath || '',
|
||||
creatorId: createdFile.creatorId || user.id,
|
||||
creationTime: createdFile.creationTime ? new Date(createdFile.creationTime) : new Date(),
|
||||
}
|
||||
|
||||
uploadedFiles.push(newFile)
|
||||
} catch (error) {
|
||||
console.error('File upload failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Yeni ActivityItem'ı local state'e ekle
|
||||
const newActivityItem: ActivityItem = {
|
||||
id: createdActivityItem.id,
|
||||
type: 'note',
|
||||
entityName,
|
||||
entityId,
|
||||
subject: createdActivityItem.subject || '',
|
||||
content: createdActivityItem.content || '',
|
||||
creatorId: createdActivityItem.creatorId || user.id,
|
||||
creationTime: createdActivityItem.creationTime ? new Date(createdActivityItem.creationTime) : new Date(),
|
||||
attachedFiles: uploadedFiles
|
||||
}
|
||||
|
||||
setActivityItems((prev) => [...prev, newActivityItem])
|
||||
if (uploadedFiles.length > 0) {
|
||||
setFiles((prev) => [...prev, ...uploadedFiles])
|
||||
}
|
||||
|
||||
return newActivityItem
|
||||
} catch (error) {
|
||||
console.error('Failed to create note:', error)
|
||||
throw error
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const deleteActivity = async (itemId: string) => {
|
||||
try {
|
||||
// API'dan ActivityItem'ı sil
|
||||
await activityService.deleteItem(itemId)
|
||||
|
||||
// Local state'ten kaldır
|
||||
setActivityItems((prev) => prev.filter((item) => item.id !== itemId))
|
||||
|
||||
// İlgili dosyaları da kaldır
|
||||
setFiles((prev) => prev.filter((file) =>
|
||||
!(file as any).activityItemId || (file as any).activityItemId !== itemId
|
||||
))
|
||||
} catch (error) {
|
||||
console.error('Failed to delete activity:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const deleteFile = async (fileId: string) => {
|
||||
try {
|
||||
// API'dan ActivityFile'ı sil
|
||||
await activityService.deleteFile(fileId)
|
||||
|
||||
// Local state'ten kaldır
|
||||
setFiles((prev) => prev.filter((file) => file.id !== fileId))
|
||||
|
||||
// ActivityItem'dan da kaldır
|
||||
setActivityItems((prev) =>
|
||||
prev.map(item => ({
|
||||
...item,
|
||||
attachedFiles: item.attachedFiles?.filter(file => file.id !== fileId) || []
|
||||
}))
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete file:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const sendMessage = async (
|
||||
recipients: Array<{ value: string; label: string; email?: string }>,
|
||||
subject: string,
|
||||
content: string,
|
||||
) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const newMessageItems: ActivityItem[] = []
|
||||
|
||||
// Her recipient için ayrı mesaj aktivitesi oluştur
|
||||
for (const recipient of recipients) {
|
||||
try {
|
||||
// Activity oluştur
|
||||
const activityDto: Partial<ActivityDto> = {
|
||||
type: 'message',
|
||||
subject,
|
||||
content,
|
||||
recipientUserName: recipient.label,
|
||||
}
|
||||
|
||||
const createdActivity = await activityService.create(activityDto as ActivityDto)
|
||||
|
||||
// ActivityItem oluştur
|
||||
const activityItemDto: Partial<ActivityItemDto> = {
|
||||
activityId: createdActivity.id,
|
||||
type: 'message',
|
||||
entityName,
|
||||
entityId,
|
||||
recipientUserId: recipient.value,
|
||||
recipientUserName: recipient.label,
|
||||
subject,
|
||||
content,
|
||||
}
|
||||
|
||||
const createdActivityItem = await activityService.createItem(activityItemDto as ActivityItemDto)
|
||||
|
||||
const newMessageItem: ActivityItem = {
|
||||
id: createdActivityItem.id,
|
||||
type: 'message',
|
||||
entityName,
|
||||
entityId,
|
||||
recipientUserId: createdActivityItem.recipientUserId,
|
||||
recipientUserName: createdActivityItem.recipientUserName,
|
||||
subject: createdActivityItem.subject || '',
|
||||
content: createdActivityItem.content || '',
|
||||
creatorId: createdActivityItem.creatorId || user.id,
|
||||
creationTime: createdActivityItem.creationTime ? new Date(createdActivityItem.creationTime) : new Date(),
|
||||
}
|
||||
|
||||
newMessageItems.push(newMessageItem)
|
||||
} catch (error) {
|
||||
console.error('Failed to send message to recipient:', recipient.label, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Tüm mesajları activityItems'a ekle
|
||||
setActivityItems((prev) => [...prev, ...newMessageItems])
|
||||
return newMessageItems
|
||||
} catch (error) {
|
||||
console.error('Failed to send messages:', error)
|
||||
throw error
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activityItems,
|
||||
files,
|
||||
activities,
|
||||
loading,
|
||||
// Ana aktivite fonksiyonları
|
||||
deleteActivity,
|
||||
addNote,
|
||||
sendMessage,
|
||||
// Dosya işlemleri
|
||||
deleteFile,
|
||||
// Veri yenileme
|
||||
refreshActivities: loadActivitiesFromAPI,
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import { useGridData } from './useGridData'
|
|||
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
|
||||
import { Badge } from '@/components/ui'
|
||||
import { useState } from 'react'
|
||||
import { FormActivityPanel } from './FormActivityPanel/FormActivityPanel'
|
||||
import { ActivityPanel } from './FormActivityPanel/ActivityPanel'
|
||||
|
||||
const FormEdit = (
|
||||
props: FormProps = {
|
||||
|
|
@ -124,7 +124,7 @@ const FormEdit = (
|
|||
|
||||
{/* Activity Panel - sadece ana formda göster */}
|
||||
{!isSubForm && listFormCode && id && gridDto?.gridOptions?.showActivity && (
|
||||
<FormActivityPanel
|
||||
<ActivityPanel
|
||||
entityName={listFormCode}
|
||||
entityId={id}
|
||||
isVisible={isActivityPanelVisible}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { useGridData } from './useGridData'
|
|||
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
|
||||
import { Badge } from '@/components/ui'
|
||||
import { useState } from 'react'
|
||||
import { FormActivityPanel } from './FormActivityPanel/FormActivityPanel'
|
||||
import { ActivityPanel } from './FormActivityPanel/ActivityPanel'
|
||||
|
||||
const FormView = (
|
||||
props: FormProps = {
|
||||
|
|
@ -119,7 +119,7 @@ const FormView = (
|
|||
|
||||
{/* Activity Panel - sadece ana formda göster */}
|
||||
{!isSubForm && listFormCode && id && gridDto?.gridOptions?.showActivity && (
|
||||
<FormActivityPanel
|
||||
<ActivityPanel
|
||||
entityName={listFormCode}
|
||||
entityId={id}
|
||||
isVisible={isActivityPanelVisible}
|
||||
|
|
|
|||
Loading…
Reference in a new issue