Intranet App Service ve Intranet Dashboard

This commit is contained in:
Sedat Öztürk 2026-05-05 20:59:30 +03:00
parent 0e102b3dfc
commit c350acbf2a
101 changed files with 8321 additions and 1362 deletions

View file

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.Hr;
public class DepartmentDto : FullAuditedEntityDto<Guid>
{
public Guid? TenantId { get; set; }
public string Name { get; set; }
public Guid? ParentId { get; set; }
}

View file

@ -0,0 +1,15 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.Hr;
public class JobPositionDto : FullAuditedEntityDto<Guid>
{
public Guid? TenantId { get; set; }
public string Name { get; set; }
public Guid DepartmentId { get; set; }
public Guid? ParentId { get; set; }
}

View file

@ -4,13 +4,15 @@ using Volo.Abp.ObjectExtending;
namespace Sozsoft.Platform.Identity.Dto;
public class UserInfoViewModel: ExtensibleObject
public class UserInfoViewModel : ExtensibleObject
{
public Guid Id { get; set; }
public Guid? TenantId { get; set; }
public string ConcurrencyStamp { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string FullName => $"{Name} {Surname}".TrimEnd();
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool IsActive { get; set; }

View file

@ -0,0 +1,23 @@
using System;
using Sozsoft.Platform.Identity.Dto;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.Intranet;
public class AnnouncementDto : FullAuditedEntityDto<Guid>
{
public Guid? TenantId { get; set; }
public string Title { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public string ImageUrl { get; set; }
public string Category { get; set; }
public Guid? UserId { get; set; }
public UserInfoViewModel User { get; set; }
public DateTime PublishDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public bool IsPinned { get; set; }
public int ViewCount { get; set; }
public string Attachments { get; set; }
}

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Sozsoft.Platform.Intranet;
public interface IIntranetAppService : IApplicationService
{
Task<IntranetDashboardDto> GetIntranetDashboardAsync();
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Sozsoft.Platform.FileManagement;
using Sozsoft.Platform.Identity.Dto;
namespace Sozsoft.Platform.Intranet;
public class IntranetDashboardDto
{
public List<UserInfoViewModel> Birthdays { get; set; } = [];
public List<FileItemDto> Documents { get; set; } = [];
public List<AnnouncementDto> Announcements { get; set; } = [];
public List<SurveyDto> Surveys { get; set; } = [];
public List<SocialPostDto> SocialPosts { get; set; } = [];
}

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using Sozsoft.Platform.Identity.Dto;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.Intranet;
public class SocialPostDto : FullAuditedEntityDto<Guid>
{
public Guid? UserId { get; set; }
public UserInfoViewModel? User { get; set; }
public string Content { get; set; }
public int LikeCount { get; set; }
public bool IsLiked { get; set; }
public bool IsOwnPost { get; set; }
public SocialLocationDto? Location { get; set; }
public SocialMediaDto? Media { get; set; }
public List<SocialCommentDto> Comments { get; set; }
public List<SocialLikeDto> Likes { get; set; }
}
public class SocialLocationDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public string Name { get; set; }
public string? Address { get; set; }
public double? Lat { get; set; }
public double? Lng { get; set; }
public string? PlaceId { get; set; }
}
public class SocialMediaDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public string Type { get; set; } // image | video | poll
public string[] Urls { get; set; }
// Poll Fields
public string? PollQuestion { get; set; }
public int? PollTotalVotes { get; set; }
public DateTime? PollEndsAt { get; set; }
public string? PollUserVoteId { get; set; }
public List<SocialPollOptionDto> PollOptions { get; set; }
}
public class SocialPollOptionDto : FullAuditedEntityDto<Guid>
{
public Guid SocialMediaId { get; set; }
public string Text { get; set; }
public int Votes { get; set; }
}
public class SocialCommentDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public Guid? UserId { get; set; }
public UserInfoViewModel? User { get; set; }
public string Content { get; set; }
}
public class SocialLikeDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public Guid? UserId { get; set; }
public UserInfoViewModel? User { get; set; }
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.Intranet;
public class SurveyDto : FullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
public int Responses { get; set; }
public string Status { get; set; } // draft | active | closed
public bool IsAnonymous { get; set; }
public List<SurveyQuestionDto> Questions { get; set; }
}
public class SurveyQuestionDto : FullAuditedEntityDto<Guid>
{
public Guid SurveyId { get; set; }
public string QuestionText { get; set; }
public string Type { get; set; } // rating | multiple-choice | text | textarea | yes-no
public int Order { get; set; }
public bool IsRequired { get; set; }
public List<SurveyQuestionOptionDto> Options { get; set; }
}
public class SurveyQuestionOptionDto : FullAuditedEntityDto<Guid>
{
public Guid QuestionId { get; set; }
public string Text { get; set; }
public int Order { get; set; }
}
public class SurveyResponseDto : FullAuditedEntityDto<Guid>
{
public Guid SurveyId { get; set; }
public Guid? UserId { get; set; }
public DateTime SubmissionTime { get; set; }
public List<SurveyAnswerDto> Answers { get; set; }
}
public class SurveyAnswerDto : FullAuditedEntityDto<Guid>
{
public Guid ResponseId { get; set; }
public Guid QuestionId { get; set; }
public string QuestionType { get; set; }
public string Value { get; set; }
}

View file

@ -33,7 +33,7 @@ public class SearchBlogPostsInput : PagedAndSortedResultRequestDto
public string Query { get; set; }
public Guid? CategoryId { get; set; }
public string Tag { get; set; }
public Guid? EmployeeId { get; set; }
public Guid? UserId { get; set; }
public bool? IsPublished { get; set; }
}

View file

@ -0,0 +1,15 @@
using AutoMapper;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Hr;
namespace Sozsoft.Platform.Intranet;
public class HrAutoMapperProfile : Profile
{
public HrAutoMapperProfile()
{
CreateMap<Department, DepartmentDto>();
CreateMap<JobPosition, JobPositionDto>();
}
}

View file

@ -11,7 +11,14 @@ public class IdentityAutoMapperProfile : Profile
{
public IdentityAutoMapperProfile()
{
CreateMap<IdentityUser, UserInfoViewModel>();
CreateMap<IdentityUser, UserInfoViewModel>()
.ForMember(dest => dest.Roles, opt => opt.Ignore())
.ForMember(dest => dest.Branches, opt => opt.Ignore())
.ForMember(dest => dest.Claims, opt => opt.Ignore())
.ForMember(dest => dest.WorkHours, opt => opt.Ignore())
.ForMember(dest => dest.Departments, opt => opt.Ignore())
.ForMember(dest => dest.JobPositions, opt => opt.Ignore())
.ForMember(dest => dest.userRoleNames, opt => opt.Ignore());
CreateMap<OrganizationUnit, OrganizationUnitDto>();
CreateMap<CreateUpdateOrganizationUnitDto, OrganizationUnit>();
CreateMap<Tenant, CustomTenantDto>();

View file

@ -0,0 +1,350 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Sozsoft.Platform.BlobStoring;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.FileManagement;
using Sozsoft.Platform.Identity.Dto;
using Sozsoft.Platform.Extensions;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Sozsoft.Platform.Intranet;
[Authorize]
public class IntranetAppService : PlatformAppService, IIntranetAppService
{
private readonly ICurrentTenant _currentTenant;
private readonly BlobManager _blobContainer;
private readonly IConfiguration _configuration;
private readonly IIdentityUserAppService _identityUserAppService;
private readonly IIdentityUserRepository _identityUserRepository;
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IRepository<Announcement, Guid> _announcementRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SocialPost, Guid> _socialPostRepository;
public IntranetAppService(
ICurrentTenant currentTenant,
BlobManager blobContainer,
IConfiguration configuration,
IIdentityUserAppService identityUserAppService,
IIdentityUserRepository identityUserRepository,
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
IRepository<Announcement, Guid> announcementRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SocialPost, Guid> socialPostRepository
)
{
_currentTenant = currentTenant;
_blobContainer = blobContainer;
_configuration = configuration;
_identityUserAppService = identityUserAppService;
_identityUserRepository = identityUserRepository;
_departmentRepository = departmentRepository;
_jobPositionRepository = jobPositionRepository;
_announcementRepository = announcementRepository;
_surveyRepository = surveyRepository;
_socialPostRepository = socialPostRepository;
}
[UnitOfWork]
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
{
return new IntranetDashboardDto
{
Birthdays = await GetBirthdaysAsync(), //1
Documents = await GetIntranetDocumentsAsync(BlobContainerNames.Intranet), //2
Announcements = await GetAnnouncementsAsync(), //3
Surveys = await GetSurveysAsync(), //4
SocialPosts = await GetSocialPostsAsync(), //5
};
}
private async Task<List<UserInfoViewModel>> GetBirthdaysAsync()
{
var today = DateTime.Now;
var users = await _identityUserRepository.GetListAsync();
var userList = users
.Where(u =>
{
var birthDate = u.GetBirthDate();
return birthDate.HasValue
&& birthDate.Value.Day == today.Day
&& birthDate.Value.Month == today.Month;
})
.ToList();
var allDepartments = await _departmentRepository.GetListAsync();
var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name);
var allJobPositions = await _jobPositionRepository.GetListAsync();
var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j);
var result = ObjectMapper.Map<List<IdentityUser>, List<UserInfoViewModel>>(userList);
for (var i = 0; i < userList.Count; i++)
{
var user = userList[i];
result[i].BirthDate = user.GetBirthDate();
result[i].WorkHour = user.GetWorkHour();
result[i].Nationality = user.GetNationality();
var deptId = user.GetDepartmentId();
if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName))
{
result[i].DepartmentId = deptId;
result[i].Departments =
[
new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true }
];
}
var jobPosId = user.GetJobPositionId();
if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition))
{
result[i].JobPositionId = jobPosId;
result[i].JobPositions =
[
new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true }
];
}
}
return result;
}
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
{
var announcements = await _announcementRepository.GetListAsync();
var announcementDtos = new List<AnnouncementDto>();
var allDepartments = await _departmentRepository.GetListAsync();
var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name);
var allJobPositions = await _jobPositionRepository.GetListAsync();
var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j);
foreach (var announcement in announcements)
{
var dto = ObjectMapper.Map<Announcement, AnnouncementDto>(announcement);
var user = await _identityUserRepository.FindAsync(announcement.UserId ?? Guid.Empty);
if (user != null)
{
var userVm = ObjectMapper.Map<IdentityUser, UserInfoViewModel>(user);
userVm.BirthDate = user.GetBirthDate();
userVm.WorkHour = user.GetWorkHour();
userVm.Nationality = user.GetNationality();
var deptId = user.GetDepartmentId();
if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName))
{
userVm.DepartmentId = deptId;
userVm.Departments =
[
new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true }
];
}
var jobPosId = user.GetJobPositionId();
if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition))
{
userVm.JobPositionId = jobPosId;
userVm.JobPositions =
[
new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true }
];
}
dto.User = userVm;
}
announcementDtos.Add(dto);
}
return announcementDtos;
}
private async Task<List<SurveyDto>> GetSurveysAsync()
{
var queryable = await _surveyRepository.GetQueryableAsync();
var surveys = await AsyncExecuter.ToListAsync(
queryable
.Where(s => s.Status == "active")
.Include(s => s.Questions)
.ThenInclude(q => q.Options)
);
return ObjectMapper.Map<List<Survey>, List<SurveyDto>>(surveys);
}
private async Task<List<SocialPostDto>> GetSocialPostsAsync()
{
var queryable = await _socialPostRepository
.WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes);
var socialPosts = await AsyncExecuter.ToListAsync(queryable);
var dtos = ObjectMapper.Map<List<SocialPost>, List<SocialPostDto>>(socialPosts);
// Collect all unique user IDs to resolve in a single query
var userIds = dtos
.Select(p => p.UserId)
.Union(dtos.SelectMany(p => p.Comments.Select(c => c.UserId)))
.Union(dtos.SelectMany(p => p.Likes.Select(l => l.UserId)))
.Where(id => id.HasValue)
.Select(id => id!.Value)
.Distinct()
.ToList();
if (userIds.Count > 0)
{
var allDepartments = await _departmentRepository.GetListAsync();
var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name);
var allJobPositions = await _jobPositionRepository.GetListAsync();
var jobPositionDict = allJobPositions.ToDictionary(j => j.Id, j => j);
var users = await _identityUserRepository.GetListAsync();
var userMap = users
.Where(u => userIds.Contains(u.Id))
.ToDictionary(u => u.Id, u =>
{
var vm = ObjectMapper.Map<IdentityUser, UserInfoViewModel>(u);
vm.BirthDate = u.GetBirthDate();
vm.WorkHour = u.GetWorkHour();
vm.Nationality = u.GetNationality();
var deptId = u.GetDepartmentId();
if (deptId != Guid.Empty && departmentDict.TryGetValue(deptId, out var deptName))
{
vm.DepartmentId = deptId;
vm.Departments =
[
new AssignedDepartmentViewModel { Id = deptId, Name = deptName, IsAssigned = true }
];
}
var jobPosId = u.GetJobPositionId();
if (jobPosId != Guid.Empty && jobPositionDict.TryGetValue(jobPosId, out var jobPosition))
{
vm.JobPositionId = jobPosId;
vm.JobPositions =
[
new AssignedJobPoisitionViewModel { Id = jobPosId, Name = jobPosition.Name, DepartmentId = jobPosition.DepartmentId, IsAssigned = true }
];
}
return vm;
});
foreach (var dto in dtos)
{
if (dto.UserId.HasValue && userMap.TryGetValue(dto.UserId.Value, out var postUser))
dto.User = postUser;
foreach (var comment in dto.Comments)
if (comment.UserId.HasValue && userMap.TryGetValue(comment.UserId.Value, out var commentUser))
comment.User = commentUser;
foreach (var like in dto.Likes)
if (like.UserId.HasValue && userMap.TryGetValue(like.UserId.Value, out var likeUser))
like.User = likeUser;
}
}
return dtos;
}
public async Task<List<FileItemDto>> GetIntranetDocumentsAsync(string folderPath)
{
var items = new List<FileItemDto>();
var cdnBasePath = _configuration["App:CdnPath"];
if (string.IsNullOrEmpty(cdnBasePath))
{
Logger.LogWarning("CDN path is not configured");
return items;
}
var tenantId = _currentTenant.Id?.ToString() ?? "host";
var fullPath = Path.Combine(cdnBasePath, tenantId);
if (!string.IsNullOrEmpty(folderPath))
{
fullPath = Path.Combine(fullPath, folderPath);
}
if (!Directory.Exists(fullPath))
{
Logger.LogWarning($"Directory not found: {fullPath}");
return items;
}
var files = Directory.GetFiles(fullPath);
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}";
items.Add(new FileItemDto
{
Id = Guid.NewGuid().ToString(),
Name = fileInfo.Name,
Type = "file",
Size = fileInfo.Length,
Extension = fileInfo.Extension,
MimeType = GetMimeType(fileInfo.Extension),
CreatedAt = fileInfo.CreationTime,
ModifiedAt = fileInfo.LastWriteTime,
Path = relativePath,
ParentId = string.Empty,
IsReadOnly = false,
ChildCount = 0,
TenantId = _currentTenant.Id?.ToString()
});
}
return items.OrderBy(x => x.Name).ToList();
}
private string GetMimeType(string extension)
{
return extension.ToLowerInvariant() switch
{
".pdf" => "application/pdf",
".doc" => "application/msword",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls" => "application/vnd.ms-excel",
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt" => "application/vnd.ms-powerpoint",
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".txt" => "text/plain",
".zip" => "application/zip",
".rar" => "application/x-rar-compressed",
_ => "application/octet-stream"
};
}
}

View file

@ -0,0 +1,25 @@
using AutoMapper;
using Sozsoft.Platform.Entities;
namespace Sozsoft.Platform.Intranet;
public class IntranetAutoMapperProfile : Profile
{
public IntranetAutoMapperProfile()
{
CreateMap<Announcement, AnnouncementDto>();
CreateMap<Survey, SurveyDto>();
CreateMap<SurveyQuestion, SurveyQuestionDto>();
CreateMap<SurveyQuestionOption, SurveyQuestionOptionDto>();
CreateMap<SurveyResponse, SurveyResponseDto>();
CreateMap<SurveyAnswer, SurveyAnswerDto>();
CreateMap<SocialPost, SocialPostDto>();
CreateMap<SocialLocation, SocialLocationDto>();
CreateMap<SocialMedia, SocialMediaDto>();
CreateMap<SocialPollOption, SocialPollOptionDto>();
CreateMap<SocialComment, SocialCommentDto>();
CreateMap<SocialLike, SocialLikeDto>();
}
}

View file

@ -138,12 +138,6 @@
"en": "Administration",
"tr": "Yönetim"
},
{
"resourceName": "Platform",
"key": "App.Intranet",
"en": "Intranet",
"tr": "Intranet"
},
{
"resourceName": "Platform",
"key": "App.SupplyChain",
@ -3644,6 +3638,12 @@
"en": "Audit Logs",
"tr": "Audit Günlükleri"
},
{
"resourceName": "Platform",
"key": "App.Intranet",
"en": "Intranet",
"tr": "Intranet"
},
{
"resourceName": "Platform",
"key": "App.AuditLogs.FetchFailed",
@ -6092,6 +6092,12 @@
"tr": "Eğitim Durumu",
"en": "Education Status"
},
{
"resourceName": "Platform",
"key": "App.Intranet.SocialComment",
"tr": "Sosyal Yorumlar",
"en": "Social Comments"
},
{
"resourceName": "Platform",
"key": "App.Intranet.Events",
@ -12284,6 +12290,12 @@
"tr": "Acil",
"en": "Urgent"
},
{
"resourceName": "Platform",
"key": "App.Platform.Intranet.Widgets.ActiveSurveys.Questions",
"tr": "Sorular",
"en": "Questions"
},
{
"resourceName": "Platform",
"key": "App.Platform.Intranet.Widgets.ActiveSurveys.Title",

View file

@ -2989,6 +2989,15 @@
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "AbpIdentity.Users.Widget",
"ParentName": "AbpIdentity.Users",
"DisplayName": "Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "AbpIdentity.Users.Update.ManageRoles",
@ -3304,6 +3313,15 @@
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Files.Widget",
"ParentName": "App.Files",
"DisplayName": "Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.ForumManagement.Publish",
@ -3312,6 +3330,429 @@
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet",
"ParentName": null,
"DisplayName": "App.Intranet",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Events.EventType",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Create",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Update",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Delete",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Export",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Import",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventType.Note",
"ParentName": "App.Intranet.Events.EventType",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Events.EventCategory",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Create",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Update",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Delete",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Export",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Import",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventCategory.Note",
"ParentName": "App.Intranet.Events.EventCategory",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Events.Event",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Create",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Update",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Delete",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Export",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Import",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Note",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.Event.Widget",
"ParentName": "App.Intranet.Events.Event",
"DisplayName": "Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Announcement",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Create",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Update",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Delete",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Export",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Import",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Note",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Widget",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.SocialPost",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Create",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Update",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Delete",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Export",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Import",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Note",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost.Widget",
"ParentName": "App.Intranet.SocialPost",
"DisplayName": "Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.SocialComment",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Create",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Update",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Delete",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Export",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Import",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialComment.Note",
"ParentName": "App.Intranet.SocialComment",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Survey.Widget",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Survey.Widget",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
}
]
}

View file

@ -70,14 +70,14 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var wizardDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", "WizardData");
if (!Directory.Exists(wizardDataPath))
{
_logger.LogInformation("Seeds/WizardData directory not found, skipping.");
_logger.LogInformation("WizardData directory not found, skipping.");
return;
}
var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json").OrderBy(f => Path.GetFileName(f)).ToArray();
if (jsonFiles.Length == 0)
{
_logger.LogInformation("No JSON files found in Seeds/WizardData directory, skipping.");
_logger.LogInformation("No JSON files found in WizardData directory, skipping.");
return;
}

View file

@ -64,4 +64,16 @@ public enum TableNameEnum
BlogPost,
Demo,
Contact,
Announcement,
Survey,
SurveyQuestion,
SurveyQuestionOption,
SurveyResponse,
SurveyAnswer,
SocialPost,
SocialLocation,
SocialMedia,
SocialPollOption,
SocialComment,
SocialLike
}

View file

@ -76,6 +76,21 @@ public static class TableNameResolver
{ nameof(TableNameEnum.Note), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.ReportCategory), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.ReportTemplate), (TablePrefix.TenantByName, MenuPrefix.Administration) },
// 🔹 INTRANET TABLOLARI
{ nameof(TableNameEnum.Announcement), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.Survey), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SurveyQuestion), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SurveyQuestionOption), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SurveyResponse), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SurveyAnswer), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialPost), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialLocation), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialMedia), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialPollOption), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialComment), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.SocialLike), (TablePrefix.TenantByName, MenuPrefix.Administration) },
};
public static string GetFullTableName(string tableName)

View file

@ -0,0 +1,23 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
public class Announcement : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string Title { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public string ImageUrl { get; set; }
public string Category { get; set; }
public Guid? UserId { get; set; }
public DateTime PublishDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public bool IsPinned { get; set; }
public int ViewCount { get; set; }
public string Attachments { get; set; }
}

View file

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
public class SocialPost : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid? UserId { get; set; }
public string Content { get; set; }
public int LikeCount { get; set; }
public bool IsLiked { get; set; }
public bool IsOwnPost { get; set; }
// Relations
public SocialLocation Location { get; set; }
public SocialMedia Media { get; set; }
public ICollection<SocialComment> Comments { get; set; }
public ICollection<SocialLike> Likes { get; set; }
}
public class SocialLocation : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SocialPostId { get; set; }
public SocialPost SocialPost { get; set; }
public string Name { get; set; }
public string? Address { get; set; }
public double? Lat { get; set; }
public double? Lng { get; set; }
public string? PlaceId { get; set; }
}
public class SocialMedia : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SocialPostId { get; set; }
public SocialPost SocialPost { get; set; }
public string Type { get; set; } // image | video | poll
public string[] Urls { get; set; }
// Poll fields
public string? PollQuestion { get; set; }
public int? PollTotalVotes { get; set; }
public DateTime? PollEndsAt { get; set; }
public string? PollUserVoteId { get; set; }
public ICollection<SocialPollOption> PollOptions { get; set; }
}
public class SocialPollOption : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SocialMediaId { get; set; }
public SocialMedia SocialMedia { get; set; }
public string Text { get; set; }
public int Votes { get; set; }
}
public class SocialComment : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SocialPostId { get; set; }
public SocialPost SocialPost { get; set; }
public Guid? UserId { get; set; }
public string Content { get; set; }
}
public class SocialLike : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SocialPostId { get; set; }
public SocialPost SocialPost { get; set; }
public Guid? UserId { get; set; }
}

View file

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
public class Survey : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
public int Responses { get; set; }
public string Status { get; set; } // draft | active | closed
public bool IsAnonymous { get; set; }
public ICollection<SurveyQuestion> Questions { get; set; }
public ICollection<SurveyResponse> SurveyResponses { get; set; }
}
public class SurveyQuestion : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SurveyId { get; set; }
public Survey Survey { get; set; }
public string QuestionText { get; set; }
public string Type { get; set; } // rating | multiple-choice | text | textarea | yes-no
public int Order { get; set; }
public bool IsRequired { get; set; }
public ICollection<SurveyQuestionOption> Options { get; set; }
}
public class SurveyQuestionOption : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid QuestionId { get; set; }
public SurveyQuestion Question { get; set; }
public string Text { get; set; }
public int Order { get; set; }
}
public class SurveyResponse : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid SurveyId { get; set; }
public Survey Survey { get; set; }
public Guid? UserId { get; set; }
public DateTime SubmissionTime { get; set; }
public ICollection<SurveyAnswer> Answers { get; set; }
}
public class SurveyAnswer : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid ResponseId { get; set; }
public SurveyResponse Response { get; set; }
public Guid QuestionId { get; set; }
public SurveyQuestion Question { get; set; }
public string QuestionType { get; set; } // rating | multiple-choice | text | textarea | yes-no
public string Value { get; set; }
}

View file

@ -109,6 +109,23 @@ public class PlatformDbContext :
public DbSet<JobPosition> JobPositions { get; set; }
#endregion
#region Intranet
public DbSet<Announcement> Announcements { get; set; }
public DbSet<Survey> Surveys { get; set; }
public DbSet<SurveyQuestion> SurveyQuestions { get; set; }
public DbSet<SurveyQuestionOption> SurveyQuestionOptions { get; set; }
public DbSet<SurveyResponse> SurveyResponses { get; set; }
public DbSet<SurveyAnswer> SurveyAnswers { get; set; }
public DbSet<SocialPost> SocialPosts { get; set; }
public DbSet<SocialLocation> SocialLocations { get; set; }
public DbSet<SocialMedia> SocialMedias { get; set; }
public DbSet<SocialPollOption> SocialPollOptions { get; set; }
public DbSet<SocialComment> SocialComments { get; set; }
public DbSet<SocialLike> SocialLikes { get; set; }
#endregion
public PlatformDbContext(DbContextOptions<PlatformDbContext> options)
: base(options)
{
@ -1026,5 +1043,171 @@ public class PlatformDbContext :
b.Property(x => x.PrimaryEntityType).HasMaxLength(256);
b.Property(x => x.ControllerName).HasMaxLength(256);
});
builder.Entity<Announcement>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
b.Property(x => x.Excerpt).IsRequired().HasMaxLength(512);
b.Property(x => x.Content).IsRequired().HasMaxLength(4096);
b.Property(x => x.ImageUrl).HasMaxLength(512);
b.Property(x => x.Category).IsRequired().HasMaxLength(64);
b.Property(x => x.PublishDate).IsRequired();
b.Property(x => x.Attachments).HasMaxLength(2048);
b.Property(x => x.ViewCount).HasDefaultValue(0);
});
builder.Entity<Survey>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Survey)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
b.Property(x => x.Description).HasMaxLength(2048);
b.Property(x => x.Deadline).IsRequired();
b.Property(x => x.Responses).HasDefaultValue(0);
b.Property(x => x.Status).IsRequired().HasMaxLength(10);
});
builder.Entity<SurveyQuestion>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SurveyQuestion)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.QuestionText).IsRequired().HasMaxLength(1024);
b.Property(x => x.Type).IsRequired().HasMaxLength(64);
b.HasOne(x => x.Survey)
.WithMany(x => x.Questions)
.HasForeignKey(x => x.SurveyId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SurveyQuestionOption>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SurveyQuestionOption)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Text).IsRequired().HasMaxLength(512);
b.Property(x => x.Order).IsRequired();
b.HasOne(x => x.Question)
.WithMany(x => x.Options)
.HasForeignKey(x => x.QuestionId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SurveyResponse>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SurveyResponse)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.SubmissionTime).IsRequired();
b.HasOne(x => x.Survey)
.WithMany(x => x.SurveyResponses)
.HasForeignKey(x => x.SurveyId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SurveyAnswer>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SurveyAnswer)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.QuestionType).IsRequired().HasMaxLength(64);
b.Property(x => x.Value).IsRequired().HasMaxLength(1024);
b.HasOne(x => x.Response)
.WithMany(x => x.Answers)
.HasForeignKey(x => x.ResponseId)
.OnDelete(DeleteBehavior.Cascade);
b.HasOne(x => x.Question)
.WithMany()
.HasForeignKey(x => x.QuestionId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<SocialPost>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPost)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Content).IsRequired().HasMaxLength(4096);
b.Property(x => x.LikeCount).HasDefaultValue(0);
b.Property(x => x.IsOwnPost).HasDefaultValue(false);
b.Property(x => x.IsLiked).HasDefaultValue(false);
});
builder.Entity<SocialLocation>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialLocation)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
b.Property(x => x.Address).HasMaxLength(512);
b.Property(x => x.PlaceId).HasMaxLength(128);
b.HasOne(x => x.SocialPost)
.WithOne(p => p.Location)
.HasForeignKey<SocialLocation>(x => x.SocialPostId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SocialMedia>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialMedia)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Type).IsRequired().HasMaxLength(64);
b.Property(x => x.Urls).HasMaxLength(2048);
b.Property(x => x.PollQuestion).HasMaxLength(512);
b.Property(x => x.PollUserVoteId).HasMaxLength(128);
b.HasOne(x => x.SocialPost)
.WithOne(p => p.Media)
.HasForeignKey<SocialMedia>(x => x.SocialPostId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SocialPollOption>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPollOption)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Text).IsRequired().HasMaxLength(512);
b.HasOne(x => x.SocialMedia)
.WithMany(x => x.PollOptions)
.HasForeignKey(x => x.SocialMediaId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SocialComment>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialComment)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Content).IsRequired().HasMaxLength(8192);
b.HasOne(x => x.SocialPost)
.WithMany(x => x.Comments)
.HasForeignKey(x => x.SocialPostId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<SocialLike>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialLike)), Prefix.DbSchema);
b.ConfigureByConvention();
b.HasOne(x => x.SocialPost)
.WithMany(x => x.Likes)
.HasForeignKey(x => x.SocialPostId)
.OnDelete(DeleteBehavior.Cascade);
});
}
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260505071050_Initial")]
[Migration("20260505120031_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -559,6 +559,95 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_AiBot", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Announcement", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Attachments")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Excerpt")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime?>("ExpiryDate")
.HasColumnType("datetime2");
b.Property<string>("ImageUrl")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPinned")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime>("PublishDate")
.HasColumnType("datetime2");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<int>("ViewCount")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.HasKey("Id");
b.ToTable("Adm_T_Announcement", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BackgroundWorker", b =>
{
b.Property<Guid>("Id")
@ -3757,6 +3846,691 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_SkillType", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialComment", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(8192)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SocialPostId");
b.ToTable("Adm_T_SocialComment", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLike", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SocialPostId");
b.ToTable("Adm_T_SocialLike", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLocation", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Address")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<double?>("Lat")
.HasColumnType("float");
b.Property<double?>("Lng")
.HasColumnType("float");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PlaceId")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("SocialPostId")
.IsUnique();
b.ToTable("Adm_T_SocialLocation", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("PollEndsAt")
.HasColumnType("datetime2");
b.Property<string>("PollQuestion")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<int?>("PollTotalVotes")
.HasColumnType("int");
b.Property<string>("PollUserVoteId")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.PrimitiveCollection<string>("Urls")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.HasKey("Id");
b.HasIndex("SocialPostId")
.IsUnique();
b.ToTable("Adm_T_SocialMedia", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPollOption", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialMediaId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<int>("Votes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("SocialMediaId");
b.ToTable("Adm_T_SocialPollOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPost", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsLiked")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<bool>("IsOwnPost")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("LikeCount")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.ToTable("Adm_T_SocialPost", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Survey", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<DateTime>("Deadline")
.HasColumnType("datetime2");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<bool>("IsAnonymous")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Responses")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.ToTable("Adm_T_Survey", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyAnswer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("QuestionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("QuestionType")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid>("ResponseId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("Adm_T_SurveyAnswer", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsRequired")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<string>("QuestionText")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<Guid>("SurveyId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("Id");
b.HasIndex("SurveyId");
b.ToTable("Adm_T_SurveyQuestion", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestionOption", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<Guid>("QuestionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Adm_T_SurveyQuestionOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime>("SubmissionTime")
.HasColumnType("datetime2");
b.Property<Guid>("SurveyId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SurveyId");
b.ToTable("Adm_T_SurveyResponse", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
{
b.Property<string>("Id")
@ -6429,6 +7203,113 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("SkillType");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialComment", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithMany("Comments")
.HasForeignKey("SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLike", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithMany("Likes")
.HasForeignKey("SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLocation", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithOne("Location")
.HasForeignKey("Sozsoft.Platform.Entities.SocialLocation", "SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithOne("Media")
.HasForeignKey("Sozsoft.Platform.Entities.SocialMedia", "SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPollOption", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialMedia", "SocialMedia")
.WithMany("PollOptions")
.HasForeignKey("SocialMediaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialMedia");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyAnswer", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SurveyQuestion", "Question")
.WithMany()
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Sozsoft.Platform.Entities.SurveyResponse", "Response")
.WithMany("Answers")
.HasForeignKey("ResponseId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
b.Navigation("Response");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Survey", "Survey")
.WithMany("Questions")
.HasForeignKey("SurveyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Survey");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestionOption", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SurveyQuestion", "Question")
.WithMany("Options")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Survey", "Survey")
.WithMany("SurveyResponses")
.HasForeignKey("SurveyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Survey");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
{
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
@ -6673,6 +7554,39 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("Skills");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.Navigation("PollOptions");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPost", b =>
{
b.Navigation("Comments");
b.Navigation("Likes");
b.Navigation("Location");
b.Navigation("Media");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Survey", b =>
{
b.Navigation("Questions");
b.Navigation("SurveyResponses");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.Navigation("Options");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
{
b.Navigation("Uoms");

View file

@ -462,6 +462,36 @@ namespace Sozsoft.Platform.Migrations
table.PrimaryKey("PK_AbpUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_Announcement",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Excerpt = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: false),
ImageUrl = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Category = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
PublishDate = table.Column<DateTime>(type: "datetime2", nullable: false),
ExpiryDate = table.Column<DateTime>(type: "datetime2", nullable: true),
IsPinned = table.Column<bool>(type: "bit", nullable: false),
ViewCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
Attachments = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_Announcement", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_Department",
columns: table => new
@ -572,6 +602,55 @@ namespace Sozsoft.Platform.Migrations
table.PrimaryKey("PK_Adm_T_Sector", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialPost",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: false),
LikeCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
IsLiked = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
IsOwnPost = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialPost", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_Survey",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Description = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
Deadline = table.Column<DateTime>(type: "datetime2", nullable: false),
Responses = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
Status = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
IsAnonymous = table.Column<bool>(type: "bit", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_Survey", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Adm_T_WorkHour",
columns: table => new
@ -1867,6 +1946,182 @@ namespace Sozsoft.Platform.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialComment",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SocialPostId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 8192, nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialComment", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SocialComment_Adm_T_SocialPost_SocialPostId",
column: x => x.SocialPostId,
principalTable: "Adm_T_SocialPost",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialLike",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SocialPostId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialLike", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SocialLike_Adm_T_SocialPost_SocialPostId",
column: x => x.SocialPostId,
principalTable: "Adm_T_SocialPost",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialLocation",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SocialPostId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Address = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Lat = table.Column<double>(type: "float", nullable: true),
Lng = table.Column<double>(type: "float", nullable: true),
PlaceId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialLocation", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SocialLocation_Adm_T_SocialPost_SocialPostId",
column: x => x.SocialPostId,
principalTable: "Adm_T_SocialPost",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialMedia",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SocialPostId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Type = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Urls = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
PollQuestion = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
PollTotalVotes = table.Column<int>(type: "int", nullable: true),
PollEndsAt = table.Column<DateTime>(type: "datetime2", nullable: true),
PollUserVoteId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialMedia", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SocialMedia_Adm_T_SocialPost_SocialPostId",
column: x => x.SocialPostId,
principalTable: "Adm_T_SocialPost",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SurveyQuestion",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SurveyId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
QuestionText = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
Type = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Order = table.Column<int>(type: "int", nullable: false),
IsRequired = table.Column<bool>(type: "bit", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SurveyQuestion", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SurveyQuestion_Adm_T_Survey_SurveyId",
column: x => x.SurveyId,
principalTable: "Adm_T_Survey",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SurveyResponse",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SurveyId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SubmissionTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SurveyResponse", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SurveyResponse_Adm_T_Survey_SurveyId",
column: x => x.SurveyId,
principalTable: "Adm_T_Survey",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OpenIddictAuthorizations",
columns: table => new
@ -2365,6 +2620,97 @@ namespace Sozsoft.Platform.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SocialPollOption",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
SocialMediaId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Text = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Votes = table.Column<int>(type: "int", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SocialPollOption", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SocialPollOption_Adm_T_SocialMedia_SocialMediaId",
column: x => x.SocialMediaId,
principalTable: "Adm_T_SocialMedia",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SurveyQuestionOption",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
QuestionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Text = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Order = table.Column<int>(type: "int", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SurveyQuestionOption", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SurveyQuestionOption_Adm_T_SurveyQuestion_QuestionId",
column: x => x.QuestionId,
principalTable: "Adm_T_SurveyQuestion",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_SurveyAnswer",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
ResponseId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
QuestionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
QuestionType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Value = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Adm_T_SurveyAnswer", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_SurveyAnswer_Adm_T_SurveyQuestion_QuestionId",
column: x => x.QuestionId,
principalTable: "Adm_T_SurveyQuestion",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Adm_T_SurveyAnswer_Adm_T_SurveyResponse_ResponseId",
column: x => x.ResponseId,
principalTable: "Adm_T_SurveyResponse",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OpenIddictTokens",
columns: table => new
@ -2781,6 +3127,58 @@ namespace Sozsoft.Platform.Migrations
table: "Adm_T_ReportTemplate",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SocialComment_SocialPostId",
table: "Adm_T_SocialComment",
column: "SocialPostId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SocialLike_SocialPostId",
table: "Adm_T_SocialLike",
column: "SocialPostId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SocialLocation_SocialPostId",
table: "Adm_T_SocialLocation",
column: "SocialPostId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SocialMedia_SocialPostId",
table: "Adm_T_SocialMedia",
column: "SocialPostId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SocialPollOption_SocialMediaId",
table: "Adm_T_SocialPollOption",
column: "SocialMediaId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SurveyAnswer_QuestionId",
table: "Adm_T_SurveyAnswer",
column: "QuestionId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SurveyAnswer_ResponseId",
table: "Adm_T_SurveyAnswer",
column: "ResponseId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SurveyQuestion_SurveyId",
table: "Adm_T_SurveyQuestion",
column: "SurveyId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SurveyQuestionOption_QuestionId",
table: "Adm_T_SurveyQuestionOption",
column: "QuestionId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_SurveyResponse_SurveyId",
table: "Adm_T_SurveyResponse",
column: "SurveyId");
migrationBuilder.CreateIndex(
name: "IX_OpenIddictApplications_ClientId",
table: "OpenIddictApplications",
@ -3065,6 +3463,9 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "AbpUserTokens");
migrationBuilder.DropTable(
name: "Adm_T_Announcement");
migrationBuilder.DropTable(
name: "Adm_T_IpRestriction");
@ -3080,6 +3481,24 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "Adm_T_Sector");
migrationBuilder.DropTable(
name: "Adm_T_SocialComment");
migrationBuilder.DropTable(
name: "Adm_T_SocialLike");
migrationBuilder.DropTable(
name: "Adm_T_SocialLocation");
migrationBuilder.DropTable(
name: "Adm_T_SocialPollOption");
migrationBuilder.DropTable(
name: "Adm_T_SurveyAnswer");
migrationBuilder.DropTable(
name: "Adm_T_SurveyQuestionOption");
migrationBuilder.DropTable(
name: "Adm_T_WorkHour");
@ -3221,6 +3640,15 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "Adm_T_ReportCategory");
migrationBuilder.DropTable(
name: "Adm_T_SocialMedia");
migrationBuilder.DropTable(
name: "Adm_T_SurveyResponse");
migrationBuilder.DropTable(
name: "Adm_T_SurveyQuestion");
migrationBuilder.DropTable(
name: "OpenIddictAuthorizations");
@ -3263,6 +3691,12 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "AbpAuditLogs");
migrationBuilder.DropTable(
name: "Adm_T_SocialPost");
migrationBuilder.DropTable(
name: "Adm_T_Survey");
migrationBuilder.DropTable(
name: "OpenIddictApplications");

View file

@ -556,6 +556,95 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_AiBot", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Announcement", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Attachments")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Excerpt")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime?>("ExpiryDate")
.HasColumnType("datetime2");
b.Property<string>("ImageUrl")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPinned")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime>("PublishDate")
.HasColumnType("datetime2");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<int>("ViewCount")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.HasKey("Id");
b.ToTable("Adm_T_Announcement", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BackgroundWorker", b =>
{
b.Property<Guid>("Id")
@ -3754,6 +3843,691 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_SkillType", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialComment", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(8192)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SocialPostId");
b.ToTable("Adm_T_SocialComment", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLike", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SocialPostId");
b.ToTable("Adm_T_SocialLike", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLocation", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Address")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<double?>("Lat")
.HasColumnType("float");
b.Property<double?>("Lng")
.HasColumnType("float");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PlaceId")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("SocialPostId")
.IsUnique();
b.ToTable("Adm_T_SocialLocation", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("PollEndsAt")
.HasColumnType("datetime2");
b.Property<string>("PollQuestion")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<int?>("PollTotalVotes")
.HasColumnType("int");
b.Property<string>("PollUserVoteId")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<Guid>("SocialPostId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.PrimitiveCollection<string>("Urls")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.HasKey("Id");
b.HasIndex("SocialPostId")
.IsUnique();
b.ToTable("Adm_T_SocialMedia", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPollOption", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SocialMediaId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<int>("Votes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("SocialMediaId");
b.ToTable("Adm_T_SocialPollOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPost", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsLiked")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<bool>("IsOwnPost")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("LikeCount")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.ToTable("Adm_T_SocialPost", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Survey", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<DateTime>("Deadline")
.HasColumnType("datetime2");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<bool>("IsAnonymous")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Responses")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.ToTable("Adm_T_Survey", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyAnswer", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("QuestionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("QuestionType")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<Guid>("ResponseId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("Adm_T_SurveyAnswer", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsRequired")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<string>("QuestionText")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<Guid>("SurveyId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("Id");
b.HasIndex("SurveyId");
b.ToTable("Adm_T_SurveyQuestion", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestionOption", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<Guid>("QuestionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Adm_T_SurveyQuestionOption", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime>("SubmissionTime")
.HasColumnType("datetime2");
b.Property<Guid>("SurveyId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("SurveyId");
b.ToTable("Adm_T_SurveyResponse", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
{
b.Property<string>("Id")
@ -6426,6 +7200,113 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("SkillType");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialComment", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithMany("Comments")
.HasForeignKey("SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLike", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithMany("Likes")
.HasForeignKey("SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialLocation", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithOne("Location")
.HasForeignKey("Sozsoft.Platform.Entities.SocialLocation", "SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialPost", "SocialPost")
.WithOne("Media")
.HasForeignKey("Sozsoft.Platform.Entities.SocialMedia", "SocialPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialPost");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPollOption", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SocialMedia", "SocialMedia")
.WithMany("PollOptions")
.HasForeignKey("SocialMediaId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SocialMedia");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyAnswer", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SurveyQuestion", "Question")
.WithMany()
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Sozsoft.Platform.Entities.SurveyResponse", "Response")
.WithMany("Answers")
.HasForeignKey("ResponseId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
b.Navigation("Response");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Survey", "Survey")
.WithMany("Questions")
.HasForeignKey("SurveyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Survey");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestionOption", b =>
{
b.HasOne("Sozsoft.Platform.Entities.SurveyQuestion", "Question")
.WithMany("Options")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Survey", "Survey")
.WithMany("SurveyResponses")
.HasForeignKey("SurveyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Survey");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Uom", b =>
{
b.HasOne("Sozsoft.Platform.Entities.UomCategory", "UomCategory")
@ -6670,6 +7551,39 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("Skills");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialMedia", b =>
{
b.Navigation("PollOptions");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SocialPost", b =>
{
b.Navigation("Comments");
b.Navigation("Likes");
b.Navigation("Location");
b.Navigation("Media");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Survey", b =>
{
b.Navigation("Questions");
b.Navigation("SurveyResponses");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyQuestion", b =>
{
b.Navigation("Options");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.SurveyResponse", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.UomCategory", b =>
{
b.Navigation("Uoms");

View file

@ -1289,5 +1289,395 @@
"Name": "Muhasebe Şefi",
"ParentName": "Muhasebe Müdürü"
}
],
"Announcements": [
{
"title": "🎉 Yeni Ofis Açılışı",
"content": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızıılış törenimize davet ediyoruz.",
"excerpt": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!",
"category": "general",
"userName": "system@sozsoft.com",
"publishDate": "12-10-2024",
"isPinned": true,
"viewCount": 156,
"imageUrl": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&q=80"
},
{
"title": "📅 Performans Değerlendirme Dönemi",
"content": "Yıl sonu performans değerlendirmelerimiz 20 Ekim - 5 Kasım tarihleri arasında gerçekleştirilecektir. Lütfen formları zamanında doldurunuz.",
"excerpt": "Yıl sonu performans değerlendirmeleri başlıyor.",
"category": "hr",
"userName": "system@sozsoft.com",
"publishDate": "08-10-2024",
"expiryDate": "05-11-2024",
"isPinned": true,
"viewCount": 89
},
{
"title": "💻 Sistem Bakımı Duyurusu",
"content": "Bu Cumartesi saat 02: 00 - 06: 00 arası sistemlerimizde bakım çalışması yapılacaktır. Bu süre içinde sistemlere erişim sağlanamayacaktır.",
"excerpt": "Cumartesi gecesi planlı bakım çalışması",
"category": "it",
"userName": "system@sozsoft.com",
"publishDate": "08-10-2024",
"isPinned": false,
"viewCount": 234
},
{
"title": "🎓 React İleri Seviye Eğitimi",
"content": "Yazılım Geliştirme ekibimiz için React İleri Seviye eğitimi 25-26 Ekim tarihlerinde düzenlenecektir. Katılım için IK birimine başvurunuz.",
"excerpt": "React İleri Seviye eğitimi kayıtları başladı",
"category": "event",
"userName": "system@sozsoft.com",
"publishDate": "09-10-2024",
"isPinned": false,
"viewCount": 67
},
{
"title": "⚠️ Güvenlik Politikası Güncellemesi",
"content": "Bilgi güvenliği politikamız güncellenmiştir. Tüm çalışanlarımızın yeni politikayı okuması ve onaylaması gerekmektedir.",
"excerpt": "Güvenlik politikası güncellendi - Onay gerekli",
"category": "urgent",
"userName": "system@sozsoft.com",
"publishDate": "04-10-2024",
"isPinned": true,
"viewCount": 312
}
],
"Surveys": [
{
"Title": "Çalışan Memnuniyet Anketi 2024",
"Description": "Yıllık çalışan memnuniyeti ve bağlılık araştırması",
"Deadline": "2024-10-31T00:00:00",
"Responses": 45,
"Status": "active",
"IsAnonymous": true
},
{
"Title": "Eğitim İhtiyaç Analizi",
"Description": "2025 yılı eğitim planlaması için ihtiyaç tespiti",
"Deadline": "2024-11-15T00:00:00",
"Responses": 28,
"Status": "active",
"IsAnonymous": false
},
{
"Title": "Kafeterya Memnuniyet Anketi",
"Description": "Yemek kalitesi ve servis değerlendirmesi",
"Deadline": "2024-09-30T00:00:00",
"Responses": 62,
"Status": "passive",
"IsAnonymous": true
}
],
"SurveyQuestions": [
{
"SurveyTitle": "Çalışan Memnuniyet Anketi 2024",
"QuestionText": "Genel memnuniyet düzeyiniz nedir?",
"Type": "rating",
"Order": 1,
"IsRequired": true
},
{
"SurveyTitle": "Çalışan Memnuniyet Anketi 2024",
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Type": "multiple-choice",
"Order": 2,
"IsRequired": true
},
{
"SurveyTitle": "Çalışan Memnuniyet Anketi 2024",
"QuestionText": "Görüş ve önerileriniz",
"Type": "textarea",
"Order": 3,
"IsRequired": false
},
{
"SurveyTitle": "Çalışan Memnuniyet Anketi 2024",
"QuestionText": "Çalışma ortamından memnun musunuz?",
"Type": "yes-no",
"Order": 4,
"IsRequired": true
},
{
"SurveyTitle": "Eğitim İhtiyaç Analizi",
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Type": "multiple-choice",
"Order": 1,
"IsRequired": true
},
{
"SurveyTitle": "Eğitim İhtiyaç Analizi",
"QuestionText": "Eğitim formatı tercihiniz nedir?",
"Type": "multiple-choice",
"Order": 2,
"IsRequired": true
},
{
"SurveyTitle": "Eğitim İhtiyaç Analizi",
"QuestionText": "Eğitim için haftalık ne kadar zaman ayırabilirsiniz?",
"Type": "rating",
"Order": 3,
"IsRequired": true
},
{
"SurveyTitle": "Kafeterya Memnuniyet Anketi",
"QuestionText": "Yemek kalitesini nasıl değerlendiriyorsunuz?",
"Type": "rating",
"Order": 1,
"IsRequired": true
},
{
"SurveyTitle": "Kafeterya Memnuniyet Anketi",
"QuestionText": "Hangi yemekleri daha sık görmek istiyorsunuz?",
"Type": "textarea",
"Order": 2,
"IsRequired": false
},
{
"SurveyTitle": "Kafeterya Memnuniyet Anketi",
"QuestionText": "Servis hızından memnun musunuz?",
"Type": "yes-no",
"Order": 3,
"IsRequired": true
}
],
"SurveyQuestionOptions": [
{
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Text": "Bilgi Teknolojileri",
"Order": 1
},
{
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Text": "İnsan Kaynakları",
"Order": 2
},
{
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Text": "Finans",
"Order": 3
},
{
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Text": "Satış",
"Order": 4
},
{
"QuestionText": "Hangi departmanda çalışıyorsunuz?",
"Text": "Pazarlama",
"Order": 5
},
{
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Text": "React / Frontend",
"Order": 1
},
{
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Text": "Node.js / Backend",
"Order": 2
},
{
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Text": "Database / SQL",
"Order": 3
},
{
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Text": "DevOps / Cloud",
"Order": 4
},
{
"QuestionText": "Hangi teknoloji konularında eğitim almak istiyorsunuz?",
"Text": "Mobile Development",
"Order": 5
},
{
"QuestionText": "Eğitim formatı tercihiniz nedir?",
"Text": "Online Eğitim",
"Order": 1
},
{
"QuestionText": "Eğitim formatı tercihiniz nedir?",
"Text": "Yüz Yüze Eğitim",
"Order": 2
},
{
"QuestionText": "Eğitim formatı tercihiniz nedir?",
"Text": "Hibrit (Karma)",
"Order": 3
}
],
"SocialPosts": [
{
"content": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "system@sozsoft.com",
"likeCount": 24,
"isLiked": true,
"isOwnPost": false
},
{
"content": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"userName": "system@sozsoft.com",
"likeCount": 18,
"isLiked": false,
"isOwnPost": true
},
{
"content": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "system@sozsoft.com",
"likeCount": 42,
"isLiked": true,
"isOwnPost": false
},
{
"content": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"userName": "system@sozsoft.com",
"likeCount": 31,
"isLiked": false,
"isOwnPost": false
},
{
"content": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "system@sozsoft.com",
"likeCount": 56,
"isLiked": true,
"isOwnPost": false
},
{
"content": "Bugün müşteri ile harika bir toplantı yaptık! Yeni projenin detaylarını konuştuk. 🎯",
"userName": "system@sozsoft.com",
"likeCount": 18,
"isLiked": false,
"isOwnPost": false
}
],
"SocialLocations": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"name": "Taksim Meydanı",
"address": "Taksim, Gümüşsuyu Mahallesi, 34437 Beyoğlu/İstanbul",
"lat": 41.0369,
"lng": 28.985,
"placeId": "ChIJBQRGmL25yhQRXwqRTHAwAAQ"
},
{
"postContent": "Bugün müşteri ile harika bir toplantı yaptık! Yeni projenin detaylarını konuştuk. 🎯",
"name": "Sultanahmet Meydanı",
"address": "Sultanahmet Mahallesi, 34122 Fatih/İstanbul",
"lat": 41.0058,
"lng": 28.9768,
"placeId": "ChIJ7fVVZiy5yhQRzsXXXXXXXXk"
}
],
"SocialMedias": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"type": "image",
"urls": [
"https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&q=80"
]
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"type": "poll",
"pollQuestion": "Hangi özelliği öncelikli olarak geliştirmeliyiz?",
"pollTotalVotes": 40,
"pollEndsAt": "2024-10-20T23:59:59",
"pollUserVoteId": "p3"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"type": "image",
"urls": [
"https://images.unsplash.com/photo-1561070791-2526d30994b5?w=800&q=80",
"https://images.unsplash.com/photo-1586717799252-bd134ad00e26?w=800&q=80",
"https://images.unsplash.com/photo-1609921212029-bb5a28e60960?w=800&q=80"
]
},
{
"postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"type": "video",
"urls": ["https://www.w3schools.com/html/mov_bbb.mp4"]
}
],
"SocialPollOptions": [
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"pollQuestion": "Hangi özelliği öncelikli olarak geliştirmeliyiz?",
"Text": "Kullanıcı profilleri",
"Votes": 12
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"pollQuestion": "Hangi özelliği öncelikli olarak geliştirmeliyiz?",
"Text": "Bildirim sistemi",
"Votes": 8
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"pollQuestion": "Hangi özelliği öncelikli olarak geliştirmeliyiz?",
"Text": "Mesajlaşma",
"Votes": 15
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"pollQuestion": "Hangi özelliği öncelikli olarak geliştirmeliyiz?",
"Text": "Raporlama",
"Votes": 5
}
],
"SocialComments": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "system@sozsoft.com",
"content": "Harika görünüyor! Başarılar 👏"
},
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "system@sozsoft.com",
"content": "TypeScript gerçekten fark yaratıyor!"
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"userName": "system@sozsoft.com",
"content": "Mesajlaşma özelliğine kesinlikle ihtiyacımız var!"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "system@sozsoft.com",
"content": "Tasarımlar çok şık! Renk paleti özellikle güzel 😍"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "system@sozsoft.com",
"content": "Dark mode opsiyonu da olacak mı?"
},
{
"postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"userName": "system@sozsoft.com",
"content": "Harika iş! Detayları paylaşabilir misin?"
},
{
"postContent": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "system@sozsoft.com",
"content": "Ne zaman başlıyor?"
},
{
"postContent": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "system@sozsoft.com",
"content": "Gelecek hafta başlıyoruz! Kayıt linki mail ile paylaşılacak."
}
],
"SocialLikes": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "system@sozsoft.com"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "system@sozsoft.com"
}
]
}

View file

@ -15,6 +15,7 @@ using Volo.Abp.Timing;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
using Sozsoft.Platform.Extensions;
using System.Linq;
namespace Sozsoft.Platform.Data.Seeds;
@ -52,6 +53,113 @@ public class TenantSeederDto
//Forum
public List<ForumCategorySeedDto> ForumCategories { get; set; }
//Intranet
public List<AnnouncementSeedDto> Announcements { get; set; }
public List<SurveySeedDto> Surveys { get; set; }
public List<SurveyQuestionSeedDto> SurveyQuestions { get; set; }
public List<SurveyQuestionOptionSeedDto> SurveyQuestionOptions { get; set; }
public List<SocialPostSeedDto> SocialPosts { get; set; }
public List<SocialLocationSeedDto> SocialLocations { get; set; }
public List<SocialMediaSeedDto> SocialMedias { get; set; }
public List<SocialPollOptionSeedDto> SocialPollOptions { get; set; }
public List<SocialCommentSeedDto> SocialComments { get; set; }
public List<SocialLikeSeedDto> SocialLikes { get; set; }
}
public class SocialPostSeedDto
{
public string Content { get; set; }
public string UserName { get; set; }
public int LikeCount { get; set; }
public bool IsLiked { get; set; }
public bool IsOwnPost { get; set; }
}
public class SocialLocationSeedDto
{
public string PostContent { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public double? Lat { get; set; }
public double? Lng { get; set; }
public string PlaceId { get; set; }
}
public class SocialMediaSeedDto
{
public string PostContent { get; set; }
public string Type { get; set; }
public string[] Urls { get; set; }
public string PollQuestion { get; set; }
public int? PollTotalVotes { get; set; }
public DateTime? PollEndsAt { get; set; }
public string PollUserVoteId { get; set; }
public List<SocialPollOptionSeedDto> PollOptions { get; set; }
}
public class SocialPollOptionSeedDto
{
public string PostContent { get; set; }
public string PollQuestion { get; set; }
public string Text { get; set; }
public int Votes { get; set; }
}
public class SocialCommentSeedDto
{
public string PostContent { get; set; }
public string UserName { get; set; }
public string Content { get; set; }
}
public class SocialLikeSeedDto
{
public string PostContent { get; set; }
public string UserName { get; set; }
}
public class SurveySeedDto
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
public int Responses { get; set; }
public string Status { get; set; }
public bool IsAnonymous { get; set; }
}
public class SurveyQuestionSeedDto
{
public string QuestionText { get; set; }
public string SurveyTitle { get; set; }
public string Type { get; set; }
public int Order { get; set; }
public bool IsRequired { get; set; }
}
public class SurveyQuestionOptionSeedDto
{
public string QuestionText { get; set; }
public string Text { get; set; }
public int Order { get; set; }
}
public class AnnouncementSeedDto
{
public string Title { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public string ImageUrl { get; set; }
public string Category { get; set; }
public string UserName { get; set; }
public DateTime PublishDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public bool IsPinned { get; set; }
public int ViewCount { get; set; }
public string DepartmentNames { get; set; }
}
public class JobPositionSeedDto
@ -291,6 +399,16 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<Product, Guid> _productRepository;
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IRepository<Announcement, Guid> _announcementRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SurveyQuestion, Guid> _surveyQuestionRepository;
private readonly IRepository<SurveyQuestionOption, Guid> _surveyQuestionOptionRepository;
private readonly IRepository<SocialPost, Guid> _socialPostRepository;
private readonly IRepository<SocialLocation, Guid> _socialLocationRepository;
private readonly IRepository<SocialMedia, Guid> _socialMediaRepository;
private readonly IRepository<SocialPollOption, Guid> _socialPollOptionRepository;
private readonly IRepository<SocialComment, Guid> _socialCommentRepository;
private readonly IRepository<SocialLike, Guid> _socialLikeRepository;
private readonly ICurrentTenant _currentTenant;
public TenantDataSeeder(
@ -322,6 +440,18 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<OrganizationUnit, Guid> organizationUnitRepository,
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
IRepository<Announcement, Guid> announcementRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SurveyQuestion, Guid> surveyQuestionRepository,
IRepository<SurveyQuestionOption, Guid> surveyQuestionOptionRepository,
IRepository<SocialPost, Guid> socialPostRepository,
IRepository<SocialLocation, Guid> socialLocationRepository,
IRepository<SocialMedia, Guid> socialMediaRepository,
IRepository<SocialPollOption, Guid> socialPollOptionRepository,
IRepository<SocialComment, Guid> socialCommentRepository,
IRepository<SocialLike, Guid> socialLikeRepository,
OrganizationUnitManager organizationUnitManager,
ICurrentTenant currentTenant
)
@ -354,6 +484,16 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
_organizationUnitRepository = organizationUnitRepository;
_departmentRepository = departmentRepository;
_jobPositionRepository = jobPositionRepository;
_announcementRepository = announcementRepository;
_surveyRepository = surveyRepository;
_surveyQuestionRepository = surveyQuestionRepository;
_surveyQuestionOptionRepository = surveyQuestionOptionRepository;
_socialPostRepository = socialPostRepository;
_socialLocationRepository = socialLocationRepository;
_socialMediaRepository = socialMediaRepository;
_socialPollOptionRepository = socialPollOptionRepository;
_socialCommentRepository = socialCommentRepository;
_socialLikeRepository = socialLikeRepository;
_organizationUnitManager = organizationUnitManager;
_currentTenant = currentTenant;
}
@ -770,6 +910,204 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
}, autoSave: true);
}
foreach (var item in items.Announcements)
{
var exists = await _announcementRepository.AnyAsync(x => x.Title == item.Title);
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
await _announcementRepository.InsertAsync(new Announcement
{
Title = item.Title,
Excerpt = item.Excerpt,
Content = item.Content,
ImageUrl = item.ImageUrl,
Category = item.Category,
UserId = user != null ? user.Id : null,
PublishDate = item.PublishDate,
ExpiryDate = item.ExpiryDate,
IsPinned = item.IsPinned,
ViewCount = item.ViewCount
});
}
foreach (var item in items.Surveys)
{
var exists = await _surveyRepository.AnyAsync(x => x.Title == item.Title);
if (exists)
continue;
await _surveyRepository.InsertAsync(new Survey
{
Title = item.Title,
Description = item.Description,
Deadline = item.Deadline,
Responses = item.Responses,
Status = item.Status,
IsAnonymous = item.IsAnonymous
}, autoSave: true);
}
foreach (var item in items.SurveyQuestions)
{
var exists = await _surveyQuestionRepository.AnyAsync(x => x.QuestionText == item.QuestionText);
if (exists)
continue;
var survey = await _surveyRepository.FirstOrDefaultAsync(x => x.Title == item.SurveyTitle);
await _surveyQuestionRepository.InsertAsync(new SurveyQuestion
{
SurveyId = survey != null ? survey.Id : Guid.Empty,
QuestionText = item.QuestionText,
Type = item.Type,
Order = item.Order,
IsRequired = item.IsRequired
}, autoSave: true);
}
foreach (var item in items.SurveyQuestionOptions)
{
var surveyQuestion = await _surveyQuestionRepository.FirstOrDefaultAsync(x => x.QuestionText == item.QuestionText);
if (surveyQuestion == null)
continue;
var exists = await _surveyQuestionOptionRepository.AnyAsync(x => x.QuestionId == surveyQuestion.Id && x.Text == item.Text);
if (exists)
continue;
await _surveyQuestionOptionRepository.InsertAsync(new SurveyQuestionOption
{
QuestionId = surveyQuestion.Id,
Text = item.Text,
Order = item.Order
}, autoSave: true);
}
foreach (var item in items.SocialPosts)
{
var exists = await _socialPostRepository.AnyAsync(x => x.Content == item.Content);
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
await _socialPostRepository.InsertAsync(new SocialPost
{
UserId = user != null ? user.Id : null,
Content = item.Content,
LikeCount = item.LikeCount,
IsLiked = item.IsLiked,
IsOwnPost = item.IsOwnPost
}, autoSave: true);
}
foreach (var item in items.SocialLocations)
{
var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent);
if (post == null)
continue;
var exists = await _socialLocationRepository.AnyAsync(x => x.SocialPostId == post.Id && x.Name == item.Name);
if (exists)
continue;
await _socialLocationRepository.InsertAsync(new SocialLocation
{
SocialPostId = post != null ? post.Id : Guid.Empty,
Name = item.Name,
Address = item.Address,
Lat = item.Lat,
Lng = item.Lng,
PlaceId = item.PlaceId
}, autoSave: true);
}
foreach (var item in items.SocialMedias)
{
var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent);
if (post == null)
continue;
var exists = await _socialMediaRepository.AnyAsync(x => x.SocialPostId == post.Id);
if (exists)
continue;
await _socialMediaRepository.InsertAsync(new SocialMedia
{
SocialPostId = post != null ? post.Id : Guid.Empty,
Type = item.Type,
Urls = item.Urls,
PollQuestion = item.PollQuestion,
PollTotalVotes = item.PollTotalVotes,
PollEndsAt = item.PollEndsAt,
PollUserVoteId = item.PollUserVoteId
}, autoSave: true);
}
foreach (var item in items.SocialPollOptions)
{
var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent);
if (post == null)
continue;
var media = await _socialMediaRepository.FirstOrDefaultAsync(x => x.SocialPostId == post.Id && x.PollQuestion == item.PollQuestion);
if (media == null)
continue;
var exists = await _socialPollOptionRepository.AnyAsync(x => x.SocialMediaId == media.Id && x.Text == item.Text);
if (exists)
continue;
await _socialPollOptionRepository.InsertAsync(new SocialPollOption
{
SocialMediaId = media != null ? media.Id : Guid.Empty,
Text = item.Text,
Votes = item.Votes
}, autoSave: true);
}
foreach (var item in items.SocialComments)
{
var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent);
if (post == null)
continue;
var exists = await _socialCommentRepository.AnyAsync(x => x.SocialPostId == post.Id && x.Content == item.Content);
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
await _socialCommentRepository.InsertAsync(new SocialComment
{
UserId = user != null ? user.Id : null,
SocialPostId = post != null ? post.Id : Guid.Empty,
Content = item.Content
}, autoSave: true);
}
foreach (var item in items.SocialLikes)
{
var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent);
if (post == null)
continue;
var exists = await _socialLikeRepository.AnyAsync(x => x.SocialPostId == post.Id);
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
await _socialLikeRepository.InsertAsync(new SocialLike
{
SocialPostId = post != null ? post.Id : Guid.Empty,
UserId = user != null ? user.Id : null
}, autoSave: true);
}
//admin kullanının departmen ve pozisyonunu default olarak belirliyoruz
var adminUser = await _repositoryUser.FindByNormalizedEmailAsync(PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
if (adminUser != null)

View file

@ -23,7 +23,7 @@ const ComponentSelector: React.FC<ComponentSelectorProps> = ({
</label>
<Button
variant='solid'
size="xs"
size="sm"
onClick={onRefresh}
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors"
title="Refresh component list"

View file

@ -528,7 +528,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
{/* Sil Butonu */}
<Button
variant="solid"
size="xs"
size="sm"
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 transition-colors text-sm"
onClick={() => {
if (selectedComponent) {
@ -551,7 +551,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
<div className="p-4 border-t">
<div className="flex gap-2">
<Button
size="xs"
size="sm"
variant="solid"
onClick={
activeTab === "props"
@ -568,7 +568,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
Uygula
</Button>
<Button
size="xs"
size="sm"
variant="default"
onClick={
activeTab === "props"

View file

@ -37,7 +37,7 @@ const CopyButton = () => {
<Button
shape="circle"
variant="plain"
size="xs"
size="sm"
onClick={handleCopy}
title={translate('::SidePanel.SaveConfig')}
icon={<FaSave />}

View file

@ -70,7 +70,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
`h-${CONTROL_SIZES.lg}`,
icon && !children
? `w-${CONTROL_SIZES.lg} ${sizeIconClass} text-2xl`
: 'px-8 py-2 text-base'
: 'px-8 text-base'
)
break
case SIZES.SM:
@ -78,7 +78,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
`h-${CONTROL_SIZES.sm}`,
icon && !children
? `w-${CONTROL_SIZES.sm} ${sizeIconClass} text-lg`
: 'px-3 py-2 text-sm'
: 'px-3 text-sm'
)
break
case SIZES.XS:
@ -86,7 +86,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
`h-${CONTROL_SIZES.xs}`,
icon && !children
? `w-${CONTROL_SIZES.xs} ${sizeIconClass} text-base`
: 'px-2 py-1 text-xs'
: 'px-2 text-xs'
)
break
default:
@ -94,7 +94,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
`h-${CONTROL_SIZES.md}`,
icon && !children
? `w-${CONTROL_SIZES.md} ${sizeIconClass} text-xl`
: 'px-8 py-2'
: 'px-8'
)
break
}

View file

@ -1,44 +0,0 @@
import { BankAccount, BankAccountTypeEnum } from "@/proxy/intranet/models";
export const mockBanks: BankAccount[] = [
{
id: "1",
accountCode: "BANKA001",
bankName: "İş Bankası",
branchName: "Levent Şubesi",
accountNumber: "1234567890",
iban: "TR120006400000112345678901",
accountType: BankAccountTypeEnum.Current,
currency: "TRY",
balance: 125000,
overdraftLimit: 50000,
dailyTransferLimit: 100000,
isActive: true,
contactPerson: "Mehmet Ak",
phoneNumber: "+90 212 555 1111",
creationTime: new Date("2024-01-01"),
lastModificationTime: new Date("2024-01-20"),
swiftCode: "ISBKTRIS",
isDefault: true,
},
{
id: "2",
accountCode: "BANKA002",
bankName: "Garanti BBVA",
branchName: "Şişli Şubesi",
accountNumber: "9876543210",
iban: "TR980006200012600006298453",
accountType: BankAccountTypeEnum.Deposit,
currency: "USD",
balance: 15000,
overdraftLimit: 0,
dailyTransferLimit: 50000,
isActive: true,
contactPerson: "Ayşe Yıldız",
phoneNumber: "+90 212 555 2222",
creationTime: new Date("2024-01-01"),
lastModificationTime: new Date("2024-01-18"),
swiftCode: "TGBATRIS",
isDefault: false,
},
];

View file

@ -1,100 +0,0 @@
import { DepartmentDto } from "@/proxy/intranet/models"
export const mockDepartments: DepartmentDto[] = [
{
id: '1',
name: 'Üretim',
description: 'Üretim departmanı',
parentDepartmentId: undefined,
parentDepartment: undefined,
subDepartments: [],
managerId: '1',
manager: undefined,
costCenterId: 'cc-005',
costCenter: undefined,
budget: 8500000,
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
},
{
id: '2',
name: 'Bakım',
description: 'Bakım departmanı',
parentDepartmentId: undefined,
parentDepartment: undefined,
subDepartments: [],
managerId: '7',
manager: undefined,
costCenterId: 'cc-011',
costCenter: undefined,
budget: 2200000,
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
},
{
id: '3',
name: 'Kalite Kontrol',
description: 'Kalite kontrol departmanı',
parentDepartmentId: '1',
parentDepartment: undefined,
subDepartments: [],
managerId: '5',
manager: undefined,
costCenterId: 'cc-007',
costCenter: undefined,
budget: 1200000,
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
},
{
id: '4',
name: 'Depo',
description: 'Depo departmanı',
parentDepartmentId: '1',
parentDepartment: undefined,
subDepartments: [],
managerId: '3',
manager: undefined,
costCenterId: 'cc-008',
costCenter: undefined,
budget: 2800000,
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
},
{
id: '5',
name: 'İdari İşler',
description: 'İdari işler departmanı',
parentDepartmentId: undefined,
parentDepartment: undefined,
subDepartments: [],
managerId: '2',
manager: undefined,
costCenterId: 'cc-001',
costCenter: undefined,
budget: 2500000,
isActive: true,
creationTime: new Date('2022-03-15'),
lastModificationTime: new Date('2024-01-15'),
},
]
mockDepartments.forEach((dept) => {
if (dept.parentDepartmentId) {
dept.parentDepartment = mockDepartments.find((d) => d.id === dept.parentDepartmentId)
}
})
mockDepartments.forEach((dept) => {
if (dept.parentDepartmentId) {
const parent = mockDepartments.find((d) => d.id === dept.parentDepartmentId)
if (parent) {
dept.parentDepartment = parent
parent.subDepartments.push(dept) // subDepartments bağlantısı
}
}
})

View file

@ -1,602 +0,0 @@
import { EmployeeDto } from "@/proxy/intranet/models";
import {
EmployeeStatusEnum,
EmploymentTypeEnum,
GenderEnum,
MaritalStatusEnum,
} from "../types/hr";
import { mockBanks } from "./mockBanks";
import { mockDepartments } from "./mockDepartments";
import { mockJobPositions } from "./mockJobPositions";
export const mockEmployees: EmployeeDto[] = [
{
id: "1",
code: "EMP-001",
firstName: "Ali",
lastName: "Öztürk",
name: "Ali Öztürk",
email: "ali.ozturk@company.com",
phoneNumber: "2125550100",
mobileNumber: "5325550101",
avatar: "https://i.pravatar.cc/150?img=12",
nationalId: "12345678901",
birthDate: new Date("1988-10-20"),
gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Kızılay Cd. No:12",
city: "Ankara",
state: "Ankara",
postalCode: "06050",
country: "Türkiye",
},
emergencyContact: {
name: "Ayşe Öztürk",
relationship: "Eşi",
phoneNumber: "+90 532 555 0100",
},
hireDate: new Date("2020-01-15"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "1",
jobPosition: mockJobPositions.find((jp) => jp.id === "1")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 65000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "1",
bankAccount: mockBanks.find((b) => b.id === "1")!,
workLocation: "Ankara Merkez",
workSchedule: {
id: "1",
scheduleCode: "STD",
name: "Standart Mesai",
description: "08:30-17:30 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: false,
isActive: true,
},
badgeNumber: "B001",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2020-01-15"),
lastModificationTime: new Date("2024-01-18"),
},
{
id: "2",
code: "EMP-002",
firstName: "Ayşe",
lastName: "Kaya",
name: "Ayşe Kaya",
email: "ayse.kaya@company.com",
phoneNumber: "2125550102",
mobileNumber: "5325550103",
avatar: "https://i.pravatar.cc/150?img=5",
nationalId: "12345678902",
birthDate: new Date("1990-08-22"),
gender: GenderEnum.Female,
maritalStatus: MaritalStatusEnum.Single,
address: {
street: "İnönü Bulvarı No:456",
city: "Ankara",
state: "Ankara",
postalCode: "06000",
country: "Türkiye",
},
emergencyContact: {
name: "Fatma Kaya",
relationship: "Anne",
phoneNumber: "+90 532 555 0104",
},
hireDate: new Date("2021-06-01"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "2",
jobPosition: mockJobPositions.find((jp) => jp.id === "2")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 72000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "2",
bankAccount: mockBanks.find((b) => b.id === "2")!,
workLocation: "Ankara Şube",
workSchedule: {
id: "2",
scheduleCode: "STD",
name: "Standart Mesai",
description: "08:30-17:30 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: false,
isActive: true,
},
badgeNumber: "B002",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2021-06-01"),
lastModificationTime: new Date("2024-01-18"),
},
{
id: "3",
code: "EMP-003",
firstName: "Mehmet",
lastName: "Yılmaz",
name: "Mehmet Yılmaz",
email: "mehmet.yilmaz@company.com",
phoneNumber: "2125550105",
mobileNumber: "5325550106",
avatar: "https://i.pravatar.cc/150?img=8",
nationalId: "12345678903",
birthDate: new Date("1987-03-12"),
gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Cumhuriyet Cad. No:123",
city: "İstanbul",
state: "İstanbul",
postalCode: "34000",
country: "Türkiye",
},
emergencyContact: {
name: "Zeynep Yılmaz",
relationship: "Eşi",
phoneNumber: "+90 532 555 0107",
},
hireDate: new Date("2020-02-15"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "3",
jobPosition: mockJobPositions.find((jp) => jp.id === "3")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 85000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "2",
bankAccount: mockBanks.find((b) => b.id === "2")!,
workLocation: "İstanbul HQ",
workSchedule: {
id: "2",
scheduleCode: "FLEX",
name: "Esnek Çalışma",
description: "09:00-18:00 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: true,
isActive: true,
},
badgeNumber: "B003",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2020-02-15"),
lastModificationTime: new Date("2024-02-01"),
},
{
id: "4",
code: "EMP-004",
firstName: "Selin",
lastName: "Demir",
name: "Selin Demir",
email: "selin.demir@company.com",
phoneNumber: "3125550108",
mobileNumber: "5425550109",
avatar: "https://i.pravatar.cc/150?img=9",
nationalId: "12345678904",
birthDate: new Date("1993-05-25"),
gender: GenderEnum.Female,
maritalStatus: MaritalStatusEnum.Single,
address: {
street: "Atatürk Bulvarı No:78",
city: "Ankara",
state: "Ankara",
postalCode: "06100",
country: "Türkiye",
},
emergencyContact: {
name: "Ali Demir",
relationship: "Baba",
phoneNumber: "532 555 0110",
},
hireDate: new Date("2022-01-10"),
employmentType: EmploymentTypeEnum.PartTime,
jobPositionId: "4",
jobPosition: mockJobPositions.find((jp) => jp.id === "4")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 60000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "3",
bankAccount: mockBanks.find((b) => b.id === "3")!,
workLocation: "Ankara Şube",
workSchedule: {
id: "3",
scheduleCode: "PT",
name: "Yarı Zamanlı",
description: "09:00-13:00 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 20,
isFlexible: false,
isActive: true,
},
badgeNumber: "B004",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2022-01-10"),
lastModificationTime: new Date("2024-01-20"),
},
{
id: "5",
code: "EMP-005",
firstName: "Ahmet",
lastName: "Çelik",
name: "Ahmet Çelik",
email: "ahmet.celik@company.com",
phoneNumber: "2125550111",
mobileNumber: "5325550112",
avatar: "https://i.pravatar.cc/150?img=33",
nationalId: "12345678905",
birthDate: new Date("1985-09-10"),
gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Bağdat Cad. No:25",
city: "İstanbul",
state: "İstanbul",
postalCode: "34728",
country: "Türkiye",
},
emergencyContact: {
name: "Emine Çelik",
relationship: "Eşi",
phoneNumber: "532 555 0113",
},
hireDate: new Date("2019-04-01"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "5",
jobPosition: mockJobPositions.find((jp) => jp.id === "5")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 95000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "4",
bankAccount: mockBanks.find((b) => b.id === "4")!,
workLocation: "İstanbul HQ",
workSchedule: {
id: "4",
scheduleCode: "STD",
name: "Standart Mesai",
description: "08:30-17:30 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: false,
isActive: true,
},
badgeNumber: "B005",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2019-04-01"),
lastModificationTime: new Date("2024-01-10"),
},
{
id: "6",
code: "EMP-006",
firstName: "Zeynep",
lastName: "Arslan",
name: "Zeynep Arslan",
email: "zeynep.arslan@company.com",
phoneNumber: "2165550114",
mobileNumber: "5325550115",
avatar: "https://i.pravatar.cc/150?img=10",
nationalId: "12345678906",
birthDate: new Date("1995-01-30"),
gender: GenderEnum.Female,
maritalStatus: MaritalStatusEnum.Single,
address: {
street: "Yıldız Mah. No:19",
city: "İzmir",
state: "İzmir",
postalCode: "35000",
country: "Türkiye",
},
emergencyContact: {
name: "Hasan Arslan",
relationship: "Baba",
phoneNumber: "532 555 0116",
},
hireDate: new Date("2023-03-20"),
employmentType: EmploymentTypeEnum.Intern,
jobPositionId: "6",
jobPosition: mockJobPositions.find((jp) => jp.id === "6")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 15000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "1",
bankAccount: mockBanks.find((b) => b.id === "1")!,
workLocation: "İzmir Ofis",
workSchedule: {
id: "5",
scheduleCode: "INT",
name: "Staj Programı",
description: "09:00-16:00 Pazartesi-Perşembe",
workingDays: [],
totalHoursPerWeek: 30,
isFlexible: true,
isActive: true,
},
badgeNumber: "B006",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2023-03-20"),
lastModificationTime: new Date("2024-02-15"),
},
{
id: "7",
code: "EMP-007",
firstName: "Burak",
lastName: "Koç",
name: "Burak Koç",
email: "burak.koc@company.com",
phoneNumber: "2245550117",
mobileNumber: "5325550118",
avatar: "https://i.pravatar.cc/150?img=14",
nationalId: "12345678907",
birthDate: new Date("1991-06-18"),
gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Osmangazi Mah. No:45",
city: "Bursa",
state: "Bursa",
postalCode: "16000",
country: "Türkiye",
},
emergencyContact: {
name: "Elif Koç",
relationship: "Eşi",
phoneNumber: "+90 532 555 0119",
},
hireDate: new Date("2021-07-12"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "7",
jobPosition: mockJobPositions.find((jp) => jp.id === "7")!,
departmentId: "2",
department: mockDepartments.find((d) => d.id === "2")!,
baseSalary: 75000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "3",
bankAccount: mockBanks.find((b) => b.id === "3")!,
workLocation: "Bursa Depo",
workSchedule: {
id: "6",
scheduleCode: "STD",
name: "Standart Mesai",
description: "08:00-17:00 Pazartesi-Cumartesi",
workingDays: [],
totalHoursPerWeek: 45,
isFlexible: false,
isActive: true,
},
badgeNumber: "B007",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2021-07-12"),
lastModificationTime: new Date("2024-01-05"),
},
{
id: "8",
code: "EMP-008",
firstName: "Elif",
lastName: "Şahin",
name: "Elif Şahin",
email: "elif.sahin@company.com",
phoneNumber: "2325550120",
mobileNumber: "5325550121",
avatar: "https://i.pravatar.cc/150?img=20",
nationalId: "12345678908",
birthDate: new Date("1989-11-05"),
gender: GenderEnum.Female,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Alsancak Mah. No:88",
city: "İzmir",
state: "İzmir",
postalCode: "35220",
country: "Türkiye",
},
emergencyContact: {
name: "Murat Şahin",
relationship: "Eşi",
phoneNumber: "+90 532 555 0122",
},
hireDate: new Date("2018-09-01"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "8",
jobPosition: mockJobPositions.find((jp) => jp.id === "8")!,
departmentId: "2",
department: mockDepartments.find((d) => d.id === "2")!,
baseSalary: 130000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "2",
bankAccount: mockBanks.find((b) => b.id === "2")!,
workLocation: "İzmir Bölge Ofisi",
workSchedule: {
id: "7",
scheduleCode: "STD",
name: "Standart Mesai",
description: "08:30-17:30 Pazartesi-Cuma",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: false,
isActive: true,
},
badgeNumber: "B008",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2018-09-01"),
lastModificationTime: new Date("2024-01-12"),
},
{
id: "9",
code: "EMP-009",
firstName: "Canan",
lastName: "Öztürk",
name: "Canan Öztürk",
email: "canan.ozturk@company.com",
phoneNumber: "3125550123",
mobileNumber: "5325550124",
avatar: "https://i.pravatar.cc/150?img=25",
nationalId: "12345678909",
birthDate: new Date("1992-04-14"),
gender: GenderEnum.Female,
maritalStatus: MaritalStatusEnum.Single,
address: {
street: "Bahçelievler Mah. No:55",
city: "Ankara",
state: "Ankara",
postalCode: "06490",
country: "Türkiye",
},
emergencyContact: {
name: "Hüseyin Öztürk",
relationship: "Baba",
phoneNumber: "+90 532 555 0125",
},
hireDate: new Date("2020-11-02"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "9",
jobPosition: mockJobPositions.find((jp) => jp.id === "9")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 50000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "1",
bankAccount: mockBanks.find((b) => b.id === "1")!,
workLocation: "Ankara Çağrı Merkezi",
workSchedule: {
id: "8",
scheduleCode: "SHIFT",
name: "Vardiya",
description: "3 vardiya sistemi",
workingDays: [],
totalHoursPerWeek: 40,
isFlexible: true,
isActive: true,
},
badgeNumber: "B009",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2020-11-02"),
lastModificationTime: new Date("2024-01-18"),
},
{
id: "10",
code: "EMP-010",
firstName: "Murat",
lastName: "Aydın",
name: "Murat Aydın",
email: "murat.aydin@company.com",
phoneNumber: "2125550126",
mobileNumber: "5325550127",
avatar: "https://i.pravatar.cc/150?img=30",
nationalId: "12345678910",
birthDate: new Date("1984-12-22"),
gender: GenderEnum.Male,
maritalStatus: MaritalStatusEnum.Married,
address: {
street: "Şişli Mah. No:101",
city: "İstanbul",
state: "İstanbul",
postalCode: "34360",
country: "Türkiye",
},
emergencyContact: {
name: "Ayten Aydın",
relationship: "Eşi",
phoneNumber: "+90 532 555 0128",
},
hireDate: new Date("2017-05-15"),
employmentType: EmploymentTypeEnum.FullTime,
jobPositionId: "10",
jobPosition: mockJobPositions.find((jp) => jp.id === "10")!,
departmentId: "1",
department: mockDepartments.find((d) => d.id === "1")!,
baseSalary: 250000,
currency: "TRY",
payrollGroup: "MONTHLY",
bankAccountId: "4",
bankAccount: mockBanks.find((b) => b.id === "4")!,
workLocation: "İstanbul Genel Merkez",
workSchedule: {
id: "9",
scheduleCode: "EXEC",
name: "Yönetici Çalışma Programı",
description: "Esnek yönetici programı",
workingDays: [],
totalHoursPerWeek: 50,
isFlexible: true,
isActive: true,
},
badgeNumber: "B010",
employeeStatus: EmployeeStatusEnum.Active,
isActive: true,
leaves: [],
evaluations: [],
trainings: [],
disciplinaryActions: [],
creationTime: new Date("2017-05-15"),
lastModificationTime: new Date("2024-01-22"),
},
];

View file

@ -1,39 +0,0 @@
import { Exam } from "@/types/coordinator";
import { generateMockPools } from "./mockPools";
export const generateMockExam = (): Exam[] => [
{
id: "exam-1",
title: "Grammar Assessment",
description: "Comprehensive grammar evaluation",
type: "exam",
questions: generateMockPools()[0].questions,
timeLimit: 30,
totalPoints: 25,
passingScore: 60,
allowReview: true,
randomizeQuestions: false,
showResults: true,
maxAttempts: 1,
isActive: true,
creationTime: new Date(),
lastModificationTime: new Date(),
},
{
id: "assignment-1",
title: "Assignment 1",
description: "First assignment on grammar topics",
type: "assignment",
questions: generateMockPools()[0].questions,
timeLimit: 30,
totalPoints: 25,
passingScore: 60,
allowReview: true,
randomizeQuestions: false,
showResults: true,
maxAttempts: 1,
isActive: true,
creationTime: new Date(),
lastModificationTime: new Date(),
},
];

View file

@ -1,301 +0,0 @@
import { JobPositionDto } from "@/proxy/intranet/models";
import { mockDepartments } from "./mockDepartments";
import { JobLevelEnum } from "@/types/hr";
export const mockJobPositions: JobPositionDto[] = [
{
id: "1",
name: "Software Developer",
description: "Responsible for developing and maintaining web applications",
departmentId: "1",
department: mockDepartments.find((dept) => dept.id === "1"),
level: JobLevelEnum.Mid,
minSalary: 80000,
maxSalary: 120000,
currency: "USD",
requiredSkills: ["JavaScript", "TypeScript", "React", "Node.js"],
responsibilities: [
"Develop frontend and backend applications",
"Write clean and maintainable code",
"Participate in code reviews",
"Collaborate with team members",
],
qualifications: [
"Bachelor's degree in Computer Science or related field",
"3+ years of experience in web development",
"Strong knowledge of JavaScript and TypeScript",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-15T08:00:00Z"),
lastModificationTime: new Date("2024-01-15T08:00:00Z"),
},
{
id: "2",
name: "Project Manager",
description: "Lead and manage software development projects",
departmentId: "1",
department: mockDepartments.find((dept) => dept.id === "1"),
level: JobLevelEnum.Manager,
minSalary: 100000,
maxSalary: 150000,
currency: "USD",
requiredSkills: ["Project Management", "Agile", "Scrum", "Leadership"],
responsibilities: [
"Plan and execute project timelines",
"Coordinate with cross-functional teams",
"Manage project budgets and resources",
"Ensure project delivery within scope and timeline",
],
qualifications: [
"Bachelor's degree in Business or related field",
"5+ years of project management experience",
"PMP certification preferred",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-16T08:00:00Z"),
lastModificationTime: new Date("2024-01-16T08:00:00Z"),
},
{
id: "3",
name: "Quality Assurance Engineer",
description: "Ensure software quality through testing and automation",
departmentId: "1",
department: mockDepartments.find((dept) => dept.id === "1"),
level: JobLevelEnum.Mid,
minSalary: 70000,
maxSalary: 100000,
currency: "USD",
requiredSkills: ["Testing", "Automation", "Selenium", "API Testing"],
responsibilities: [
"Design and execute test cases",
"Automate testing processes",
"Report and track bugs",
"Collaborate with development team",
],
qualifications: [
"Bachelor's degree in Computer Science or related field",
"3+ years of QA experience",
"Experience with automation tools",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-17T08:00:00Z"),
lastModificationTime: new Date("2024-01-17T08:00:00Z"),
},
{
id: "4",
name: "UX/UI Designer",
description: "Design user interfaces and improve user experience",
departmentId: "2",
department: mockDepartments.find((dept) => dept.id === "2"),
level: JobLevelEnum.Mid,
minSalary: 75000,
maxSalary: 110000,
currency: "USD",
requiredSkills: [
"Figma",
"Adobe Creative Suite",
"User Research",
"Prototyping",
],
responsibilities: [
"Create wireframes and prototypes",
"Conduct user research",
"Design intuitive user interfaces",
"Collaborate with development team",
],
qualifications: [
"Bachelor's degree in Design or related field",
"3+ years of UX/UI design experience",
"Strong portfolio demonstrating design skills",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-18T08:00:00Z"),
lastModificationTime: new Date("2024-01-18T08:00:00Z"),
},
{
id: "5",
name: "Data Analyst",
description: "Analyze business data and provide insights",
departmentId: "1",
department: mockDepartments.find((dept) => dept.id === "1"),
level: JobLevelEnum.Mid,
minSalary: 85000,
maxSalary: 125000,
currency: "USD",
requiredSkills: ["SQL", "Python", "Excel", "Tableau", "Statistics"],
responsibilities: [
"Analyze large datasets",
"Create reports and dashboards",
"Identify trends and patterns",
"Present findings to stakeholders",
],
qualifications: [
"Bachelor's degree in Statistics, Mathematics, or related field",
"3+ years of data analysis experience",
"Strong analytical and problem-solving skills",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-19T08:00:00Z"),
lastModificationTime: new Date("2024-01-19T08:00:00Z"),
},
{
id: "6",
name: "HR Specialist",
description: "Support human resources operations and employee relations",
departmentId: "4",
department: mockDepartments.find((dept) => dept.id === "4"),
level: JobLevelEnum.Mid,
minSalary: 60000,
maxSalary: 85000,
currency: "USD",
requiredSkills: [
"HR Management",
"Recruitment",
"Employee Relations",
"HRIS",
],
responsibilities: [
"Manage recruitment processes",
"Handle employee relations issues",
"Maintain HR records and policies",
"Support employee onboarding",
],
qualifications: [
"Bachelor's degree in Human Resources or related field",
"2+ years of HR experience",
"Knowledge of employment laws and regulations",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-20T08:00:00Z"),
lastModificationTime: new Date("2024-01-20T08:00:00Z"),
},
{
id: "7",
name: "Sales Associate",
description: "Generate sales leads and maintain customer relationships",
departmentId: "2",
department: mockDepartments.find((dept) => dept.id === "2"),
level: JobLevelEnum.Junior,
minSalary: 45000,
maxSalary: 70000,
currency: "USD",
requiredSkills: ["Sales", "CRM", "Communication", "Customer Service"],
responsibilities: [
"Identify and pursue sales opportunities",
"Maintain customer relationships",
"Prepare sales proposals",
"Meet monthly sales targets",
],
qualifications: [
"Bachelor's degree in Business or related field",
"1+ years of sales experience",
"Excellent communication skills",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-21T08:00:00Z"),
lastModificationTime: new Date("2024-01-21T08:00:00Z"),
},
{
id: "8",
name: "Accountant",
description: "Manage financial records and ensure compliance",
departmentId: "3",
department: mockDepartments.find((dept) => dept.id === "3"),
level: JobLevelEnum.Mid,
minSalary: 55000,
maxSalary: 80000,
currency: "USD",
requiredSkills: [
"Accounting",
"Excel",
"Financial Analysis",
"Tax Preparation",
],
responsibilities: [
"Maintain financial records",
"Prepare financial statements",
"Ensure regulatory compliance",
"Assist with budget planning",
],
qualifications: [
"Bachelor's degree in Accounting or Finance",
"CPA certification preferred",
"3+ years of accounting experience",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-22T08:00:00Z"),
lastModificationTime: new Date("2024-01-22T08:00:00Z"),
},
{
id: "9",
name: "Customer Support Representative",
description: "Provide excellent customer service and technical support",
departmentId: "2",
department: mockDepartments.find((dept) => dept.id === "2"),
level: JobLevelEnum.Entry,
minSalary: 35000,
maxSalary: 50000,
currency: "USD",
requiredSkills: [
"Customer Service",
"Communication",
"Problem Solving",
"CRM",
],
responsibilities: [
"Respond to customer inquiries",
"Resolve technical issues",
"Document customer interactions",
"Escalate complex issues when needed",
],
qualifications: [
"High school diploma or equivalent",
"1+ years of customer service experience",
"Strong communication skills",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-23T08:00:00Z"),
lastModificationTime: new Date("2024-01-23T08:00:00Z"),
},
{
id: "10",
name: "IT Support Specialist",
description: "Provide technical support and maintain IT infrastructure",
departmentId: "1",
department: mockDepartments.find((dept) => dept.id === "1"),
level: JobLevelEnum.Junior,
minSalary: 50000,
maxSalary: 75000,
currency: "USD",
requiredSkills: [
"Windows",
"Network Administration",
"Hardware",
"Troubleshooting",
],
responsibilities: [
"Provide technical support to employees",
"Maintain computer systems and networks",
"Install and configure software",
"Document IT procedures",
],
qualifications: [
"Associate degree in Information Technology or related field",
"2+ years of IT support experience",
"CompTIA A+ certification preferred",
],
isActive: true,
employees: [],
creationTime: new Date("2024-01-24T08:00:00Z"),
lastModificationTime: new Date("2024-01-24T08:00:00Z"),
},
];

View file

@ -1,43 +0,0 @@
import { QuestionPoolDto } from "@/types/coordinator";
// Dynamic data - no hardcoded content
export const generateMockPools = (): QuestionPoolDto[] => [
{
id: "pool-1",
name: "Grammar Fundamentals",
description: "Essential grammar concepts and structures",
questions: [
{
id: "q1",
questionType: "multiple-choice",
title: "Verb Tenses",
content: "Select the appropriate tense form for the given context.",
options: [
{ id: "opt1", text: "Option A", isCorrect: false },
{ id: "opt2", text: "Option B", isCorrect: true },
{ id: "opt3", text: "Option C", isCorrect: false },
{ id: "opt4", text: "Option D", isCorrect: false },
],
correctAnswer: "opt2",
points: 10,
difficulty: "easy",
creationTime: new Date(),
lastModificationTime: new Date(),
},
{
id: "q2",
questionType: "fill-blank",
title: "Articles",
content:
'Fill in the blank: "I saw _____ elephant at the zoo yesterday."',
correctAnswer: "an",
points: 15,
difficulty: "medium",
creationTime: new Date(),
lastModificationTime: new Date(),
},
],
tags: ["grammar", "fundamentals"],
creationTime: new Date(),
},
];

View file

@ -1,36 +0,0 @@
import { TagItem } from "@/types/coordinator";
export const generateMockTags = (): TagItem[] => [
{
id: "tag-1",
name: "Grammar",
description: "Grammar related questions",
color: "#3B82F6",
usageCount: 25,
creationTime: new Date(),
},
{
id: "tag-2",
name: "Vocabulary",
description: "Vocabulary building exercises",
color: "#10B981",
usageCount: 18,
creationTime: new Date(),
},
{
id: "tag-3",
name: "Reading",
description: "Reading comprehension tasks",
color: "#F59E0B",
usageCount: 12,
creationTime: new Date(),
},
{
id: "tag-4",
name: "Writing",
description: "Writing skill assessments",
color: "#EF4444",
usageCount: 8,
creationTime: new Date(),
},
];

View file

@ -1,48 +0,0 @@
import { Exam } from "@/types/coordinator";
export const generateMockPDFTest = (): Exam => ({
id: "test-1",
title: "Reading Assessment",
description: "Document-based reading evaluation",
type: "test",
testDocument: {
url: "/sample-documents/reading-test.pdf",
type: "pdf",
name: "reading-assessment.pdf",
},
answerKeyTemplate: [
{
id: "ak1",
questionNumber: 1,
type: "multiple-choice",
options: ["A", "B", "C", "D"],
points: 10,
correctAnswer: "A",
},
{
id: "ak2",
questionNumber: 2,
type: "true-false",
points: 5,
correctAnswer: "true",
},
{
id: "ak3",
questionNumber: 3,
type: "fill-blank",
points: 15,
correctAnswer: "answer",
},
],
questions: [],
timeLimit: 45,
totalPoints: 30,
passingScore: 70,
allowReview: true,
randomizeQuestions: false,
showResults: true,
maxAttempts: 1,
isActive: true,
creationTime: new Date(),
lastModificationTime: new Date(),
});

View file

@ -63,6 +63,7 @@ export interface IdentityUserDto extends ExtensibleFullAuditedEntityDto<string>
userName?: string
name?: string
surname?: string
fullName?: string
email?: string
emailConfirmed: boolean
phoneNumber?: string
@ -109,10 +110,12 @@ export class ExtensibleFullAuditedEntityDto<
export interface UserInfoViewModel extends ExtensibleObject {
id?: string
tenantId?: string
concurrencyStamp?: string
userName?: string
name?: string
surname?: string
fullName?: string
email?: string
phoneNumber?: string
isActive: boolean
@ -139,6 +142,7 @@ export interface UserInfoViewModel extends ExtensibleObject {
lastModificationTime: Date | string
workHour?: string
departmentId?: string
department?: AssignedDepartmentViewModel
jobPositionId?: string
nationality?: string
sskNo?: string

View file

@ -0,0 +1,137 @@
import { UserInfoViewModel } from "../admin/models"
export interface IntranetDashboardDto {
birthdays: UserInfoViewModel[]
documents: DocumentDto[]
announcements: AnnouncementDto[]
surveys: SurveyDto[]
socialPosts: SocialPostDto[]
}
// Doküman (FileItemDto ile uyumlu)
export interface DocumentDto {
id: string
name: string
type: string // "file" or "folder"
size: number
extension: string
mimeType: string
createdAt: Date
modifiedAt: Date
path: string
parentId: string
isReadOnly: boolean
childCount: number
}
// Duyuru
export interface AnnouncementDto {
id: string
title: string
excerpt: string
content: string
imageUrl?: string
category: string
userId: string
user: UserInfoViewModel
publishDate: Date
expiryDate?: Date
isPinned: boolean
viewCount: number
departments?: string[]
attachments?: { name: string; url: string; size: string }[]
}
// Anket Cevap
export interface SurveyAnswerDto {
questionId: string
questionType: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
value: string | number | string[]
}
// Anket Cevabı
export interface SurveyResponseDto {
id: string
surveyId: string
respondentId?: string // Anonymous ise null
submissionTime: Date
answers: SurveyAnswerDto[]
}
// Anket Sorusu Seçeneği
export interface SurveyQuestionOptionDto {
id: string
text: string
order: number
}
// Anket Sorusu
export interface SurveyQuestionDto {
id: string
surveyId: string
questionText: string
type: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
order: number
isRequired: boolean
options?: SurveyQuestionOptionDto[]
}
// Anket
export interface SurveyDto {
id: string
title: string
description: string
creatorId: UserInfoViewModel
creationTime: Date
deadline: Date
questions: SurveyQuestionDto[]
responses: number
targetAudience: string[]
status: 'draft' | 'active' | 'closed'
isAnonymous: boolean
}
// Sosyal Duvar - Comment Interface
export interface SocialCommentDto {
id: string
creator: UserInfoViewModel
content: string
creationTime: Date
}
export interface SocialPollOptionDto {
id: string
text: string
votes: number
}
// Sosyal Duvar - Social Media Interface
export interface SocialMediaDto {
id?: string
type: 'image' | 'video' | 'poll'
// Ortak alanlar
urls?: string[]
// Anket (poll) ile ilgili alanlar doğrudan burada
pollQuestion?: string
pollOptions?: SocialPollOptionDto[]
pollTotalVotes?: number
pollEndsAt?: Date
pollUserVoteId?: string
}
// Sosyal Duvar - Ana Interface
export interface SocialPostDto {
id: string
user: UserInfoViewModel
content: string
locationJson?: string
media?: SocialMediaDto
likeCount: number
isLiked: boolean
likeUsers: UserInfoViewModel[]
comments: SocialCommentDto[]
isOwnPost: boolean
creationTime: Date
}

View file

@ -0,0 +1,69 @@
import {
FaFileAlt,
FaFilePdf,
FaFileWord,
FaFileExcel,
FaFilePowerpoint,
FaFileImage,
FaFileArchive,
FaFileCode,
} from 'react-icons/fa'
export const getFileIcon = (extension: string) => {
switch (extension.toLowerCase()) {
case '.pdf':
return <FaFilePdf className="w-4 h-4 text-red-600 dark:text-red-400" />
case '.doc':
case '.docx':
return <FaFileWord className="w-4 h-4 text-blue-600 dark:text-blue-400" />
case '.xls':
case '.xlsx':
return <FaFileExcel className="w-4 h-4 text-green-600 dark:text-green-400" />
case '.ppt':
case '.pptx':
return <FaFilePowerpoint className="w-4 h-4 text-orange-600 dark:text-orange-400" />
case '.jpg':
case '.jpeg':
case '.png':
case '.gif':
return <FaFileImage className="w-4 h-4 text-purple-600 dark:text-purple-400" />
case '.zip':
case '.rar':
return <FaFileArchive className="w-4 h-4 text-yellow-600 dark:text-yellow-400" />
case '.txt':
return <FaFileCode className="w-4 h-4 text-gray-600 dark:text-gray-400" />
default:
return <FaFileAlt className="w-4 h-4 text-gray-600 dark:text-gray-400" />
}
}
export const getFileType = (extension: string) => {
switch (extension.toLowerCase()) {
case '.pdf':
return '📄 PDF'
case '.doc':
case '.docx':
return '📝 Word'
case '.xls':
case '.xlsx':
return '📊 Excel'
case '.ppt':
case '.pptx':
return '📽️ PowerPoint'
case '.jpg':
case '.jpeg':
return '🖼️ JPEG'
case '.png':
return '🖼️ PNG'
case '.gif':
return '🖼️ GIF'
case '.zip':
return '🗜️ ZIP'
case '.rar':
return '🗜️ RAR'
case '.txt':
return '📝 Text'
default:
return '📄 Dosya'
}
}

View file

@ -81,7 +81,9 @@ export const ROUTES_ENUM = {
pivot: '/admin/pivot/:listFormCode',
},
participant: {},
sqlManager: '/admin/sql-manager',
accessDenied: '/admin/access-denied',
coordinator: {
classroom: {
@ -237,9 +239,5 @@ export const ROUTES_ENUM = {
bank: '/admin/accounting/bank',
checkNote: '/admin/accounting/check-note',
},
sqlManager: '/admin/sql-manager',
accessDenied: '/admin/access-denied',
},
}

View file

@ -0,0 +1,17 @@
import { IntranetDashboardDto } from '@/proxy/intranet/models'
import apiService, { Config } from './api.service'
export class IntranetService {
apiName = 'Default'
getDashboard = (config?: Partial<Config>) =>
apiService.fetchData<IntranetDashboardDto>(
{
method: 'GET',
url: '/api/app/intranet/intranet-dashboard',
},
{ apiName: this.apiName, ...config },
)
}
export const intranetService = new IntranetService()

View file

@ -52,9 +52,10 @@ const interpolate = (text: string, params: Record<string, string | number>) => {
const pattern = new RegExp(`\\{${key}\\}`, 'g')
return acc.replace(pattern, String(value))
} else {
// Named: {{ key }}
const pattern = new RegExp(`{{\\s*${key}\\s*}}`, 'g')
return acc.replace(pattern, String(value))
// Named: {{ key }} or {key}
let result = acc.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), String(value))
result = result.replace(new RegExp(`(?<!{){${key}}(?!})`, 'g'), String(value))
return result
}
}, text)
}

View file

@ -33,7 +33,7 @@ const AccessDenied = () => {
<p className="text-base">{translate('::AccessDeniedMessage')}</p>
</div>
<Button
size="xs"
size="sm"
className="mt-2"
variant="default"
onClick={() =>

View file

@ -1,6 +1,7 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import IntranetDashboard from './intranet/Dashboard'
const Dashboard = () => {
const { translate } = useLocalization()
@ -12,6 +13,7 @@ const Dashboard = () => {
title={translate('::' + 'Dashboard')}
defaultTitle={APP_NAME}
/>
<IntranetDashboard />
</>
)
}

View file

@ -22,7 +22,7 @@ function fallbackRender({ error, resetErrorBoundary }: { error: Error; resetErro
<Alert showIcon className="mb-4" type="danger">
<h5>{error.name ?? 'Hata!'}</h5>
<div>{error.message}</div>
<Button size="xs" className="mt-2" variant="default" onClick={resetErrorBoundary}>
<Button size="sm" className="mt-2" variant="default" onClick={resetErrorBoundary}>
<FaArrowLeft />
</Button>
</Alert>

View file

@ -746,7 +746,7 @@ const FileManager = () => {
<FaBuilding className="text-gray-500 flex-shrink-0" />
{isHostContext ? (
<Select
size="xs"
size="sm"
isLoading={tenantsLoading}
options={[
{ value: '', label: 'Host' },

View file

@ -19,7 +19,7 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
<div key={item.path} className="flex items-center">
{index > 0 && <FaChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
<Button
size="xs"
size="sm"
onClick={() => onNavigate(item)}
className={classNames(
'flex items-center px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',

View file

@ -316,7 +316,7 @@ const FileUploadModal = forwardRef<HTMLDivElement, FileUploadModalProps>((props,
{(file.status === 'pending' || file.status === 'error') && (
<Button
variant="plain"
size="xs"
size="sm"
icon={<FaTimes />}
onClick={() => removeFile(file.id)}
disabled={uploading}

View file

@ -86,7 +86,7 @@ function ChartTabAnnotations(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -118,7 +118,7 @@ function ChartTabAnnotations(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -136,7 +136,7 @@ function ChartTabAnnotations(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async () => {

View file

@ -79,7 +79,7 @@ function ChartTabAxis(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaPlus />}
onClick={async (e) => {
@ -111,7 +111,7 @@ function ChartTabAxis(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -129,7 +129,7 @@ function ChartTabAxis(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async () => {

View file

@ -78,7 +78,7 @@ function ChartTabPanes(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -107,7 +107,7 @@ function ChartTabPanes(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -125,7 +125,7 @@ function ChartTabPanes(props: FormEditProps) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async () => {

View file

@ -139,7 +139,7 @@ function ChartTabSeries(props: FormEditProps & { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaPlus />}
onClick={async (e) => {
@ -173,7 +173,7 @@ function ChartTabSeries(props: FormEditProps & { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -191,7 +191,7 @@ function ChartTabSeries(props: FormEditProps & { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async () => {

View file

@ -65,7 +65,7 @@ function FormCustomization({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -89,7 +89,7 @@ function FormCustomization({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -104,7 +104,7 @@ function FormCustomization({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async (e) => {

View file

@ -40,7 +40,7 @@ function FormTabCommands() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -75,7 +75,7 @@ function FormTabCommands() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -93,7 +93,7 @@ function FormTabCommands() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={async () => {

View file

@ -135,7 +135,7 @@ function FormTabDatabaseDelete({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -167,7 +167,7 @@ function FormTabDatabaseDelete({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -187,7 +187,7 @@ function FormTabDatabaseDelete({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -133,7 +133,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -165,7 +165,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -185,7 +185,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {
@ -232,7 +232,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -264,7 +264,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -283,7 +283,7 @@ function FormTabDatabaseInsert({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -88,7 +88,7 @@ function FormTabDatabaseSelect({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -120,7 +120,7 @@ function FormTabDatabaseSelect({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -140,7 +140,7 @@ function FormTabDatabaseSelect({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -135,7 +135,7 @@ function FormTabDatabaseUpdate({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -167,7 +167,7 @@ function FormTabDatabaseUpdate({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -187,7 +187,7 @@ function FormTabDatabaseUpdate({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -39,7 +39,7 @@ function FormTabEditForm(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -71,7 +71,7 @@ function FormTabEditForm(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -89,7 +89,7 @@ function FormTabEditForm(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -39,7 +39,7 @@ function FormTabExtraFilters(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -71,7 +71,7 @@ function FormTabExtraFilters(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -89,7 +89,7 @@ function FormTabExtraFilters(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -40,7 +40,7 @@ function FormTabSubForm() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -71,7 +71,7 @@ function FormTabSubForm() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -89,7 +89,7 @@ function FormTabSubForm() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -39,7 +39,7 @@ function FormTabWidgets(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={async (e) => {
@ -71,7 +71,7 @@ function FormTabWidgets(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={() => {
@ -89,7 +89,7 @@ function FormTabWidgets(props: { listFormCode: string }) {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrash />}
onClick={() => {

View file

@ -87,7 +87,7 @@ function FormFieldTabConditionalFormatting({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaPlusSquare />}
onClick={() => setItemIndex(-1)}
@ -109,7 +109,7 @@ function FormFieldTabConditionalFormatting({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -121,7 +121,7 @@ function FormFieldTabConditionalFormatting({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrashAlt />}
onClick={async (e) => {

View file

@ -101,7 +101,7 @@ function FormFieldTabValidationRules({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaPlusSquare />}
onClick={() => setItemIndex(-1)}
@ -125,7 +125,7 @@ function FormFieldTabValidationRules({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Edit"
icon={<FaEdit />}
onClick={async (e) => {
@ -137,7 +137,7 @@ function FormFieldTabValidationRules({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrashAlt />}
onClick={async (e) => {

View file

@ -404,7 +404,7 @@ function FormFields({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileMedical />}
onClick={() => {
@ -416,7 +416,7 @@ function FormFields({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Copy"
icon={<FaCopy />}
onClick={async (e) => {
@ -429,7 +429,7 @@ function FormFields({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Tüm Sütunları Ekle"
icon={<FaTable />}
loading={isAddingAllColumns}
@ -461,7 +461,7 @@ function FormFields({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaMinus />}
onClick={async (e) => {

View file

@ -420,7 +420,7 @@ function JsonRowOpDialogAxis({
shape="circle"
type="button"
className="mt-7"
size="xs"
size="sm"
icon={<FaMinus />}
onClick={() => remove(index)}
/>
@ -430,7 +430,7 @@ function JsonRowOpDialogAxis({
: null}
<div>
<Button
size="xs"
size="sm"
type="button"
onClick={() => {
push({

View file

@ -430,7 +430,7 @@ function JsonRowOpDialogEditForm({
shape="circle"
variant="solid"
type="button"
size="xs"
size="sm"
title="Options"
icon={<FaTag />}
onClick={() =>
@ -584,7 +584,7 @@ function JsonRowOpDialogEditForm({
shape="circle"
variant="solid"
type="button"
size="xs"
size="sm"
title="Options"
icon={<FaTag />}
onClick={() =>
@ -769,7 +769,7 @@ function JsonRowOpDialogEditForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarMinus />}
onClick={() => {
@ -780,7 +780,7 @@ function JsonRowOpDialogEditForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarPlus />}
onClick={() => {
@ -795,7 +795,7 @@ function JsonRowOpDialogEditForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarPlus />}
onClick={() => {

View file

@ -250,7 +250,7 @@ function JsonRowOpDialogSubForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarMinus />}
onClick={() => {
@ -261,7 +261,7 @@ function JsonRowOpDialogSubForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarPlus />}
onClick={() => {
@ -276,7 +276,7 @@ function JsonRowOpDialogSubForm({
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaCalendarPlus />}
onClick={() => {

View file

@ -122,7 +122,7 @@ const WizardFileManager = () => {
<div className="relative">
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
<Input
size="xs"
size="sm"
className="pl-6 w-44"
placeholder={translate('::App.Platform.Search') || 'Search...'}
value={search}
@ -130,7 +130,7 @@ const WizardFileManager = () => {
/>
</div>
<Button
size="xs"
size="sm"
variant="default"
title={translate('::App.Platform.Refresh') || 'Yenile'}
loading={loading}
@ -139,7 +139,7 @@ const WizardFileManager = () => {
<FaSync />
</Button>
<Button
size="xs"
size="sm"
variant="default"
icon={<FcAcceptDatabase />}
onClick={() => setShowDbMigrateDialog(true)}
@ -148,7 +148,7 @@ const WizardFileManager = () => {
{translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'}
</Button>
<Button
size="xs"
size="sm"
variant="solid"
onClick={() => navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard)}
className="flex items-center"

View file

@ -471,7 +471,7 @@ const OrganizationUnits = () => {
<div className="flex gap-1">
<Button
variant="solid"
size="xs"
size="sm"
title={translate('::Abp.Identity.OrganizationUnit.AddUnit')}
onClick={(e) => {
e.preventDefault()
@ -486,7 +486,7 @@ const OrganizationUnits = () => {
<Button
variant="solid"
size="xs"
size="sm"
title={translate('::Abp.Identity.OrganizationUnit.MoveAllUsers')}
onClick={(e) => {
e.preventDefault()
@ -576,7 +576,7 @@ const OrganizationUnits = () => {
<Button
className="mr-auto"
type="button"
size="xs"
size="sm"
onClick={() => {
setDeleteRow({
id: user.id ?? '',
@ -647,7 +647,7 @@ const OrganizationUnits = () => {
<Button
className="mr-auto"
type="button"
size="xs"
size="sm"
onClick={() => {
setDeleteRow({
id: role.id ?? '',

View file

@ -99,7 +99,7 @@ const NotificationSettings = () => {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Reset"
icon={<FaHistory />}
onClick={async (e) => {

View file

@ -232,7 +232,7 @@ function UserDetails() {
component={Input}
/>
</FormItem>
<FormItem size="xs" label={translate('::RocketUsername')}>
<FormItem size="sm" label={translate('::RocketUsername')}>
<Field
type="text"
name="rocketUsername"
@ -1340,7 +1340,7 @@ function UserDetails() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Add"
icon={<FaFileAlt />}
onClick={async (e) => {
@ -1362,7 +1362,7 @@ function UserDetails() {
shape="circle"
variant="plain"
type="button"
size="xs"
size="sm"
title="Delete"
icon={<FaTrashAlt />}
onClick={() => setConfirmDeleteClaim(claim)}

View file

@ -75,7 +75,7 @@ function BranchSeed({
))}
</div>
<div>
<Button size="xs" variant="solid" onClick={handleRunSeed} loading={isLoading}>
<Button size="sm" variant="solid" onClick={handleRunSeed} loading={isLoading}>
{isLoading ? 'Seed Çalıştırılıyor...' : 'Seed İşlemini Başlat'}
</Button>
</div>

View file

@ -908,17 +908,16 @@ GO`,
))}
</select>
<Button
size="xs"
size="sm"
variant="default"
icon={<FcAcceptDatabase />}
onClick={() => setShowDbMigrateConfirmDialog(true)}
className="shadow-sm px-2 py-1"
title={translate('::App.DbMigrate.StartMessage') || 'Run DB Migration'}
>
{translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'}
</Button>
<Button
size="xs"
size="sm"
variant="default"
icon={<FaFolderOpen />}
onClick={handleOpenSqlDataFilesDialog}
@ -931,7 +930,7 @@ GO`,
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
<Button
size="xs"
size="sm"
variant="default"
icon={<FaCopy />}
onClick={handleOpenCopyDialog}
@ -945,7 +944,7 @@ GO`,
{translate('::App.Platform.CopySelectedObjects') || 'Copy Selected Objects'}
</Button>
<Button
size="xs"
size="sm"
variant="default"
icon={<FaFileAlt />}
onClick={handleNewQuery}
@ -954,7 +953,7 @@ GO`,
{translate('::App.Platform.NewQuery') || 'New Query'}
</Button>
<Button
size="xs"
size="sm"
variant="solid"
color="blue-600"
icon={<FaPlay />}

View file

@ -1646,14 +1646,14 @@ const SqlTableDesignerDialog = ({
<>
<div className="flex items-center justify-between py-2">
<div className="flex items-center gap-2">
<Button size="xs" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
<Button size="sm" variant="solid" color="blue-600" onClick={addFullAuditedColumns}>
{translate('::App.SqlQueryManager.AddFullAuditedColumns')}
</Button>
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
<Button size="sm" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
</Button>
<Button
size="xs"
size="sm"
variant="solid"
color="amber-600"
onClick={importColumnsFromRememberedCreateTable}
@ -1664,7 +1664,7 @@ const SqlTableDesignerDialog = ({
</div>
<div className="flex items-center gap-2">
<Button
size="xs"
size="sm"
variant="solid"
color="red-600"
icon={<FaTrash />}
@ -1673,7 +1673,7 @@ const SqlTableDesignerDialog = ({
{translate('::App.SqlQueryManager.ClearAllColumns')}
</Button>
<Button
size="xs"
size="sm"
variant="solid"
color="blue-600"
icon={<FaPlus />}

View file

@ -141,7 +141,7 @@ const FormButtons = (props: {
<Button
key={'toolbarButton-' + i}
variant="default"
size="xs"
size="sm"
icon={hasValidIcon ? <IconComp className="text-gray-400" /> : null}
onClick={item.options?.onClick}
>
@ -161,7 +161,7 @@ const FormButtons = (props: {
<Button
key={'commandColumnButton-' + i}
variant="default"
size="xs"
size="sm"
title={item.hint}
onClick={(e: any) => {
if (item.onClick) {
@ -180,7 +180,7 @@ const FormButtons = (props: {
{!isSubForm && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaChevronLeft />}
@ -197,7 +197,7 @@ const FormButtons = (props: {
)}
{mode != 'new' && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaTrashAlt />}
@ -211,7 +211,7 @@ const FormButtons = (props: {
)}
{mode != 'new' && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaPlusCircle />}
@ -228,7 +228,7 @@ const FormButtons = (props: {
)}
{mode == 'view' && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaPencilAlt />}
@ -249,7 +249,7 @@ const FormButtons = (props: {
)}
{(mode == 'edit' || (onActionView && mode == 'new')) && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaEye />}
@ -270,7 +270,7 @@ const FormButtons = (props: {
)}
{(mode == 'edit' || mode == 'new') && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaSave />}
@ -281,7 +281,7 @@ const FormButtons = (props: {
)}
{checkPermission(gridDto?.gridOptions.permissionDto.c) && !isSubForm && (
<Button
size="xs"
size="sm"
variant="default"
className="dx-button dx-button-mode-contained dx-button-normal"
icon={<FaCog />}

View file

@ -345,7 +345,7 @@ export const NoteList: React.FC<NoteListProps> = ({
<div className="flex items-center justify-end mb-2">
<Button
variant="solid"
size="xs"
size="sm"
type="button"
onClick={onAddNote}
disabled={!onAddNote}
@ -400,7 +400,7 @@ export const NoteList: React.FC<NoteListProps> = ({
{user?.id === note.creatorId && (
<Button
variant="plain"
size="xs"
size="sm"
onClick={() => onDeleteNote?.(note.id as string)}
title={translate('::Delete')}
className="text-red-400 hover:text-red-600"
@ -430,7 +430,7 @@ export const NoteList: React.FC<NoteListProps> = ({
<span className="truncate max-w-[150px]">{file.FileName}</span>
<Button
variant="plain"
size="xs"
size="sm"
onClick={() => onDownloadFile?.(file)}
title={translate('::Download')}
className="text-blue-500 hover:text-blue-700 ml-1"
@ -452,7 +452,7 @@ export const NoteList: React.FC<NoteListProps> = ({
<TabContent value="audit">
<div className="flex items-center justify-end mb-2">
<Button
size="xs"
size="sm"
variant="default"
type="button"
onClick={loadAuditLogs}

View file

@ -245,7 +245,7 @@ export const NoteModal: React.FC<NoteModalProps> = ({
</div>
<Button
variant="plain"
size="xs"
size="sm"
type="button"
onClick={() => removeFile(index)}
className="text-red-500 hover:text-red-700"

View file

@ -164,7 +164,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
</div>
<div className="flex items-center gap-3">
<Button variant="plain" size="xs" onClick={onToggle} title="Paneli kapat">
<Button variant="plain" size="sm" onClick={onToggle} title="Paneli kapat">
<FaTimes />
</Button>
</div>

View file

@ -0,0 +1,625 @@
import React, { useState, useEffect } from 'react'
import { AnimatePresence } from 'framer-motion'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import isBetween from 'dayjs/plugin/isBetween'
import localizedFormat from 'dayjs/plugin/localizedFormat'
// Widgets
import TodayBirthdays from './widgets/TodayBirthdays'
import RecentDocuments from './widgets/RecentDocuments'
import Surveys from './widgets/Surveys'
// Modals
import SurveyModal from './widgets/SurveyModal'
import AnnouncementModal from './widgets/AnnouncementModal'
// Social Wall
import SocialWall from './SocialWall'
import { Container } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission'
import {
AnnouncementDto,
IntranetDashboardDto,
SurveyAnswerDto,
SurveyDto,
} from '@/proxy/intranet/models'
import { intranetService } from '@/services/intranet.service'
import Announcements from './widgets/Announcements'
import { useLocalization } from '@/utils/hooks/useLocalization'
import useLocale from '@/utils/hooks/useLocale'
import { currentLocalDate } from '@/utils/dateUtils'
import { useStoreState } from '@/store/store'
dayjs.extend(relativeTime)
dayjs.extend(isBetween)
dayjs.extend(localizedFormat)
const WIDGET_ORDER_KEY = 'dashboard-widget-order'
const IntranetDashboard: React.FC = () => {
const { checkPermission } = usePermission()
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null)
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
const [showSurveyModal, setShowSurveyModal] = useState(false)
const [isDesignMode, setIsDesignMode] = useState(false)
const [widgetOrder, setWidgetOrder] = useState<Record<string, string[]>>({
left: [],
center: [],
right: [],
})
const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>()
const { translate } = useLocalization()
const currentLocale = useLocale()
const fetchIntranetDashboard = async () => {
const dashboard = await intranetService.getDashboard()
if (dashboard.data) {
setIntranetDashboard(dashboard.data)
}
}
useEffect(() => {
fetchIntranetDashboard()
}, [])
// Drag state'leri birleştirildi
const [dragState, setDragState] = useState<{
draggedId: string | null
targetColumn: string | null
targetIndex: number | null
}>({
draggedId: null,
targetColumn: null,
targetIndex: null,
})
const handleTakeSurvey = (survey: SurveyDto) => {
setSelectedSurvey(survey)
setShowSurveyModal(true)
}
const handleSubmitSurvey = (answers: SurveyAnswerDto[]) => {
setShowSurveyModal(false)
setSelectedSurvey(null)
}
// Widget metadata (component'lar yerine sadece meta bilgiler)
const widgetMetadata = [
{ id: 'today-birthdays', permission: 'AbpIdentity.Users.Widget', column: 'left' },
{ id: 'documents', permission: 'App.Files.Widget', column: 'left' },
{ id: 'active-surveys', permission: 'App.Intranet.Survey.Widget', column: 'left' },
{ id: 'social-wall', permission: 'App.Intranet.SocialPost.Widget', column: 'center' },
{
id: 'announcements',
permission: 'App.Intranet.Announcement.Widget',
column: 'right',
},
]
// If permissions arrive after mount, initialize default order when needed
const grantedPolicies = useStoreState((state) => state.abpConfig?.config?.auth.grantedPolicies)
const initializeDefaultOrder = () => {
const defaultOrder = {
left: widgetMetadata
.filter((w) => w.column === 'left' && checkPermission(w.permission))
.map((w) => w.id),
center: widgetMetadata
.filter((w) => w.column === 'center' && checkPermission(w.permission))
.map((w) => w.id),
right: widgetMetadata
.filter((w) => w.column === 'right' && checkPermission(w.permission))
.map((w) => w.id),
}
setWidgetOrder(defaultOrder)
}
// Widget sıralamasını yükle — grantedPolicies gelince çalış ki checkPermission doğru çalışsın
useEffect(() => {
if (!grantedPolicies) return
const savedOrder = localStorage.getItem(WIDGET_ORDER_KEY)
if (savedOrder) {
try {
const parsed = JSON.parse(savedOrder) as Record<string, unknown[]>
const order: Record<string, string[]> = {
left: [...new Set((parsed.left || []) as string[])],
center: [...new Set((parsed.center || []) as string[])],
right: [...new Set((parsed.right || []) as string[])],
}
// Kaydedilmiş düzende olmayan ama izni olan widgetları varsayılan kolonuna ekle
const allAssigned = new Set([...order.left, ...order.center, ...order.right])
widgetMetadata.forEach((w) => {
if (!allAssigned.has(w.id) && checkPermission(w.permission)) {
order[w.column as keyof typeof order].push(w.id)
}
})
setWidgetOrder(order)
} catch (error) {
console.error('Widget order parse error:', error)
initializeDefaultOrder()
}
} else {
initializeDefaultOrder()
}
}, [grantedPolicies])
// Widget sıralamasını kaydet
const saveWidgetOrder = (newOrder: Record<string, string[]>) => {
setWidgetOrder(newOrder)
localStorage.setItem(WIDGET_ORDER_KEY, JSON.stringify(newOrder))
}
const handleDragStart = (e: React.DragEvent, widgetId: string, column: string) => {
setDragState({ draggedId: widgetId, targetColumn: null, targetIndex: null })
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('widgetId', widgetId)
e.dataTransfer.setData('sourceColumn', column)
}
const handleDragEnterWidget = (e: React.DragEvent, column: string, index: number) => {
// Sadece widget'ın üst kısmına yakınsa indicator göster
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
const mouseY = e.clientY
const widgetTop = rect.top
const widgetHeight = rect.height
const threshold = widgetHeight * 0.3 // Üst %30'luk alan
if (mouseY - widgetTop < threshold) {
// Üst kısma yakın - indicator göster
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: index }))
} else {
// Widget'ın ortasında veya altında - indicator gösterme
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null }))
}
}
const handleDragEnterColumn = (column: string) => {
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null }))
}
const handleDragLeaveColumn = () => {
setDragState((prev) => ({ ...prev, targetColumn: null, targetIndex: null }))
}
const handleDrop = (e: React.DragEvent, targetColumn: string, targetIndex?: number) => {
e.preventDefault()
e.stopPropagation()
const widgetId = e.dataTransfer.getData('widgetId')
const sourceColumn = e.dataTransfer.getData('sourceColumn')
if (!widgetId || !sourceColumn) return
const newOrder = { ...widgetOrder }
// ÖNCE tüm kolonlardan bu widget'ı kaldır (duplicate önleme)
Object.keys(newOrder).forEach((col) => {
newOrder[col] = newOrder[col].filter((id) => id !== widgetId)
})
// SONRA hedef kolona ekle
if (targetIndex !== undefined) {
newOrder[targetColumn].splice(targetIndex, 0, widgetId)
} else {
newOrder[targetColumn].push(widgetId)
}
// Duplicate'leri temizle
Object.keys(newOrder).forEach((col) => {
newOrder[col] = [...new Set(newOrder[col])]
})
saveWidgetOrder(newOrder)
setDragState({ draggedId: null, targetColumn: null, targetIndex: null })
}
const handleDragEnd = () => {
setDragState({ draggedId: null, targetColumn: null, targetIndex: null })
}
// Widget component'ını render et
const renderWidgetComponent = (widgetId: string) => {
switch (widgetId) {
case 'today-birthdays':
return <TodayBirthdays employees={intranetDashboard?.birthdays || []} />
case 'documents':
return <RecentDocuments documents={intranetDashboard?.documents || []} />
case 'announcements':
return (
<Announcements
announcements={intranetDashboard?.announcements || []}
onAnnouncementClick={setSelectedAnnouncement}
/>
)
case 'active-surveys':
return (
<Surveys
surveys={intranetDashboard?.surveys || []}
onTakeSurvey={handleTakeSurvey}
/>
)
case 'social-wall':
return <SocialWall posts={intranetDashboard?.socialPosts || []} />
default:
return null
}
}
// Widget'ları render et
const renderWidgets = (column: 'left' | 'center' | 'right') => {
const columnWidgets = widgetOrder[column] || []
// Duplicate'leri filtrele
const uniqueWidgets = [...new Set(columnWidgets)]
return uniqueWidgets
.map((widgetId, index) => {
const metadata = widgetMetadata.find((w) => w.id === widgetId)
if (!metadata || !checkPermission(metadata.permission)) return null
const isDragging = dragState.draggedId === widgetId
const isDropTarget = dragState.targetColumn === column && dragState.targetIndex === index
return (
<div key={`${column}-${widgetId}-${index}`} className="relative group">
{/* Drop indicator - SADECE widget'ların arasına (üst %30'luk alana) gelince göster */}
{isDesignMode && isDropTarget && !isDragging && (
<div className="absolute -top-5 left-0 right-0 z-20 animate-in fade-in slide-in-from-top-2 duration-300">
{/* Çizgi */}
<div className="h-2 bg-gradient-to-r from-transparent via-blue-500 to-transparent rounded-full shadow-lg" />
{/* Badge */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-xs px-4 py-2 rounded-full whitespace-nowrap shadow-xl font-semibold flex items-center gap-2 border-2 border-white dark:border-gray-800">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 14l-7 7m0 0l-7-7m7 7V3"
/>
</svg>
<span>Buraya Bırak</span>
</div>
</div>
)}
<div
draggable={isDesignMode}
onDragStart={(e) => {
if (isDesignMode) {
handleDragStart(e, widgetId, column)
// Drag ghost image'i gizle
const ghost = document.createElement('div')
ghost.style.opacity = '0'
e.dataTransfer.setDragImage(ghost, 0, 0)
}
}}
onDragOver={(e) => {
if (!isDesignMode) return
e.preventDefault()
e.stopPropagation()
// Throttle: Sadece düzenli aralıklarla güncelle
const now = Date.now()
if (
!e.currentTarget.dataset.lastUpdate ||
now - parseInt(e.currentTarget.dataset.lastUpdate) > 150
) {
e.currentTarget.dataset.lastUpdate = now.toString()
handleDragEnterWidget(e, column, index)
}
}}
onDragLeave={(e) => {
// Widget'tan çıkınca indicator'ı kaldır
if (isDesignMode) {
setDragState((prev) => ({
...prev,
targetColumn: prev.targetColumn,
targetIndex: null,
}))
}
}}
onDrop={(e) => {
if (!isDesignMode) return
e.stopPropagation()
// Drop pozisyonunu hesapla
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
const mouseY = e.clientY
const widgetTop = rect.top
const widgetHeight = rect.height
const threshold = widgetHeight * 0.3
if (mouseY - widgetTop < threshold) {
// Üst kısma bırak - mevcut index'e ekle
handleDrop(e, column, index)
} else {
// Alt kısma bırak - sonraki index'e ekle
handleDrop(e, column, index + 1)
}
}}
onDragEnd={handleDragEnd}
className={`
relative
${
isDesignMode
? `border-2 border-dashed rounded-lg cursor-move
${
isDragging
? 'border-blue-400 opacity-70 bg-blue-50/30 dark:bg-blue-900/10'
: 'border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:shadow-md'
}
transition-all duration-300 ease-out`
: 'border-0 transition-none'
}
`}
style={{
touchAction: 'none',
transition:
'border-color 0.3s ease-out, opacity 0.3s ease-out, box-shadow 0.3s ease-out',
willChange: isDragging ? 'opacity' : 'auto',
}}
>
{/* Dragging overlay - daha minimal */}
{isDesignMode && isDragging && (
<div className="absolute inset-0 bg-white/60 dark:bg-gray-900/40 rounded-lg z-10 flex items-center justify-center backdrop-blur-[1px] transition-opacity duration-300">
<div className="bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg font-medium shadow-lg flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
/>
</svg>
<span>Taşınıyor</span>
</div>
</div>
)}
<div
className={`${isDesignMode ? 'p-1.5' : ''} transition-all duration-500 ease-out`}
>
{renderWidgetComponent(widgetId)}
</div>
</div>
</div>
)
})
.filter(Boolean)
}
return (
<Container>
<div className="mx-auto space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
{/* Design Mode Toggle */}
<div className="flex items-center gap-2">
<label
htmlFor="design-mode-toggle"
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
>
🎨 {translate('::App.Platform.Intranet.Dashboard.DesignMode')}
</label>
<button
id="design-mode-toggle"
onClick={() => setIsDesignMode(!isDesignMode)}
className={`
relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
${isDesignMode ? 'bg-blue-600' : 'bg-gray-300 dark:bg-gray-600'}
`}
role="switch"
aria-checked={isDesignMode}
>
<span
className={`
inline-block h-4 w-4 transform rounded-full bg-white transition-transform
${isDesignMode ? 'translate-x-6' : 'translate-x-1'}
`}
/>
</button>
</div>
{/* Reset Button - Sadece design mode aktifken görünsün */}
{isDesignMode && (
<button
onClick={() => {
localStorage.removeItem(WIDGET_ORDER_KEY)
initializeDefaultOrder()
}}
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors flex items-center gap-1"
title="Widget düzenini varsayılana döndür"
>
🔄 {translate('::App.Platform.Intranet.Dashboard.Reset')}
</button>
)}
</div>
<div>
<p className="text-gray-600 dark:text-gray-400 mt-1">
<span className="font-medium">{translate('::AI.Welcome')},</span>{' '}
{currentLocalDate(new Date(), currentLocale || 'tr')}
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12">
<div
className={`lg:col-span-3 space-y-6 min-h-[100px] rounded-xl p-1
${
isDesignMode && dragState.targetColumn === 'left' && dragState.targetIndex === null
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
: 'bg-transparent'
}
transition-all duration-700 ease-out
`}
onDragOver={(e) => {
if (!isDesignMode) return
e.preventDefault()
// Throttle: Sadece her 150ms'de bir güncelle
const now = Date.now()
const target = e.currentTarget as HTMLElement
if (
!target.dataset.lastColumnUpdate ||
now - parseInt(target.dataset.lastColumnUpdate) > 150
) {
target.dataset.lastColumnUpdate = now.toString()
handleDragEnterColumn('left')
}
}}
onDragLeave={() => {
if (isDesignMode) handleDragLeaveColumn()
}}
onDrop={(e) => {
if (!isDesignMode) return
e.stopPropagation()
const columnWidgets = widgetOrder['left'] || []
handleDrop(e, 'left', columnWidgets.length)
}}
>
{renderWidgets('left')}
{isDesignMode &&
dragState.targetColumn === 'left' &&
widgetOrder['left']?.length === 0 && (
<div className="flex items-center justify-center h-40 border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
<span>Widget'ı buraya bırakın</span>
</p>
</div>
)}
</div>
<div
className={`lg:col-span-6 space-y-6 min-h-[100px] rounded-xl p-1
${
isDesignMode &&
dragState.targetColumn === 'center' &&
dragState.targetIndex === null
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
: 'bg-transparent'
}
transition-all duration-700 ease-out
`}
onDragOver={(e) => {
if (!isDesignMode) return
e.preventDefault()
const now = Date.now()
const target = e.currentTarget as HTMLElement
if (
!target.dataset.lastColumnUpdate ||
now - parseInt(target.dataset.lastColumnUpdate) > 150
) {
target.dataset.lastColumnUpdate = now.toString()
handleDragEnterColumn('center')
}
}}
onDragLeave={() => {
if (isDesignMode) handleDragLeaveColumn()
}}
onDrop={(e) => {
if (!isDesignMode) return
e.stopPropagation()
const columnWidgets = widgetOrder['center'] || []
handleDrop(e, 'center', columnWidgets.length)
}}
>
{renderWidgets('center')}
{isDesignMode &&
dragState.targetColumn === 'center' &&
widgetOrder['center']?.length === 0 && (
<div className="flex items-center justify-center rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
<span>Widget'ı buraya bırakın</span>
</p>
</div>
)}
</div>
<div
className={`lg:col-span-3 space-y-6 min-h-[100px] rounded-xl p-1
${
isDesignMode && dragState.targetColumn === 'right' && dragState.targetIndex === null
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
: 'bg-transparent'
}
transition-all duration-700 ease-out
`}
onDragOver={(e) => {
if (!isDesignMode) return
e.preventDefault()
const now = Date.now()
const target = e.currentTarget as HTMLElement
if (
!target.dataset.lastColumnUpdate ||
now - parseInt(target.dataset.lastColumnUpdate) > 150
) {
target.dataset.lastColumnUpdate = now.toString()
handleDragEnterColumn('right')
}
}}
onDragLeave={() => {
if (isDesignMode) handleDragLeaveColumn()
}}
onDrop={(e) => {
if (!isDesignMode) return
e.stopPropagation()
const columnWidgets = widgetOrder['right'] || []
handleDrop(e, 'right', columnWidgets.length)
}}
>
{renderWidgets('right')}
{isDesignMode &&
dragState.targetColumn === 'right' &&
widgetOrder['right']?.length === 0 && (
<div className="flex items-center justify-center h-40 border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
<span>Widget'ı buraya bırakın</span>
</p>
</div>
)}
</div>
</div>
</div>
<AnimatePresence>
{showSurveyModal && selectedSurvey && (
<SurveyModal
survey={selectedSurvey}
onClose={() => setShowSurveyModal(false)}
onSubmit={handleSubmitSurvey}
/>
)}
</AnimatePresence>
<AnimatePresence>
{selectedAnnouncement && (
<AnnouncementModal
announcement={selectedAnnouncement}
onClose={() => setSelectedAnnouncement(null)}
/>
)}
</AnimatePresence>
</Container>
)
}
export default IntranetDashboard

View file

@ -0,0 +1,466 @@
import React, { useState, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import classNames from 'classnames'
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
import {
FaChartBar,
FaSmile,
FaTimes,
FaImages,
FaMapMarkerAlt
} from 'react-icons/fa'
import MediaManager from './MediaManager'
import LocationPicker from './LocationPicker'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface CreatePostProps {
onCreatePost: (post: {
content: string
location?: string
media?: {
type: 'mixed' | 'poll'
mediaItems?: SocialMediaDto[]
poll?: {
question: string
options: Array<{ text: string }>
}
}
}) => void
}
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store/store'
import { Avatar } from '@/components/ui'
import { AVATAR_URL } from '@/constants/app.constant'
const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
const { translate } = useLocalization();
const [content, setContent] = useState('')
const [mediaType, setMediaType] = useState<'media' | 'poll' | null>(null)
const [mediaItems, setMediaItems] = useState<SocialMediaDto[]>([])
const [location, setLocation] = useState<string | null>(null)
const [pollQuestion, setPollQuestion] = useState('')
const [pollOptions, setPollOptions] = useState(['', ''])
const [isExpanded, setIsExpanded] = useState(false)
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [showMediaManager, setShowMediaManager] = useState(false)
const [showLocationPicker, setShowLocationPicker] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const emojiPickerRef = useRef<HTMLDivElement>(null)
const { user, tenant } = useStoreState((state) => state.auth)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!content.trim() && mediaItems.length === 0 && !mediaType) return
let media = undefined
if (mediaType === 'media' && mediaItems.length > 0) {
media = {
type: 'mixed' as const,
mediaItems
}
} else if (mediaType === 'poll' && pollQuestion && pollOptions.filter(o => o.trim()).length >= 2) {
media = {
type: 'poll' as const,
poll: {
question: pollQuestion,
options: pollOptions.filter(o => o.trim()).map(text => ({ text }))
}
}
}
onCreatePost({
content,
media,
location: location || undefined
})
// Reset form
setContent('')
setMediaType(null)
setMediaItems([])
setLocation(null)
setPollQuestion('')
setPollOptions(['', ''])
setIsExpanded(false)
setShowEmojiPicker(false)
}
const handleEmojiClick = (emojiData: EmojiClickData) => {
const emoji = emojiData.emoji
const textarea = textareaRef.current
if (!textarea) return
const start = textarea.selectionStart
const end = textarea.selectionEnd
const text = content
const before = text.substring(0, start)
const after = text.substring(end)
setContent(before + emoji + after)
// Set cursor position after emoji
setTimeout(() => {
textarea.selectionStart = textarea.selectionEnd = start + emoji.length
textarea.focus()
}, 0)
}
const addPollOption = () => {
if (pollOptions.length < 6) {
setPollOptions([...pollOptions, ''])
}
}
const removePollOption = (index: number) => {
if (pollOptions.length > 2) {
setPollOptions(pollOptions.filter((_, i) => i !== index))
}
}
const updatePollOption = (index: number, value: string) => {
const newOptions = [...pollOptions]
newOptions[index] = value
setPollOptions(newOptions)
}
const clearMedia = () => {
setMediaType(null)
setMediaItems([])
setPollQuestion('')
setPollOptions(['', ''])
}
const removeMediaItem = (id: string | undefined) => {
if (!id) return
setMediaItems(mediaItems.filter((m) => m.id !== id))
}
// Close emoji picker when clicking outside
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target as Node)) {
setShowEmojiPicker(false)
}
}
if (showEmojiPicker) {
document.addEventListener('mousedown', handleClickOutside)
}
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [showEmojiPicker])
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 mb-6">
<form onSubmit={handleSubmit}>
{/* Text Input */}
<div className="flex gap-3 mb-4">
<Avatar size={32} shape="circle" src={AVATAR_URL(user.id, tenant.tenantId)} />
<div className="flex-1">
<textarea
ref={textareaRef}
value={content}
onChange={(e) => setContent(e.target.value)}
onFocus={() => setIsExpanded(true)}
placeholder={translate('::App.Platform.Intranet.SocialWall.CreatePost.Placeholder')}
className={classNames(
'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none transition-all',
isExpanded ? 'min-h-[120px]' : 'min-h-[48px]'
)}
rows={isExpanded ? 4 : 1}
/>
</div>
</div>
{/* Media Preview */}
<AnimatePresence>
{mediaType === 'media' && mediaItems.length > 0 && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="mb-4"
>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">
{translate('::App.Platform.Intranet.SocialWall.CreatePost.MediaTitle')} ({mediaItems.length})
</h4>
<button
type="button"
onClick={() => {
clearMedia()
}}
className="text-sm text-red-600 hover:text-red-700 font-medium"
title={translate('::App.Platform.Intranet.SocialWall.CreatePost.RemoveAllMediaTitle')}
>
{translate('::Cancel')}
</button>
</div>
<div className="grid grid-cols-4 gap-2">
{mediaItems.map((item) => (
<div key={item.id} className="relative group">
{item.type === 'image' ? (
<img
src={item.urls?.[0]}
alt="Preview"
className="w-full h-24 object-cover rounded-lg"
/>
) : (
<div className="w-full h-24 bg-gray-900 rounded-lg relative">
<video src={item.urls?.[0]} className="w-full h-full object-cover rounded-lg" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-10 h-10 bg-black bg-opacity-50 rounded-full flex items-center justify-center">
<div className="w-0 h-0 border-t-8 border-t-transparent border-l-12 border-l-white border-b-8 border-b-transparent ml-1"></div>
</div>
</div>
</div>
)}
<button
type="button"
onClick={() => removeMediaItem(item.id)}
className="absolute -top-2 -right-2 p-1 bg-red-600 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
>
<FaTimes className="w-4 h-4" />
</button>
<div className="absolute bottom-1 left-1 px-2 py-0.5 bg-black bg-opacity-70 text-white text-xs rounded">
{item.type === 'image' ? '📷' : '🎥'}
</div>
</div>
))}
<button
type="button"
onClick={() => setShowMediaManager(true)}
className="h-24 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg flex items-center justify-center hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
<FaImages className="w-6 h-6 text-gray-400" />
</button>
</div>
</motion.div>
)}
{location && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="mb-4"
>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">{translate('::App.Platform.Intranet.SocialWall.CreatePost.Location')}</h4>
<button
type="button"
onClick={() => setLocation(null)}
className="text-sm text-red-600 hover:text-red-700 font-medium"
title={translate('::App.Platform.Intranet.SocialWall.CreatePost.RemoveLocationTitle')}
>
{translate('::Cancel')}
</button>
</div>
<div className="p-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-50 dark:bg-gray-700">
<div className="flex items-start gap-2">
<FaMapMarkerAlt className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
{JSON.parse(location).name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{JSON.parse(location).address}
</p>
</div>
</div>
</div>
</motion.div>
)}
{mediaType === 'poll' && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="mb-4"
>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">{translate('::App.Platform.Intranet.SocialWall.CreatePost.Poll')}</h4>
<button
type="button"
onClick={() => {
clearMedia()
}}
className="text-sm text-red-600 hover:text-red-700 font-medium"
title={translate('::App.Platform.Intranet.SocialWall.CreatePost.RemovePollTitle')}
>
{translate('::Cancel')}
</button>
</div>
<input
type="text"
value={pollQuestion}
onChange={(e) => setPollQuestion(e.target.value)}
placeholder={translate('::App.Platform.Intranet.SocialWall.CreatePost.PollQuestionPlaceholder')}
className="w-full px-4 py-2 mb-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<div className="space-y-2">
{pollOptions.map((option, index) => (
<div key={index} className="flex gap-2">
<input
type="text"
value={option}
onChange={(e) => updatePollOption(index, e.target.value)}
placeholder={translate('::App.Platform.Intranet.SocialWall.CreatePost.PollOptionPlaceholder')}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{pollOptions.length > 2 && (
<button
type="button"
onClick={() => removePollOption(index)}
className="p-2 text-gray-500 hover:text-red-600"
>
<FaTimes className="w-5 h-5" />
</button>
)}
</div>
))}
</div>
{pollOptions.length < 6 && (
<button
type="button"
onClick={addPollOption}
className="mt-2 text-sm text-blue-600 hover:text-blue-700 font-medium"
>
+ {translate('::App.Platform.Intranet.SocialWall.CreatePost.AddOption')}
</button>
)}
</motion.div>
)}
</AnimatePresence>
{/* Actions */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-700"
>
<div className="flex gap-2 relative">
<button
type="button"
onClick={() => {
if (mediaType === 'media' && mediaItems.length > 0) {
// Eğer zaten medya varsa, yöneticiyi aç
setShowMediaManager(true)
} else {
// Başka bir tip seçiliyse temizle ve medya modunu aç
clearMedia()
setMediaType('media')
setShowMediaManager(true)
}
}}
className={classNames(
'p-2 rounded-full transition-colors',
mediaType === 'media'
? 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-400'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700'
)}
title={mediaType === 'media' ? translate('::App.Platform.Intranet.SocialWall.CreatePost.EditMediaTitle') : translate('::App.Platform.Intranet.SocialWall.CreatePost.AddMediaTitle')}
>
<FaImages className="w-5 h-5" />
{mediaType === 'media' && mediaItems.length > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-blue-600 text-white text-xs rounded-full flex items-center justify-center">
{mediaItems.length}
</span>
)}
</button>
<button
type="button"
onClick={() => {
// Başka bir tip seçiliyse temizle
if (mediaType !== 'poll') {
clearMedia()
}
setMediaType(mediaType === 'poll' ? null : 'poll')
}}
className={classNames(
'p-2 rounded-full transition-colors',
mediaType === 'poll'
? 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-400'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700'
)}
title={mediaType === 'poll' ? translate('::App.Platform.Intranet.SocialWall.CreatePost.RemovePollTitle') : translate('::App.Platform.Intranet.SocialWall.CreatePost.AddPollTitle')}
>
<FaChartBar className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
className="p-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors"
title={translate('::App.Platform.Intranet.SocialWall.CreatePost.AddEmojiTitle')}
>
<FaSmile className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => setShowLocationPicker(true)}
className={classNames(
'p-2 rounded-full transition-colors',
location
? 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-400'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700'
)}
title={location ? translate('::App.Platform.Intranet.SocialWall.CreatePost.EditLocationTitle') : translate('::App.Platform.Intranet.SocialWall.CreatePost.AddLocationTitle')}
>
<FaMapMarkerAlt className="w-5 h-5" />
</button>
{/* Emoji Picker */}
{showEmojiPicker && (
<div ref={emojiPickerRef} className="absolute bottom-12 left-0 z-50">
<EmojiPicker
onEmojiClick={handleEmojiClick}
autoFocusSearch={false}
/>
</div>
)}
</div>
<button
type="submit"
disabled={!content.trim() && mediaItems.length === 0 && !mediaType}
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-full hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{translate('::App.Platform.Intranet.SocialWall.CreatePost.Submit')}
</button>
</motion.div>
)}
</AnimatePresence>
</form>
{/* Media Manager Modal */}
<AnimatePresence>
{showMediaManager && (
<MediaManager
media={mediaItems}
onChange={setMediaItems}
onClose={() => setShowMediaManager(false)}
/>
)}
</AnimatePresence>
{/* Location Picker Modal */}
<AnimatePresence>
{showLocationPicker && (
<LocationPicker
onSelect={setLocation}
onClose={() => setShowLocationPicker(false)}
/>
)}
</AnimatePresence>
</div>
)
}
export default CreatePost

View file

@ -0,0 +1,109 @@
import React from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { FaExternalLinkAlt, FaMapMarkerAlt } from 'react-icons/fa'
interface LocationData {
id: string
name: string
address: string
lat: number
lng: number
placeId?: string
}
interface LocationMapProps {
location: string // JSON string
className?: string
showDirections?: boolean
}
const LocationMap: React.FC<LocationMapProps> = ({
location,
className = '',
showDirections = true
}) => {
const locationData: LocationData = JSON.parse(location)
const handleOpenGoogleMaps = () => {
const url = `https://www.google.com/maps/search/?api=1&query=${locationData.lat},${locationData.lng}&query_place_id=${locationData.placeId || ''}`
window.open(url, '_blank')
}
// Google Maps Static API URL (gerçek uygulamada API key eklenecek)
const getMapImageUrl = () => {
const { lat, lng } = locationData
const zoom = 15
const size = '600x300'
const marker = `color:red|${lat},${lng}`
// Production'da gerçek API key kullanılacak
// const apiKey = 'YOUR_GOOGLE_MAPS_API_KEY'
// return `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=${zoom}&size=${size}&markers=${marker}&key=${apiKey}`
// Demo için OpenStreetMap kullanıyoruz
return `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 0.01},${lat - 0.01},${lng + 0.01},${lat + 0.01}&layer=mapnik&marker=${lat},${lng}`
}
const { translate } = useLocalization();
return (
<div className={`relative rounded-lg overflow-hidden bg-gray-200 dark:bg-gray-700 ${className}`}>
{/* Map Container */}
<div className="relative w-full h-64 group">
{/* OpenStreetMap iframe for demo */}
<iframe
title={`Map of ${locationData.name}`}
src={getMapImageUrl()}
className="w-full h-full border-0"
allowFullScreen
loading="lazy"
/>
{/* Overlay with location info */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
{/* Location Info */}
<div className="absolute bottom-0 left-0 right-0 p-4 text-white pointer-events-none">
<div className="flex items-start gap-2">
<FaMapMarkerAlt className="w-5 h-5 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<h3 className="font-bold text-lg mb-1 drop-shadow-lg">{locationData.name}</h3>
<p className="text-sm text-white/90 drop-shadow-md line-clamp-2">
{locationData.address}
</p>
</div>
</div>
</div>
{/* Click to open overlay - invisible but clickable */}
<button
onClick={handleOpenGoogleMaps}
className="absolute inset-0 w-full h-full cursor-pointer group"
aria-label={translate('::App.Platform.Intranet.SocialWall.LocationMap.OpenInGoogleMaps')}
>
<span className="sr-only">{translate('::App.Platform.Intranet.SocialWall.LocationMap.OpenInGoogleMaps')}</span>
</button>
{/* Hover Effect */}
<div className="absolute inset-0 bg-blue-600/0 group-hover:bg-blue-600/10 transition-colors duration-200" />
</div>
{/* Directions Button */}
{showDirections && (
<div className="p-3 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
<button
onClick={handleOpenGoogleMaps}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
>
<FaExternalLinkAlt className="w-5 h-5" />
<span>{translate('::App.Platform.Intranet.SocialWall.LocationMap.OpenInGoogleMaps')}</span>
</button>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2 text-center">
{translate('::App.Platform.Intranet.SocialWall.LocationMap.ClickForDirections')}
</p>
</div>
)}
</div>
)
}
export default LocationMap

View file

@ -0,0 +1,388 @@
import React, { useState, useEffect, useRef } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { motion } from 'framer-motion'
import { FaTimes, FaSearch, FaMapMarkerAlt } from 'react-icons/fa'
import classNames from 'classnames'
interface LocationPickerProps {
onSelect: (location: string) => void
onClose: () => void
}
interface LocationData {
id: string
name: string
address: string
lat: number
lng: number
placeId?: string
}
// Google Maps API key - .env dosyasından alınmalı
const GOOGLE_API_KEY = import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ''
declare global {
interface Window {
google: any
initGoogleMaps?: () => void
}
}
const LocationPicker: React.FC<LocationPickerProps> = ({ onSelect, onClose }) => {
const { translate } = useLocalization();
const [searchQuery, setSearchQuery] = useState('')
const [locations, setLocations] = useState<LocationData[]>([])
const [selectedLocation, setSelectedLocation] = useState<LocationData | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [isGoogleLoaded, setIsGoogleLoaded] = useState(false)
const searchInputRef = useRef<HTMLInputElement>(null)
const autocompleteServiceRef = useRef<any>(null)
const placesServiceRef = useRef<any>(null)
const debounceTimerRef = useRef<NodeJS.Timeout>()
const scriptLoadedRef = useRef(false)
// Google Maps SDK'yı yükle
useEffect(() => {
if (scriptLoadedRef.current) return
const loadGoogleMaps = () => {
if (window.google && window.google.maps && window.google.maps.places) {
setIsGoogleLoaded(true)
autocompleteServiceRef.current = new window.google.maps.places.AutocompleteService()
const mapDiv = document.createElement('div')
const map = new window.google.maps.Map(mapDiv)
placesServiceRef.current = new window.google.maps.places.PlacesService(map)
return
}
if (!GOOGLE_API_KEY) {
setError(translate('::App.Platform.Intranet.SocialWall.LocationPicker.ApiKeyError'))
return
}
// Script zaten yüklendiyse sadece bekle
const existingScript = document.querySelector('script[src*="maps.googleapis.com"]')
if (existingScript) {
const checkInterval = setInterval(() => {
if (window.google && window.google.maps && window.google.maps.places) {
clearInterval(checkInterval)
setIsGoogleLoaded(true)
autocompleteServiceRef.current = new window.google.maps.places.AutocompleteService()
const mapDiv = document.createElement('div')
const map = new window.google.maps.Map(mapDiv)
placesServiceRef.current = new window.google.maps.places.PlacesService(map)
}
}, 100)
return
}
// Yeni script ekle
const script = document.createElement('script')
script.src = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&libraries=places&language=tr`
script.async = true
script.defer = true
script.onload = () => {
if (window.google && window.google.maps && window.google.maps.places) {
setIsGoogleLoaded(true)
autocompleteServiceRef.current = new window.google.maps.places.AutocompleteService()
const mapDiv = document.createElement('div')
const map = new window.google.maps.Map(mapDiv)
placesServiceRef.current = new window.google.maps.places.PlacesService(map)
}
}
script.onerror = () => {
setError(translate('::App.Platform.Intranet.SocialWall.LocationPicker.GoogleMapsLoadError'))
}
document.head.appendChild(script)
scriptLoadedRef.current = true
}
loadGoogleMaps()
}, [])
useEffect(() => {
searchInputRef.current?.focus()
}, [])
// Google Places Autocomplete ile konum arama
useEffect(() => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
if (searchQuery.trim() === '') {
setLocations([])
setError(null)
return
}
if (!isGoogleLoaded) {
return
}
debounceTimerRef.current = setTimeout(async () => {
setIsLoading(true)
setError(null)
try {
// Google Places Autocomplete Service kullan (CORS yok)
autocompleteServiceRef.current.getPlacePredictions(
{
input: searchQuery,
componentRestrictions: { country: 'tr' },
language: 'tr'
},
async (predictions: any, status: any) => {
if (status === window.google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
setLocations([])
setIsLoading(false)
return
}
if (status !== window.google.maps.places.PlacesServiceStatus.OK) {
setError(translate('::App.Platform.Intranet.SocialWall.LocationPicker.SearchFailed'))
setIsLoading(false)
return
}
if (!predictions || predictions.length === 0) {
setLocations([])
setIsLoading(false)
return
}
// Her bir prediction için detaylı bilgi al
const detailedLocations: LocationData[] = []
let completed = 0
predictions.forEach((prediction: any) => {
placesServiceRef.current.getDetails(
{
placeId: prediction.place_id,
fields: ['name', 'formatted_address', 'geometry', 'place_id']
},
(place: any, placeStatus: any) => {
completed++
if (placeStatus === window.google.maps.places.PlacesServiceStatus.OK && place) {
detailedLocations.push({
id: place.place_id,
name: place.name,
address: place.formatted_address,
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
placeId: place.place_id
})
}
// Tüm istekler tamamlandıysa state'i güncelle
if (completed === predictions.length) {
setLocations(detailedLocations)
setIsLoading(false)
}
}
)
})
}
)
} catch (err) {
console.error('Location search error:', err)
setError(translate('::App.Platform.Intranet.SocialWall.LocationPicker.SearchError'))
setIsLoading(false)
}
}, 500) // 500ms debounce
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current)
}
}
}, [searchQuery, isGoogleLoaded])
const handleSelect = (location: LocationData) => {
setSelectedLocation(location)
}
const handleConfirm = () => {
if (selectedLocation) {
onSelect(JSON.stringify(selectedLocation))
onClose()
}
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col"
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">{translate('::App.Platform.Intranet.SocialWall.LocationPicker.AddLocation')}</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500 dark:text-gray-400" />
</button>
</div>
{/* Search */}
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
ref={searchInputRef}
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={translate('::App.Platform.Intranet.SocialWall.LocationPicker.SearchPlaceholder')}
disabled={!isGoogleLoaded}
className="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-600 disabled:cursor-not-allowed"
/>
</div>
{!isGoogleLoaded && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2">
{translate('::App.Platform.Intranet.SocialWall.LocationPicker.LoadingGoogleMaps')}
</p>
)}
</div>
{/* Location List */}
<div className="flex-1 overflow-y-auto p-4">
{!isGoogleLoaded ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.SocialWall.LocationPicker.LoadingGoogleMaps')}</p>
</div>
) : isLoading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.SocialWall.LocationPicker.SearchingLocations')}</p>
</div>
) : error ? (
<div className="text-center py-12">
<FaMapMarkerAlt className="w-16 h-16 mx-auto mb-4 text-red-400" />
<p className="text-red-500 dark:text-red-400">{error}</p>
</div>
) : searchQuery.trim() === '' ? (
<div className="text-center py-12">
<FaSearch className="w-16 h-16 mx-auto mb-4 text-gray-400" />
<p className="text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.SocialWall.LocationPicker.TypeToSearch')}
</p>
<p className="text-sm text-gray-400 dark:text-gray-500 mt-2">
{translate('::App.Platform.Intranet.SocialWall.LocationPicker.Example')}
</p>
</div>
) : locations.length === 0 ? (
<div className="text-center py-12">
<FaMapMarkerAlt className="w-16 h-16 mx-auto mb-4 text-gray-400" />
<p className="text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.SocialWall.LocationPicker.NotFound')}
</p>
</div>
) : (
<div className="space-y-2">
{locations.map((location) => (
<button
key={location.id}
onClick={() => handleSelect(location)}
className={classNames(
'w-full text-left p-3 rounded-lg transition-all hover:bg-gray-50 dark:hover:bg-gray-700',
selectedLocation?.id === location.id
? 'bg-blue-50 dark:bg-blue-900/30 border-2 border-blue-500'
: 'border-2 border-transparent'
)}
>
<div className="flex items-start gap-3">
<div className="mt-1">
<FaMapMarkerAlt
className={classNames(
'w-5 h-5',
selectedLocation?.id === location.id
? 'text-blue-600'
: 'text-gray-400'
)}
/>
</div>
<div className="flex-1 min-w-0">
<h3
className={classNames(
'font-semibold mb-1',
selectedLocation?.id === location.id
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-900 dark:text-gray-100'
)}
>
{location.name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{location.address}
</p>
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</p>
</div>
{selectedLocation?.id === location.id && (
<div className="mt-1">
<div className="w-5 h-5 bg-blue-600 rounded-full flex items-center justify-center">
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
</div>
</div>
)}
</div>
</button>
))}
</div>
)}
</div>
{/* Footer */}
<div className="flex items-center justify-between p-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-750">
<div className="text-sm text-gray-600 dark:text-gray-400">
{selectedLocation ? (
<span className="flex items-center gap-2">
<FaMapMarkerAlt className="w-4 h-4 text-blue-600" />
<span className="font-medium text-gray-900 dark:text-gray-100">
{selectedLocation.name}
</span>
</span>
) : (
<span>{translate('::App.Platform.Intranet.SocialWall.LocationPicker.SelectLocation')}</span>
)}
</div>
<div className="flex gap-2">
<button
onClick={onClose}
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
{translate('::Cancel')}
</button>
<button
onClick={handleConfirm}
disabled={!selectedLocation}
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{translate('::ListForms.Wizard.Add')}
</button>
</div>
</div>
</motion.div>
</div>
)
}
export default LocationPicker

View file

@ -0,0 +1,67 @@
import React from 'react'
import Lightbox from 'yet-another-react-lightbox'
import 'yet-another-react-lightbox/styles.css'
import Video from 'yet-another-react-lightbox/plugins/video'
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
import Counter from 'yet-another-react-lightbox/plugins/counter'
import 'yet-another-react-lightbox/plugins/counter.css'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaLightboxProps {
isOpen: boolean
onClose: () => void
media: SocialMediaDto
startIndex?: number
}
const MediaLightbox: React.FC<MediaLightboxProps> = ({
isOpen,
onClose,
media,
startIndex = 0
}) => {
const slides = React.useMemo(() => {
if (media.type === 'video' && media.urls && media.urls.length > 0) {
return [
{
type: 'video' as const,
sources: [
{
src: media.urls[0],
type: 'video/mp4'
}
]
}
]
}
const urls = media.urls || []
return urls.map((url) => ({
src: url
}))
}, [media])
return (
<Lightbox
open={isOpen}
close={onClose}
slides={slides}
index={startIndex}
plugins={[Video, Zoom, Counter]}
counter={{ container: { style: { top: 'unset', bottom: 0 } } }}
zoom={{
maxZoomPixelRatio: 3,
scrollToZoom: true
}}
video={{
controls: true,
autoPlay: false
}}
styles={{
container: { backgroundColor: 'rgba(0, 0, 0, 0.95)' }
}}
/>
)
}
export default MediaLightbox

View file

@ -0,0 +1,240 @@
import React, { useState } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { motion } from 'framer-motion'
import { FaTimes, FaLink, FaUpload } from 'react-icons/fa'
import classNames from 'classnames'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaManagerProps {
media: SocialMediaDto[]
onChange: (media: SocialMediaDto[]) => void
onClose: () => void
}
const MediaManager: React.FC<MediaManagerProps> = ({ media, onChange, onClose }) => {
const { translate } = useLocalization();
const [activeTab, setActiveTab] = useState<'upload' | 'url'>('upload')
const [urlInput, setUrlInput] = useState('')
const [mediaType, setMediaType] = useState<'image' | 'video'>('image')
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (!files) return
const newMedia: SocialMediaDto[] = Array.from(files).map((file) => ({
id: Math.random().toString(36).substr(2, 9),
type: file.type.startsWith('video/') ? 'video' : 'image',
urls: [URL.createObjectURL(file)],
file
}))
onChange([...media, ...newMedia])
e.target.value = ''
}
const handleUrlAdd = () => {
if (!urlInput.trim()) return
const newMedia: SocialMediaDto = {
id: Math.random().toString(36).substr(2, 9),
type: mediaType,
urls: [urlInput]
}
onChange([...media, newMedia])
setUrlInput('')
}
const removeMedia = (id: string | undefined) => {
if (!id) return
onChange(media.filter((m) => m.id !== id))
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-3xl max-h-[90vh] overflow-hidden"
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">{translate('::App.Platform.Intranet.SocialWall.MediaManager.AddMedia')}</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500 dark:text-gray-400" />
</button>
</div>
{/* Tabs */}
<div className="flex border-b border-gray-200 dark:border-gray-700 px-4">
<button
onClick={() => setActiveTab('upload')}
className={classNames(
'px-4 py-3 font-medium border-b-2 transition-colors',
activeTab === 'upload'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
)}
>
<div className="flex items-center gap-2">
<FaUpload className="w-5 h-5" />
<span>{translate('::App.Platform.Intranet.SocialWall.MediaManager.SelectFromComputer')}</span>
</div>
</button>
<button
onClick={() => setActiveTab('url')}
className={classNames(
'px-4 py-3 font-medium border-b-2 transition-colors',
activeTab === 'url'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
)}
>
<div className="flex items-center gap-2">
<FaLink className="w-5 h-5" />
<span>{translate('::App.Platform.Intranet.SocialWall.MediaManager.AddByUrl')}</span>
</div>
</button>
</div>
{/* Content */}
<div className="p-4 overflow-y-auto max-h-[calc(90vh-240px)]">
{activeTab === 'upload' ? (
<div>
<label className="block">
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors cursor-pointer">
<FaUpload className="w-12 h-12 mx-auto mb-4 text-gray-400" />
<p className="text-gray-700 dark:text-gray-300 font-medium mb-1">
{translate('::App.Platform.Intranet.SocialWall.MediaManager.ClickToSelectFile')}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.SocialWall.MediaManager.ImageOrVideoFormats')}
</p>
</div>
<input
type="file"
accept="image/*,video/*"
multiple
onChange={handleFileSelect}
className="hidden"
/>
</label>
</div>
) : (
<div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::App.Platform.Intranet.SocialWall.MediaManager.MediaType')}
</label>
<div className="flex gap-2">
<button
onClick={() => setMediaType('image')}
className={classNames(
'flex-1 py-2 px-4 rounded-lg font-medium transition-colors',
mediaType === 'image'
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
)}
>
{translate('::App.Platform.Intranet.SocialWall.MediaManager.Image')}
</button>
<button
onClick={() => setMediaType('video')}
className={classNames(
'flex-1 py-2 px-4 rounded-lg font-medium transition-colors',
mediaType === 'video'
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
)}
>
{translate('::App.Platform.Intranet.SocialWall.MediaManager.Video')}
</button>
</div>
</div>
<div className="flex gap-2">
<input
type="url"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleUrlAdd()}
placeholder={mediaType === 'image' ? translate('::App.Platform.Intranet.SocialWall.MediaManager.EnterImageUrl') : translate('::App.Platform.Intranet.SocialWall.MediaManager.EnterVideoUrl')}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleUrlAdd}
disabled={!urlInput.trim()}
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{translate('::ListForms.Wizard.Add')}
</button>
</div>
</div>
)}
{/* Media Preview */}
{media.length > 0 && (
<div className="mt-6">
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
{translate('::App.Platform.Intranet.SocialWall.MediaManager.AddedMedia')} ({media.length})
</h3>
<div className="grid grid-cols-4 gap-3">
{media.map((item) => (
<div key={item.id} className="relative group">
{item.type === 'image' ? (
<img
src={item.urls?.[0]}
alt="Media preview"
className="w-full h-24 object-cover rounded-lg"
/>
) : (
<div className="w-full h-24 bg-gray-900 rounded-lg flex items-center justify-center">
<video src={item.urls?.[0]} className="w-full h-full object-cover rounded-lg" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-10 h-10 bg-black bg-opacity-50 rounded-full flex items-center justify-center">
<div className="w-0 h-0 border-t-8 border-t-transparent border-l-12 border-l-white border-b-8 border-b-transparent ml-1"></div>
</div>
</div>
</div>
)}
<button
onClick={() => removeMedia(item.id)}
className="absolute -top-2 -right-2 p-1 bg-red-600 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
>
<FaTimes className="w-4 h-4" />
</button>
<div className="absolute bottom-1 left-1 px-2 py-0.5 bg-black bg-opacity-70 text-white text-xs rounded">
{item.type === 'image' ? translate('::App.Platform.Intranet.SocialWall.MediaManager.ImageIcon') : translate('::App.Platform.Intranet.SocialWall.MediaManager.VideoIcon')}
</div>
</div>
))}
</div>
</div>
)}
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-2 p-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={onClose}
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
{translate('::Cancel')}
</button>
<button
onClick={onClose}
disabled={media.length === 0}
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{translate('::App.Platform.Intranet.SocialWall.MediaManager.Done', { count: media.length })}
</button>
</div>
</motion.div>
</div>
)
}
export default MediaManager

View file

@ -0,0 +1,409 @@
import React, { useState, useRef, useEffect } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { motion, AnimatePresence } from 'framer-motion'
import classNames from 'classnames'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/tr'
import { FaHeart, FaRegHeart, FaRegCommentAlt, FaTrash, FaPaperPlane } from 'react-icons/fa'
import MediaLightbox from './MediaLightbox'
import LocationMap from './LocationMap'
import UserProfileCard from './UserProfileCard'
import { SocialPostDto } from '@/proxy/intranet/models'
import { useStoreState } from '@/store/store'
import { AVATAR_URL } from '@/constants/app.constant'
import { Avatar } from '@/components/ui'
dayjs.extend(relativeTime)
dayjs.locale('tr')
interface PostItemProps {
post: SocialPostDto
onLike: (postId: string) => void
onComment: (postId: string, content: string) => void
onDelete: (postId: string) => void
onVote: (postId: string, optionId: string) => void
}
const PostItem: React.FC<PostItemProps> = ({ post, onLike, onComment, onDelete, onVote }) => {
const { translate } = useLocalization()
const [showComments, setShowComments] = useState(false)
const [commentText, setCommentText] = useState('')
const [showAllImages, setShowAllImages] = useState(false)
const [lightboxOpen, setLightboxOpen] = useState(false)
const [lightboxIndex, setLightboxIndex] = useState(0)
const [showUserCard, setShowUserCard] = useState(false)
const [hoveredCommentAuthor, setHoveredCommentAuthor] = useState<string | null>(null)
const videoRef = useRef<HTMLVideoElement>(null)
const { user } = useStoreState((state) => state.auth)
// Intersection Observer for video autoplay/pause
useEffect(() => {
const video = videoRef.current
if (!video) return
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Video ekranda görünür - oynat
video.play().catch((err) => {
console.log('Video autoplay failed:', err)
})
} else {
// Video ekrandan çıktı - durdur
video.pause()
}
})
},
{
threshold: 0.5, // Video %50 görünür olduğunda oynat
},
)
observer.observe(video)
return () => {
observer.disconnect()
}
}, [post.media?.type])
const handleSubmitComment = (e: React.FormEvent) => {
e.preventDefault()
if (commentText.trim()) {
onComment(post.id, commentText)
setCommentText('')
}
}
const getImageLayout = (images: string[]) => {
const count = images.length
if (count === 1) return 'single'
if (count === 2) return 'double'
if (count === 3) return 'triple'
return 'multiple'
}
const renderMedia = () => {
if (!post.media) return null
switch (post.media.type) {
case 'image':
if (post.media.urls && post.media.urls.length > 0) {
const layout = getImageLayout(post.media.urls)
const displayImages = showAllImages ? post.media.urls : post.media.urls.slice(0, 4)
const hasMore = post.media.urls.length > 4
return (
<>
<div
className={classNames('mt-3 rounded-lg overflow-hidden', {
'grid gap-1': layout !== 'single',
'grid-cols-2': layout === 'double' || layout === 'multiple',
'grid-cols-3': layout === 'triple',
})}
>
{displayImages.map((url, index) => (
<div
key={index}
className={classNames('relative', {
'col-span-2': layout === 'triple' && index === 0,
'aspect-video': layout === 'single',
'aspect-square': layout !== 'single',
})}
>
<img
src={url}
alt={`Post image ${index + 1}`}
className="w-full h-full object-cover cursor-pointer hover:opacity-90 transition-opacity"
onClick={() => {
setLightboxIndex(index)
setLightboxOpen(true)
}}
/>
{hasMore && index === 3 && !showAllImages && post.media?.urls && (
<div
className="absolute inset-0 bg-black bg-opacity-60 flex items-center justify-center cursor-pointer"
onClick={(e) => {
e.stopPropagation()
setShowAllImages(true)
}}
>
<span className="text-white text-2xl font-bold">
+{post.media.urls.length - 4}
</span>
</div>
)}
</div>
))}
</div>
<MediaLightbox
isOpen={lightboxOpen}
onClose={() => setLightboxOpen(false)}
media={{ type: 'image', urls: post.media.urls }}
startIndex={lightboxIndex}
/>
</>
)
}
break
case 'video':
if (post.media.urls && post.media.urls.length > 0) {
return (
<>
<div
className="mt-3 rounded-lg overflow-hidden cursor-pointer relative group"
onClick={() => setLightboxOpen(true)}
>
<video
ref={videoRef}
src={post.media.urls[0]}
className="w-full max-h-96 object-cover"
controls
playsInline
muted
loop
/>
</div>
<MediaLightbox
isOpen={lightboxOpen}
onClose={() => setLightboxOpen(false)}
media={{ type: 'video', urls: [post.media.urls[0]] }}
/>
</>
)
}
break
case 'poll':
if (post.media.pollQuestion && post.media.pollOptions) {
const isExpired = post.media.pollEndsAt ? new Date() > post.media.pollEndsAt : false
const hasVoted = !!post.media.pollUserVoteId
const totalVotes = post.media.pollTotalVotes || 0
const pollUserVoteId = post.media.pollUserVoteId
return (
<div className="mt-3 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-3">
{post.media.pollQuestion}
</h4>
<div className="space-y-2">
{post.media.pollOptions.map((option) => {
const percentage = totalVotes > 0 ? (option.votes / totalVotes) * 100 : 0
const isSelected = pollUserVoteId === option.id
return (
<button
key={option.id}
onClick={() => !hasVoted && !isExpired && onVote(post.id, option.id)}
disabled={hasVoted || isExpired}
className={classNames(
'w-full text-left p-3 rounded-lg relative overflow-hidden transition-all',
{
'bg-blue-100 dark:bg-blue-900 border-2 border-blue-500': isSelected,
'bg-white dark:bg-gray-600 hover:bg-gray-50 dark:hover:bg-gray-500':
!isSelected && !hasVoted && !isExpired,
'bg-white dark:bg-gray-600 cursor-not-allowed': hasVoted || isExpired,
},
)}
>
{hasVoted && (
<div
className="absolute inset-y-0 left-0 bg-blue-200 dark:bg-blue-800 transition-all"
style={{ width: `${percentage}%` }}
/>
)}
<div className="relative z-10 flex justify-between items-center">
<span className="font-medium text-gray-900 dark:text-gray-100">
{option.text}
</span>
{hasVoted && (
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">
{percentage.toFixed(0)}%
</span>
)}
</div>
</button>
)
})}
</div>
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400">
{totalVotes} oy {' '}
{isExpired
? 'Sona erdi'
: post.media.pollEndsAt
? dayjs(post.media.pollEndsAt).fromNow() + ' bitiyor'
: ''}
</div>
</div>
)
}
break
}
return null
}
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 mb-4"
>
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div
className="relative"
onMouseEnter={() => setShowUserCard(true)}
onMouseLeave={() => setShowUserCard(false)}
>
<Avatar size={32} shape="circle" src={AVATAR_URL(post.user.id, post.user.tenantId)} />
<AnimatePresence>
{showUserCard && (
<UserProfileCard
user={{
id: post.user.id || '',
name: post.user.fullName || '',
title: post.user.jobPositions?.[0]?.name || '',
email: post.user.email,
phoneNumber: post.user.phoneNumber,
department: post.user.departments?.[0]?.name,
tenantId: post.user.tenantId || '',
}}
position="bottom"
/>
)}
</AnimatePresence>
</div>
<div>
<h3 className="text-sm text-gray-900 dark:text-gray-100">{post.user.fullName}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{post.user.jobPositions?.[0]?.name || ''} {dayjs(post.creationTime).fromNow()}
</p>
</div>
</div>
{post.isOwnPost && (
<button
onClick={() => onDelete(post.id)}
className="p-2 text-gray-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-full transition-colors"
title={translate('::App.Platform.Intranet.SocialWall.PostItem.DeletePost')}
>
<FaTrash className="w-5 h-5" />
</button>
)}
</div>
{/* Content */}
<div className="mb-3">
<p className="text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{post.content}</p>
{renderMedia()}
</div>
{/* Actions */}
<div className="flex items-center gap-6 pt-3 border-t border-gray-100 dark:border-gray-700">
<button
onClick={() => onLike(post.id)}
className={classNames(
'flex items-center gap-2 transition-colors',
post.isLiked
? 'text-red-600 hover:text-red-700'
: 'text-gray-600 dark:text-gray-400 hover:text-red-600',
)}
>
{post.isLiked ? <FaHeart className="w-5 h-5" /> : <FaRegHeart className="w-5 h-5" />}
<span className="text-sm font-medium">{post.likeCount}</span>
</button>
<button
onClick={() => setShowComments(!showComments)}
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-blue-600 transition-colors"
>
<FaRegCommentAlt className="w-5 h-5" />
<span className="text-sm font-medium">{post.comments.length}</span>
</button>
</div>
{/* Comments Section */}
<AnimatePresence>
{showComments && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700"
>
{/* Comment Form */}
<form onSubmit={handleSubmitComment} className="mb-4">
<div className="flex gap-2">
<input
type="text"
value={commentText}
onChange={(e) => setCommentText(e.target.value)}
placeholder={translate(
'::App.Platform.Intranet.SocialWall.PostItem.CommentPlaceholder',
)}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-full bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
disabled={!commentText.trim()}
className="p-2 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
<FaPaperPlane className="w-5 h-5" />
</button>
</div>
</form>
{/* Comments List */}
<div className="space-y-3">
{post.comments.map((comment) => (
<div key={comment.id} className="flex gap-3">
<div
className="relative"
onMouseEnter={() => setHoveredCommentAuthor(comment.id)}
onMouseLeave={() => setHoveredCommentAuthor(null)}
>
<Avatar
size={32}
shape="circle"
src={AVATAR_URL(comment.creator.id, comment.creator.tenantId)}
/>
<AnimatePresence>
{hoveredCommentAuthor === comment.id && (
<UserProfileCard
user={{
id: comment.creator.id || '',
name: comment.creator.fullName || '',
title: comment.creator.jobPositions?.[0]?.name || '',
tenantId: comment.creator.tenantId || '',
}}
position="bottom"
/>
)}
</AnimatePresence>
</div>
<div className="flex-1">
<div className="bg-gray-100 dark:bg-gray-700 rounded-lg px-4 py-2">
<h4 className="font-semibold text-sm text-gray-900 dark:text-gray-100">
{comment.creator.fullName}
</h4>
<p className="text-sm text-gray-800 dark:text-gray-200">{comment.content}</p>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 ml-4">
{dayjs(comment.creationTime).fromNow()}
</p>
</div>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
)
}
export default PostItem

View file

@ -0,0 +1,100 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaEnvelope, FaPhone, FaBriefcase, FaMapMarkerAlt } from 'react-icons/fa'
import { AVATAR_URL } from '@/constants/app.constant'
import { Avatar } from '@/components/ui'
interface UserProfileCardProps {
user: {
id: string
name: string
title: string
email?: string
phoneNumber?: string
department?: string
location?: string
tenantId: string
}
position?: 'top' | 'bottom'
}
const UserProfileCard: React.FC<UserProfileCardProps> = ({ user, position = 'bottom' }) => {
return (
<motion.div
initial={{ opacity: 0, scale: 0.95, y: position === 'bottom' ? -10 : 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: position === 'bottom' ? -10 : 10 }}
transition={{ duration: 0.15 }}
className={`absolute left-0 ${
position === 'bottom' ? 'top-full mt-2' : 'bottom-full mb-2'
} z-50 w-72 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 p-4`}
>
{/* Header */}
<div className="flex items-start gap-3 mb-3 pb-3 border-b border-gray-200 dark:border-gray-700">
<Avatar size={48} shape="circle" src={AVATAR_URL(user.id, user.tenantId)} />
<div className="flex-1 min-w-0">
<h3 className="font-bold text-gray-900 dark:text-gray-100 text-lg mb-1">
{user.name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
<FaBriefcase className="w-4 h-4" />
{user.title}
</p>
</div>
</div>
{/* Contact Info */}
<div className="space-y-2">
{user.email && (
<div className="flex items-center gap-2 text-sm">
<FaEnvelope className="w-4 h-4 text-gray-400 flex-shrink-0" />
<a
href={`mailto:${user.email}`}
className="text-blue-600 dark:text-blue-400 hover:underline truncate"
>
{user.email}
</a>
</div>
)}
{user.phoneNumber && (
<div className="flex items-center gap-2 text-sm">
<FaPhone className="w-4 h-4 text-gray-400 flex-shrink-0" />
<a
href={`tel:${user.phoneNumber}`}
className="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400"
>
{user.phoneNumber}
</a>
</div>
)}
{user.department && (
<div className="flex items-center gap-2 text-sm">
<FaBriefcase className="w-4 h-4 text-gray-400 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">{user.department}</span>
</div>
)}
{user.location && (
<div className="flex items-center gap-2 text-sm">
<FaMapMarkerAlt className="w-4 h-4 text-gray-400 flex-shrink-0" />
<span className="text-gray-700 dark:text-gray-300">{user.location}</span>
</div>
)}
</div>
{/* Arrow indicator */}
<div
className={`absolute left-6 ${
position === 'bottom' ? '-top-2' : '-bottom-2'
} w-4 h-4 bg-white dark:bg-gray-800 border-l border-t border-gray-200 dark:border-gray-700 transform ${
position === 'bottom' ? 'rotate-45' : '-rotate-135'
}`}
/>
</motion.div>
)
}
export default UserProfileCard

View file

@ -0,0 +1,222 @@
import React, { useState } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { AnimatePresence } from 'framer-motion'
import PostItem from './PostItem'
import CreatePost from './CreatePost'
import { SocialMediaDto, SocialPostDto } from '@/proxy/intranet/models'
import { useStoreState } from '@/store/store'
import { IdentityUserDto } from '@/proxy/admin/models'
const SocialWall: React.FC<{ posts: SocialPostDto[] }> = ({ posts }) => {
// const [posts, setPosts] = useState<SocialPost[]>(mockSocialPosts)
const [filter, setFilter] = useState<'all' | 'mine'>('all')
const { translate } = useLocalization()
// Ali Öztürk'ü "Siz" kullanıcısı olarak kullan
const { user } = useStoreState((state) => state.auth)
const currentUserAuthor: IdentityUserDto = {
id: user?.id || '0',
userName: user.userName || 'unknown',
name: user?.name || 'Siz',
email: user?.email || '',
isActive: true,
emailConfirmed: true,
phoneNumberConfirmed: true,
lockoutEnabled: false,
}
const handleCreatePost = (postData: {
content: string
location?: string
media?: {
type: 'mixed' | 'poll'
mediaItems?: SocialMediaDto[]
poll?: {
question: string
options: Array<{ text: string }>
}
}
}) => {
let mediaForPost = undefined
if (postData.media) {
if (postData.media.type === 'mixed' && postData.media.mediaItems) {
// Convert MediaItems to post format
const images = postData.media.mediaItems.filter((m) => m.type === 'image')
const videos = postData.media.mediaItems.filter((m) => m.type === 'video')
if (images.length > 0 && videos.length === 0) {
mediaForPost = {
type: 'image' as const,
urls: images.map((i) => i.urls?.[0]).filter((url) => url !== undefined) as string[],
}
} else if (videos.length > 0 && images.length === 0) {
mediaForPost = {
type: 'video' as const,
urls: videos[0].urls || [],
}
} else if (images.length > 0 || videos.length > 0) {
// Mixed media - use first image for now
mediaForPost = {
type: 'image' as const,
urls: images.map((i) => i.urls?.[0]).filter((url) => url !== undefined) as string[],
}
}
} else if (postData.media.type === 'poll' && postData.media.poll) {
mediaForPost = {
type: 'poll' as const,
pollQuestion: postData.media.poll.question,
pollOptions: postData.media.poll.options.map((opt, index) => ({
id: `opt-${index}`,
text: opt.text,
votes: 0,
})),
pollTotalVotes: 0,
pollEndsAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
}
}
}
const newPost: SocialPostDto = {
id: Date.now().toString(),
user: currentUserAuthor,
content: postData.content,
creationTime: new Date(),
media: mediaForPost,
locationJson: postData.location,
likeCount: 0,
isLiked: false,
likeUsers: [],
comments: [],
isOwnPost: true,
}
// setPosts([newPost, ...posts])
}
const handleLike = (postId: string) => {
// setPosts(
// posts.map((post) => {
// if (post.id === postId) {
// return {
// ...post,
// likeCount: post.isLiked ? post.likeCount - 1 : post.likeCount + 1,
// isLiked: !post.isLiked
// }
// }
// return post
// })
// )
}
const handleComment = (postId: string, content: string) => {
// setPosts(
// posts.map((post) => {
// if (post.id === postId) {
// const commentAuthor = currentUserAuthor
// const newComment = {
// id: Date.now().toString(),
// creator: commentAuthor,
// content,
// creationTime: new Date()
// }
// return {
// ...post,
// comments: [...post.comments, newComment]
// }
// }
// return post
// })
// )
}
const handleDelete = (postId: string) => {
if (window.confirm(translate('::App.Platform.Intranet.SocialWall.DeleteConfirm'))) {
// setPosts(posts.filter((post) => post.id !== postId))
}
}
const handleVote = (postId: string, optionId: string) => {
// setPosts(
// posts.map((post) => {
// if (post.id === postId && post.media?.type === 'poll' && post.media.pollOptions) {
// // If user already voted, don't allow voting again
// if (post.media.pollUserVoteId) {
// return post
// }
// return {
// ...post,
// media: {
// ...post.media,
// pollOptions: post.media.pollOptions.map((opt) =>
// opt.id === optionId ? { ...opt, votes: opt.votes + 1 } : opt
// ),
// pollTotalVotes: (post.media.pollTotalVotes || 0) + 1,
// pollUserVoteId: optionId
// }
// }
// }
// return post
// })
// )
}
const filteredPosts = filter === 'mine' ? posts.filter((post) => post.isOwnPost) : posts
return (
<div className="mx-auto px-4">
{/* Filter Tabs */}
<div className="flex gap-4 mb-6 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setFilter('all')}
className={`pb-3 px-1 border-b-2 transition-colors font-medium ${
filter === 'all'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
}`}
>
{translate('::App.Platform.Intranet.SocialWall.AllPosts')}
</button>
<button
onClick={() => setFilter('mine')}
className={`pb-3 px-1 border-b-2 transition-colors font-medium ${
filter === 'mine'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
}`}
>
{translate('::App.Platform.Intranet.SocialWall.MyPosts')}
</button>
</div>
{/* Create Post */}
<CreatePost onCreatePost={handleCreatePost} />
{/* Posts Feed */}
<AnimatePresence>
{filteredPosts.length > 0 ? (
filteredPosts.map((post) => (
<PostItem
key={post.id}
post={post}
onLike={handleLike}
onComment={handleComment}
onDelete={handleDelete}
onVote={handleVote}
/>
))
) : (
<div className="text-center py-12">
<p className="text-gray-500 dark:text-gray-400 text-lg">
{filter === 'mine'
? translate('::App.Platform.Intranet.SocialWall.NoMyPosts')
: translate('::App.Platform.Intranet.SocialWall.NoPosts')}
</p>
</div>
)}
</AnimatePresence>
</div>
)
}
export default SocialWall

View file

@ -0,0 +1,211 @@
import React from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { motion } from 'framer-motion'
import { FaTimes, FaEye, FaClipboard } from 'react-icons/fa'
import { AnnouncementDto } from '@/proxy/intranet/models'
import useLocale from '@/utils/hooks/useLocale'
import { currentLocalDate } from '@/utils/dateUtils'
import Avatar from '@/components/ui/Avatar/Avatar'
import { AVATAR_URL } from '@/constants/app.constant'
interface AnnouncementModalProps {
announcement: AnnouncementDto
onClose: () => void
}
const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
announcement,
onClose,
}) => {
const { translate } = useLocalization()
const currentLocale = useLocale()
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
event: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[category] || colors.general
}
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<span
className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(announcement.category)}`}
>
{announcement.category === 'general' &&
`📢 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.General')}`}
{announcement.category === 'hr' &&
`👥 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.HR')}`}
{announcement.category === 'it' &&
`💻 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.IT')}`}
{announcement.category === 'event' &&
`🎉 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.Event')}`}
{announcement.category === 'urgent' &&
`🚨 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.Urgent')}`}
</span>
{announcement.isPinned && (
<span className="text-yellow-500 text-sm">
📌 {translate('::App.Platform.Intranet.AnnouncementDetailModal.Pinned')}
</span>
)}
</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{announcement.title}
</h2>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-6 h-6 text-gray-500" />
</button>
</div>
{/* Author Info */}
<div className="flex items-center gap-3 mt-4">
<Avatar
size={32}
shape="circle"
src={AVATAR_URL(announcement.user.id, announcement.user.tenantId)}
/>
<div>
<p className="font-semibold text-gray-900 dark:text-white">
{announcement.user.fullName}
</p>
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
<span>{currentLocalDate(announcement.publishDate, currentLocale || 'tr')}</span>
<span></span>
<span className="flex items-center gap-1">
<FaEye className="w-4 h-4" />
{announcement.viewCount}{' '}
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Views')}
</span>
</div>
</div>
</div>
</div>
{/* Content */}
<div className="p-6 max-h-[60vh] overflow-y-auto">
{/* Image if exists */}
{announcement.imageUrl && (
<img
src={announcement.imageUrl}
alt={announcement.title}
className="w-full rounded-lg mb-6"
/>
)}
{/* Full Content */}
<div className="prose prose-sm dark:prose-invert max-w-none">
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
{announcement.content}
</p>
</div>
{/* Attachments */}
{announcement.attachments && announcement.attachments.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
<FaClipboard className="w-5 h-5" />
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Attachments')} (
{announcement.attachments.length})
</h3>
<div className="space-y-2">
{announcement.attachments.map((attachment, idx) => (
<a
key={idx}
href={attachment.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
<FaClipboard className="w-5 h-5 text-gray-400" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
{attachment.name}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{attachment.size}
</p>
</div>
<span className="text-sm text-blue-600 dark:text-blue-400">
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Download')}
</span>
</a>
))}
</div>
</div>
)}
{/* Departments */}
{announcement.departments && announcement.departments.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
{translate('::App.Platform.Intranet.AnnouncementDetailModal.TargetDepartments')}
</h3>
<div className="flex flex-wrap gap-2">
{announcement.departments?.map((dept, idx) => (
<span
key={idx}
className="px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm rounded-full"
>
{dept}
</span>
))}
</div>
</div>
)}
{/* Expiry Date */}
{announcement.expiryDate && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<p className="text-sm text-gray-600 dark:text-gray-400">
<span className="font-medium">
{translate('::App.Platform.Intranet.AnnouncementDetailModal.ExpiryDate')}:
</span>{' '}
{currentLocalDate(announcement.expiryDate, currentLocale || 'tr')}
</p>
</div>
)}
</div>
{/* Footer */}
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
<button
onClick={onClose}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
{translate('::App.Platform.Close')}
</button>
</div>
</motion.div>
</div>
</>
)
}
export default AnnouncementModal

View file

@ -0,0 +1,105 @@
import React from 'react'
import { FaBell, FaClipboardCheck, FaEye } from 'react-icons/fa'
import dayjs from 'dayjs'
import { AnnouncementDto } from '@/proxy/intranet/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Avatar } from '@/components/ui'
import { AVATAR_URL } from '@/constants/app.constant'
interface AnnouncementsProps {
announcements: AnnouncementDto[]
onAnnouncementClick: (announcement: AnnouncementDto) => void
}
const Announcements: React.FC<AnnouncementsProps> = ({ announcements, onAnnouncementClick }) => {
const pinnedAnnouncements = announcements.filter((a) => a.isPinned).slice(0, 3)
const { translate } = useLocalization()
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
event: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[category] || colors.general
}
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<FaBell className="w-5 h-5" />
{translate('::App.Platform.Intranet.Widgets.Announcements.Title')}
</h2>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{pinnedAnnouncements.map((announcement) => (
<div
key={announcement.id}
onClick={() => onAnnouncementClick(announcement)}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors"
>
<div className="flex items-start gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
{announcement.title}
</h3>
{announcement.category && (
<span
className={`px-2 py-1 text-center text-xs rounded-full ${getCategoryColor(announcement.category)}`}
>
{(() => {
const key = `::App.Platform.Intranet.Widgets.Announcements.Category.${announcement.category.charAt(0).toUpperCase() + announcement.category.slice(1)}`
const translated = translate(key)
return translated === key ? announcement.category : translated
})()}
</span>
)}
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{announcement.excerpt}
</p>
<div className="flex items-center gap-2 mt-3 text-xs text-gray-500 dark:text-gray-400">
<Avatar
size={24}
shape="circle"
src={AVATAR_URL(announcement.user.id, announcement.user.tenantId)}
/>
<span>{announcement.user.fullName}</span>
<span></span>
<span>{dayjs(announcement.publishDate).fromNow()}</span>
<span></span>
<span className="flex items-center gap-1">
<FaEye className="w-3 h-3" />
{announcement.viewCount}
</span>
</div>
</div>
</div>
</div>
))}
{pinnedAnnouncements.length === 0 && (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
<FaClipboardCheck className="w-8 h-8 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
{translate('::App.Platform.Intranet.Widgets.Announcements.NoActive')}
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.Widgets.Announcements.WillAppearHere')}
</p>
</div>
)}
</div>
</div>
)
}
export default Announcements

View file

@ -0,0 +1,84 @@
import React from 'react'
import { FaFileAlt, FaDownload } from 'react-icons/fa'
import dayjs from 'dayjs'
import { DocumentDto } from '@/proxy/intranet/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { getFileIcon, getFileType } from '@/proxy/intranet/utils'
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
}
const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents }) => {
const { translate } = useLocalization();
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<FaFileAlt className="w-5 h-5" />
{translate('::App.Platform.Intranet.Widgets.RecentDocuments.Title')}
</h2>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{documents.length > 0 ? (
documents.slice(0, 3).map((doc) => (
<div
key={doc.id}
className="p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-3">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
{getFileIcon(doc.extension)}
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
{doc.name}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
{getFileType(doc.extension)}
<span className="mx-1"></span>
{formatFileSize(doc.size)}
</p>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2">
<span>{dayjs(doc.modifiedAt).fromNow()}</span>
{doc.isReadOnly && (
<>
<span></span>
<span className="text-orange-500">🔒 {translate('::App.Platform.Intranet.Widgets.RecentDocuments.ReadOnly')}</span>
</>
)}
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation()
const link = document.createElement('a')
link.href = `/cdn/${doc.path}`
link.download = doc.name
link.click()
}}
className="p-2 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-lg transition-colors group"
title={translate('::App.Platform.Intranet.Widgets.RecentDocuments.Download')}
>
<FaDownload className="w-5 h-5 text-gray-600 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors" />
</button>
</div>
</div>
))
) : (
<div className="p-4 text-center text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.Widgets.RecentDocuments.NoDocuments')}
</div>
)}
</div>
</div>
)
}
export default RecentDocuments

View file

@ -0,0 +1,275 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
import { SurveyAnswerDto, SurveyDto, SurveyQuestionDto } from '@/proxy/intranet/models'
interface SurveyModalProps {
survey: SurveyDto
onClose: () => void
onSubmit: (answers: SurveyAnswerDto[]) => void
}
import { useLocalization } from '@/utils/hooks/useLocalization'
const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit }) => {
const { translate } = useLocalization();
const [answers, setAnswers] = useState<{ [questionId: string]: any }>({})
const [errors, setErrors] = useState<{ [questionId: string]: string }>({})
const handleAnswerChange = (questionId: string, value: any) => {
setAnswers((prev) => ({
...prev,
[questionId]: value,
}))
// Clear error when user provides an answer
if (errors[questionId]) {
setErrors((prev) => ({
...prev,
[questionId]: '',
}))
}
}
const validateAnswers = (): boolean => {
const newErrors: { [questionId: string]: string } = {}
survey.questions.forEach((question) => {
if (question.isRequired && (!answers[question.id] || answers[question.id] === '')) {
newErrors[question.id] = translate('::App.Platform.Intranet.SurveyModal.RequiredField')
}
})
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!validateAnswers()) {
return
}
const surveyAnswers: SurveyAnswerDto[] = survey.questions.map((question) => ({
questionId: question.id,
questionType: question.type,
value:
answers[question.id] ||
(question.type === 'multiple-choice' ? '' : question.type === 'rating' ? 0 : ''),
}))
onSubmit(surveyAnswers)
}
const renderQuestion = (question: SurveyQuestionDto, index: number) => {
const questionNumber = index + 1
const hasError = !!errors[question.id]
switch (question.type) {
case 'rating':
return (
<div key={question.id} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
case 'multiple-choice':
return (
<div key={question.id} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<div className="space-y-2">
{question.options?.map((option) => (
<label
key={option.id}
className={`flex items-center gap-3 p-3 border rounded-lg cursor-pointer transition-colors ${
answers[question.id] === option.id
? 'bg-blue-50 border-blue-500 dark:bg-blue-900/20 dark:border-blue-400'
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
}`}
>
<input
type="radio"
name={`question-${question.id}`}
value={option.id}
checked={answers[question.id] === option.id}
onChange={(e) => handleAnswerChange(question.id, e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<span className="text-sm text-gray-900 dark:text-white">{option.text}</span>
</label>
))}
</div>
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
case 'yes-no':
return (
<div key={question.id} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<div className="flex gap-4">
{['yes', 'no'].map((value) => (
<label
key={value}
className={`flex items-center gap-2 px-4 py-2 border rounded-lg cursor-pointer transition-colors ${
answers[question.id] === value
? 'bg-blue-50 border-blue-500 dark:bg-blue-900/20 dark:border-blue-400'
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
}`}
>
<input
type="radio"
name={`question-${question.id}`}
value={value}
checked={answers[question.id] === value}
onChange={(e) => handleAnswerChange(question.id, e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<span className="text-sm text-gray-900 dark:text-white">
{value === 'yes' ? 'Evet' : 'Hayır'}
</span>
</label>
))}
</div>
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
case 'text':
return (
<div key={question.id} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<input
type="text"
value={answers[question.id] || ''}
onChange={(e) => handleAnswerChange(question.id, e.target.value)}
className={`w-full px-4 py-2 border rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 ${
hasError ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
}`}
placeholder={translate('::App.Platform.Intranet.SurveyModal.AnswerPlaceholder')}
/>
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
case 'textarea':
return (
<div key={question.id} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<textarea
rows={4}
value={answers[question.id] || ''}
onChange={(e) => handleAnswerChange(question.id, e.target.value)}
className={`w-full px-4 py-2 border rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 ${
hasError ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
}`}
placeholder={translate('::App.Platform.Intranet.SurveyModal.CommentPlaceholder')}
/>
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
default:
return null
}
}
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{survey.title}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{survey.description}</p>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6">
<div className="space-y-6">
{survey.questions
.sort((a, b) => a.order - b.order)
.map((question, index) => renderQuestion(question, index))}
</div>
{!survey.isAnonymous && (
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-sm text-blue-700 dark:text-blue-300">
Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız kaydedilecektir.
</p>
</div>
)}
{survey.isAnonymous && (
<div className="bg-green-50 dark:bg-green-900/20 rounded-lg p-3">
<p className="text-sm text-green-700 dark:text-green-300">
Bu anket anonimdir. Kimlik bilgileriniz kaydedilmeyecektir.
</p>
</div>
)}
<div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
{translate('::Cancel')}
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
{translate('::App.Platform.Intranet.SurveyModal.Submit')}
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default SurveyModal

View file

@ -0,0 +1,149 @@
import React from 'react'
import { FaClipboardCheck, FaQuestionCircle, FaUsers, FaClock, FaArrowRight } from 'react-icons/fa'
import dayjs from 'dayjs'
import { SurveyDto } from '@/proxy/intranet/models'
import useLocale from '@/utils/hooks/useLocale'
import { currentLocalDate } from '@/utils/dateUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface SurveysProps {
surveys?: SurveyDto[]
onTakeSurvey: (survey: SurveyDto) => void
}
const Surveys: React.FC<SurveysProps> = ({ surveys, onTakeSurvey }) => {
const currentLocale = useLocale()
const { translate } = useLocalization()
return (
<div className="bg-gradient-to-br from-white to-gray-50 dark:from-gray-800 dark:to-gray-850 rounded-xl shadow-lg border border-gray-200/50 dark:border-gray-700/50 overflow-hidden">
{/* Header with gradient */}
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<FaClipboardCheck className="w-5 h-5" />
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Title')}
</h2>
</div>
<div className="p-6 space-y-4">
{surveys?.map((survey, index) => {
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal'
return (
<div
key={survey.id}
onClick={() => onTakeSurvey(survey)}
className="group relative p-5 rounded-xl bg-white dark:bg-gray-750 border border-gray-200 dark:border-gray-600 hover:border-purple-300 dark:hover:border-purple-500 cursor-pointer transition-all duration-300 hover:shadow-lg hover:-translate-y-1"
>
{/* Background gradient on hover */}
<div className="absolute inset-0 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/10 dark:to-pink-900/10 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<div className="relative">
{/* Survey Title */}
<div className="flex items-start justify-between mb-3">
<h4 className="text-base font-semibold text-gray-900 dark:text-white group-hover:text-purple-700 dark:group-hover:text-purple-300 transition-colors">
{survey.title}
</h4>
<div
className={`px-2 py-1 text-center rounded-full text-xs font-medium ${
urgency === 'urgent'
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
: urgency === 'warning'
? 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'
: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
}`}
>
{daysLeft > 0 ? translate('::App.Platform.Intranet.Widgets.ActiveSurveys.DaysLeft', { count: daysLeft }) : translate('::App.Platform.Intranet.Widgets.ActiveSurveys.LastDay')}
</div>
</div>
{/* Survey Stats */}
<div className="grid grid-cols-3 gap-4 mb-4">
<div className="flex items-center gap-2 text-sm">
<div className="p-1.5 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<FaQuestionCircle className="w-3 h-3 text-blue-600 dark:text-blue-400" />
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Questions')}</p>
<p className="font-semibold text-gray-900 dark:text-white">
{survey.questions.length}
</p>
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<div className="p-1.5 bg-green-100 dark:bg-green-900/30 rounded-lg">
<FaUsers className="w-3 h-3 text-green-600 dark:text-green-400" />
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Responses')}</p>
<p className="font-semibold text-gray-900 dark:text-white">
{survey.responses}
</p>
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<div className="p-1.5 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
<FaClock className="w-3 h-3 text-purple-600 dark:text-purple-400" />
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.Duration')}</p>
<p className="font-semibold text-gray-900 dark:text-white">~5dk</p>
</div>
</div>
</div>
{/* Progress Bar */}
<div className="mb-4">
<div className="flex justify-between text-xs mb-1">
<span className="text-gray-600 dark:text-gray-400">{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.CompletionRate')}</span>
<span className="text-gray-800 dark:text-gray-200 font-medium">
{Math.round((survey.responses / 100) * 100)}%
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
<div
className="bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full transition-all duration-500"
style={{ width: `${Math.min((survey.responses / 100) * 100, 100)}%` }}
></div>
</div>
</div>
{/* Deadline */}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
<FaClock className="inline w-3 h-3 mr-1" />
{currentLocalDate(survey.deadline, currentLocale || 'tr')}
</p>
{/* Action Button */}
<button className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white text-sm font-medium rounded-lg transition-all duration-300 transform group-hover:scale-[1.02] shadow-sm hover:shadow-md">
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.FillSurvey')}
<FaArrowRight className="w-3 h-3 transition-transform group-hover:translate-x-1" />
</button>
</div>
</div>
)
})}
{surveys?.length === 0 && (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
<FaClipboardCheck className="w-8 h-8 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.NoActive')}
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.Intranet.Widgets.ActiveSurveys.WillAppearHere')}
</p>
</div>
)}
</div>
</div>
)
}
export default Surveys

View file

@ -0,0 +1,58 @@
import React from 'react'
import dayjs from 'dayjs'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { UserInfoViewModel } from '@/proxy/admin/models'
import { Avatar } from '@/components/ui'
import { AVATAR_URL } from '@/constants/app.constant'
const TodayBirthdays: React.FC<{ employees: UserInfoViewModel[] }> = ({ employees }) => {
const today = dayjs()
const { translate } = useLocalization();
console.log('TodayBirthdays rendered with employees:', employees)
return (
<div className="bg-gradient-to-br from-pink-50 to-purple-50 dark:from-pink-900/20 dark:to-purple-900/20 rounded-lg shadow-sm border border-pink-200 dark:border-pink-800">
<div className="p-4 border-b border-pink-200 dark:border-pink-700">
<h2 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
🎂 {translate('::App.Platform.Intranet.Widgets.TodayBirthdays.Title')}
</h2>
</div>
<div className="p-2 space-y-3">
{employees.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{employees.map((birthday, index) => (
<div
key={index}
className="flex items-center gap-2 p-2"
>
<Avatar
size={48}
shape="circle"
src={AVATAR_URL(birthday.id, birthday.tenantId)}
alt={birthday.fullName}
/>
<div className="flex-1">
<p className="text-sm font-semibold text-gray-900 dark:text-white">
{birthday.fullName}
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
{translate('::App.Platform.Intranet.Widgets.TodayBirthdays.Age', { age: today.diff(dayjs(birthday.birthDate), 'year') })} 🎉
</p>
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
{birthday.department?.name}
</p>
</div>
</div>
))}
</div>
) : (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
{translate('::App.Platform.Intranet.Widgets.TodayBirthdays.NoBirthday')}
</p>
)}
</div>
</div>
)
}
export default TodayBirthdays

View file

@ -341,7 +341,7 @@ const Chart = (props: ChartProps) => {
className="p-1 pl-6 pr-2 border border-1 outline-none text-xs text-gray-700 dark:text-gray-200 placeholder-gray-400 rounded"
/>
<Button
size="xs"
size="sm"
variant={'default'}
className="text-sm flex items-center gap-1"
onClick={async () => {
@ -353,7 +353,7 @@ const Chart = (props: ChartProps) => {
<FaSyncAlt className="w-3 h-3" /> {translate('::App.Platform.Refresh')}
</Button>
<Button
size="xs"
size="sm"
variant="default"
className="text-sm flex items-center gap-1"
onClick={() => setOpenDrawer(true)}
@ -364,7 +364,7 @@ const Chart = (props: ChartProps) => {
{checkPermission(gridDto?.gridOptions.permissionDto.u) && (
<Button
size="xs"
size="sm"
variant={'default'}
className="text-sm"
onClick={() => {

View file

@ -187,7 +187,7 @@ const ChartDrawer = ({
</span>
<Button
type="button"
size="xs"
size="sm"
variant="plain"
icon={<FaMinus />}
className="text-red-500 hover:bg-red-100"

View file

@ -160,7 +160,7 @@ const List: React.FC = () => {
gridDto?.gridOptions?.schedulerOptionDto?.textExpr &&
gridDto?.gridOptions?.schedulerOptionDto?.startDateExpr && (
<Button
size="xs"
size="sm"
variant={viewMode === 'scheduler' ? 'solid' : 'default'}
onClick={() => setLayout('scheduler')}
onMouseEnter={() => preload.scheduler()}
@ -173,7 +173,7 @@ const List: React.FC = () => {
gridDto?.gridOptions?.ganttOptionDto?.parentIdExpr &&
gridDto?.gridOptions?.ganttOptionDto?.titleExpr && (
<Button
size="xs"
size="sm"
variant={viewMode === 'gantt' ? 'solid' : 'default'}
onClick={() => setLayout('gantt')}
onMouseEnter={() => preload.gantt()}
@ -185,7 +185,7 @@ const List: React.FC = () => {
{gridDto?.gridOptions?.layoutDto.tree &&
gridDto?.gridOptions?.treeOptionDto?.parentIdExpr && (
<Button
size="xs"
size="sm"
variant={viewMode === 'tree' ? 'solid' : 'default'}
onClick={() => setLayout('tree')}
onMouseEnter={() => preload.tree()}
@ -195,7 +195,7 @@ const List: React.FC = () => {
)}
<Button
size="xs"
size="sm"
variant={viewMode === 'grid' ? 'solid' : 'default'}
onClick={() => setLayout('grid')}
onMouseEnter={() => preload.grid()}
@ -205,7 +205,7 @@ const List: React.FC = () => {
{gridDto.gridOptions.layoutDto.pivot && (
<Button
size="xs"
size="sm"
variant={viewMode === 'pivot' ? 'solid' : 'default'}
onClick={() => setLayout('pivot')}
onMouseEnter={() => preload.pivot()}
@ -216,7 +216,7 @@ const List: React.FC = () => {
{gridDto.gridOptions.layoutDto.chart && (
<Button
size="xs"
size="sm"
variant={viewMode === 'chart' ? 'solid' : 'default'}
onClick={() => setLayout('chart')}
onMouseEnter={() => preload.chart()}

Some files were not shown because too many files have changed in this diff Show more