AnnouncementComment ve AnnouncementLike sistemi dahil edildi.

This commit is contained in:
Sedat ÖZTÜRK 2026-06-15 16:08:54 +03:00
parent 75381731b6
commit 56233c54df
21 changed files with 1419 additions and 20 deletions

View file

@ -19,5 +19,19 @@ public class AnnouncementDto : FullAuditedEntityDto<Guid>
public DateTime? ExpiryDate { get; set; }
public bool IsPinned { get; set; }
public int ViewCount { get; set; }
public int Likes { get; set; }
public bool IsLiked { get; set; }
public string Attachments { get; set; } = string.Empty;
public System.Collections.Generic.List<AnnouncementCommentDto> Comments { get; set; } = [];
}
public class AnnouncementCommentDto
{
public Guid Id { get; set; }
public Guid AnnouncementId { get; set; }
public Guid UserId { get; set; }
public UserInfoViewModel? User { get; set; }
public string Content { get; set; } = string.Empty;
public int Likes { get; set; }
public DateTime CreationTime { get; set; }
}

View file

@ -16,6 +16,9 @@ public interface IIntranetAppService : IApplicationService
Task<SocialCommentDto> CommentSocialPostAsync(System.Guid id, string content);
Task VoteSocialPollAsync(System.Guid postId, System.Guid optionId);
Task IncrementAnnouncementViewCountAsync(System.Guid id);
Task<List<AnnouncementCommentDto>> GetAnnouncementCommentsAsync(Guid announcementId);
Task<AnnouncementCommentDto> CreateAnnouncementCommentAsync(Guid announcementId, string content);
Task<AnnouncementDto> LikeAnnouncementAsync(Guid id);
Task<List<EventCommentDto>> GetEventCommentsAsync(Guid eventId);
Task<EventCommentDto> CreateEventCommentAsync(Guid eventId, string content);
Task<EventDto> LikeEventAsync(Guid id);

View file

@ -45,6 +45,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly IRepository<SocialPollOption, Guid> _socialPollOptionRepository;
private readonly IRepository<EventComment, Guid> _eventCommentRepository;
private readonly IRepository<EventLike, Guid> _eventLikeRepository;
private readonly IRepository<AnnouncementComment, Guid> _announcementCommentRepository;
private readonly IRepository<AnnouncementLike, Guid> _announcementLikeRepository;
public IntranetAppService(
ICurrentTenant currentTenant,
@ -67,7 +69,9 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IRepository<SocialMedia, Guid> socialMediaRepository,
IRepository<SocialPollOption, Guid> socialPollOptionRepository,
IRepository<EventComment, Guid> eventCommentRepository,
IRepository<EventLike, Guid> eventLikeRepository
IRepository<EventLike, Guid> eventLikeRepository,
IRepository<AnnouncementComment, Guid> announcementCommentRepository,
IRepository<AnnouncementLike, Guid> announcementLikeRepository
)
{
_currentTenant = currentTenant;
@ -90,6 +94,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_socialPollOptionRepository = socialPollOptionRepository;
_eventCommentRepository = eventCommentRepository;
_eventLikeRepository = eventLikeRepository;
_announcementCommentRepository = announcementCommentRepository;
_announcementLikeRepository = announcementLikeRepository;
}
[UnitOfWork]
@ -368,22 +374,67 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
var announcements = await _announcementRepository.GetListAsync();
var announcementDtos = new List<AnnouncementDto>();
if (announcements.Count == 0)
return announcementDtos;
var announcementIds = announcements.Select(a => a.Id).ToList();
var commentsQueryable = await _announcementCommentRepository.GetQueryableAsync();
var allComments = await AsyncExecuter.ToListAsync(
commentsQueryable.Where(c => announcementIds.Contains(c.AnnouncementId)).OrderBy(c => c.CreationTime)
);
var likesQueryable = await _announcementLikeRepository.GetQueryableAsync();
var allLikes = await AsyncExecuter.ToListAsync(
likesQueryable.Where(l => announcementIds.Contains(l.AnnouncementId))
);
var likedAnnouncementIds = allLikes
.Where(l => l.UserId == CurrentUser.Id)
.Select(l => l.AnnouncementId)
.ToHashSet();
var userIds = announcements
.Where(a => a.UserId.HasValue)
.Select(a => a.UserId!.Value)
.Union(allComments.Select(c => c.UserId))
.Distinct()
.ToList();
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
var fallbackUser = await GetDashboardFallbackUserAsync(departmentDict, jobPositionDict);
var users = await _identityUserRepository.GetListAsync();
var userDict = users
.Where(u => userIds.Contains(u.Id))
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
var commentsByAnnouncement = allComments
.GroupBy(c => c.AnnouncementId)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var announcement in announcements)
{
var dto = ObjectMapper.Map<Announcement, AnnouncementDto>(announcement);
if (announcement.UserId.HasValue)
if (announcement.UserId.HasValue && userDict.TryGetValue(announcement.UserId.Value, out var announcementUser))
{
var user = await _identityUserRepository.FindAsync(announcement.UserId.Value);
if (user != null)
{
dto.User = MapUserInfoViewModel(user, departmentDict, jobPositionDict);
}
dto.User = announcementUser;
}
dto.IsLiked = likedAnnouncementIds.Contains(announcement.Id);
dto.Comments = commentsByAnnouncement.TryGetValue(announcement.Id, out var comments)
? comments.Select(c => new AnnouncementCommentDto
{
Id = c.Id,
AnnouncementId = c.AnnouncementId,
UserId = c.UserId,
User = userDict.TryGetValue(c.UserId, out var commentUser) ? commentUser : null,
Content = c.Content,
Likes = c.Likes,
CreationTime = c.CreationTime
}).ToList()
: [];
dto.User ??= fallbackUser;
announcementDtos.Add(dto);
}
@ -391,6 +442,100 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
return announcementDtos;
}
public async Task<List<AnnouncementCommentDto>> GetAnnouncementCommentsAsync(Guid announcementId)
{
var commentsQueryable = await _announcementCommentRepository.GetQueryableAsync();
var comments = await AsyncExecuter.ToListAsync(
commentsQueryable.Where(c => c.AnnouncementId == announcementId).OrderBy(c => c.CreationTime)
);
if (comments.Count == 0)
return [];
var userIds = comments.Select(c => c.UserId).Distinct().ToList();
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
var users = await _identityUserRepository.GetListAsync();
var userDict = users
.Where(u => userIds.Contains(u.Id))
.ToDictionary(u => u.Id, u => MapUserInfoViewModel(u, departmentDict, jobPositionDict));
return comments.Select(c => new AnnouncementCommentDto
{
Id = c.Id,
AnnouncementId = c.AnnouncementId,
UserId = c.UserId,
User = userDict.TryGetValue(c.UserId, out var u) ? u : null,
Content = c.Content,
Likes = c.Likes,
CreationTime = c.CreationTime
}).ToList();
}
public async Task<AnnouncementCommentDto> CreateAnnouncementCommentAsync(Guid announcementId, string content)
{
var comment = new AnnouncementComment
{
AnnouncementId = announcementId,
UserId = CurrentUser.Id ?? Guid.Empty,
Content = content,
Likes = 0
};
comment = await _announcementCommentRepository.InsertAsync(comment, autoSave: true);
var (departmentDict, jobPositionDict) = await GetUserLookupDictionariesAsync();
var user = await _identityUserRepository.FindAsync(comment.UserId);
var userViewModel = user != null ? MapUserInfoViewModel(user, departmentDict, jobPositionDict) : null;
return new AnnouncementCommentDto
{
Id = comment.Id,
AnnouncementId = comment.AnnouncementId,
UserId = comment.UserId,
User = userViewModel,
Content = comment.Content,
Likes = comment.Likes,
CreationTime = comment.CreationTime
};
}
[HttpPost("api/app/intranet/like-announcement")]
public async Task<AnnouncementDto> LikeAnnouncementAsync(Guid id)
{
var announcement = await _announcementRepository.GetAsync(id);
var likeQueryable = await _announcementLikeRepository.GetQueryableAsync();
var existingLike = await AsyncExecuter.FirstOrDefaultAsync(
likeQueryable.Where(l => l.AnnouncementId == id && l.UserId == CurrentUser.Id));
bool isNowLiked;
if (existingLike != null)
{
await _announcementLikeRepository.DeleteAsync(existingLike.Id);
announcement.Likes = Math.Max(0, announcement.Likes - 1);
isNowLiked = false;
}
else
{
await _announcementLikeRepository.InsertAsync(new AnnouncementLike(Guid.NewGuid())
{
AnnouncementId = id,
UserId = CurrentUser.Id,
});
announcement.Likes++;
isNowLiked = true;
}
await _announcementRepository.UpdateAsync(announcement, autoSave: true);
return new AnnouncementDto
{
Id = announcement.Id,
Likes = announcement.Likes,
IsLiked = isNowLiked,
};
}
private async Task<List<SurveyDto>> GetSurveysAsync()
{
var queryable = await _surveyRepository.GetQueryableAsync();

View file

@ -6674,7 +6674,7 @@
},
{
"resourceName": "Platform",
"key": "App.Intranet.Events.EventComment",
"key": "App.Intranet.Events.NoComment",
"tr": "Henüz yorum yok. İlk yorumu sen yap!",
"en": "No comments yet. Be the first to comment!"
},
@ -6720,6 +6720,12 @@
"tr": "Etkinlik Beğenileri",
"en": "Event Likes"
},
{
"resourceName": "Platform",
"key": "App.Intranet.Events.EventComment",
"tr": "Etkinlik Yorumları",
"en": "Event Comments"
},
{
"resourceName": "Platform",
"key": "App.Hr.Training",
@ -13484,6 +13490,30 @@
}
],
"LanguageFieldTitles": [
{
"resourceName": "Platform",
"key": "App.Intranet.Announcement.AnnouncementLike",
"en": "Announcements Like",
"tr": "Duyuru Beğenileri"
},
{
"resourceName": "Platform",
"key": "App.Intranet.Announcement.AnnouncementComment",
"en": "Announcements Comments",
"tr": "Duyuru Yorumları"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.AnnouncementId",
"en": "Announcement Number",
"tr": "Duyuru No"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.EventId",
"en": "Event Number",
"tr": "Olay No"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.Photos",

View file

@ -84,6 +84,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(AbpIdentity.PermissionGroups.Create, AbpIdentity.PermissionGroups.Default, AbpIdentity.PermissionGroups.Update, AbpIdentity.PermissionGroups.Delete, AbpIdentity.PermissionGroups.Export, AbpIdentity.PermissionGroups.Import, AbpIdentity.PermissionGroups.Note),
PagerOptionJson = DefaultPagerOptionJson,
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 350, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
{
@ -215,6 +216,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
PermissionJson = DefaultPermissionJson(AbpIdentity.Permissions.Create, listFormName, AbpIdentity.Permissions.Update, AbpIdentity.Permissions.Delete, AbpIdentity.Permissions.Export, AbpIdentity.Permissions.Import, AbpIdentity.Permissions.Note),
PagerOptionJson = DefaultPagerOptionJson,
TreeOptionJson = DefaultTreeOptionJson("Name", "ParentName", true),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 600, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
{
@ -2665,7 +2667,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
}
#endregion
#region Announncements
#region Announcement
listFormName = AppCodes.Intranet.Announcement;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
@ -2676,7 +2678,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
PageSize = 10,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = false,
ShowNote = true,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
@ -2703,7 +2705,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Announcement)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 750, 700, true, true, true, true, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 750, 700, true, true, true, true, false, true),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
{
@ -2724,6 +2726,32 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
{
new() { FieldName = "IsPinned", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "PublishDate", FieldDbType = DbType.Date, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
}),
SubFormsJson = JsonSerializer.Serialize(new List<dynamic>() {
new {
TabType = ListFormTabTypeEnum.List,
TabTitle = AppCodes.Intranet.AnnouncementLike,
Code = AppCodes.Intranet.AnnouncementLike,
Relation = new List<dynamic>() {
new {
ParentFieldName = "Id",
DbType = DbType.Guid,
ChildFieldName = "AnnouncementId"
}
}
},
new {
TabType = ListFormTabTypeEnum.List,
TabTitle = AppCodes.Intranet.AnnouncementComment,
Code = AppCodes.Intranet.AnnouncementComment,
Relation = new List<dynamic>() {
new {
ParentFieldName = "Id",
DbType = DbType.Guid,
ChildFieldName = "AnnouncementId"
}
}
}
})
}
);
@ -2910,6 +2938,234 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
}
#endregion
#region AnnouncementLike
listFormName = AppCodes.Intranet.AnnouncementLike;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
new ListForm()
{
ListFormType = ListFormTypeEnum.List,
PageSize = 10,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = false,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = true,
IsBranch = false,
IsOrganizationUnit = false,
Description = listFormName,
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.AnnouncementLike)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.AnnouncementLike)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 250, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
new EditingFormItemDto { Order = 1, DataField = "UserId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox },
]}
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
}
);
#region AnnouncementLike Fields
await _listFormFieldRepository.InsertManyAsync(new ListFormField[] {
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
CaptionName = "App.Listform.ListformField.Id",
Width = 0,
ListOrderNo = 1,
Visible = false,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "UserId",
CaptionName = "App.Listform.ListformField.UserId",
Width = 0,
ListOrderNo = 2,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto {
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = LookupQueryValues.UserValues
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "AnnouncementId",
CaptionName = "App.Listform.ListformField.AnnouncementId",
Width = 0,
ListOrderNo = 3,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
});
#endregion
}
#endregion
#region AnnouncementComment
listFormName = AppCodes.Intranet.AnnouncementComment;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
new ListForm()
{
ListFormType = ListFormTypeEnum.List,
PageSize = 10,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = false,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = true,
IsBranch = false,
IsOrganizationUnit = false,
Description = listFormName,
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.AnnouncementComment)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.AnnouncementComment)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 450, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
new EditingFormItemDto { Order = 1, DataField = "UserId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 2, DataField = "Content", ColSpan = 1, EditorType2=EditorTypes.dxTextArea },
]}
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
}
);
#region AnnouncementComment Fields
await _listFormFieldRepository.InsertManyAsync(new ListFormField[] {
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
CaptionName = "App.Listform.ListformField.Id",
Width = 0,
ListOrderNo = 1,
Visible = false,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "UserId",
CaptionName = "App.Listform.ListformField.UserId",
Width = 0,
ListOrderNo = 2,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto {
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = LookupQueryValues.UserValues
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Content",
CaptionName = "App.Listform.ListformField.Content",
Width = 0,
ListOrderNo = 3,
Visible = true,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "AnnouncementId",
CaptionName = "App.Listform.ListformField.AnnouncementId",
Width = 0,
ListOrderNo = 4,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
});
#endregion
}
#endregion
#region Survey
listFormName = AppCodes.Intranet.Survey;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
@ -4237,6 +4493,18 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ChildFieldName = "EventId"
}
}
},
new {
TabType = ListFormTabTypeEnum.List,
TabTitle = AppCodes.Intranet.EventComment,
Code = AppCodes.Intranet.EventComment,
Relation = new List<dynamic>() {
new {
ParentFieldName = "Id",
DbType = DbType.Guid,
ChildFieldName = "EventId"
}
}
}
})
}
@ -4579,5 +4847,127 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
#endregion
}
#endregion
#region EventComment
listFormName = AppCodes.Intranet.EventComment;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
new ListForm()
{
ListFormType = ListFormTypeEnum.List,
PageSize = 10,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = false,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = true,
IsBranch = false,
IsOrganizationUnit = false,
Description = listFormName,
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.EventComment)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.EventComment)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 450, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
new EditingFormItemDto { Order = 1, DataField = "UserId", ColSpan = 1, EditorType2=EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 2, DataField = "Content", ColSpan = 1, EditorType2=EditorTypes.dxTextArea },
]}
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
}
);
#region EventComment Fields
await _listFormFieldRepository.InsertManyAsync(new ListFormField[] {
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
CaptionName = "App.Listform.ListformField.Id",
Width = 0,
ListOrderNo = 1,
Visible = false,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "UserId",
CaptionName = "App.Listform.ListformField.UserId",
Width = 0,
ListOrderNo = 2,
Visible = true,
IsActive = true,
AllowSearch = true,
LookupJson = JsonSerializer.Serialize(new LookupDto {
DataSourceType = UiLookupDataSourceTypeEnum.Query,
DisplayExpr = "Name",
ValueExpr = "Key",
LookupQuery = LookupQueryValues.UserValues
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Content",
CaptionName = "App.Listform.ListformField.Content",
Width = 0,
ListOrderNo = 3,
Visible = true,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "EventId",
CaptionName = "App.Listform.ListformField.EventId",
Width = 0,
ListOrderNo = 4,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
});
#endregion
}
#endregion
}
}

View file

@ -1064,6 +1064,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
PagerOptionJson = DefaultPagerOptionJson,
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
DeleteCommand = $"DELETE FROM \"{TableNameResolver.GetFullTableName(nameof(TableNameEnum.GlobalSearch))}\" WHERE \"Id\"=@Id",
DeleteFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "Id", FieldDbType = DbType.Int32, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }

View file

@ -3754,6 +3754,60 @@
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Events.EventComment",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment.Create",
"ParentName": "App.Intranet.Events.EventComment",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment.Update",
"ParentName": "App.Intranet.Events.EventComment",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment.Delete",
"ParentName": "App.Intranet.Events.EventComment",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment.Export",
"ParentName": "App.Intranet.Events.EventComment",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Events.EventComment.Import",
"ParentName": "App.Intranet.Events.EventComment",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement",
@ -3817,6 +3871,125 @@
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.Note",
"ParentName": "App.Intranet.Announcement",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Announcement.AnnouncementLike",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike.Create",
"ParentName": "App.Intranet.Announcement.AnnouncementLike",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike.Update",
"ParentName": "App.Intranet.Announcement.AnnouncementLike",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike.Delete",
"ParentName": "App.Intranet.Announcement.AnnouncementLike",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike.Export",
"ParentName": "App.Intranet.Announcement.AnnouncementLike",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementLike.Import",
"ParentName": "App.Intranet.Announcement.AnnouncementLike",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment",
"ParentName": "App.Intranet",
"DisplayName": "App.Intranet.Announcement.AnnouncementComment",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment.Create",
"ParentName": "App.Intranet.Announcement.AnnouncementComment",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment.Update",
"ParentName": "App.Intranet.Announcement.AnnouncementComment",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment.Delete",
"ParentName": "App.Intranet.Announcement.AnnouncementComment",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment.Export",
"ParentName": "App.Intranet.Announcement.AnnouncementComment",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.Announcement.AnnouncementComment.Import",
"ParentName": "App.Intranet.Announcement.AnnouncementComment",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Intranet.SocialPost",
@ -4286,4 +4459,4 @@
"MenuGroup": "Kurs"
}
]
}
}

View file

@ -68,6 +68,8 @@ public enum TableNameEnum
Demo,
Contact,
Announcement,
AnnouncementComment,
AnnouncementLike,
Survey,
SurveyQuestion,
SurveyQuestionOption,

View file

@ -87,6 +87,8 @@ public static class TableNameResolver
// 🔹 INTRANET TABLOLARI
{ nameof(TableNameEnum.Announcement), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.AnnouncementComment), (TablePrefix.TenantByName, MenuPrefix.Administration) },
{ nameof(TableNameEnum.AnnouncementLike), (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) },

View file

@ -327,6 +327,8 @@ public static class SeedConsts
public const string Default = Prefix.App + ".Intranet";
public const string Announcement = Default + ".Announcement";
public const string AnnouncementLike = Announcement + ".AnnouncementLike";
public const string AnnouncementComment = Announcement + ".AnnouncementComment";
public const string Survey = Default + ".Survey";
public const string SurveyQuestion = Default + ".SurveyQuestion";
@ -342,6 +344,7 @@ public static class SeedConsts
public const string EventCategory = Events + ".EventCategory";
public const string Event = Events + ".Event";
public const string EventLike = Events + ".EventLike";
public const string EventComment = Events + ".EventComment";
}
public static class Definitions

View file

@ -18,6 +18,10 @@ public class Announcement : FullAuditedEntity<Guid>, IMultiTenant
public DateTime? ExpiryDate { get; set; }
public bool IsPinned { get; set; }
public int ViewCount { get; set; }
public int Likes { get; set; }
public string Attachments { get; set; }
public System.Collections.Generic.ICollection<AnnouncementComment> Comments { get; set; } = [];
public System.Collections.Generic.ICollection<AnnouncementLike> AnnouncementLikes { get; set; } = [];
}

View file

@ -0,0 +1,19 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
public class AnnouncementComment : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid AnnouncementId { get; set; }
public Announcement Announcement { get; set; }
public Guid UserId { get; set; }
public string Content { get; set; }
public int Likes { get; set; }
Guid? IMultiTenant.TenantId => TenantId;
}

View file

@ -0,0 +1,24 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Platform.Entities;
public class AnnouncementLike : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid AnnouncementId { get; set; }
public Announcement Announcement { get; set; }
public Guid? UserId { get; set; }
public AnnouncementLike(Guid id)
{
Id = id;
}
protected AnnouncementLike() { }
Guid? IMultiTenant.TenantId => TenantId;
}

View file

@ -124,6 +124,8 @@ public class PlatformDbContext :
public DbSet<MessengerConversationMessage> MessengerConversationMessages { get; set; }
public DbSet<Announcement> Announcements { get; set; }
public DbSet<AnnouncementComment> AnnouncementComments { get; set; }
public DbSet<AnnouncementLike> AnnouncementLikes { get; set; }
public DbSet<Survey> Surveys { get; set; }
public DbSet<SurveyQuestion> SurveyQuestions { get; set; }
@ -1151,6 +1153,31 @@ public class PlatformDbContext :
b.HasIndex(x => new { x.TenantId, x.Title }).IsUnique().HasFilter("[IsDeleted] = 0");
});
builder.Entity<AnnouncementComment>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.AnnouncementComment)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Content).IsRequired().HasMaxLength(8192);
b.Property(x => x.Likes).HasDefaultValue(0);
b.HasOne(x => x.Announcement)
.WithMany(x => x.Comments)
.HasForeignKey(x => x.AnnouncementId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<AnnouncementLike>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.AnnouncementLike)), Prefix.DbSchema);
b.ConfigureByConvention();
b.HasOne(x => x.Announcement)
.WithMany(x => x.AnnouncementLikes)
.HasForeignKey(x => x.AnnouncementId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Survey>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Survey)), Prefix.DbSchema);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260612183658_Initial")]
[Migration("20260615110840_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -771,6 +771,9 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Likes")
.HasColumnType("int");
b.Property<DateTime>("PublishDate")
.HasColumnType("datetime2");
@ -800,6 +803,120 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Adm_T_Announcement", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementComment", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AnnouncementId")
.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<int>("Likes")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("AnnouncementId");
b.ToTable("Adm_T_AnnouncementComment", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementLike", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AnnouncementId")
.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?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("AnnouncementId");
b.ToTable("Adm_T_AnnouncementLike", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BackgroundWorker", b =>
{
b.Property<Guid>("Id")
@ -8295,6 +8412,28 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("NotificationType");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementComment", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Announcement", "Announcement")
.WithMany("Comments")
.HasForeignKey("AnnouncementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Announcement");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementLike", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Announcement", "Announcement")
.WithMany("AnnouncementLikes")
.HasForeignKey("AnnouncementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Announcement");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BlogPost", b =>
{
b.HasOne("Sozsoft.Platform.Entities.BlogCategory", "Category")
@ -8824,6 +8963,13 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("Notifications");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Announcement", b =>
{
b.Navigation("AnnouncementLikes");
b.Navigation("Comments");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BlogCategory", b =>
{
b.Navigation("Posts");

View file

@ -497,6 +497,7 @@ namespace Sozsoft.Platform.Migrations
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),
Likes = table.Column<int>(type: "int", nullable: false),
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),
@ -2074,6 +2075,62 @@ namespace Sozsoft.Platform.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_AnnouncementComment",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
AnnouncementId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 8192, nullable: false),
Likes = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
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_AnnouncementComment", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_AnnouncementComment_Adm_T_Announcement_AnnouncementId",
column: x => x.AnnouncementId,
principalTable: "Adm_T_Announcement",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_AnnouncementLike",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
AnnouncementId = 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_AnnouncementLike", x => x.Id);
table.ForeignKey(
name: "FK_Adm_T_AnnouncementLike_Adm_T_Announcement_AnnouncementId",
column: x => x.AnnouncementId,
principalTable: "Adm_T_Announcement",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Adm_T_JobPosition",
columns: table => new
@ -3563,6 +3620,16 @@ namespace Sozsoft.Platform.Migrations
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_AnnouncementComment_AnnouncementId",
table: "Adm_T_AnnouncementComment",
column: "AnnouncementId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_AnnouncementLike_AnnouncementId",
table: "Adm_T_AnnouncementLike",
column: "AnnouncementId");
migrationBuilder.CreateIndex(
name: "IX_Adm_T_Department_TenantId_Name",
table: "Adm_T_Department",
@ -4211,7 +4278,10 @@ namespace Sozsoft.Platform.Migrations
name: "AbpUserTokens");
migrationBuilder.DropTable(
name: "Adm_T_Announcement");
name: "Adm_T_AnnouncementComment");
migrationBuilder.DropTable(
name: "Adm_T_AnnouncementLike");
migrationBuilder.DropTable(
name: "Adm_T_EventComment");
@ -4408,6 +4478,9 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "AbpUsers");
migrationBuilder.DropTable(
name: "Adm_T_Announcement");
migrationBuilder.DropTable(
name: "Adm_T_Event");

View file

@ -768,6 +768,9 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<int>("Likes")
.HasColumnType("int");
b.Property<DateTime>("PublishDate")
.HasColumnType("datetime2");
@ -797,6 +800,120 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Adm_T_Announcement", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementComment", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AnnouncementId")
.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<int>("Likes")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("AnnouncementId");
b.ToTable("Adm_T_AnnouncementComment", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementLike", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AnnouncementId")
.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?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("AnnouncementId");
b.ToTable("Adm_T_AnnouncementLike", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BackgroundWorker", b =>
{
b.Property<Guid>("Id")
@ -8292,6 +8409,28 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("NotificationType");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementComment", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Announcement", "Announcement")
.WithMany("Comments")
.HasForeignKey("AnnouncementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Announcement");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.AnnouncementLike", b =>
{
b.HasOne("Sozsoft.Platform.Entities.Announcement", "Announcement")
.WithMany("AnnouncementLikes")
.HasForeignKey("AnnouncementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Announcement");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BlogPost", b =>
{
b.HasOne("Sozsoft.Platform.Entities.BlogCategory", "Category")
@ -8821,6 +8960,13 @@ namespace Sozsoft.Platform.Migrations
b.Navigation("Notifications");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.Announcement", b =>
{
b.Navigation("AnnouncementLikes");
b.Navigation("Comments");
});
modelBuilder.Entity("Sozsoft.Platform.Entities.BlogCategory", b =>
{
b.Navigation("Posts");

View file

@ -36,6 +36,16 @@ export interface EventCommentDto {
likes: number
}
export interface AnnouncementCommentDto {
id: string
announcementId: string
userId: string
user: UserInfoViewModel
content: string
creationTime: Date
likes: number
}
// Doküman (FileItemDto ile uyumlu)
export interface DocumentDto {
id: string
@ -68,6 +78,9 @@ export interface AnnouncementDto {
expiryDate?: Date
isPinned: boolean
viewCount: number
likes: number
isLiked: boolean
comments: AnnouncementCommentDto[]
departments?: string[]
attachments?: { name: string; url: string; size: string }[]
}

View file

@ -1,4 +1,12 @@
import { EventCommentDto, EventDto, IntranetDashboardDto, SocialCommentDto, SocialPostDto } from '@/proxy/intranet/models'
import {
AnnouncementCommentDto,
AnnouncementDto,
EventCommentDto,
EventDto,
IntranetDashboardDto,
SocialCommentDto,
SocialPostDto,
} from '@/proxy/intranet/models'
import apiService, { Config } from './api.service'
export interface CreateSocialPostInput {
@ -106,6 +114,39 @@ export class IntranetService {
{ apiName: this.apiName, ...config },
)
getAnnouncementComments = (announcementId: string, config?: Partial<Config>) =>
apiService.fetchData<AnnouncementCommentDto[]>(
{
method: 'GET',
url: `/api/app/intranet/announcement-comments/${announcementId}`,
},
{ apiName: this.apiName, ...config },
)
createAnnouncementComment = (
announcementId: string,
content: string,
config?: Partial<Config>,
) =>
apiService.fetchData<AnnouncementCommentDto>(
{
method: 'POST',
url: `/api/app/intranet/announcement-comment/${announcementId}`,
params: { content },
},
{ apiName: this.apiName, ...config },
)
likeAnnouncement = (id: string, config?: Partial<Config>) =>
apiService.fetchData<AnnouncementDto>(
{
method: 'POST',
url: `/api/app/intranet/like-announcement`,
params: { id },
},
{ apiName: this.apiName, ...config },
)
getEventComments = (eventId: string, config?: Partial<Config>) =>
apiService.fetchData<EventCommentDto[]>(
{

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { motion, AnimatePresence } from 'framer-motion'
@ -9,15 +9,22 @@ import {
FaChevronLeft,
FaChevronRight,
FaExpand,
FaCommentAlt,
FaPaperPlane,
FaHeart,
} from 'react-icons/fa'
import { Dialog } from '@/components/ui'
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
import { AnnouncementDto } from '@/proxy/intranet/models'
import { AnnouncementCommentDto, 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'
import { intranetService } from '@/services/intranet.service'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
interface AnnouncementModalProps {
announcement: AnnouncementDto
@ -31,6 +38,13 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
const [activePhoto, setActivePhoto] = useState(0)
const [lightboxOpen, setLightboxOpen] = useState(false)
const [lightboxIndex, setLightboxIndex] = useState(0)
const [comments, setComments] = useState<AnnouncementCommentDto[]>(announcement.comments || [])
const [commentText, setCommentText] = useState('')
const [submitting, setSubmitting] = useState(false)
const [likes, setLikes] = useState(announcement.likes ?? 0)
const [isLiked, setIsLiked] = useState(announcement.isLiked ?? false)
const [liking, setLiking] = useState(false)
const commentsEndRef = useRef<HTMLDivElement>(null)
const openLightbox = (idx: number) => {
setLightboxIndex(idx)
@ -50,6 +64,60 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
intranetService.incrementAnnouncementViewCount(announcement.id)
}, [announcement.id])
useEffect(() => {
intranetService.getAnnouncementComments(announcement.id).then((res) => {
if (res.data) setComments(res.data)
})
}, [announcement.id])
const scrollToBottom = () => {
commentsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}
const handleSubmitComment = async () => {
if (!commentText.trim() || submitting) return
setSubmitting(true)
try {
const res = await intranetService.createAnnouncementComment(
announcement.id,
commentText.trim(),
)
if (res.data) {
setComments((prev) => [...prev, res.data!])
setCommentText('')
setTimeout(scrollToBottom, 100)
}
} finally {
setSubmitting(false)
}
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSubmitComment()
}
}
const handleLike = async () => {
if (liking) return
setLiking(true)
setIsLiked((prev) => !prev)
setLikes((prev) => (isLiked ? Math.max(0, prev - 1) : prev + 1))
try {
const res = await intranetService.likeAnnouncement(announcement.id)
if (res.data) {
setLikes(res.data.likes ?? 0)
setIsLiked(res.data.isLiked ?? false)
}
} catch {
setIsLiked((prev) => !prev)
setLikes((prev) => (isLiked ? prev + 1 : Math.max(0, prev - 1)))
} finally {
setLiking(false)
}
}
const announcementUser = announcement.user ?? {}
const announcementUserName =
announcementUser.fullName ||
@ -129,6 +197,19 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
{announcement.viewCount}{' '}
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Views')}
</span>
<button
type="button"
onClick={handleLike}
disabled={liking}
className={`flex items-center gap-2 border-0 transition-colors text-xs font-medium ${
isLiked
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/40'
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
<FaHeart className={`w-4 h-4 ${isLiked ? 'text-red-500' : ''}`} />
{likes > 0 && likes}
</button>
</div>
</div>
</div>
@ -272,13 +353,75 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
</div>
</div>
)}
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-2 mb-2">
<FaCommentAlt className="w-4 h-4 text-blue-500" />
{translate('::App.Intranet.Events.Comments')} ({comments.length})
</h3>
<div className="max-h-64 overflow-y-auto space-y-3">
{comments.length === 0 ? (
<p className="text-sm text-gray-400 dark:text-gray-500 text-center">
{translate('::App.Intranet.Events.NoComment')}
</p>
) : (
comments.map((comment) => (
<div key={comment.id} className="flex gap-3">
{comment.user && (
<Avatar
size={32}
shape="circle"
src={AVATAR_URL(comment.user.id, comment.user.tenantId)}
className="flex-shrink-0"
/>
)}
<div className="flex-1 min-w-0">
<div className="bg-gray-50 dark:bg-gray-700/60 rounded-xl px-3 py-2">
<p className="text-xs font-semibold text-gray-800 dark:text-gray-200">
{comment.user?.fullName ?? 'Kullanıcı'}
</p>
<p className="text-sm text-gray-700 dark:text-gray-300 mt-0.5 whitespace-pre-line">
{comment.content}
</p>
</div>
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1 px-1">
{dayjs(comment.creationTime).fromNow()}
</p>
</div>
</div>
))
)}
<div ref={commentsEndRef} />
</div>
</div>
</div>
</Dialog.Body>
<Dialog.Footer className="pt-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
<Dialog.Footer className="p-1 pt-2 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 flex items-end gap-3">
<div className="flex flex-1 gap-2 items-end">
<textarea
value={commentText}
autoFocus
onChange={(e) => setCommentText(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={translate('::App.Platform.Intranet.SocialWall.PostItem.CommentPlaceholder')}
rows={1}
className="flex-1 resize-none px-3 py-2 text-sm rounded-xl border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors"
/>
<button
type="button"
onClick={handleSubmitComment}
disabled={!commentText.trim() || submitting}
className="p-2.5 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl transition-colors flex-shrink-0"
>
<FaPaperPlane className="w-4 h-4" />
</button>
</div>
<button
onClick={onClose}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
type="button"
className="px-2 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm font-medium flex-shrink-0"
>
{translate('::App.Platform.Close')}
</button>

View file

@ -284,7 +284,7 @@ function EventModalContent({ event, onClose }: EventModalProps) {
<div className="max-h-64 overflow-y-auto">
{comments.length === 0 ? (
<p className="text-sm text-gray-400 dark:text-gray-500 text-center">
{translate('::App.Intranet.Events.EventComment')}
{translate('::App.Intranet.Events.NoComment')}
</p>
) : (
comments.map((comment) => (