Codex 5.6 Sol Genel Optimizasyon
This commit is contained in:
parent
351eddfc0a
commit
e7be51a1be
48 changed files with 678 additions and 485 deletions
|
|
@ -153,6 +153,32 @@ public class NotificationAppService : ReadOnlyAppService<
|
||||||
return ObjectMapper.Map<Notification, NotificationDto>(item);
|
return ObjectMapper.Map<Notification, NotificationDto>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> UpdateReadManyAsync(List<Guid> notificationIds, bool isRead)
|
||||||
|
{
|
||||||
|
if (notificationIds.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = await repository.GetQueryableAsync();
|
||||||
|
return await query
|
||||||
|
.Where(a => a.UserId == CurrentUser.Id && notificationIds.Contains(a.Id) && a.IsRead != isRead)
|
||||||
|
.ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsRead, isRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> UpdateSentManyAsync(List<Guid> notificationIds, bool isSent)
|
||||||
|
{
|
||||||
|
if (notificationIds.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = await repository.GetQueryableAsync();
|
||||||
|
return await query
|
||||||
|
.Where(a => a.UserId == CurrentUser.Id && notificationIds.Contains(a.Id) && a.IsSent != isSent)
|
||||||
|
.ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsSent, isSent));
|
||||||
|
}
|
||||||
|
|
||||||
[RemoteService(false)]
|
[RemoteService(false)]
|
||||||
public override Task<NotificationDto> GetAsync(Guid id) => throw new NotImplementedException();
|
public override Task<NotificationDto> GetAsync(Guid id) => throw new NotImplementedException();
|
||||||
[RemoteService(false)]
|
[RemoteService(false)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.DeveloperKit;
|
namespace Sozsoft.Platform.DeveloperKit;
|
||||||
|
|
@ -6,6 +7,7 @@ namespace Sozsoft.Platform.DeveloperKit;
|
||||||
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string RoutePath { get; set; } = string.Empty;
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
@ -16,6 +18,10 @@ public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||||
public class CreateUpdateCustomComponentDto
|
public class CreateUpdateCustomComponentDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
[Required]
|
||||||
|
[StringLength(512)]
|
||||||
|
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")]
|
||||||
|
public string RoutePath { get; set; } = string.Empty;
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
if (string.IsNullOrWhiteSpace(input.Query))
|
if (string.IsNullOrWhiteSpace(input.Query))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
var query = input.Query.ToLower();
|
var query = input.Query.Trim();
|
||||||
|
|
||||||
// Search in categories
|
// Search in categories
|
||||||
if (input.SearchInCategories)
|
if (input.SearchInCategories)
|
||||||
|
|
@ -57,8 +57,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
var categoryQuery = await _categoryRepository.GetQueryableAsync();
|
var categoryQuery = await _categoryRepository.GetQueryableAsync();
|
||||||
var categories = await AsyncExecuter.ToListAsync(
|
var categories = await AsyncExecuter.ToListAsync(
|
||||||
categoryQuery.Where(c => c.IsActive &&
|
categoryQuery.Where(c => c.IsActive &&
|
||||||
(c.Name.ToLower().Contains(query) ||
|
(c.Name.Contains(query) ||
|
||||||
c.Description.ToLower().Contains(query)))
|
c.Description.Contains(query)))
|
||||||
.Take(10)
|
.Take(10)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -71,9 +71,9 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
var topicQuery = await _topicRepository.GetQueryableAsync();
|
var topicQuery = await _topicRepository.GetQueryableAsync();
|
||||||
var topics = await AsyncExecuter.ToListAsync(
|
var topics = await AsyncExecuter.ToListAsync(
|
||||||
topicQuery.Where(t =>
|
topicQuery.Where(t =>
|
||||||
t.Title.ToLower().Contains(query) ||
|
t.Title.Contains(query) ||
|
||||||
t.Content.ToLower().Contains(query) ||
|
t.Content.Contains(query) ||
|
||||||
t.AuthorName.ToLower().Contains(query))
|
t.AuthorName.Contains(query))
|
||||||
.OrderByDescending(t => t.CreationTime)
|
.OrderByDescending(t => t.CreationTime)
|
||||||
.Take(20)
|
.Take(20)
|
||||||
);
|
);
|
||||||
|
|
@ -87,8 +87,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
var postQuery = await _postRepository.GetQueryableAsync();
|
var postQuery = await _postRepository.GetQueryableAsync();
|
||||||
var posts = await AsyncExecuter.ToListAsync(
|
var posts = await AsyncExecuter.ToListAsync(
|
||||||
postQuery.Where(p =>
|
postQuery.Where(p =>
|
||||||
p.Content.ToLower().Contains(query) ||
|
p.Content.Contains(query) ||
|
||||||
p.AuthorName.ToLower().Contains(query))
|
p.AuthorName.Contains(query))
|
||||||
.OrderByDescending(p => p.CreationTime)
|
.OrderByDescending(p => p.CreationTime)
|
||||||
.Take(30)
|
.Take(30)
|
||||||
);
|
);
|
||||||
|
|
@ -112,10 +112,10 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(input.Search))
|
if (!string.IsNullOrWhiteSpace(input.Search))
|
||||||
{
|
{
|
||||||
var search = input.Search.ToLower();
|
var search = input.Search.Trim();
|
||||||
queryable = queryable.Where(c =>
|
queryable = queryable.Where(c =>
|
||||||
c.Name.ToLower().Contains(search) ||
|
c.Name.Contains(search) ||
|
||||||
c.Description.ToLower().Contains(search));
|
c.Description.Contains(search));
|
||||||
}
|
}
|
||||||
|
|
||||||
queryable = queryable.OrderBy(c => c.DisplayOrder);
|
queryable = queryable.OrderBy(c => c.DisplayOrder);
|
||||||
|
|
@ -126,13 +126,34 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
var maxResultCount = input.MaxResultCount > 0 ? input.MaxResultCount : 10;
|
var maxResultCount = input.MaxResultCount > 0 ? input.MaxResultCount : 10;
|
||||||
|
|
||||||
var categories = await AsyncExecuter.ToListAsync(
|
var categories = await AsyncExecuter.ToListAsync(
|
||||||
queryable.Skip(input.SkipCount).Take(input.MaxResultCount)
|
queryable.Skip(skipCount).Take(maxResultCount)
|
||||||
);
|
);
|
||||||
|
|
||||||
return new PagedResultDto<ForumCategoryDto>(
|
var categoryDtos = ObjectMapper.Map<List<ForumCategory>, List<ForumCategoryDto>>(categories);
|
||||||
totalCount,
|
var categoryIds = categories.Select(category => category.Id).ToList();
|
||||||
ObjectMapper.Map<List<ForumCategory>, List<ForumCategoryDto>>(categories)
|
if (categoryIds.Count > 0)
|
||||||
);
|
{
|
||||||
|
var topicQuery = await _topicRepository.GetQueryableAsync();
|
||||||
|
var postQuery = await _postRepository.GetQueryableAsync();
|
||||||
|
var topicCountItems = await AsyncExecuter.ToListAsync(
|
||||||
|
topicQuery.Where(topic => categoryIds.Contains(topic.CategoryId))
|
||||||
|
.GroupBy(topic => topic.CategoryId)
|
||||||
|
.Select(group => new { CategoryId = group.Key, Count = group.Count() }));
|
||||||
|
var topicCounts = topicCountItems.ToDictionary(item => item.CategoryId, item => item.Count);
|
||||||
|
var postCountItems = await AsyncExecuter.ToListAsync(
|
||||||
|
postQuery.Where(post => categoryIds.Contains(post.Topic.CategoryId))
|
||||||
|
.GroupBy(post => post.Topic.CategoryId)
|
||||||
|
.Select(group => new { CategoryId = group.Key, Count = group.Count() }));
|
||||||
|
var postCounts = postCountItems.ToDictionary(item => item.CategoryId, item => item.Count);
|
||||||
|
|
||||||
|
foreach (var category in categoryDtos)
|
||||||
|
{
|
||||||
|
category.TopicCount = topicCounts.GetValueOrDefault(category.Id);
|
||||||
|
category.PostCount = postCounts.GetValueOrDefault(category.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PagedResultDto<ForumCategoryDto>(totalCount, categoryDtos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ForumCategoryDto> GetCategoryAsync(Guid id)
|
public async Task<ForumCategoryDto> GetCategoryAsync(Guid id)
|
||||||
|
|
@ -251,10 +272,10 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(input.Search))
|
if (!string.IsNullOrWhiteSpace(input.Search))
|
||||||
{
|
{
|
||||||
var search = input.Search.ToLower();
|
var search = input.Search.Trim();
|
||||||
queryable = queryable.Where(t =>
|
queryable = queryable.Where(t =>
|
||||||
t.Title.ToLower().Contains(search) ||
|
t.Title.Contains(search) ||
|
||||||
t.Content.ToLower().Contains(search));
|
t.Content.Contains(search));
|
||||||
}
|
}
|
||||||
|
|
||||||
queryable = queryable.OrderByDescending(t => t.IsPinned)
|
queryable = queryable.OrderByDescending(t => t.IsPinned)
|
||||||
|
|
@ -265,10 +286,24 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
queryable.Skip(input.SkipCount).Take(input.MaxResultCount)
|
queryable.Skip(input.SkipCount).Take(input.MaxResultCount)
|
||||||
);
|
);
|
||||||
|
|
||||||
return new PagedResultDto<ForumTopicDto>(
|
var topicDtos = ObjectMapper.Map<List<ForumTopic>, List<ForumTopicDto>>(topics);
|
||||||
totalCount,
|
var topicIds = topics.Select(topic => topic.Id).ToList();
|
||||||
ObjectMapper.Map<List<ForumTopic>, List<ForumTopicDto>>(topics)
|
if (topicIds.Count > 0)
|
||||||
);
|
{
|
||||||
|
var postQuery = await _postRepository.GetQueryableAsync();
|
||||||
|
var replyCountItems = await AsyncExecuter.ToListAsync(
|
||||||
|
postQuery.Where(post => topicIds.Contains(post.TopicId))
|
||||||
|
.GroupBy(post => post.TopicId)
|
||||||
|
.Select(group => new { TopicId = group.Key, Count = group.Count() }));
|
||||||
|
var replyCounts = replyCountItems.ToDictionary(item => item.TopicId, item => item.Count);
|
||||||
|
|
||||||
|
foreach (var topic in topicDtos)
|
||||||
|
{
|
||||||
|
topic.ReplyCount = replyCounts.GetValueOrDefault(topic.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PagedResultDto<ForumTopicDto>(totalCount, topicDtos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ForumTopicDto> GetTopicAsync(Guid id)
|
public async Task<ForumTopicDto> GetTopicAsync(Guid id)
|
||||||
|
|
@ -368,9 +403,6 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
if (input.TopicId.HasValue)
|
if (input.TopicId.HasValue)
|
||||||
{
|
{
|
||||||
queryable = queryable.Where(p => p.TopicId == input.TopicId.Value);
|
queryable = queryable.Where(p => p.TopicId == input.TopicId.Value);
|
||||||
|
|
||||||
// Increment view count
|
|
||||||
var topic = await _topicRepository.GetAsync(input.TopicId.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.IsAcceptedAnswer.HasValue)
|
if (input.IsAcceptedAnswer.HasValue)
|
||||||
|
|
@ -380,8 +412,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(input.Search))
|
if (!string.IsNullOrWhiteSpace(input.Search))
|
||||||
{
|
{
|
||||||
var search = input.Search.ToLower();
|
var search = input.Search.Trim();
|
||||||
queryable = queryable.Where(p => p.Content.ToLower().Contains(search));
|
queryable = queryable.Where(p => p.Content.Contains(search));
|
||||||
}
|
}
|
||||||
|
|
||||||
queryable = queryable.OrderBy(p => p.CreationTime);
|
queryable = queryable.OrderBy(p => p.CreationTime);
|
||||||
|
|
@ -465,7 +497,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
await _postRepository.DeleteAsync(id);
|
await _postRepository.DeleteAsync(id);
|
||||||
|
|
||||||
topic.ReplyCount = Math.Max(0, topic.ReplyCount - 1);
|
topic.ReplyCount = Math.Max(0, topic.ReplyCount - 1);
|
||||||
category.PostCount = Math.Max(0, category.PostCount ?? 0 - 1);
|
category.PostCount = Math.Max(0, (category.PostCount ?? 0) - 1);
|
||||||
|
|
||||||
// Last post değişti mi kontrol et
|
// Last post değişti mi kontrol et
|
||||||
var postsQueryable = await _postRepository.GetQueryableAsync();
|
var postsQueryable = await _postRepository.GetQueryableAsync();
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,8 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
|
|
||||||
//Branch
|
//Branch
|
||||||
var queryBranch = await branchUsersRepository.GetQueryableAsync();
|
var queryBranch = await branchUsersRepository.GetQueryableAsync();
|
||||||
var branchUsers = queryBranch.Where(a => a.UserId == UserId).Select(r => r.BranchId).ToList();
|
var branchUsers = await AsyncExecuter.ToListAsync(
|
||||||
|
queryBranch.Where(a => a.UserId == UserId).Select(r => r.BranchId));
|
||||||
var branchList = await branchRepository.GetListAsync(a => a.TenantId == currentTenantId);
|
var branchList = await branchRepository.GetListAsync(a => a.TenantId == currentTenantId);
|
||||||
var branches = branchList.Select(branch => new AssignedBranchViewModel
|
var branches = branchList.Select(branch => new AssignedBranchViewModel
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +130,7 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
var departmentList = await departmentRepository.GetListAsync();
|
var departmentList = await departmentRepository.GetListAsync();
|
||||||
var departments = (await departmentRepository.GetListAsync()).Select(department => new AssignedDepartmentViewModel
|
var departments = departmentList.Select(department => new AssignedDepartmentViewModel
|
||||||
{
|
{
|
||||||
Id = department.Id,
|
Id = department.Id,
|
||||||
Name = department.Name,
|
Name = department.Name,
|
||||||
|
|
@ -137,7 +138,7 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
var jobPositionList = await jobPositionRepository.GetListAsync();
|
var jobPositionList = await jobPositionRepository.GetListAsync();
|
||||||
var jobPositions = (await jobPositionRepository.GetListAsync()).Select(jobPosition => new AssignedJobPoisitionViewModel
|
var jobPositions = jobPositionList.Select(jobPosition => new AssignedJobPoisitionViewModel
|
||||||
{
|
{
|
||||||
Id = jobPosition.Id,
|
Id = jobPosition.Id,
|
||||||
Name = jobPosition.Name,
|
Name = jobPosition.Name,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ namespace Sozsoft.Platform.Intranet;
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class IntranetAppService : PlatformAppService, IIntranetAppService
|
public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
{
|
{
|
||||||
|
private const int DashboardItemLimit = 50;
|
||||||
|
private Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)>? _userLookupDictionariesTask;
|
||||||
private readonly ICurrentTenant _currentTenant;
|
private readonly ICurrentTenant _currentTenant;
|
||||||
private readonly BlobManager _blobContainer;
|
private readonly BlobManager _blobContainer;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
@ -32,6 +34,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
private readonly IRepository<Event, Guid> _eventRepository;
|
private readonly IRepository<Event, Guid> _eventRepository;
|
||||||
private readonly IIdentityUserAppService _identityUserAppService;
|
private readonly IIdentityUserAppService _identityUserAppService;
|
||||||
private readonly IIdentityUserRepository _identityUserRepository;
|
private readonly IIdentityUserRepository _identityUserRepository;
|
||||||
|
private readonly IRepository<Volo.Abp.Identity.IdentityUser, Guid> _identityUserEntityRepository;
|
||||||
private readonly IRepository<Department, Guid> _departmentRepository;
|
private readonly IRepository<Department, Guid> _departmentRepository;
|
||||||
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
|
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
|
||||||
private readonly IRepository<Announcement, Guid> _announcementRepository;
|
private readonly IRepository<Announcement, Guid> _announcementRepository;
|
||||||
|
|
@ -57,6 +60,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
IRepository<Event, Guid> eventRepository,
|
IRepository<Event, Guid> eventRepository,
|
||||||
IIdentityUserAppService identityUserAppService,
|
IIdentityUserAppService identityUserAppService,
|
||||||
IIdentityUserRepository identityUserRepository,
|
IIdentityUserRepository identityUserRepository,
|
||||||
|
IRepository<Volo.Abp.Identity.IdentityUser, Guid> identityUserEntityRepository,
|
||||||
IRepository<Department, Guid> departmentRepository,
|
IRepository<Department, Guid> departmentRepository,
|
||||||
IRepository<JobPosition, Guid> jobPositionRepository,
|
IRepository<JobPosition, Guid> jobPositionRepository,
|
||||||
IRepository<Announcement, Guid> announcementRepository,
|
IRepository<Announcement, Guid> announcementRepository,
|
||||||
|
|
@ -81,6 +85,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
_eventRepository = eventRepository;
|
_eventRepository = eventRepository;
|
||||||
_identityUserAppService = identityUserAppService;
|
_identityUserAppService = identityUserAppService;
|
||||||
_identityUserRepository = identityUserRepository;
|
_identityUserRepository = identityUserRepository;
|
||||||
|
_identityUserEntityRepository = identityUserEntityRepository;
|
||||||
_departmentRepository = departmentRepository;
|
_departmentRepository = departmentRepository;
|
||||||
_jobPositionRepository = jobPositionRepository;
|
_jobPositionRepository = jobPositionRepository;
|
||||||
_announcementRepository = announcementRepository;
|
_announcementRepository = announcementRepository;
|
||||||
|
|
@ -111,7 +116,12 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)> GetUserLookupDictionariesAsync()
|
private Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)> GetUserLookupDictionariesAsync()
|
||||||
|
{
|
||||||
|
return _userLookupDictionariesTask ??= LoadUserLookupDictionariesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(Dictionary<Guid, string> DepartmentDict, Dictionary<Guid, JobPosition> JobPositionDict)> LoadUserLookupDictionariesAsync()
|
||||||
{
|
{
|
||||||
var departments = await _departmentRepository.GetListAsync();
|
var departments = await _departmentRepository.GetListAsync();
|
||||||
var jobPositions = await _jobPositionRepository.GetListAsync();
|
var jobPositions = await _jobPositionRepository.GetListAsync();
|
||||||
|
|
@ -122,6 +132,17 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<Volo.Abp.Identity.IdentityUser>> GetUsersByIdsAsync(IEnumerable<Guid> userIds)
|
||||||
|
{
|
||||||
|
var ids = userIds.Where(id => id != Guid.Empty).Distinct().ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _identityUserEntityRepository.GetListAsync(user => ids.Contains(user.Id));
|
||||||
|
}
|
||||||
|
|
||||||
private UserInfoViewModel MapUserInfoViewModel(
|
private UserInfoViewModel MapUserInfoViewModel(
|
||||||
Volo.Abp.Identity.IdentityUser user,
|
Volo.Abp.Identity.IdentityUser user,
|
||||||
IReadOnlyDictionary<Guid, string> departmentDict,
|
IReadOnlyDictionary<Guid, string> departmentDict,
|
||||||
|
|
@ -153,7 +174,10 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
.WithDetailsAsync(e => e.Category, e => e.Type);
|
.WithDetailsAsync(e => e.Category, e => e.Type);
|
||||||
|
|
||||||
var events = await AsyncExecuter.ToListAsync(
|
var events = await AsyncExecuter.ToListAsync(
|
||||||
queryable.Where(e => e.isPublished).OrderByDescending(e => e.CreationTime)
|
queryable
|
||||||
|
.Where(e => e.isPublished)
|
||||||
|
.OrderByDescending(e => e.CreationTime)
|
||||||
|
.Take(DashboardItemLimit)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (events.Count == 0)
|
if (events.Count == 0)
|
||||||
|
|
@ -170,7 +194,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
// Load all likes for these events
|
// Load all likes for these events
|
||||||
var likesQueryable = await _eventLikeRepository.GetQueryableAsync();
|
var likesQueryable = await _eventLikeRepository.GetQueryableAsync();
|
||||||
var allLikes = await AsyncExecuter.ToListAsync(
|
var allLikes = await AsyncExecuter.ToListAsync(
|
||||||
likesQueryable.Where(l => eventIds.Contains(l.EventId))
|
likesQueryable.Where(l =>
|
||||||
|
eventIds.Contains(l.EventId) && l.UserId == CurrentUser.Id)
|
||||||
);
|
);
|
||||||
var likedEventIds = allLikes
|
var likedEventIds = allLikes
|
||||||
.Where(l => l.UserId == CurrentUser.Id)
|
.Where(l => l.UserId == CurrentUser.Id)
|
||||||
|
|
@ -192,9 +217,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
var fallbackUser = await GetDashboardFallbackUserAsync(departmentDict, jobPositionDict);
|
var fallbackUser = await GetDashboardFallbackUserAsync(departmentDict, jobPositionDict);
|
||||||
|
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userDict = users
|
var userDict = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
var commentsByEvent = allComments.GroupBy(c => c.EventId)
|
var commentsByEvent = allComments.GroupBy(c => c.EventId)
|
||||||
|
|
@ -264,9 +288,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
|
|
||||||
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
|
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userDict = users
|
var userDict = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
return comments.Select(c => new EventCommentDto
|
return comments.Select(c => new EventCommentDto
|
||||||
|
|
@ -371,7 +394,11 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
|
|
||||||
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
|
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
|
||||||
{
|
{
|
||||||
var announcements = await _announcementRepository.GetListAsync();
|
var announcementQueryable = await _announcementRepository.GetQueryableAsync();
|
||||||
|
var announcements = await AsyncExecuter.ToListAsync(
|
||||||
|
announcementQueryable
|
||||||
|
.OrderByDescending(announcement => announcement.CreationTime)
|
||||||
|
.Take(DashboardItemLimit));
|
||||||
var announcementDtos = new List<AnnouncementDto>();
|
var announcementDtos = new List<AnnouncementDto>();
|
||||||
|
|
||||||
if (announcements.Count == 0)
|
if (announcements.Count == 0)
|
||||||
|
|
@ -386,7 +413,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
|
|
||||||
var likesQueryable = await _announcementLikeRepository.GetQueryableAsync();
|
var likesQueryable = await _announcementLikeRepository.GetQueryableAsync();
|
||||||
var allLikes = await AsyncExecuter.ToListAsync(
|
var allLikes = await AsyncExecuter.ToListAsync(
|
||||||
likesQueryable.Where(l => announcementIds.Contains(l.AnnouncementId))
|
likesQueryable.Where(l =>
|
||||||
|
announcementIds.Contains(l.AnnouncementId) && l.UserId == CurrentUser.Id)
|
||||||
);
|
);
|
||||||
|
|
||||||
var likedAnnouncementIds = allLikes
|
var likedAnnouncementIds = allLikes
|
||||||
|
|
@ -403,9 +431,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
|
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
var fallbackUser = await GetDashboardFallbackUserAsync(departmentDict, jobPositionDict);
|
var fallbackUser = await GetDashboardFallbackUserAsync(departmentDict, jobPositionDict);
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userDict = users
|
var userDict = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
var commentsByAnnouncement = allComments
|
var commentsByAnnouncement = allComments
|
||||||
|
|
@ -454,9 +481,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
|
|
||||||
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
|
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userDict = users
|
var userDict = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
return comments.Select(c => new AnnouncementCommentDto
|
return comments.Select(c => new AnnouncementCommentDto
|
||||||
|
|
@ -617,9 +643,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
{
|
{
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
|
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userMap = users
|
var userMap = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
foreach (var dto in dtos)
|
foreach (var dto in dtos)
|
||||||
|
|
@ -947,9 +972,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
||||||
if (userIds.Count > 0)
|
if (userIds.Count > 0)
|
||||||
{
|
{
|
||||||
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
|
||||||
var users = await _identityUserRepository.GetListAsync();
|
var users = await GetUsersByIdsAsync(userIds);
|
||||||
var userMap = users
|
var userMap = users
|
||||||
.Where(u => userIds.Contains(u.Id))
|
|
||||||
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
|
||||||
|
|
||||||
if (dto.UserId.HasValue && userMap.TryGetValue(dto.UserId.Value, out var postUser))
|
if (dto.UserId.HasValue && userMap.TryGetValue(dto.UserId.Value, out var postUser))
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class MessengerAppService : ApplicationService
|
||||||
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
||||||
private readonly IRepository<MessengerConversation, Guid> _conversationRepository;
|
private readonly IRepository<MessengerConversation, Guid> _conversationRepository;
|
||||||
private readonly IRepository<MessengerConversationMessage, Guid> _messageRepository;
|
private readonly IRepository<MessengerConversationMessage, Guid> _messageRepository;
|
||||||
private readonly IIdentitySessionRepository _identitySessionRepository;
|
private readonly IRepository<IdentitySession, Guid> _identitySessionRepository;
|
||||||
private readonly BlobManager _blobManager;
|
private readonly BlobManager _blobManager;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly IHubContext<MessengerHub> _messengerHubContext;
|
private readonly IHubContext<MessengerHub> _messengerHubContext;
|
||||||
|
|
@ -37,7 +37,7 @@ public class MessengerAppService : ApplicationService
|
||||||
IRepository<IdentityUser, Guid> userRepository,
|
IRepository<IdentityUser, Guid> userRepository,
|
||||||
IRepository<MessengerConversation, Guid> conversationRepository,
|
IRepository<MessengerConversation, Guid> conversationRepository,
|
||||||
IRepository<MessengerConversationMessage, Guid> messageRepository,
|
IRepository<MessengerConversationMessage, Guid> messageRepository,
|
||||||
IIdentitySessionRepository identitySessionRepository,
|
IRepository<IdentitySession, Guid> identitySessionRepository,
|
||||||
BlobManager blobManager,
|
BlobManager blobManager,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
IHubContext<MessengerHub> messengerHubContext)
|
IHubContext<MessengerHub> messengerHubContext)
|
||||||
|
|
@ -450,11 +450,14 @@ public class MessengerAppService : ApplicationService
|
||||||
return new HashSet<Guid>();
|
return new HashSet<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessions = await _identitySessionRepository.GetListAsync();
|
var sessionsQuery = await _identitySessionRepository.GetQueryableAsync();
|
||||||
return sessions
|
var onlineUserIds = await AsyncExecuter.ToListAsync(
|
||||||
.Where(session => normalizedUserIds.Contains(session.UserId))
|
sessionsQuery
|
||||||
.Select(session => session.UserId)
|
.Where(session => normalizedUserIds.Contains(session.UserId))
|
||||||
.ToHashSet();
|
.Select(session => session.UserId)
|
||||||
|
.Distinct());
|
||||||
|
|
||||||
|
return onlineUserIds.ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MessengerConversation?> FindConversationByParticipantKeyAsync(string participantKey)
|
private async Task<MessengerConversation?> FindConversationByParticipantKeyAsync(string participantKey)
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,15 @@ public class OrgChartAppService : PlatformAppService
|
||||||
var users = await _userRepository.GetListAsync();
|
var users = await _userRepository.GetListAsync();
|
||||||
|
|
||||||
var jobById = jobPositions.ToDictionary(x => x.Id);
|
var jobById = jobPositions.ToDictionary(x => x.Id);
|
||||||
|
var positionsByDepartment = jobPositions
|
||||||
|
.GroupBy(position => position.DepartmentId)
|
||||||
|
.ToDictionary(group => group.Key, group => group.OrderBy(position => position.Name).ToList());
|
||||||
|
var usersByJobPosition = GroupUsersByJobPosition(users);
|
||||||
var topPositionByDepartment = new Dictionary<Guid, JobPosition>();
|
var topPositionByDepartment = new Dictionary<Guid, JobPosition>();
|
||||||
|
|
||||||
foreach (var department in departments)
|
foreach (var department in departments)
|
||||||
{
|
{
|
||||||
var departmentPositions = jobPositions
|
if (!positionsByDepartment.TryGetValue(department.Id, out var departmentPositions))
|
||||||
.Where(x => x.DepartmentId == department.Id)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (!departmentPositions.Any())
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -77,19 +77,9 @@ public class OrgChartAppService : PlatformAppService
|
||||||
ParentId = d.ParentId,
|
ParentId = d.ParentId,
|
||||||
JobPositionId = topPosition?.Id,
|
JobPositionId = topPosition?.Id,
|
||||||
JobPositionName = topPosition?.Name,
|
JobPositionName = topPosition?.Name,
|
||||||
Users = users
|
Users = topPosition != null && usersByJobPosition.TryGetValue(topPosition.Id, out var positionUsers)
|
||||||
.Where(u =>
|
? positionUsers
|
||||||
topPosition != null &&
|
: [],
|
||||||
u.GetJobPositionId() == topPosition.Id)
|
|
||||||
.Select(u => new OrgChartUserDto
|
|
||||||
{
|
|
||||||
Id = u.Id,
|
|
||||||
FullName = u.GetFullName(),
|
|
||||||
Email = u.Email,
|
|
||||||
UserName = u.UserName,
|
|
||||||
PhoneNumber = u.PhoneNumber
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
|
@ -104,6 +94,7 @@ public class OrgChartAppService : PlatformAppService
|
||||||
var users = await _userRepository.GetListAsync();
|
var users = await _userRepository.GetListAsync();
|
||||||
|
|
||||||
var deptDict = departments.ToDictionary(d => d.Id, d => d.Name);
|
var deptDict = departments.ToDictionary(d => d.Id, d => d.Name);
|
||||||
|
var usersByJobPosition = GroupUsersByJobPosition(users);
|
||||||
|
|
||||||
var nodes = jobPositions.Select(jp => new OrgChartNodeDto
|
var nodes = jobPositions.Select(jp => new OrgChartNodeDto
|
||||||
{
|
{
|
||||||
|
|
@ -114,19 +105,28 @@ public class OrgChartAppService : PlatformAppService
|
||||||
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
|
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
|
||||||
JobPositionId = jp.Id,
|
JobPositionId = jp.Id,
|
||||||
JobPositionName = jp.Name,
|
JobPositionName = jp.Name,
|
||||||
Users = users
|
Users = usersByJobPosition.TryGetValue(jp.Id, out var positionUsers)
|
||||||
.Where(u => u.GetJobPositionId() == jp.Id)
|
? positionUsers
|
||||||
.Select(u => new OrgChartUserDto
|
: [],
|
||||||
{
|
|
||||||
Id = u.Id,
|
|
||||||
FullName = u.GetFullName(),
|
|
||||||
Email = u.Email,
|
|
||||||
UserName = u.UserName,
|
|
||||||
PhoneNumber = u.PhoneNumber
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Dictionary<Guid, List<OrgChartUserDto>> GroupUsersByJobPosition(
|
||||||
|
IEnumerable<IdentityUser> users)
|
||||||
|
{
|
||||||
|
return users
|
||||||
|
.GroupBy(user => user.GetJobPositionId())
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.Select(user => new OrgChartUserDto
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
FullName = user.GetFullName(),
|
||||||
|
Email = user.Email,
|
||||||
|
UserName = user.UserName,
|
||||||
|
PhoneNumber = user.PhoneNumber
|
||||||
|
}).ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5675,7 +5675,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
ValueExpr = "key",
|
ValueExpr = "key",
|
||||||
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
|
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
|
||||||
new () { Key="normal",Name="Normal" },
|
new () { Key="normal",Name="Normal" },
|
||||||
new () { Key="dynamic",Name="Dynamic" },
|
// new () { Key="dynamic",Name="Dynamic" },
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
{
|
{
|
||||||
"Routes": [
|
"Routes": [
|
||||||
{
|
|
||||||
"key": "roleListComponent",
|
|
||||||
"path": "/admin/RoleListComponent",
|
|
||||||
"componentType": "dynamic",
|
|
||||||
"componentPath": "RoleListComponent",
|
|
||||||
"routeType": "protected",
|
|
||||||
"authority": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "home",
|
"key": "home",
|
||||||
"path": "/home",
|
"path": "/home",
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,17 @@ public class CustomComponent : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
public virtual Guid? TenantId { get; protected set; }
|
public virtual Guid? TenantId { get; protected set; }
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string RoutePath { get; set; } = string.Empty;
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public string? Dependencies { get; set; } // JSON string of component names
|
public string? Dependencies { get; set; } // JSON string of component names
|
||||||
|
|
||||||
public CustomComponent(string name, string code, string? props, string? description, bool isActive, string? dependencies)
|
public CustomComponent(string name, string routePath, string code, string? props, string? description, bool isActive, string? dependencies)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
RoutePath = routePath;
|
||||||
Code = code;
|
Code = code;
|
||||||
Props = props;
|
Props = props;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
|
|
||||||
|
|
@ -637,12 +637,14 @@ public class PlatformDbContext :
|
||||||
b.ConfigureByConvention();
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
b.Property(x => x.RoutePath).IsRequired().HasMaxLength(512);
|
||||||
b.Property(x => x.Code).IsRequired();
|
b.Property(x => x.Code).IsRequired();
|
||||||
b.Property(x => x.Props).HasMaxLength(1024);
|
b.Property(x => x.Props).HasMaxLength(1024);
|
||||||
b.Property(x => x.Description).HasMaxLength(512);
|
b.Property(x => x.Description).HasMaxLength(512);
|
||||||
b.Property(x => x.Dependencies).HasMaxLength(2048);
|
b.Property(x => x.Dependencies).HasMaxLength(2048);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
b.HasIndex(x => new { x.TenantId, x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
b.HasIndex(x => new { x.TenantId, x.RoutePath }).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<ReportCategory>(b =>
|
builder.Entity<ReportCategory>(b =>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260709175406_Initial")]
|
[Migration("20260711200250_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1826,6 +1826,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("nvarchar(1024)");
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("RoutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
b.Property<Guid?>("TenantId")
|
b.Property<Guid?>("TenantId")
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("TenantId");
|
.HasColumnName("TenantId");
|
||||||
|
|
@ -1836,6 +1841,10 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[IsDeleted] = 0");
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "RoutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
b.ToTable("Sas_H_CustomComponent", (string)null);
|
b.ToTable("Sas_H_CustomComponent", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1128,6 +1128,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||||
|
RoutePath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||||
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
Props = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
Props = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||||
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||||
|
|
@ -4020,6 +4021,13 @@ namespace Sozsoft.Platform.Migrations
|
||||||
unique: true,
|
unique: true,
|
||||||
filter: "[IsDeleted] = 0");
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Sas_H_CustomComponent_TenantId_RoutePath",
|
||||||
|
table: "Sas_H_CustomComponent",
|
||||||
|
columns: new[] { "TenantId", "RoutePath" },
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_CustomEndpoint_TenantId_Name",
|
name: "IX_Sas_H_CustomEndpoint_TenantId_Name",
|
||||||
table: "Sas_H_CustomEndpoint",
|
table: "Sas_H_CustomEndpoint",
|
||||||
|
|
@ -1823,6 +1823,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("nvarchar(1024)");
|
.HasColumnType("nvarchar(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("RoutePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("nvarchar(512)");
|
||||||
|
|
||||||
b.Property<Guid?>("TenantId")
|
b.Property<Guid?>("TenantId")
|
||||||
.HasColumnType("uniqueidentifier")
|
.HasColumnType("uniqueidentifier")
|
||||||
.HasColumnName("TenantId");
|
.HasColumnName("TenantId");
|
||||||
|
|
@ -1833,6 +1838,10 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[IsDeleted] = 0");
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "RoutePath")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
b.ToTable("Sas_H_CustomComponent", (string)null);
|
b.ToTable("Sas_H_CustomComponent", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,7 @@
|
||||||
"CustomComponents": [
|
"CustomComponents": [
|
||||||
{
|
{
|
||||||
"name": "DynamicEntityComponent",
|
"name": "DynamicEntityComponent",
|
||||||
|
"routePath": "/admin/dynamic-entity",
|
||||||
"code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\",\n});\n\nconst DynamicEntityComponent: React.FC<DynamicEntityComponentProps> = ({ title }) => {\n const [data, setData] = useState<Array<{ id: string; name: string }>>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return <div>Loading...</div>;\n if (error) return <div className=\"text-red-600 dark:text-red-400\">Error: {error}</div>;\n if (!data.length) return <div>No records found</div>;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n <div className=\"overflow-auto\">\n <table className=\"min-w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 shadow-sm rounded-lg\">\n <thead className=\"bg-slate-100 dark:bg-slate-800\">\n <tr>\n {headers.map((key) => (\n <th\n key={key}\n className=\"text-left px-4 py-2 border-b border-slate-200 dark:border-slate-700 text-sm font-medium text-slate-700 dark:text-slate-200\"\n >\n {key === \"actions\" ? \"Actions\" : key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((item, rowIndex) => (\n <tr key={rowIndex} className=\"hover:bg-slate-50 dark:hover:bg-slate-800\">\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.id}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.name}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800\">\n <button\n type=\"button\"\n onClick={() => alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 dark:bg-blue-800 dark:hover:bg-blue-900 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\nexport default DynamicEntityComponent;",
|
"code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\",\n});\n\nconst DynamicEntityComponent: React.FC<DynamicEntityComponentProps> = ({ title }) => {\n const [data, setData] = useState<Array<{ id: string; name: string }>>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return <div>Loading...</div>;\n if (error) return <div className=\"text-red-600 dark:text-red-400\">Error: {error}</div>;\n if (!data.length) return <div>No records found</div>;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n <div className=\"overflow-auto\">\n <table className=\"min-w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 shadow-sm rounded-lg\">\n <thead className=\"bg-slate-100 dark:bg-slate-800\">\n <tr>\n {headers.map((key) => (\n <th\n key={key}\n className=\"text-left px-4 py-2 border-b border-slate-200 dark:border-slate-700 text-sm font-medium text-slate-700 dark:text-slate-200\"\n >\n {key === \"actions\" ? \"Actions\" : key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((item, rowIndex) => (\n <tr key={rowIndex} className=\"hover:bg-slate-50 dark:hover:bg-slate-800\">\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.id}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800 text-sm text-slate-800 dark:text-slate-100\">\n {item.name}\n </td>\n <td className=\"px-4 py-2 border-b border-slate-100 dark:border-slate-800\">\n <button\n type=\"button\"\n onClick={() => alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 dark:bg-blue-800 dark:hover:bg-blue-900 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n};\n\nexport default DynamicEntityComponent;",
|
||||||
"props": null,
|
"props": null,
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
@ -357,6 +358,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "RoleListComponent",
|
"name": "RoleListComponent",
|
||||||
|
"routePath": "/admin/roles-list",
|
||||||
"code": "const RoleListComponent = ({\n title = \"AbpRoles\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default RoleListComponent;",
|
"code": "const RoleListComponent = ({\n title = \"AbpRoles\"\n}) => {\n return (\n <DynamicEntityComponent id=\"c_mdljvvmq_fno52v\" title={title} />\n );\n};\n\nexport default RoleListComponent;",
|
||||||
"props": null,
|
"props": null,
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,7 @@ public class InstallmentOptionSeedDto
|
||||||
public class CustomComponentSeedDto
|
public class CustomComponentSeedDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
public string RoutePath { get; set; }
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
public string Props { get; set; }
|
public string Props { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
@ -851,6 +852,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
{
|
{
|
||||||
await _customComponentRepository.InsertAsync(new CustomComponent(
|
await _customComponentRepository.InsertAsync(new CustomComponent(
|
||||||
item.Name,
|
item.Name,
|
||||||
|
item.RoutePath,
|
||||||
item.Code,
|
item.Code,
|
||||||
item.Props,
|
item.Props,
|
||||||
item.Description,
|
item.Description,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Extensions.DependencyInjection;
|
using Microsoft.AspNetCore.Extensions.DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
using Microsoft.Extensions.Caching.StackExchangeRedis;
|
using Microsoft.Extensions.Caching.StackExchangeRedis;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -141,10 +142,22 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
ConfigureBlobStoring(configuration);
|
ConfigureBlobStoring(configuration);
|
||||||
ConfigureAuditing();
|
ConfigureAuditing();
|
||||||
|
|
||||||
|
context.Services.AddResponseCompression(options =>
|
||||||
|
{
|
||||||
|
options.EnableForHttps = true;
|
||||||
|
options.Providers.Add<BrotliCompressionProvider>();
|
||||||
|
options.Providers.Add<GzipCompressionProvider>();
|
||||||
|
});
|
||||||
|
|
||||||
ConfigureDynamicServices(context);
|
ConfigureDynamicServices(context);
|
||||||
ConfigureDevExpressReporting(context, configuration);
|
ConfigureDevExpressReporting(context, configuration);
|
||||||
|
|
||||||
context.Services.AddSignalR();
|
var signalR = context.Services.AddSignalR();
|
||||||
|
if (configuration.GetValue<bool>("Redis:IsEnabled") &&
|
||||||
|
!configuration["Redis:Configuration"].IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
signalR.AddStackExchangeRedis(configuration["Redis:Configuration"]!);
|
||||||
|
}
|
||||||
|
|
||||||
Configure<AbpExceptionHttpStatusCodeOptions>(options =>
|
Configure<AbpExceptionHttpStatusCodeOptions>(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -489,6 +502,7 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
// }
|
// }
|
||||||
|
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
|
app.UseResponseCompression();
|
||||||
app.MapAbpStaticAssets();
|
app.MapAbpStaticAssets();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseCors();
|
app.UseCors();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.PostgreSQL" Version="2.3.0" />
|
<PackageReference Include="Serilog.Sinks.PostgreSQL" Version="2.3.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="8.2.0" />
|
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="8.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"commit": "c42a8d2",
|
"commit": "351eddf",
|
||||||
"releases": [
|
"releases": [
|
||||||
{
|
{
|
||||||
"version": "1.1.06",
|
"version": "1.1.06",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ import { ComponentProvider } from './contexts/ComponentContext'
|
||||||
import { registerServiceWorker } from './views/version/swRegistration'
|
import { registerServiceWorker } from './views/version/swRegistration'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: 30_000,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import type { ErrorInfo } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
hasError: boolean;
|
|
||||||
error: Error | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { hasError: false, error: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error): State {
|
|
||||||
return { hasError: true, error };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
|
||||||
// Eğer component değişmişse, hata state'ini sıfırla
|
|
||||||
if (prevProps.children !== this.props.children && this.state.hasError) {
|
|
||||||
this.setState({ hasError: false, error: null });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
||||||
console.error("Render hatası:", error, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "red",
|
|
||||||
background: "#fff0f0",
|
|
||||||
border: "1px solid #f44336",
|
|
||||||
padding: "1rem",
|
|
||||||
borderRadius: "8px",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
⚠️ Render Hatası:
|
|
||||||
<br />
|
|
||||||
<details open style={{ marginTop: "0.5rem" }}>
|
|
||||||
<summary>Detayları Göster</summary>
|
|
||||||
{this.state.error?.message || String(this.state.error)}
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary;
|
|
||||||
|
|
@ -19,9 +19,9 @@ import { messengerSignalR } from '@/services/messenger.signalr'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
|
import type { EmojiClickData } from 'emoji-picker-react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
FaPaperclip,
|
FaPaperclip,
|
||||||
|
|
@ -38,6 +38,7 @@ import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
||||||
const CONTACT_REFRESH_INTERVAL = 30000
|
const CONTACT_REFRESH_INTERVAL = 30000
|
||||||
|
const EmojiPicker = lazy(() => import('emoji-picker-react'))
|
||||||
|
|
||||||
const formatFileSize = (size: number) => {
|
const formatFileSize = (size: number) => {
|
||||||
if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`
|
if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`
|
||||||
|
|
@ -704,11 +705,13 @@ const MessengerWidgetContent = ({ className }: { className?: string }) => {
|
||||||
<div className="relative border-t border-gray-200 p-3 dark:border-gray-700">
|
<div className="relative border-t border-gray-200 p-3 dark:border-gray-700">
|
||||||
{showEmoji && (
|
{showEmoji && (
|
||||||
<div className="absolute bottom-16 left-2 z-10 sm:left-10">
|
<div className="absolute bottom-16 left-2 z-10 sm:left-10">
|
||||||
<EmojiPicker
|
<Suspense fallback={<div className="h-[380px] w-[320px] max-w-[calc(100vw-32px)]" />}>
|
||||||
onEmojiClick={onEmojiClick}
|
<EmojiPicker
|
||||||
width="min(320px, calc(100vw - 32px))"
|
onEmojiClick={onEmojiClick}
|
||||||
height={380}
|
width="min(320px, calc(100vw - 32px))"
|
||||||
/>
|
height={380}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex min-h-[52px] items-center gap-2 rounded-md border border-gray-300 bg-white px-2 py-1.5 focus-within:border-emerald-500 focus-within:ring-1 focus-within:ring-emerald-500 dark:border-gray-600 dark:bg-gray-800">
|
<div className="flex min-h-[52px] items-center gap-2 rounded-md border border-gray-300 bg-white px-2 py-1.5 focus-within:border-emerald-500 focus-within:ring-1 focus-within:ring-emerald-500 dark:border-gray-600 dark:bg-gray-800">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import Tooltip from '@/components/ui/Tooltip'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import NotificationChannels from '@/constants/notification-channel.enum'
|
import NotificationChannels from '@/constants/notification-channel.enum'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { getList, updateRead, updateReadAll, updateSent } from '@/services/notification.service'
|
import {
|
||||||
|
getList,
|
||||||
|
updateRead,
|
||||||
|
updateReadAll,
|
||||||
|
updateReadMany,
|
||||||
|
updateSentMany,
|
||||||
|
} from '@/services/notification.service'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -122,8 +128,8 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
maxResultCount: 1000,
|
maxResultCount: 1000,
|
||||||
})
|
})
|
||||||
const items = resp.data.items ?? []
|
const items = resp.data.items ?? []
|
||||||
for (const notification of items) {
|
if (items.length > 0) {
|
||||||
await updateSent(notification.id, true)
|
await updateSentMany(items.map((notification) => notification.id), true)
|
||||||
}
|
}
|
||||||
const newNotificationList = items.map(
|
const newNotificationList = items.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
|
|
@ -208,18 +214,18 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
</Notify>,
|
</Notify>,
|
||||||
{ placement: 'bottom-end' },
|
{ placement: 'bottom-end' },
|
||||||
)
|
)
|
||||||
await updateSent(notification.id, true)
|
|
||||||
await updateRead(notification.id, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Desktop
|
// Desktop
|
||||||
if (desktopGranted) {
|
const newDesktopList = desktopGranted
|
||||||
const newDesktopList = items.filter(
|
? items.filter(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.notificationChannel === NotificationChannels.Desktop &&
|
a.notificationChannel === NotificationChannels.Desktop &&
|
||||||
!desktopNotificationList.current.includes(a.id) &&
|
!desktopNotificationList.current.includes(a.id) &&
|
||||||
!a.isSent,
|
!a.isSent,
|
||||||
)
|
)
|
||||||
|
: []
|
||||||
|
if (desktopGranted) {
|
||||||
desktopNotificationList.current = [
|
desktopNotificationList.current = [
|
||||||
...desktopNotificationList.current,
|
...desktopNotificationList.current,
|
||||||
...newDesktopList.map((a) => a.id),
|
...newDesktopList.map((a) => a.id),
|
||||||
|
|
@ -238,11 +244,14 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
} else {
|
} else {
|
||||||
new window.Notification(title, options)
|
new window.Notification(title, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateSent(notification.id, true)
|
|
||||||
await updateRead(notification.id, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const processedIds = [...newToastList, ...newDesktopList].map((notification) => notification.id)
|
||||||
|
if (processedIds.length > 0) {
|
||||||
|
await updateSentMany(processedIds, true)
|
||||||
|
await updateReadMany(processedIds, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import {
|
||||||
import { developerKitService } from '@/services/developerKit.service'
|
import { developerKitService } from '@/services/developerKit.service'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
||||||
import * as Babel from '@babel/standalone'
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
|
@ -262,53 +261,56 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!components || !components?.length) return
|
if (!components || !components?.length) return
|
||||||
|
|
||||||
try {
|
let cancelled = false
|
||||||
const activeComponents = components?.filter((c) => c.isActive)
|
|
||||||
|
|
||||||
if (!activeComponents.length) {
|
const compileComponents = async () => {
|
||||||
setCompiledComponents({})
|
try {
|
||||||
return
|
const activeComponents = components?.filter((c) => c.isActive)
|
||||||
}
|
|
||||||
|
|
||||||
const componentInfos = activeComponents.map((comp) => {
|
if (!activeComponents.length) {
|
||||||
const name = comp.name
|
setCompiledComponents({})
|
||||||
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1)
|
return
|
||||||
|
|
||||||
return {
|
|
||||||
name: name,
|
|
||||||
nameCapitalized: nameCapitalized,
|
|
||||||
internalName: extractComponentInfo(comp.code, nameCapitalized),
|
|
||||||
code: comp.code
|
|
||||||
.replace(/import\s+.*?;/g, '')
|
|
||||||
.replace(/export\s+default\s+/, '')
|
|
||||||
.trim(),
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// Create cross-referencing bundle
|
const componentInfos = activeComponents.map((comp) => {
|
||||||
const componentDeclarations = componentInfos
|
const name = comp.name
|
||||||
.map((info) => `let ${info.name}_Component;`)
|
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1)
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
const componentDefinitions = componentInfos
|
return {
|
||||||
.map((info) => {
|
name: name,
|
||||||
const componentVariables = componentInfos
|
nameCapitalized: nameCapitalized,
|
||||||
.filter((other) => other.name !== info.name)
|
internalName: extractComponentInfo(comp.code, nameCapitalized),
|
||||||
.map((other) => `const ${other.name} = ${other.name}_Component;`)
|
code: comp.code
|
||||||
.join('\n ')
|
.replace(/import\s+.*?;/g, '')
|
||||||
|
.replace(/export\s+default\s+/, '')
|
||||||
|
.trim(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return `
|
// Create cross-referencing bundle
|
||||||
|
const componentDeclarations = componentInfos
|
||||||
|
.map((info) => `let ${info.name}_Component;`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const componentDefinitions = componentInfos
|
||||||
|
.map((info) => {
|
||||||
|
const componentVariables = componentInfos
|
||||||
|
.filter((other) => other.name !== info.name)
|
||||||
|
.map((other) => `const ${other.name} = ${other.name}_Component;`)
|
||||||
|
.join('\n ')
|
||||||
|
|
||||||
|
return `
|
||||||
${info.name}_Component = (function() {
|
${info.name}_Component = (function() {
|
||||||
${componentVariables}
|
${componentVariables}
|
||||||
${info.code}
|
${info.code}
|
||||||
return ${info.internalName};
|
return ${info.internalName};
|
||||||
})();`
|
})();`
|
||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
const componentBundle = componentDeclarations + '\n' + componentDefinitions
|
const componentBundle = componentDeclarations + '\n' + componentDefinitions
|
||||||
|
|
||||||
const bundledCode = `
|
const bundledCode = `
|
||||||
(function(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios) {
|
(function(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios) {
|
||||||
const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React;
|
const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React;
|
||||||
const componentRegistry = {};
|
const componentRegistry = {};
|
||||||
|
|
@ -328,106 +330,118 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||||
})(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios)
|
})(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios)
|
||||||
`
|
`
|
||||||
|
|
||||||
const compiledBundle = Babel.transform(bundledCode, {
|
// Babel is several megabytes and is only needed when an active runtime
|
||||||
presets: ['react', 'typescript'],
|
// component exists. Keep it out of the application startup bundle.
|
||||||
filename: 'components-bundle.tsx',
|
const Babel = await import('@babel/standalone')
|
||||||
}).code
|
if (cancelled) return
|
||||||
|
|
||||||
if (!compiledBundle) {
|
const compiledBundle = Babel.transform(bundledCode, {
|
||||||
throw new Error('Failed to compile components bundle')
|
presets: ['react', 'typescript'],
|
||||||
|
filename: 'components-bundle.tsx',
|
||||||
|
}).code
|
||||||
|
|
||||||
|
if (!compiledBundle) {
|
||||||
|
throw new Error('Failed to compile components bundle')
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentsFactory = new Function(
|
||||||
|
'React',
|
||||||
|
'Alert',
|
||||||
|
'Avatar',
|
||||||
|
'Badge',
|
||||||
|
'Button',
|
||||||
|
'Calendar',
|
||||||
|
'Card',
|
||||||
|
'Checkbox',
|
||||||
|
'ConfigProvider',
|
||||||
|
'DatePicker',
|
||||||
|
'Dialog',
|
||||||
|
'Drawer',
|
||||||
|
'Dropdown',
|
||||||
|
'FormItem',
|
||||||
|
'FormContainer',
|
||||||
|
'Input',
|
||||||
|
'InputGroup',
|
||||||
|
'Menu',
|
||||||
|
'MenuItem',
|
||||||
|
'Notification',
|
||||||
|
'Pagination',
|
||||||
|
'Progress',
|
||||||
|
'Radio',
|
||||||
|
'RangeCalendar',
|
||||||
|
'ScrollBar',
|
||||||
|
'Segment',
|
||||||
|
'Select',
|
||||||
|
'Skeleton',
|
||||||
|
'Spinner',
|
||||||
|
'Steps',
|
||||||
|
'Switcher',
|
||||||
|
'Table',
|
||||||
|
'Tabs',
|
||||||
|
'Tag',
|
||||||
|
'TimeInput',
|
||||||
|
'Timeline',
|
||||||
|
'toast',
|
||||||
|
'Tooltip',
|
||||||
|
'Upload',
|
||||||
|
'axios',
|
||||||
|
`return ${compiledBundle}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const compiledComponentsRegistry = componentsFactory(
|
||||||
|
React,
|
||||||
|
Alert,
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Calendar,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
ConfigProvider,
|
||||||
|
DatePicker,
|
||||||
|
Dialog,
|
||||||
|
Drawer,
|
||||||
|
Dropdown,
|
||||||
|
FormItem,
|
||||||
|
FormContainer,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Notification,
|
||||||
|
Pagination,
|
||||||
|
Progress,
|
||||||
|
Radio,
|
||||||
|
RangeCalendar,
|
||||||
|
ScrollBar,
|
||||||
|
Segment,
|
||||||
|
Select,
|
||||||
|
Skeleton,
|
||||||
|
Spinner,
|
||||||
|
Steps,
|
||||||
|
Switcher,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
TimeInput,
|
||||||
|
Timeline,
|
||||||
|
toast,
|
||||||
|
Tooltip,
|
||||||
|
Upload,
|
||||||
|
axios,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!cancelled) setCompiledComponents(compiledComponentsRegistry)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error compiling components bundle:', error)
|
||||||
|
if (!cancelled) setCompiledComponents({})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const componentsFactory = new Function(
|
void compileComponents()
|
||||||
'React',
|
|
||||||
'Alert',
|
|
||||||
'Avatar',
|
|
||||||
'Badge',
|
|
||||||
'Button',
|
|
||||||
'Calendar',
|
|
||||||
'Card',
|
|
||||||
'Checkbox',
|
|
||||||
'ConfigProvider',
|
|
||||||
'DatePicker',
|
|
||||||
'Dialog',
|
|
||||||
'Drawer',
|
|
||||||
'Dropdown',
|
|
||||||
'FormItem',
|
|
||||||
'FormContainer',
|
|
||||||
'Input',
|
|
||||||
'InputGroup',
|
|
||||||
'Menu',
|
|
||||||
'MenuItem',
|
|
||||||
'Notification',
|
|
||||||
'Pagination',
|
|
||||||
'Progress',
|
|
||||||
'Radio',
|
|
||||||
'RangeCalendar',
|
|
||||||
'ScrollBar',
|
|
||||||
'Segment',
|
|
||||||
'Select',
|
|
||||||
'Skeleton',
|
|
||||||
'Spinner',
|
|
||||||
'Steps',
|
|
||||||
'Switcher',
|
|
||||||
'Table',
|
|
||||||
'Tabs',
|
|
||||||
'Tag',
|
|
||||||
'TimeInput',
|
|
||||||
'Timeline',
|
|
||||||
'toast',
|
|
||||||
'Tooltip',
|
|
||||||
'Upload',
|
|
||||||
'axios',
|
|
||||||
`return ${compiledBundle}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const compiledComponentsRegistry = componentsFactory(
|
return () => {
|
||||||
React,
|
cancelled = true
|
||||||
Alert,
|
|
||||||
Avatar,
|
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Calendar,
|
|
||||||
Card,
|
|
||||||
Checkbox,
|
|
||||||
ConfigProvider,
|
|
||||||
DatePicker,
|
|
||||||
Dialog,
|
|
||||||
Drawer,
|
|
||||||
Dropdown,
|
|
||||||
FormItem,
|
|
||||||
FormContainer,
|
|
||||||
Input,
|
|
||||||
InputGroup,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
Notification,
|
|
||||||
Pagination,
|
|
||||||
Progress,
|
|
||||||
Radio,
|
|
||||||
RangeCalendar,
|
|
||||||
ScrollBar,
|
|
||||||
Segment,
|
|
||||||
Select,
|
|
||||||
Skeleton,
|
|
||||||
Spinner,
|
|
||||||
Steps,
|
|
||||||
Switcher,
|
|
||||||
Table,
|
|
||||||
Tabs,
|
|
||||||
Tag,
|
|
||||||
TimeInput,
|
|
||||||
Timeline,
|
|
||||||
toast,
|
|
||||||
Tooltip,
|
|
||||||
Upload,
|
|
||||||
axios,
|
|
||||||
)
|
|
||||||
|
|
||||||
setCompiledComponents(compiledComponentsRegistry)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error compiling components bundle:', error)
|
|
||||||
setCompiledComponents({})
|
|
||||||
}
|
}
|
||||||
}, [components, extractComponentInfo])
|
}, [components, extractComponentInfo])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,6 @@ import App from './App'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { UiEvalService } from './services/UiEvalService'
|
import { UiEvalService } from './services/UiEvalService'
|
||||||
|
|
||||||
import 'devextreme/ui/autocomplete'
|
|
||||||
import 'devextreme/ui/slider'
|
|
||||||
import 'devextreme/ui/text_area'
|
|
||||||
import 'devextreme/ui/html_editor'
|
|
||||||
|
|
||||||
import 'devextreme-react/text-area'
|
|
||||||
import 'devextreme-react/html-editor'
|
|
||||||
import 'devextreme-react/autocomplete'
|
|
||||||
import 'devextreme-react/slider'
|
|
||||||
|
|
||||||
import config from 'devextreme/core/config'
|
import config from 'devextreme/core/config'
|
||||||
import { licenseKey } from './devextreme-license'
|
import { licenseKey } from './devextreme-license'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,64 @@
|
||||||
export interface CrudEndpoint {
|
export interface CrudEndpoint {
|
||||||
id: string;
|
id: string
|
||||||
|
|
||||||
tenantId: string;
|
tenantId: string
|
||||||
entityName: string;
|
entityName: string
|
||||||
method: "GET" | "POST" | "PUT" | "DELETE";
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
path: string;
|
path: string
|
||||||
operationType: string;
|
operationType: string
|
||||||
isActive: boolean;
|
isActive: boolean
|
||||||
csharpCode: string;
|
csharpCode: string
|
||||||
creationTime: string;
|
creationTime: string
|
||||||
lastModificationTime?: string;
|
lastModificationTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUpdateCrudEndpointDto {
|
export interface CreateUpdateCrudEndpointDto {
|
||||||
tenantId: string;
|
tenantId: string
|
||||||
entityName: string;
|
entityName: string
|
||||||
method: "GET" | "POST" | "PUT" | "DELETE";
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
path: string;
|
path: string
|
||||||
operationType: string;
|
operationType: string
|
||||||
csharpCode: string;
|
csharpCode: string
|
||||||
isActive: boolean;
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomComponent {
|
export interface CustomComponent {
|
||||||
id: string;
|
id: string
|
||||||
tenantId?: string;
|
tenantId?: string
|
||||||
name: string;
|
name: string
|
||||||
code: string;
|
routePath: string
|
||||||
props?: string;
|
code: string
|
||||||
description?: string;
|
props?: string
|
||||||
isActive: boolean;
|
description?: string
|
||||||
dependencies?: string;
|
isActive: boolean
|
||||||
creationTime: string;
|
dependencies?: string
|
||||||
lastModificationTime?: string;
|
creationTime: string
|
||||||
|
lastModificationTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomComponentDto {
|
export interface CustomComponentDto {
|
||||||
id: string;
|
id: string
|
||||||
tenantId?: string;
|
tenantId?: string
|
||||||
name: string;
|
name: string
|
||||||
code: string;
|
routePath: string
|
||||||
props?: string;
|
code: string
|
||||||
description?: string;
|
props?: string
|
||||||
isActive: boolean;
|
description?: string
|
||||||
dependencies?: string; // JSON string of component names
|
isActive: boolean
|
||||||
creationTime: string;
|
dependencies?: string // JSON string of component names
|
||||||
lastModificationTime: string;
|
creationTime: string
|
||||||
|
lastModificationTime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUpdateCustomComponentDto {
|
export interface CreateUpdateCustomComponentDto {
|
||||||
tenantId?: string;
|
tenantId?: string
|
||||||
name: string;
|
name: string
|
||||||
code: string;
|
routePath: string
|
||||||
props?: string;
|
code: string
|
||||||
description?: string;
|
props?: string
|
||||||
isActive: boolean;
|
description?: string
|
||||||
dependencies?: string;
|
isActive: boolean
|
||||||
|
dependencies?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RelationshipType = 'OneToOne' | 'OneToMany'
|
export type RelationshipType = 'OneToOne' | 'OneToMany'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { lazy } from 'react'
|
import React, { lazy } from 'react'
|
||||||
import { RouteDto } from '@/proxy/routes/models'
|
import { RouteDto } from '@/proxy/routes/models'
|
||||||
|
import { CustomComponent } from '@/proxy/developerKit/models'
|
||||||
|
|
||||||
// Tüm view bileşenlerini import et (vite özel)
|
// Tüm view bileşenlerini import et (vite özel)
|
||||||
// shared klasörü hariç, çünkü bu bileşenler genellikle başka yerlerde statik import ediliyor
|
// shared klasörü hariç, çünkü bu bileşenler genellikle başka yerlerde statik import ediliyor
|
||||||
|
|
@ -128,3 +129,27 @@ export function mapDynamicRoutes(routes: RouteDto[]): DynamicReactRoute[] {
|
||||||
componentPath: route.componentPath,
|
componentPath: route.componentPath,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom components are the single source of truth for runtime routes.
|
||||||
|
export function mapCustomComponentRoutes(
|
||||||
|
components: Pick<CustomComponent, 'id' | 'name' | 'routePath' | 'isActive'>[],
|
||||||
|
): DynamicReactRoute[] {
|
||||||
|
return components
|
||||||
|
.filter((component) => component.isActive && component.routePath?.trim())
|
||||||
|
.map((component) => ({
|
||||||
|
key: `custom-component-${component.id}`,
|
||||||
|
path: component.routePath.startsWith('/') ? component.routePath : `/${component.routePath}`,
|
||||||
|
getComponent: (registeredComponents, renderComponent, isComponentRegistered) =>
|
||||||
|
loadComponent(
|
||||||
|
'dynamic',
|
||||||
|
component.name,
|
||||||
|
registeredComponents,
|
||||||
|
renderComponent,
|
||||||
|
isComponentRegistered,
|
||||||
|
),
|
||||||
|
routeType: component.routePath.startsWith('/admin/') ? 'protected' : 'public',
|
||||||
|
authority: [],
|
||||||
|
componentType: 'dynamic',
|
||||||
|
componentPath: component.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// DynamicRouter.tsx
|
// DynamicRouter.tsx
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
|
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
|
||||||
import { mapDynamicRoutes } from './dynamicRouteLoader'
|
import { mapCustomComponentRoutes, mapDynamicRoutes } from './dynamicRouteLoader'
|
||||||
import { useDynamicRoutes } from './dynamicRoutesContext'
|
import { useDynamicRoutes } from './dynamicRoutesContext'
|
||||||
import { useComponents } from '@/contexts/ComponentContext'
|
import { useComponents } from '@/contexts/ComponentContext'
|
||||||
import ProtectedRoute from '@/components/route/ProtectedRoute'
|
import ProtectedRoute from '@/components/route/ProtectedRoute'
|
||||||
|
|
@ -48,10 +48,17 @@ const LegacyEmailConfirmationRedirect = () => {
|
||||||
|
|
||||||
export const DynamicRouter: React.FC = () => {
|
export const DynamicRouter: React.FC = () => {
|
||||||
const { routes, loading, error } = useDynamicRoutes()
|
const { routes, loading, error } = useDynamicRoutes()
|
||||||
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
|
const { components, registeredComponents, renderComponent, isComponentRegistered } =
|
||||||
|
useComponents()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
|
const dynamicRoutes = React.useMemo(
|
||||||
|
() => [
|
||||||
|
...mapDynamicRoutes(routes),
|
||||||
|
...mapCustomComponentRoutes(components),
|
||||||
|
],
|
||||||
|
[routes, components],
|
||||||
|
)
|
||||||
|
|
||||||
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
|
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
|
||||||
if (loading && location.pathname !== '/setup') return <div>Loading...</div>
|
if (loading && location.pathname !== '/setup') return <div>Loading...</div>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,22 @@ export const updateSent = (notificationId: string, isSent: boolean) =>
|
||||||
params: { isSent },
|
params: { isSent },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const updateReadMany = (notificationIds: string[], isRead: boolean) =>
|
||||||
|
apiService.fetchData<number>({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/api/app/notification/read-many`,
|
||||||
|
params: { isRead },
|
||||||
|
data: notificationIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateSentMany = (notificationIds: string[], isSent: boolean) =>
|
||||||
|
apiService.fetchData<number>({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/api/app/notification/sent-many`,
|
||||||
|
params: { isSent },
|
||||||
|
data: notificationIds,
|
||||||
|
})
|
||||||
|
|
||||||
export const postMyNotificationByNotificationRuleId = (data: { id: string; message: string }) =>
|
export const postMyNotificationByNotificationRuleId = (data: { id: string; message: string }) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum';
|
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum';
|
||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
|
|
||||||
interface UseSearchProps {
|
interface UseSearchProps {
|
||||||
categories: ForumCategory[];
|
categories: ForumCategory[];
|
||||||
|
|
@ -16,14 +16,7 @@ interface SearchResult {
|
||||||
|
|
||||||
export function useForumSearch({ categories, topics, posts }: UseSearchProps) {
|
export function useForumSearch({ categories, topics, posts }: UseSearchProps) {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<SearchResult>({
|
const searchResults: SearchResult = useMemo(() => {
|
||||||
categories: [],
|
|
||||||
topics: [],
|
|
||||||
posts: [],
|
|
||||||
totalCount: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const performSearch = useMemo(() => {
|
|
||||||
if (!searchQuery.trim()) {
|
if (!searchQuery.trim()) {
|
||||||
return {
|
return {
|
||||||
categories: [],
|
categories: [],
|
||||||
|
|
@ -62,10 +55,6 @@ export function useForumSearch({ categories, topics, posts }: UseSearchProps) {
|
||||||
};
|
};
|
||||||
}, [searchQuery, categories, topics, posts]);
|
}, [searchQuery, categories, topics, posts]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSearchResults(performSearch);
|
|
||||||
}, [performSearch]);
|
|
||||||
|
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
11
ui/src/utils/registerDevExtremeEditors.ts
Normal file
11
ui/src/utils/registerDevExtremeEditors.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Metadata-driven forms resolve these editors by their DevExtreme widget names.
|
||||||
|
// Import this module from lazy List/Form boundaries before those forms render.
|
||||||
|
import 'devextreme/ui/autocomplete'
|
||||||
|
import 'devextreme/ui/slider'
|
||||||
|
import 'devextreme/ui/text_area'
|
||||||
|
import 'devextreme/ui/html_editor'
|
||||||
|
|
||||||
|
import 'devextreme-react/text-area'
|
||||||
|
import 'devextreme-react/html-editor'
|
||||||
|
import 'devextreme-react/autocomplete'
|
||||||
|
import 'devextreme-react/slider'
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
|
import { lazy, Suspense } from 'react'
|
||||||
import { FaMicrophoneSlash, FaUserTimes } from 'react-icons/fa'
|
import { FaMicrophoneSlash, FaUserTimes } from 'react-icons/fa'
|
||||||
import { VideoPlayer } from './VideoPlayer'
|
|
||||||
import { VideoroomParticipantDto, VideoroomLayoutDto } from '@/proxy/videoroom/models'
|
import { VideoroomParticipantDto, VideoroomLayoutDto } from '@/proxy/videoroom/models'
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
|
|
||||||
|
const VideoPlayer = lazy(() =>
|
||||||
|
import('./VideoPlayer').then((module) => ({ default: module.VideoPlayer })),
|
||||||
|
)
|
||||||
|
|
||||||
interface RoomParticipantProps {
|
interface RoomParticipantProps {
|
||||||
participants: VideoroomParticipantDto[]
|
participants: VideoroomParticipantDto[]
|
||||||
localStream?: MediaStream | null
|
localStream?: MediaStream | null
|
||||||
|
|
@ -216,17 +220,19 @@ export const RoomParticipant = ({
|
||||||
style={{ minHeight: 0, minWidth: 0 }}
|
style={{ minHeight: 0, minWidth: 0 }}
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 w-full h-full">
|
<div className="absolute inset-0 w-full h-full">
|
||||||
<VideoPlayer
|
<Suspense fallback={<div className="h-full w-full bg-gray-900" />}>
|
||||||
stream={participant.stream}
|
<VideoPlayer
|
||||||
isLocal={participant.id === currentUserId}
|
stream={participant.stream}
|
||||||
userName={participant.name}
|
isLocal={participant.id === currentUserId}
|
||||||
isAudioEnabled={
|
userName={participant.name}
|
||||||
participant.id === currentUserId ? isAudioEnabled : !participant.isAudioMuted
|
isAudioEnabled={
|
||||||
}
|
participant.id === currentUserId ? isAudioEnabled : !participant.isAudioMuted
|
||||||
isVideoEnabled={
|
}
|
||||||
participant.id === currentUserId ? isVideoEnabled : !participant.isVideoMuted
|
isVideoEnabled={
|
||||||
}
|
participant.id === currentUserId ? isVideoEnabled : !participant.isVideoMuted
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
{/* Teacher controls for students */}
|
{/* Teacher controls for students */}
|
||||||
{isTeacher && participant.id !== currentUserId && (
|
{isTeacher && participant.id !== currentUserId && (
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { Button, Checkbox, FormContainer, FormItem, Input } from '@/components/u
|
||||||
// Validation schema
|
// Validation schema
|
||||||
const validationSchema = Yup.object({
|
const validationSchema = Yup.object({
|
||||||
name: Yup.string().required(),
|
name: Yup.string().required(),
|
||||||
|
routePath: Yup.string().required().matches(/^\//, 'Route path must start with /'),
|
||||||
description: Yup.string(),
|
description: Yup.string(),
|
||||||
dependencies: Yup.array().of(Yup.string()),
|
dependencies: Yup.array().of(Yup.string()),
|
||||||
code: Yup.string(),
|
code: Yup.string(),
|
||||||
|
|
@ -32,6 +33,7 @@ const ComponentEditor: React.FC = () => {
|
||||||
// Initial values for Formik
|
// Initial values for Formik
|
||||||
const [initialValues, setInitialValues] = useState({
|
const [initialValues, setInitialValues] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
|
routePath: '',
|
||||||
description: '',
|
description: '',
|
||||||
dependencies: [] as string[],
|
dependencies: [] as string[],
|
||||||
code: '',
|
code: '',
|
||||||
|
|
@ -54,6 +56,7 @@ const ComponentEditor: React.FC = () => {
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
name: component.name,
|
name: component.name,
|
||||||
|
routePath: component.routePath,
|
||||||
description: component.description || '',
|
description: component.description || '',
|
||||||
dependencies: deps,
|
dependencies: deps,
|
||||||
code: component.code,
|
code: component.code,
|
||||||
|
|
@ -97,6 +100,7 @@ export default ${pascalCaseName}Component;`
|
||||||
try {
|
try {
|
||||||
const componentData = {
|
const componentData = {
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
|
routePath: values.routePath.trim(),
|
||||||
description: values.description.trim(),
|
description: values.description.trim(),
|
||||||
dependencies: JSON.stringify(values.dependencies), // Serialize dependencies to JSON string
|
dependencies: JSON.stringify(values.dependencies), // Serialize dependencies to JSON string
|
||||||
code: values.code.trim(),
|
code: values.code.trim(),
|
||||||
|
|
@ -104,9 +108,9 @@ export default ${pascalCaseName}Component;`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditing && id) {
|
if (isEditing && id) {
|
||||||
updateComponent(id, componentData)
|
await updateComponent(id, componentData)
|
||||||
} else {
|
} else {
|
||||||
addComponent(componentData)
|
await addComponent(componentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(ROUTES_ENUM.protected.saas.developerKit.components)
|
navigate(ROUTES_ENUM.protected.saas.developerKit.components)
|
||||||
|
|
@ -240,6 +244,19 @@ export default ${pascalCaseName}Component;`
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label="Route Path"
|
||||||
|
invalid={!!(errors.routePath && touched.routePath)}
|
||||||
|
errorMessage={errors.routePath as string}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
name="routePath"
|
||||||
|
type="text"
|
||||||
|
component={Input}
|
||||||
|
placeholder="e.g., /roles or /admin/reports"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.DetailsDescription')}
|
label={translate('::ListForms.ListFormEdit.DetailsDescription')}
|
||||||
invalid={!!(errors.description && touched.description)}
|
invalid={!!(errors.description && touched.description)}
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,9 @@ const ComponentManager: React.FC = () => {
|
||||||
{/* Sol taraf */}
|
{/* Sol taraf */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-gray-100">{component.name}</h3>
|
<h3 className="text-lg font-semibold text-slate-900 dark:text-gray-100">
|
||||||
|
{component.name}
|
||||||
|
</h3>
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${
|
className={`w-2 h-2 rounded-full ${
|
||||||
component.isActive ? 'bg-green-500' : 'bg-slate-300 dark:bg-gray-700'
|
component.isActive ? 'bg-green-500' : 'bg-slate-300 dark:bg-gray-700'
|
||||||
|
|
@ -168,7 +170,7 @@ const ComponentManager: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-slate-600 dark:text-gray-300 text-sm mb-3">
|
<p className="text-slate-600 dark:text-gray-300 text-sm mb-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(component.dependencies ?? '[]')
|
const parsed = JSON.parse(component.dependencies ?? '[]')
|
||||||
|
|
@ -181,8 +183,14 @@ const ComponentManager: React.FC = () => {
|
||||||
})()}
|
})()}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p className="text-slate-600 dark:text-gray-300 text-sm mb-2">
|
||||||
|
{component.routePath}
|
||||||
|
</p>
|
||||||
|
|
||||||
{component.description && (
|
{component.description && (
|
||||||
<p className="text-slate-600 dark:text-gray-300 text-sm mb-3">{component.description}</p>
|
<p className="text-slate-600 dark:text-gray-300 text-sm mb-2">
|
||||||
|
{component.description}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -249,7 +257,7 @@ const ComponentManager: React.FC = () => {
|
||||||
}
|
}
|
||||||
title={translate('::App.DeveloperKit.Component.Edit')}
|
title={translate('::App.DeveloperKit.Component.Edit')}
|
||||||
>
|
>
|
||||||
<FaRegEdit className="w-4 h-4" />
|
<FaRegEdit className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -266,7 +274,7 @@ const ComponentManager: React.FC = () => {
|
||||||
}
|
}
|
||||||
title={translate('::App.DeveloperKit.Component.View')}
|
title={translate('::App.DeveloperKit.Component.View')}
|
||||||
>
|
>
|
||||||
<FaEye className="w-4 h-4" />
|
<FaEye className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -276,7 +284,7 @@ const ComponentManager: React.FC = () => {
|
||||||
onClick={() => handleDelete(component.id)}
|
onClick={() => handleDelete(component.id)}
|
||||||
title={translate('::App.DeveloperKit.Component.Delete')}
|
title={translate('::App.DeveloperKit.Component.Delete')}
|
||||||
>
|
>
|
||||||
<FaTrashAlt className="w-4 h-4" />
|
<FaTrashAlt className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
import { lazy, Suspense, useState, useCallback, useEffect, useRef } from 'react'
|
||||||
import type { Dispatch, SetStateAction } from 'react'
|
import type { Dispatch, SetStateAction } from 'react'
|
||||||
import { Button, Checkbox, Dialog, Notification, toast } from '@/components/ui'
|
import { Button, Checkbox, Dialog, Notification, toast } from '@/components/ui'
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
|
|
@ -18,13 +18,11 @@ import {
|
||||||
FaArrowRight,
|
FaArrowRight,
|
||||||
FaEye,
|
FaEye,
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaFolderOpen
|
FaFolderOpen,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
|
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
|
||||||
import SqlEditor, { SqlEditorRef } from './SqlEditor'
|
import SqlEditor, { SqlEditorRef } from './SqlEditor'
|
||||||
import SqlResultsGrid from './SqlResultsGrid'
|
|
||||||
import SqlTableDesignerDialog from './SqlTableDesignerDialog'
|
|
||||||
import { Splitter } from '@/components/codeLayout/Splitter'
|
import { Splitter } from '@/components/codeLayout/Splitter'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
@ -32,6 +30,9 @@ import { APP_NAME } from '@/constants/app.constant'
|
||||||
import { UiEvalService } from '@/services/UiEvalService'
|
import { UiEvalService } from '@/services/UiEvalService'
|
||||||
import { FcAcceptDatabase } from 'react-icons/fc'
|
import { FcAcceptDatabase } from 'react-icons/fc'
|
||||||
|
|
||||||
|
const SqlResultsGrid = lazy(() => import('./SqlResultsGrid'))
|
||||||
|
const SqlTableDesignerDialog = lazy(() => import('./SqlTableDesignerDialog'))
|
||||||
|
|
||||||
interface SqlManagerState {
|
interface SqlManagerState {
|
||||||
dataSources: DataSourceDto[]
|
dataSources: DataSourceDto[]
|
||||||
selectedDataSource: string | null
|
selectedDataSource: string | null
|
||||||
|
|
@ -1345,13 +1346,15 @@ GO`,
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-hidden p-2">
|
<div className="flex-1 overflow-hidden p-2">
|
||||||
<SqlResultsGrid
|
<Suspense fallback={<div className="p-4">{translate('::App.Loading')}</div>}>
|
||||||
result={(state.executionResult || state.tableColumns)!}
|
<SqlResultsGrid
|
||||||
queryText={state.lastExecutedQuery}
|
result={(state.executionResult || state.tableColumns)!}
|
||||||
dataSourceCode={state.selectedDataSource}
|
queryText={state.lastExecutedQuery}
|
||||||
isPostgreSql={isPostgreSql}
|
dataSourceCode={state.selectedDataSource}
|
||||||
onExecuteMutation={handleResultsMutation}
|
isPostgreSql={isPostgreSql}
|
||||||
/>
|
onExecuteMutation={handleResultsMutation}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Splitter>
|
</Splitter>
|
||||||
|
|
@ -1412,9 +1415,7 @@ GO`,
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoadingSqlDataFiles ? (
|
{isLoadingSqlDataFiles ? (
|
||||||
<p className="mb-4 text-gray-600 dark:text-gray-400">
|
<p className="mb-4 text-gray-600 dark:text-gray-400">{translate('::App.Loading')}</p>
|
||||||
{translate('::App.Loading')}
|
|
||||||
</p>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex min-h-0 flex-1 flex-col gap-3 lg:flex-row">
|
<div className="flex min-h-0 flex-1 flex-col gap-3 lg:flex-row">
|
||||||
{renderSqlDataPane(
|
{renderSqlDataPane(
|
||||||
|
|
@ -1501,18 +1502,22 @@ GO`,
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* Table Designer Dialog */}
|
{/* Table Designer Dialog */}
|
||||||
<SqlTableDesignerDialog
|
{showTableDesignerDialog && (
|
||||||
isOpen={showTableDesignerDialog}
|
<Suspense fallback={null}>
|
||||||
onClose={() => {
|
<SqlTableDesignerDialog
|
||||||
setShowTableDesignerDialog(false)
|
isOpen
|
||||||
setDesignTableData(null)
|
onClose={() => {
|
||||||
}}
|
setShowTableDesignerDialog(false)
|
||||||
dataSource={state.selectedDataSource ?? ''}
|
setDesignTableData(null)
|
||||||
initialTableData={designTableData}
|
}}
|
||||||
onDeployed={() => {
|
dataSource={state.selectedDataSource ?? ''}
|
||||||
setState((prev) => ({ ...prev, refreshTrigger: prev.refreshTrigger + 1 }))
|
initialTableData={designTableData}
|
||||||
}}
|
onDeployed={() => {
|
||||||
/>
|
setState((prev) => ({ ...prev, refreshTrigger: prev.refreshTrigger + 1 }))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={showCopyDialog}
|
isOpen={showCopyDialog}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { DX_CLASSNAMES, MULTIVALUE_DELIMITER } from '@/constants/app.constant'
|
import { DX_CLASSNAMES, MULTIVALUE_DELIMITER } from '@/constants/app.constant'
|
||||||
|
import '@/utils/registerDevExtremeEditors'
|
||||||
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
||||||
import {
|
import {
|
||||||
Form as FormDx,
|
Form as FormDx,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export function Management() {
|
||||||
categories,
|
categories,
|
||||||
topics,
|
topics,
|
||||||
posts,
|
posts,
|
||||||
|
totalCounts,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
createCategory,
|
createCategory,
|
||||||
|
|
@ -81,6 +82,7 @@ export function Management() {
|
||||||
categories={categories}
|
categories={categories}
|
||||||
topics={topics}
|
topics={topics}
|
||||||
posts={posts}
|
posts={posts}
|
||||||
|
totalCounts={totalCounts}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onCreateCategory={(data) => createCategory(data).then(() => {})}
|
onCreateCategory={(data) => createCategory(data).then(() => {})}
|
||||||
onUpdateCategory={(id, data) => updateCategory(id, data).then(() => {})}
|
onUpdateCategory={(id, data) => updateCategory(id, data).then(() => {})}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ interface AdminViewProps {
|
||||||
categories: ForumCategory[]
|
categories: ForumCategory[]
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
posts: ForumPost[]
|
posts: ForumPost[]
|
||||||
|
totalCounts: { categories: number; topics: number; posts: number }
|
||||||
loading: boolean
|
loading: boolean
|
||||||
onCreateCategory: (category: {
|
onCreateCategory: (category: {
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -54,6 +55,7 @@ export function AdminView({
|
||||||
categories,
|
categories,
|
||||||
topics,
|
topics,
|
||||||
posts,
|
posts,
|
||||||
|
totalCounts,
|
||||||
loading,
|
loading,
|
||||||
onCreateCategory,
|
onCreateCategory,
|
||||||
onUpdateCategory,
|
onUpdateCategory,
|
||||||
|
|
@ -128,7 +130,7 @@ export function AdminView({
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
<div className="flex-1 bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
||||||
{activeSection === 'stats' && (
|
{activeSection === 'stats' && (
|
||||||
<AdminStats categories={categories} topics={topics} posts={posts} />
|
<AdminStats categories={categories} topics={topics} posts={posts} totalCounts={totalCounts} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeSection === 'categories' && (
|
{activeSection === 'categories' && (
|
||||||
|
|
@ -146,6 +148,7 @@ export function AdminView({
|
||||||
{activeSection === 'topics' && (
|
{activeSection === 'topics' && (
|
||||||
<TopicManagement
|
<TopicManagement
|
||||||
topics={topics}
|
topics={topics}
|
||||||
|
totalCount={totalCounts.topics}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onCreateTopic={onCreateTopic}
|
onCreateTopic={onCreateTopic}
|
||||||
|
|
@ -163,6 +166,7 @@ export function AdminView({
|
||||||
{activeSection === 'posts' && (
|
{activeSection === 'posts' && (
|
||||||
<PostManagement
|
<PostManagement
|
||||||
posts={posts}
|
posts={posts}
|
||||||
|
totalCount={totalCounts.posts}
|
||||||
topics={topics}
|
topics={topics}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onCreatePost={onCreatePost}
|
onCreatePost={onCreatePost}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ interface AdminStatsProps {
|
||||||
categories: ForumCategory[]
|
categories: ForumCategory[]
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
posts: ForumPost[]
|
posts: ForumPost[]
|
||||||
|
totalCounts: { categories: number; topics: number; posts: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Activity {
|
interface Activity {
|
||||||
|
|
@ -17,13 +18,13 @@ interface Activity {
|
||||||
date: Date
|
date: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdminStats({ categories, topics, posts }: AdminStatsProps) {
|
export function AdminStats({ categories, topics, posts, totalCounts }: AdminStatsProps) {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const totalCategories = categories.length
|
const totalCategories = totalCounts.categories
|
||||||
const activeCategories = categories.filter((c) => c.isActive).length
|
const activeCategories = categories.filter((c) => c.isActive).length
|
||||||
const totalTopics = topics.length
|
const totalTopics = totalCounts.topics
|
||||||
const solvedTopics = topics.filter((t) => t.isSolved).length
|
const solvedTopics = topics.filter((t) => t.isSolved).length
|
||||||
const totalPosts = posts.length
|
const totalPosts = totalCounts.posts
|
||||||
const acceptedAnswers = posts.filter((p) => p.isAcceptedAnswer).length
|
const acceptedAnswers = posts.filter((p) => p.isAcceptedAnswer).length
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import {
|
||||||
|
|
||||||
interface PostManagementProps {
|
interface PostManagementProps {
|
||||||
posts: ForumPost[]
|
posts: ForumPost[]
|
||||||
|
totalCount: number
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
loading: boolean
|
loading: boolean
|
||||||
onCreatePost: (post: {
|
onCreatePost: (post: {
|
||||||
|
|
@ -44,6 +45,7 @@ interface PostManagementProps {
|
||||||
|
|
||||||
export function PostManagement({
|
export function PostManagement({
|
||||||
posts,
|
posts,
|
||||||
|
totalCount,
|
||||||
topics,
|
topics,
|
||||||
loading,
|
loading,
|
||||||
onCreatePost,
|
onCreatePost,
|
||||||
|
|
@ -270,7 +272,7 @@ export function PostManagement({
|
||||||
{/* Posts List */}
|
{/* Posts List */}
|
||||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Posts ({posts.length})</h3>
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Posts ({totalCount})</h3>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { formatForumDate } from '../forum/utils'
|
||||||
|
|
||||||
interface TopicManagementProps {
|
interface TopicManagementProps {
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
|
totalCount: number
|
||||||
categories: ForumCategory[]
|
categories: ForumCategory[]
|
||||||
loading: boolean
|
loading: boolean
|
||||||
onCreateTopic: (topic: {
|
onCreateTopic: (topic: {
|
||||||
|
|
@ -45,6 +46,7 @@ interface TopicManagementProps {
|
||||||
|
|
||||||
export function TopicManagement({
|
export function TopicManagement({
|
||||||
topics,
|
topics,
|
||||||
|
totalCount,
|
||||||
categories,
|
categories,
|
||||||
loading,
|
loading,
|
||||||
onCreateTopic,
|
onCreateTopic,
|
||||||
|
|
@ -265,7 +267,7 @@ export function TopicManagement({
|
||||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="flex items-center justify-between px-3 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{translate('::App.Forum.Dashboard.Topics')} ({topics.length})
|
{translate('::App.Forum.Dashboard.Topics')} ({totalCount})
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export function useForumData() {
|
||||||
const [categories, setCategories] = useState<ForumCategory[]>([])
|
const [categories, setCategories] = useState<ForumCategory[]>([])
|
||||||
const [topics, setTopics] = useState<ForumTopic[]>([])
|
const [topics, setTopics] = useState<ForumTopic[]>([])
|
||||||
const [posts, setPosts] = useState<ForumPost[]>([])
|
const [posts, setPosts] = useState<ForumPost[]>([])
|
||||||
|
const [totalCounts, setTotalCounts] = useState({ categories: 0, topics: 0, posts: 0 })
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const response = await forumService.getCategories()
|
const response = await forumService.getCategories()
|
||||||
setCategories(response.items)
|
setCategories(response.items)
|
||||||
|
setTotalCounts((prev) => ({ ...prev, categories: response.totalCount }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to load categories')
|
setError('Failed to load categories')
|
||||||
console.error('Error loading categories:', err)
|
console.error('Error loading categories:', err)
|
||||||
|
|
@ -34,6 +36,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const response = await forumService.getTopics({ categoryId })
|
const response = await forumService.getTopics({ categoryId })
|
||||||
setTopics(response.items)
|
setTopics(response.items)
|
||||||
|
if (!categoryId) setTotalCounts((prev) => ({ ...prev, topics: response.totalCount }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to load topics')
|
setError('Failed to load topics')
|
||||||
console.error('Error loading topics:', err)
|
console.error('Error loading topics:', err)
|
||||||
|
|
@ -47,6 +50,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const response = await forumService.getPosts({ topicId })
|
const response = await forumService.getPosts({ topicId })
|
||||||
setPosts(response.items)
|
setPosts(response.items)
|
||||||
|
if (!topicId) setTotalCounts((prev) => ({ ...prev, posts: response.totalCount }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to load posts')
|
setError('Failed to load posts')
|
||||||
console.error('Error loading posts:', err)
|
console.error('Error loading posts:', err)
|
||||||
|
|
@ -61,6 +65,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const newCategory = await forumService.createCategory(categoryData)
|
const newCategory = await forumService.createCategory(categoryData)
|
||||||
setCategories((prev) => [...prev, newCategory])
|
setCategories((prev) => [...prev, newCategory])
|
||||||
|
setTotalCounts((prev) => ({ ...prev, categories: prev.categories + 1 }))
|
||||||
return newCategory
|
return newCategory
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to create category')
|
setError('Failed to create category')
|
||||||
|
|
@ -100,12 +105,13 @@ export function useForumData() {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
await forumService.deleteCategory(id)
|
await forumService.deleteCategory(id)
|
||||||
setCategories((prev) => prev.filter((cat) => cat.id !== id))
|
|
||||||
// Also remove related topics and posts
|
// Also remove related topics and posts
|
||||||
const topicsToDelete = topics.filter((topic) => topic.categoryId === id)
|
const topicsToDelete = topics.filter((topic) => topic.categoryId === id)
|
||||||
const topicIds = topicsToDelete.map((t) => t.id)
|
const topicIds = topicsToDelete.map((t) => t.id)
|
||||||
|
setCategories((prev) => prev.filter((cat) => cat.id !== id))
|
||||||
setTopics((prev) => prev.filter((topic) => topic.categoryId !== id))
|
setTopics((prev) => prev.filter((topic) => topic.categoryId !== id))
|
||||||
setPosts((prev) => prev.filter((post) => !topicIds.includes(post.topicId)))
|
setPosts((prev) => prev.filter((post) => !topicIds.includes(post.topicId)))
|
||||||
|
await Promise.all([loadCategories(), loadTopics(), loadPosts()])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to delete category')
|
setError('Failed to delete category')
|
||||||
console.error('Error deleting category:', err)
|
console.error('Error deleting category:', err)
|
||||||
|
|
@ -121,6 +127,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const newTopic = await forumService.createTopic(topicData)
|
const newTopic = await forumService.createTopic(topicData)
|
||||||
setTopics((prev) => [...prev, newTopic])
|
setTopics((prev) => [...prev, newTopic])
|
||||||
|
setTotalCounts((prev) => ({ ...prev, topics: prev.topics + 1 }))
|
||||||
// Update category topic count
|
// Update category topic count
|
||||||
setCategories((prev) =>
|
setCategories((prev) =>
|
||||||
prev.map((cat) =>
|
prev.map((cat) =>
|
||||||
|
|
@ -177,6 +184,7 @@ export function useForumData() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
await Promise.all([loadCategories(), loadTopics(), loadPosts()])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Failed to delete topic')
|
setError('Failed to delete topic')
|
||||||
console.error('Error deleting topic:', err)
|
console.error('Error deleting topic:', err)
|
||||||
|
|
@ -264,6 +272,7 @@ export function useForumData() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const newPost = await forumService.createPost(postData)
|
const newPost = await forumService.createPost(postData)
|
||||||
setPosts((prev) => [...prev, newPost])
|
setPosts((prev) => [...prev, newPost])
|
||||||
|
setTotalCounts((prev) => ({ ...prev, posts: prev.posts + 1 }))
|
||||||
|
|
||||||
// Update topic and category post counts
|
// Update topic and category post counts
|
||||||
const topic = topics.find((t) => t.id === postData.topicId)
|
const topic = topics.find((t) => t.id === postData.topicId)
|
||||||
|
|
@ -309,6 +318,7 @@ export function useForumData() {
|
||||||
const post = posts.find((p) => p.id === id)
|
const post = posts.find((p) => p.id === id)
|
||||||
await forumService.deletePost(id)
|
await forumService.deletePost(id)
|
||||||
setPosts((prev) => prev.filter((p) => p.id !== id))
|
setPosts((prev) => prev.filter((p) => p.id !== id))
|
||||||
|
setTotalCounts((prev) => ({ ...prev, posts: Math.max(0, prev.posts - 1) }))
|
||||||
|
|
||||||
// Update topic and category counts
|
// Update topic and category counts
|
||||||
if (post) {
|
if (post) {
|
||||||
|
|
@ -391,6 +401,7 @@ export function useForumData() {
|
||||||
categories,
|
categories,
|
||||||
topics,
|
topics,
|
||||||
posts,
|
posts,
|
||||||
|
totalCounts,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, useRef } from 'react'
|
import React, { useState, useRef } from 'react'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import EmojiPicker, { EmojiClickData, Theme } from 'emoji-picker-react'
|
import type { EmojiClickData, Theme } from 'emoji-picker-react'
|
||||||
import { FaChartBar, FaSmile, FaTimes, FaImages, FaMapMarkerAlt } from 'react-icons/fa'
|
import { FaChartBar, FaSmile, FaTimes, FaImages, FaMapMarkerAlt } from 'react-icons/fa'
|
||||||
import MediaManager from './MediaManager'
|
import MediaManager from './MediaManager'
|
||||||
import LocationPicker from './LocationPicker'
|
import LocationPicker from './LocationPicker'
|
||||||
|
|
@ -26,6 +26,8 @@ interface CreatePostProps {
|
||||||
}) => void
|
}) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EmojiPicker = React.lazy(() => import('emoji-picker-react'))
|
||||||
|
|
||||||
const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const [content, setContent] = useState('')
|
const [content, setContent] = useState('')
|
||||||
|
|
@ -471,13 +473,15 @@ const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
||||||
ref={emojiPickerRef}
|
ref={emojiPickerRef}
|
||||||
className="absolute bottom-6 left-0 z-50 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-2"
|
className="absolute bottom-6 left-0 z-50 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-2"
|
||||||
>
|
>
|
||||||
<EmojiPicker
|
<React.Suspense fallback={<div className="h-[350px] w-[350px] max-w-[calc(100vw-32px)]" />}>
|
||||||
searchDisabled
|
<EmojiPicker
|
||||||
theme={theme.mode === 'dark' ? Theme.DARK : Theme.LIGHT}
|
searchDisabled
|
||||||
height={350}
|
theme={(theme.mode === 'dark' ? 'dark' : 'light') as Theme}
|
||||||
onEmojiClick={handleEmojiClick}
|
height={350}
|
||||||
autoFocusSearch={false}
|
onEmojiClick={handleEmojiClick}
|
||||||
/>
|
autoFocusSearch={false}
|
||||||
|
/>
|
||||||
|
</React.Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
|
import '@/utils/registerDevExtremeEditors'
|
||||||
import { Dialog, Notification, toast } from '@/components/ui'
|
import { Dialog, Notification, toast } from '@/components/ui'
|
||||||
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { Suspense, startTransition, useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { Suspense, startTransition, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import '@/utils/registerDevExtremeEditors'
|
||||||
import { useParams, useSearchParams } from 'react-router-dom'
|
import { useParams, useSearchParams } from 'react-router-dom'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
|
import '@/utils/registerDevExtremeEditors'
|
||||||
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
||||||
import {
|
import {
|
||||||
DbTypeEnum,
|
DbTypeEnum,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
|
import '@/utils/registerDevExtremeEditors'
|
||||||
import { Dialog, Notification, toast } from '@/components/ui'
|
import { Dialog, Notification, toast } from '@/components/ui'
|
||||||
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
|
||||||
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,9 @@ export default defineConfig(async ({ mode }) => {
|
||||||
mode === 'production'
|
mode === 'production'
|
||||||
? {
|
? {
|
||||||
globDirectory: 'dist',
|
globDirectory: 'dist',
|
||||||
globPatterns: ['**/*.{js,css,html,wasm}'],
|
// Precache only the application shell. Feature chunks and the
|
||||||
|
// many optional DevExtreme themes are cached on first use.
|
||||||
|
globPatterns: ['index.html', 'assets/css/index-*.css', 'assets/js/index-*.js'],
|
||||||
|
|
||||||
// Büyük asset'leri de cache'leyebil
|
// Büyük asset'leri de cache'leyebil
|
||||||
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
|
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
|
||||||
|
|
@ -49,14 +51,28 @@ export default defineConfig(async ({ mode }) => {
|
||||||
// ⭐⭐ BU KISMI EKLEYİN: Cache sorununu çözecek runtime caching
|
// ⭐⭐ BU KISMI EKLEYİN: Cache sorununu çözecek runtime caching
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: /\.(?:js|css|json)$/,
|
urlPattern: /\.(?:js|css|wasm)$/,
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'static-resources-v2',
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 200,
|
||||||
|
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /\.json$/,
|
||||||
handler: 'NetworkFirst',
|
handler: 'NetworkFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'static-resources-v1',
|
cacheName: 'json-resources-v1',
|
||||||
networkTimeoutSeconds: 10,
|
networkTimeoutSeconds: 5,
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 50,
|
maxEntries: 50,
|
||||||
maxAgeSeconds: 24 * 60 * 60, // 24 saat
|
maxAgeSeconds: 24 * 60 * 60,
|
||||||
},
|
},
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
|
|
@ -119,8 +135,8 @@ export default defineConfig(async ({ mode }) => {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
// ⭐ YENİ EKLENEN: Hot reload için polling
|
// ⭐ YENİ EKLENEN: Hot reload için polling
|
||||||
watch: {
|
watch: {
|
||||||
usePolling: true,
|
usePolling: env.VITE_USE_POLLING === 'true',
|
||||||
interval: 1000,
|
interval: env.VITE_USE_POLLING === 'true' ? 1000 : undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -152,41 +168,6 @@ export default defineConfig(async ({ mode }) => {
|
||||||
}
|
}
|
||||||
return 'assets/[name]-[hash][extname]'
|
return 'assets/[name]-[hash][extname]'
|
||||||
},
|
},
|
||||||
manualChunks(id) {
|
|
||||||
// 🔴 DEVEXTREME (esm + non-esm + react wrapper)
|
|
||||||
if (
|
|
||||||
id.includes('node_modules/devextreme') ||
|
|
||||||
id.includes('node_modules/devextreme-react')
|
|
||||||
) {
|
|
||||||
return 'dx-framework'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔴 REPORTING
|
|
||||||
if (id.includes('devexpress-reporting') || id.includes('@devexpress')) {
|
|
||||||
return 'dx-reporting'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🟣 EXPORT TOOLS
|
|
||||||
if (
|
|
||||||
id.includes('exceljs') ||
|
|
||||||
id.includes('xlsx') ||
|
|
||||||
id.includes('jspdf') ||
|
|
||||||
id.includes('html2canvas') ||
|
|
||||||
id.includes('file-saver')
|
|
||||||
) {
|
|
||||||
return 'export-tools'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🟡 EDITOR
|
|
||||||
if (id.includes('monaco-editor') || id.includes('codemirror')) {
|
|
||||||
return 'editor'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ⚪ KALAN VENDOR
|
|
||||||
if (id.includes('node_modules')) {
|
|
||||||
return 'vendor'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue