Intranet AppService Announcement
This commit is contained in:
parent
bdf67ec1da
commit
e67b8220ec
13 changed files with 406 additions and 305 deletions
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Kurs.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? EmployeeId { get; set; }
|
||||
public EmployeeDto Employee { get; set; }
|
||||
public DateTime PublishDate { get; set; }
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
public bool IsPinned { get; set; }
|
||||
public int ViewCount { get; set; }
|
||||
public string[] Departments { get; set; }
|
||||
public string Attachments { get; set; }
|
||||
}
|
||||
|
|
@ -12,5 +12,6 @@ public class IntranetDashboardDto
|
|||
public List<TrainingDto> Trainings { get; set; } = [];
|
||||
public ExpensesDto Expenses { get; set; } = new ExpensesDto();
|
||||
public List<FileItemDto> Documents { get; set; } = [];
|
||||
public List<AnnouncementDto> Announcements { get; set; } = [];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|||
private readonly IRepository<Reservation, Guid> _reservationRepository;
|
||||
private readonly IRepository<Training, Guid> _trainingRepository;
|
||||
private readonly IRepository<Expense, Guid> _expenseRepository;
|
||||
private readonly IRepository<Announcement, Guid> _announcementRepository;
|
||||
private readonly IRepository<Department, Guid> _departmentRepository;
|
||||
|
||||
public IntranetAppService(
|
||||
ICurrentTenant currentTenant,
|
||||
|
|
@ -39,8 +41,10 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|||
IRepository<Visitor, Guid> visitorRepository,
|
||||
IRepository<Reservation, Guid> reservationRepository,
|
||||
IRepository<Training, Guid> trainingRepository,
|
||||
IRepository<Expense, Guid> expenseRepository
|
||||
)
|
||||
IRepository<Expense, Guid> expenseRepository,
|
||||
IRepository<Announcement, Guid> announcementRepository,
|
||||
IRepository<Department, Guid> departmentRepository
|
||||
)
|
||||
{
|
||||
_currentTenant = currentTenant;
|
||||
_blobContainer = blobContainer;
|
||||
|
|
@ -52,6 +56,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|||
_reservationRepository = reservationRepository;
|
||||
_trainingRepository = trainingRepository;
|
||||
_expenseRepository = expenseRepository;
|
||||
_announcementRepository = announcementRepository;
|
||||
_departmentRepository = departmentRepository;
|
||||
}
|
||||
|
||||
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
|
||||
|
|
@ -64,80 +70,59 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|||
Reservations = await GetReservationsAsync(),
|
||||
Trainings = await GetTrainingsAsync(),
|
||||
Expenses = await GetExpensesAsync(),
|
||||
Documents = await GetIntranetDocumentsAsync(BlobContainerNames.Intranet)
|
||||
Documents = await GetIntranetDocumentsAsync(BlobContainerNames.Intranet),
|
||||
Announcements = await GetAnnouncementsAsync()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<FileItemDto>> GetIntranetDocumentsAsync(string folderPath)
|
||||
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
|
||||
{
|
||||
var items = new List<FileItemDto>();
|
||||
var cdnBasePath = _configuration["App:CdnPath"];
|
||||
var announcements = await _announcementRepository
|
||||
.WithDetailsAsync(e => e.Employee)
|
||||
.ContinueWith(t => t.Result.ToList());
|
||||
|
||||
if (string.IsNullOrEmpty(cdnBasePath))
|
||||
var announcementDtos = new List<AnnouncementDto>();
|
||||
|
||||
// Tüm departmanları bir kez çek (performans için)
|
||||
var allDepartments = await _departmentRepository.GetListAsync();
|
||||
|
||||
foreach (var announcement in announcements)
|
||||
{
|
||||
Logger.LogWarning("CDN path is not configured");
|
||||
return items;
|
||||
}
|
||||
var dto = ObjectMapper.Map<Announcement, AnnouncementDto>(announcement);
|
||||
|
||||
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
|
||||
// Departments string'ini array'e çevir (pipe ile ayrılmış ID'ler)
|
||||
if (!string.IsNullOrEmpty(announcement.Departments))
|
||||
{
|
||||
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
|
||||
});
|
||||
var departmentIds = announcement.Departments
|
||||
.Split('|', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(d => d.Trim())
|
||||
.ToArray();
|
||||
|
||||
// ID'leri Department Name'lere çevir
|
||||
var departmentNames = new List<string>();
|
||||
foreach (var deptId in departmentIds)
|
||||
{
|
||||
if (Guid.TryParse(deptId, out var guid))
|
||||
{
|
||||
var department = allDepartments.FirstOrDefault(d => d.Id == guid);
|
||||
if (department != null)
|
||||
{
|
||||
departmentNames.Add(department.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dto.Departments = departmentNames.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.Departments = [];
|
||||
}
|
||||
|
||||
announcementDtos.Add(dto);
|
||||
}
|
||||
|
||||
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"
|
||||
};
|
||||
return announcementDtos;
|
||||
}
|
||||
|
||||
private async Task<ExpensesDto> GetExpensesAsync()
|
||||
|
|
@ -284,4 +269,76 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,5 +16,8 @@ public class IntranetAutoMapperProfile : Profile
|
|||
CreateMap<Training, TrainingDto>();
|
||||
CreateMap<Currency, CurrencyDto>();
|
||||
CreateMap<Expense, ExpenseDto>();
|
||||
|
||||
CreateMap<Announcement, AnnouncementDto>()
|
||||
.ForMember(dest => dest.Departments, opt => opt.Ignore()); // Manuel olarak set ediliyor
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
// Domain/Entities/Announcement.cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { DocumentDto, EventDto, ExpenseDto, ReservationDto, TrainingDto, VisitorDto } from '@/proxy/intranet/models'
|
||||
import { AnnouncementDto, DocumentDto, EventDto, ExpenseDto, ReservationDto, TrainingDto, VisitorDto } from '@/proxy/intranet/models'
|
||||
import { mockEmployees } from './mockEmployees'
|
||||
import {
|
||||
Announcement,
|
||||
Certificate,
|
||||
MealMenu,
|
||||
ShuttleRoute,
|
||||
|
|
@ -72,54 +71,6 @@ export const mockMealMenus: MealMenu[] = [
|
|||
},
|
||||
]
|
||||
|
||||
export const mockReservations: ReservationDto[] = [
|
||||
{
|
||||
id: 'res1',
|
||||
type: 'room',
|
||||
resourceName: 'Toplantı Salonu A',
|
||||
bookedBy: mockEmployees[2],
|
||||
startDate: new Date('2024-10-20T09:00:00'),
|
||||
endDate: new Date('2024-10-20T11:00:00'),
|
||||
purpose: 'Sprint Planning Toplantısı',
|
||||
status: 'approved',
|
||||
participants: 8,
|
||||
notes: 'Projeksiyon cihazı gerekli',
|
||||
},
|
||||
{
|
||||
id: 'res2',
|
||||
type: 'vehicle',
|
||||
resourceName: 'Şirket Aracı - 34 ABC 123',
|
||||
bookedBy: mockEmployees[3],
|
||||
startDate: new Date('2024-10-22T08:00:00'),
|
||||
endDate: new Date('2024-10-22T18:00:00'),
|
||||
purpose: 'Müşteri Ziyareti',
|
||||
status: 'pending',
|
||||
notes: 'Ankara çıkışı',
|
||||
},
|
||||
{
|
||||
id: 'res3',
|
||||
type: 'equipment',
|
||||
resourceName: 'Kamera ve Tripod Seti',
|
||||
bookedBy: mockEmployees[5],
|
||||
startDate: new Date('2024-10-19T14:00:00'),
|
||||
endDate: new Date('2024-10-19T17:00:00'),
|
||||
purpose: 'Ürün Tanıtım Videosu Çekimi',
|
||||
status: 'approved',
|
||||
},
|
||||
{
|
||||
id: 'res4',
|
||||
type: 'room',
|
||||
resourceName: 'Eğitim Salonu B',
|
||||
bookedBy: mockEmployees[6],
|
||||
startDate: new Date('2024-10-25T09:00:00'),
|
||||
endDate: new Date('2024-10-25T17:00:00'),
|
||||
purpose: 'Etkili İletişim Eğitimi',
|
||||
status: 'approved',
|
||||
participants: 15,
|
||||
notes: 'Tüm gün rezervasyon, öğle yemeği dahil',
|
||||
},
|
||||
]
|
||||
|
||||
export const mockShuttleRoutes: ShuttleRoute[] = [
|
||||
{
|
||||
id: 'shuttle1',
|
||||
|
|
@ -163,74 +114,6 @@ export const mockShuttleRoutes: ShuttleRoute[] = [
|
|||
},
|
||||
]
|
||||
|
||||
export const mockAnnouncements: Announcement[] = [
|
||||
{
|
||||
id: 'ann1',
|
||||
title: '🎉 Yeni Ofis Açılışı',
|
||||
content:
|
||||
'Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızı açılış törenimize davet ediyoruz.',
|
||||
excerpt: 'Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!',
|
||||
category: 'general',
|
||||
author: mockEmployees[4],
|
||||
publishDate: new Date('2024-10-15T09:00:00'),
|
||||
isPinned: true,
|
||||
viewCount: 156,
|
||||
imageUrl: 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 'ann2',
|
||||
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',
|
||||
author: mockEmployees[3],
|
||||
publishDate: new Date('2024-10-18T10:30:00'),
|
||||
expiryDate: new Date('2024-11-05'),
|
||||
isPinned: true,
|
||||
viewCount: 89,
|
||||
departments: ['Tüm Departmanlar'],
|
||||
},
|
||||
{
|
||||
id: 'ann3',
|
||||
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',
|
||||
author: mockEmployees[2],
|
||||
publishDate: new Date('2024-10-17T14:00:00'),
|
||||
isPinned: false,
|
||||
viewCount: 234,
|
||||
},
|
||||
{
|
||||
id: 'ann4',
|
||||
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',
|
||||
author: mockEmployees[0],
|
||||
publishDate: new Date('2024-10-16T11:00:00'),
|
||||
isPinned: false,
|
||||
viewCount: 67,
|
||||
departments: ['Yazılım Geliştirme'],
|
||||
},
|
||||
{
|
||||
id: 'ann5',
|
||||
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',
|
||||
author: mockEmployees[4],
|
||||
publishDate: new Date('2024-10-18T08:00:00'),
|
||||
isPinned: true,
|
||||
viewCount: 312,
|
||||
attachments: [{ name: 'Bilgi_Guvenligi_Politikasi_v2.pdf', url: '#', size: '2.4 MB' }],
|
||||
},
|
||||
]
|
||||
|
||||
export const mockSurveys: Survey[] = [
|
||||
{
|
||||
id: 'survey1',
|
||||
|
|
@ -1028,3 +911,123 @@ export const mockDocuments: DocumentDto[] = [
|
|||
},
|
||||
]
|
||||
|
||||
export const mockReservations: ReservationDto[] = [
|
||||
{
|
||||
id: 'res1',
|
||||
type: 'room',
|
||||
resourceName: 'Toplantı Salonu A',
|
||||
bookedBy: mockEmployees[2],
|
||||
startDate: new Date('2024-10-20T09:00:00'),
|
||||
endDate: new Date('2024-10-20T11:00:00'),
|
||||
purpose: 'Sprint Planning Toplantısı',
|
||||
status: 'approved',
|
||||
participants: 8,
|
||||
notes: 'Projeksiyon cihazı gerekli',
|
||||
},
|
||||
{
|
||||
id: 'res2',
|
||||
type: 'vehicle',
|
||||
resourceName: 'Şirket Aracı - 34 ABC 123',
|
||||
bookedBy: mockEmployees[3],
|
||||
startDate: new Date('2024-10-22T08:00:00'),
|
||||
endDate: new Date('2024-10-22T18:00:00'),
|
||||
purpose: 'Müşteri Ziyareti',
|
||||
status: 'pending',
|
||||
notes: 'Ankara çıkışı',
|
||||
},
|
||||
{
|
||||
id: 'res3',
|
||||
type: 'equipment',
|
||||
resourceName: 'Kamera ve Tripod Seti',
|
||||
bookedBy: mockEmployees[5],
|
||||
startDate: new Date('2024-10-19T14:00:00'),
|
||||
endDate: new Date('2024-10-19T17:00:00'),
|
||||
purpose: 'Ürün Tanıtım Videosu Çekimi',
|
||||
status: 'approved',
|
||||
},
|
||||
{
|
||||
id: 'res4',
|
||||
type: 'room',
|
||||
resourceName: 'Eğitim Salonu B',
|
||||
bookedBy: mockEmployees[6],
|
||||
startDate: new Date('2024-10-25T09:00:00'),
|
||||
endDate: new Date('2024-10-25T17:00:00'),
|
||||
purpose: 'Etkili İletişim Eğitimi',
|
||||
status: 'approved',
|
||||
participants: 15,
|
||||
notes: 'Tüm gün rezervasyon, öğle yemeği dahil',
|
||||
},
|
||||
]
|
||||
|
||||
export const mockAnnouncements: AnnouncementDto[] = [
|
||||
{
|
||||
id: 'ann1',
|
||||
title: '🎉 Yeni Ofis Açılışı',
|
||||
content:
|
||||
'Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızı açılış törenimize davet ediyoruz.',
|
||||
excerpt: 'Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!',
|
||||
category: 'general',
|
||||
employeeId: mockEmployees[4].id,
|
||||
employee: mockEmployees[4],
|
||||
publishDate: new Date('2024-10-15T09:00:00'),
|
||||
isPinned: true,
|
||||
viewCount: 156,
|
||||
imageUrl: 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 'ann2',
|
||||
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',
|
||||
employeeId: mockEmployees[3].id,
|
||||
employee: mockEmployees[3],
|
||||
publishDate: new Date('2024-10-18T10:30:00'),
|
||||
expiryDate: new Date('2024-11-05'),
|
||||
isPinned: true,
|
||||
viewCount: 89,
|
||||
departments: ['Tüm Departmanlar'],
|
||||
},
|
||||
{
|
||||
id: 'ann3',
|
||||
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',
|
||||
employeeId: mockEmployees[2].id,
|
||||
employee: mockEmployees[2],
|
||||
publishDate: new Date('2024-10-17T14:00:00'),
|
||||
isPinned: false,
|
||||
viewCount: 234,
|
||||
},
|
||||
{
|
||||
id: 'ann4',
|
||||
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',
|
||||
employeeId: mockEmployees[0].id,
|
||||
employee: mockEmployees[0],
|
||||
publishDate: new Date('2024-10-16T11:00:00'),
|
||||
isPinned: false,
|
||||
viewCount: 67,
|
||||
departments: ['Yazılım Geliştirme'],
|
||||
},
|
||||
{
|
||||
id: 'ann5',
|
||||
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',
|
||||
employeeId: mockEmployees[4].id,
|
||||
employee: mockEmployees[4],
|
||||
publishDate: new Date('2024-10-18T08:00:00'),
|
||||
isPinned: true,
|
||||
viewCount: 312,
|
||||
attachments: [{ name: 'Bilgi_Guvenligi_Politikasi_v2.pdf', url: '#', size: '2.4 MB' }],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ export interface IntranetDashboardDto {
|
|||
trainings: TrainingDto[]
|
||||
expenses: ExpensesDto
|
||||
documents: DocumentDto[]
|
||||
announcements: AnnouncementDto[]
|
||||
// surveys: Survey[]
|
||||
// mealMenu: MealMenu[]
|
||||
// shuttleRoutes: ShuttleRoute[]
|
||||
// priorityTasks: TaskDto[]
|
||||
}
|
||||
|
||||
// Etkinlik
|
||||
|
|
@ -224,3 +229,21 @@ export interface DocumentDto {
|
|||
isReadOnly: boolean
|
||||
childCount: number
|
||||
}
|
||||
|
||||
// Duyuru
|
||||
export interface AnnouncementDto {
|
||||
id: string
|
||||
title: string
|
||||
excerpt: string
|
||||
content: string
|
||||
imageUrl?: string
|
||||
category: string
|
||||
employeeId: string
|
||||
employee: EmployeeDto
|
||||
publishDate: Date
|
||||
expiryDate?: Date
|
||||
isPinned: boolean
|
||||
viewCount: number
|
||||
departments?: string[]
|
||||
attachments?: { name: string; url: string; size: string }[]
|
||||
}
|
||||
|
|
|
|||
69
ui/src/proxy/intranet/utils.tsx
Normal file
69
ui/src/proxy/intranet/utils.tsx
Normal 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'
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,5 @@
|
|||
import { EmployeeDto } from "@/proxy/intranet/models"
|
||||
|
||||
// Duyuru
|
||||
export interface Announcement {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
excerpt: string
|
||||
category: 'general' | 'hr' | 'it' | 'event' | 'urgent'
|
||||
author: EmployeeDto
|
||||
publishDate: Date
|
||||
expiryDate?: Date
|
||||
isPinned: boolean
|
||||
attachments?: { name: string; url: string; size: string }[]
|
||||
departments?: string[]
|
||||
viewCount: number
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
// Görev
|
||||
export interface Task {
|
||||
id: string
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import isBetween from 'dayjs/plugin/isBetween'
|
|||
import TodayBirthdays from './widgets/TodayBirthdays'
|
||||
import UpcomingEvents from './widgets/UpcomingEvents'
|
||||
import RecentDocuments from './widgets/RecentDocuments'
|
||||
import ImportantAnnouncements from './widgets/ImportantAnnouncements'
|
||||
import PriorityTasks from './widgets/PriorityTasks'
|
||||
import MealWeeklyMenu from './widgets/MealWeeklyMenu'
|
||||
import ShuttleSchedule from './widgets/ShuttleSchedule'
|
||||
|
|
@ -31,11 +30,12 @@ import AnnouncementDetailModal from './modals/AnnouncementDetailModal'
|
|||
|
||||
// Social Wall
|
||||
import SocialWall from './SocialWall'
|
||||
import { Announcement, Survey, SurveyAnswer } from '@/types/intranet'
|
||||
import { Survey, SurveyAnswer } from '@/types/intranet'
|
||||
import { Container } from '@/components/shared'
|
||||
import { usePermission } from '@/utils/hooks/usePermission'
|
||||
import { IntranetDashboardDto } from '@/proxy/intranet/models'
|
||||
import { AnnouncementDto, IntranetDashboardDto } from '@/proxy/intranet/models'
|
||||
import { intranetService } from '@/services/intranet.service'
|
||||
import Announcements from './widgets/Announcements'
|
||||
|
||||
dayjs.locale('tr')
|
||||
dayjs.extend(relativeTime)
|
||||
|
|
@ -45,7 +45,7 @@ const WIDGET_ORDER_KEY = 'dashboard-widget-order'
|
|||
|
||||
const IntranetDashboard: React.FC = () => {
|
||||
const { checkPermission } = usePermission()
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null)
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null)
|
||||
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
|
||||
const [showSurveyModal, setShowSurveyModal] = useState(false)
|
||||
const [showLeaveModal, setShowLeaveModal] = useState(false)
|
||||
|
|
@ -111,7 +111,7 @@ const IntranetDashboard: React.FC = () => {
|
|||
const widgetMetadata = [
|
||||
{ id: 'upcoming-events', permission: 'App.Intranet.Events.Event.Widget', column: 'left' },
|
||||
{ id: 'today-birthdays', permission: 'App.Hr.Employee.Widget', column: 'left' },
|
||||
{ id: 'recent-documents', permission: 'App.Files.Widget', column: 'left' },
|
||||
{ id: 'documents', permission: 'App.Files.Widget', column: 'left' },
|
||||
{ id: 'upcoming-trainings', permission: 'App.Hr.Training.Widget', column: 'left' },
|
||||
{ id: 'active-reservations', permission: 'App.Intranet.Reservation.Widget', column: 'left' },
|
||||
{ id: 'active-surveys', permission: 'App.Intranet.Survey.Widget', column: 'left' },
|
||||
|
|
@ -119,7 +119,7 @@ const IntranetDashboard: React.FC = () => {
|
|||
{ id: 'expense-management', permission: 'App.Hr.Expense.Widget', column: 'left' },
|
||||
{ id: 'social-wall', permission: 'App.Intranet.SocialPost.Widget', column: 'center' },
|
||||
{
|
||||
id: 'important-announcements',
|
||||
id: 'announcements',
|
||||
permission: 'App.Intranet.Announcement.Widget',
|
||||
column: 'right',
|
||||
},
|
||||
|
|
@ -272,15 +272,14 @@ const IntranetDashboard: React.FC = () => {
|
|||
onNewExpense={() => setShowExpenseModal(true)}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'recent-documents':
|
||||
case 'documents':
|
||||
return <RecentDocuments documents={intranetDashboard?.documents || []} />
|
||||
case 'announcements':
|
||||
return <Announcements announcements={intranetDashboard?.announcements || []} onAnnouncementClick={setSelectedAnnouncement} />
|
||||
case 'active-surveys':
|
||||
return <ActiveSurveys onTakeSurvey={handleTakeSurvey} />
|
||||
case 'social-wall':
|
||||
return <SocialWall />
|
||||
case 'important-announcements':
|
||||
return <ImportantAnnouncements onAnnouncementClick={setSelectedAnnouncement} />
|
||||
case 'priority-tasks':
|
||||
return <PriorityTasks />
|
||||
case 'meal-weekly-menu':
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import React from 'react'
|
|||
import { motion } from 'framer-motion'
|
||||
import { FaTimes, FaEye, FaClipboard } from 'react-icons/fa'
|
||||
import dayjs from 'dayjs'
|
||||
import { Announcement } from '@/types/intranet'
|
||||
import { AnnouncementDto } from '@/proxy/intranet/models'
|
||||
|
||||
interface AnnouncementDetailModalProps {
|
||||
announcement: Announcement
|
||||
announcement: AnnouncementDto
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
|
|
@ -71,13 +71,13 @@ const AnnouncementDetailModal: React.FC<AnnouncementDetailModalProps> = ({ annou
|
|||
{/* Author Info */}
|
||||
<div className="flex items-center gap-3 mt-4">
|
||||
<img
|
||||
src={announcement.author.avatar}
|
||||
alt={announcement.author.fullName}
|
||||
src={announcement.employee.avatar}
|
||||
alt={announcement.employee.fullName}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">
|
||||
{announcement.author.fullName}
|
||||
{announcement.employee.fullName}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>
|
||||
|
|
@ -154,7 +154,7 @@ const AnnouncementDetailModal: React.FC<AnnouncementDetailModalProps> = ({ annou
|
|||
Hedef Departmanlar
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{announcement.departments.map((dept, idx) => (
|
||||
{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"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import React from 'react'
|
||||
import { FaBell, FaEye } from 'react-icons/fa'
|
||||
import { FaBell, FaClipboardCheck, FaEye } from 'react-icons/fa'
|
||||
import dayjs from 'dayjs'
|
||||
import { mockAnnouncements } from '../../../mocks/mockIntranet'
|
||||
import { Announcement } from '@/types/intranet'
|
||||
import { AnnouncementDto } from '@/proxy/intranet/models'
|
||||
|
||||
interface ImportantAnnouncementsProps {
|
||||
onAnnouncementClick: (announcement: Announcement) => void
|
||||
interface AnnouncementsProps {
|
||||
announcements: AnnouncementDto[]
|
||||
onAnnouncementClick: (announcement: AnnouncementDto) => void
|
||||
}
|
||||
|
||||
const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnouncementClick }) => {
|
||||
const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3)
|
||||
const Announcements: React.FC<AnnouncementsProps> = ({ announcements, onAnnouncementClick }) => {
|
||||
const pinnedAnnouncements = announcements.filter((a) => a.isPinned).slice(0, 3)
|
||||
|
||||
const getCategoryColor = (category: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
|
|
@ -41,8 +41,8 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
|
|||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<img
|
||||
src={announcement.author.avatar}
|
||||
alt={announcement.author.fullName}
|
||||
src={announcement.employee.avatar}
|
||||
alt={announcement.employee.fullName}
|
||||
className="w-10 h-10 rounded-full"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
|
|
@ -53,18 +53,14 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
|
|||
<span
|
||||
className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}
|
||||
>
|
||||
{announcement.category === 'general' && 'Genel'}
|
||||
{announcement.category === 'hr' && 'İK'}
|
||||
{announcement.category === 'it' && 'IT'}
|
||||
{announcement.category === 'event' && 'Etkinlik'}
|
||||
{announcement.category === 'urgent' && 'Acil'}
|
||||
{announcement.category}
|
||||
</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-4 mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>{announcement.author.fullName}</span>
|
||||
<span>{announcement.employee.fullName}</span>
|
||||
<span>•</span>
|
||||
<span>{dayjs(announcement.publishDate).fromNow()}</span>
|
||||
<span>•</span>
|
||||
|
|
@ -77,9 +73,23 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
|
|||
</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">
|
||||
Aktif duyuru bulunmuyor
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Yeni duyurular eklendiğinde burada görünecektir.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImportantAnnouncements
|
||||
export default Announcements
|
||||
|
|
@ -1,76 +1,8 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
FaFileAlt,
|
||||
FaDownload,
|
||||
FaFilePdf,
|
||||
FaFileWord,
|
||||
FaFileExcel,
|
||||
FaFilePowerpoint,
|
||||
FaFileImage,
|
||||
FaFileArchive,
|
||||
FaFileCode,
|
||||
} from 'react-icons/fa'
|
||||
import { FaFileAlt, FaDownload } from 'react-icons/fa'
|
||||
import dayjs from 'dayjs'
|
||||
import { DocumentDto } from '@/proxy/intranet/models'
|
||||
|
||||
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" />
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
import { getFileIcon, getFileType } from '@/proxy/intranet/utils'
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
|
|
|||
Loading…
Reference in a new issue