Intranet AppService Announcement

This commit is contained in:
Sedat ÖZTÜRK 2025-10-29 14:57:08 +03:00
parent bdf67ec1da
commit e67b8220ec
13 changed files with 406 additions and 305 deletions

View file

@ -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; }
}

View file

@ -12,5 +12,6 @@ public class IntranetDashboardDto
public List<TrainingDto> Trainings { get; set; } = []; public List<TrainingDto> Trainings { get; set; } = [];
public ExpensesDto Expenses { get; set; } = new ExpensesDto(); public ExpensesDto Expenses { get; set; } = new ExpensesDto();
public List<FileItemDto> Documents { get; set; } = []; public List<FileItemDto> Documents { get; set; } = [];
public List<AnnouncementDto> Announcements { get; set; } = [];
} }

View file

@ -28,6 +28,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly IRepository<Reservation, Guid> _reservationRepository; private readonly IRepository<Reservation, Guid> _reservationRepository;
private readonly IRepository<Training, Guid> _trainingRepository; private readonly IRepository<Training, Guid> _trainingRepository;
private readonly IRepository<Expense, Guid> _expenseRepository; private readonly IRepository<Expense, Guid> _expenseRepository;
private readonly IRepository<Announcement, Guid> _announcementRepository;
private readonly IRepository<Department, Guid> _departmentRepository;
public IntranetAppService( public IntranetAppService(
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
@ -39,8 +41,10 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IRepository<Visitor, Guid> visitorRepository, IRepository<Visitor, Guid> visitorRepository,
IRepository<Reservation, Guid> reservationRepository, IRepository<Reservation, Guid> reservationRepository,
IRepository<Training, Guid> trainingRepository, IRepository<Training, Guid> trainingRepository,
IRepository<Expense, Guid> expenseRepository IRepository<Expense, Guid> expenseRepository,
) IRepository<Announcement, Guid> announcementRepository,
IRepository<Department, Guid> departmentRepository
)
{ {
_currentTenant = currentTenant; _currentTenant = currentTenant;
_blobContainer = blobContainer; _blobContainer = blobContainer;
@ -52,6 +56,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_reservationRepository = reservationRepository; _reservationRepository = reservationRepository;
_trainingRepository = trainingRepository; _trainingRepository = trainingRepository;
_expenseRepository = expenseRepository; _expenseRepository = expenseRepository;
_announcementRepository = announcementRepository;
_departmentRepository = departmentRepository;
} }
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync() public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
@ -64,80 +70,59 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
Reservations = await GetReservationsAsync(), Reservations = await GetReservationsAsync(),
Trainings = await GetTrainingsAsync(), Trainings = await GetTrainingsAsync(),
Expenses = await GetExpensesAsync(), 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 announcements = await _announcementRepository
var cdnBasePath = _configuration["App:CdnPath"]; .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"); var dto = ObjectMapper.Map<Announcement, AnnouncementDto>(announcement);
return items;
}
var tenantId = _currentTenant.Id?.ToString() ?? "host"; // Departments string'ini array'e çevir (pipe ile ayrılmış ID'ler)
var fullPath = Path.Combine(cdnBasePath, tenantId); if (!string.IsNullOrEmpty(announcement.Departments))
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(), var departmentIds = announcement.Departments
Name = fileInfo.Name, .Split('|', StringSplitOptions.RemoveEmptyEntries)
Type = "file", .Select(d => d.Trim())
Size = fileInfo.Length, .ToArray();
Extension = fileInfo.Extension,
MimeType = GetMimeType(fileInfo.Extension), // ID'leri Department Name'lere çevir
CreatedAt = fileInfo.CreationTime, var departmentNames = new List<string>();
ModifiedAt = fileInfo.LastWriteTime, foreach (var deptId in departmentIds)
Path = relativePath, {
ParentId = string.Empty, if (Guid.TryParse(deptId, out var guid))
IsReadOnly = false, {
ChildCount = 0 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(); return announcementDtos;
}
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"
};
} }
private async Task<ExpensesDto> GetExpensesAsync() private async Task<ExpensesDto> GetExpensesAsync()
@ -284,4 +269,76 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
return result; 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"
};
}
} }

View file

@ -16,5 +16,8 @@ public class IntranetAutoMapperProfile : Profile
CreateMap<Training, TrainingDto>(); CreateMap<Training, TrainingDto>();
CreateMap<Currency, CurrencyDto>(); CreateMap<Currency, CurrencyDto>();
CreateMap<Expense, ExpenseDto>(); CreateMap<Expense, ExpenseDto>();
CreateMap<Announcement, AnnouncementDto>()
.ForMember(dest => dest.Departments, opt => opt.Ignore()); // Manuel olarak set ediliyor
} }
} }

View file

@ -1,6 +1,4 @@
// Domain/Entities/Announcement.cs
using System; using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;

View file

@ -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 { mockEmployees } from './mockEmployees'
import { import {
Announcement,
Certificate, Certificate,
MealMenu, MealMenu,
ShuttleRoute, 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[] = [ export const mockShuttleRoutes: ShuttleRoute[] = [
{ {
id: 'shuttle1', 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ıı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[] = [ export const mockSurveys: Survey[] = [
{ {
id: 'survey1', 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ıı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' }],
},
]

View file

@ -22,6 +22,11 @@ export interface IntranetDashboardDto {
trainings: TrainingDto[] trainings: TrainingDto[]
expenses: ExpensesDto expenses: ExpensesDto
documents: DocumentDto[] documents: DocumentDto[]
announcements: AnnouncementDto[]
// surveys: Survey[]
// mealMenu: MealMenu[]
// shuttleRoutes: ShuttleRoute[]
// priorityTasks: TaskDto[]
} }
// Etkinlik // Etkinlik
@ -224,3 +229,21 @@ export interface DocumentDto {
isReadOnly: boolean isReadOnly: boolean
childCount: number 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 }[]
}

View file

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

View file

@ -1,22 +1,5 @@
import { EmployeeDto } from "@/proxy/intranet/models" 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 // Görev
export interface Task { export interface Task {
id: string id: string

View file

@ -9,7 +9,6 @@ import isBetween from 'dayjs/plugin/isBetween'
import TodayBirthdays from './widgets/TodayBirthdays' import TodayBirthdays from './widgets/TodayBirthdays'
import UpcomingEvents from './widgets/UpcomingEvents' import UpcomingEvents from './widgets/UpcomingEvents'
import RecentDocuments from './widgets/RecentDocuments' import RecentDocuments from './widgets/RecentDocuments'
import ImportantAnnouncements from './widgets/ImportantAnnouncements'
import PriorityTasks from './widgets/PriorityTasks' import PriorityTasks from './widgets/PriorityTasks'
import MealWeeklyMenu from './widgets/MealWeeklyMenu' import MealWeeklyMenu from './widgets/MealWeeklyMenu'
import ShuttleSchedule from './widgets/ShuttleSchedule' import ShuttleSchedule from './widgets/ShuttleSchedule'
@ -31,11 +30,12 @@ import AnnouncementDetailModal from './modals/AnnouncementDetailModal'
// Social Wall // Social Wall
import SocialWall from './SocialWall' import SocialWall from './SocialWall'
import { Announcement, Survey, SurveyAnswer } from '@/types/intranet' import { Survey, SurveyAnswer } from '@/types/intranet'
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission' 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 { intranetService } from '@/services/intranet.service'
import Announcements from './widgets/Announcements'
dayjs.locale('tr') dayjs.locale('tr')
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
@ -45,7 +45,7 @@ const WIDGET_ORDER_KEY = 'dashboard-widget-order'
const IntranetDashboard: React.FC = () => { const IntranetDashboard: React.FC = () => {
const { checkPermission } = usePermission() 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 [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
const [showSurveyModal, setShowSurveyModal] = useState(false) const [showSurveyModal, setShowSurveyModal] = useState(false)
const [showLeaveModal, setShowLeaveModal] = useState(false) const [showLeaveModal, setShowLeaveModal] = useState(false)
@ -111,7 +111,7 @@ const IntranetDashboard: React.FC = () => {
const widgetMetadata = [ const widgetMetadata = [
{ id: 'upcoming-events', permission: 'App.Intranet.Events.Event.Widget', column: 'left' }, { id: 'upcoming-events', permission: 'App.Intranet.Events.Event.Widget', column: 'left' },
{ id: 'today-birthdays', permission: 'App.Hr.Employee.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: 'upcoming-trainings', permission: 'App.Hr.Training.Widget', column: 'left' },
{ id: 'active-reservations', permission: 'App.Intranet.Reservation.Widget', column: 'left' }, { id: 'active-reservations', permission: 'App.Intranet.Reservation.Widget', column: 'left' },
{ id: 'active-surveys', permission: 'App.Intranet.Survey.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: 'expense-management', permission: 'App.Hr.Expense.Widget', column: 'left' },
{ id: 'social-wall', permission: 'App.Intranet.SocialPost.Widget', column: 'center' }, { id: 'social-wall', permission: 'App.Intranet.SocialPost.Widget', column: 'center' },
{ {
id: 'important-announcements', id: 'announcements',
permission: 'App.Intranet.Announcement.Widget', permission: 'App.Intranet.Announcement.Widget',
column: 'right', column: 'right',
}, },
@ -272,15 +272,14 @@ const IntranetDashboard: React.FC = () => {
onNewExpense={() => setShowExpenseModal(true)} onNewExpense={() => setShowExpenseModal(true)}
/> />
) )
case 'documents':
case 'recent-documents':
return <RecentDocuments documents={intranetDashboard?.documents || []} /> return <RecentDocuments documents={intranetDashboard?.documents || []} />
case 'announcements':
return <Announcements announcements={intranetDashboard?.announcements || []} onAnnouncementClick={setSelectedAnnouncement} />
case 'active-surveys': case 'active-surveys':
return <ActiveSurveys onTakeSurvey={handleTakeSurvey} /> return <ActiveSurveys onTakeSurvey={handleTakeSurvey} />
case 'social-wall': case 'social-wall':
return <SocialWall /> return <SocialWall />
case 'important-announcements':
return <ImportantAnnouncements onAnnouncementClick={setSelectedAnnouncement} />
case 'priority-tasks': case 'priority-tasks':
return <PriorityTasks /> return <PriorityTasks />
case 'meal-weekly-menu': case 'meal-weekly-menu':

View file

@ -2,10 +2,10 @@ import React from 'react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { FaTimes, FaEye, FaClipboard } from 'react-icons/fa' import { FaTimes, FaEye, FaClipboard } from 'react-icons/fa'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Announcement } from '@/types/intranet' import { AnnouncementDto } from '@/proxy/intranet/models'
interface AnnouncementDetailModalProps { interface AnnouncementDetailModalProps {
announcement: Announcement announcement: AnnouncementDto
onClose: () => void onClose: () => void
} }
@ -71,13 +71,13 @@ const AnnouncementDetailModal: React.FC<AnnouncementDetailModalProps> = ({ annou
{/* Author Info */} {/* Author Info */}
<div className="flex items-center gap-3 mt-4"> <div className="flex items-center gap-3 mt-4">
<img <img
src={announcement.author.avatar} src={announcement.employee.avatar}
alt={announcement.author.fullName} alt={announcement.employee.fullName}
className="w-12 h-12 rounded-full" className="w-12 h-12 rounded-full"
/> />
<div> <div>
<p className="font-semibold text-gray-900 dark:text-white"> <p className="font-semibold text-gray-900 dark:text-white">
{announcement.author.fullName} {announcement.employee.fullName}
</p> </p>
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400"> <div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
<span> <span>
@ -154,7 +154,7 @@ const AnnouncementDetailModal: React.FC<AnnouncementDetailModalProps> = ({ annou
Hedef Departmanlar Hedef Departmanlar
</h3> </h3>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{announcement.departments.map((dept, idx) => ( {announcement.departments?.map((dept, idx) => (
<span <span
key={idx} 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" className="px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm rounded-full"

View file

@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import { FaBell, FaEye } from 'react-icons/fa' import { FaBell, FaClipboardCheck, FaEye } from 'react-icons/fa'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { mockAnnouncements } from '../../../mocks/mockIntranet' import { AnnouncementDto } from '@/proxy/intranet/models'
import { Announcement } from '@/types/intranet'
interface ImportantAnnouncementsProps { interface AnnouncementsProps {
onAnnouncementClick: (announcement: Announcement) => void announcements: AnnouncementDto[]
onAnnouncementClick: (announcement: AnnouncementDto) => void
} }
const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnouncementClick }) => { const Announcements: React.FC<AnnouncementsProps> = ({ announcements, onAnnouncementClick }) => {
const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3) const pinnedAnnouncements = announcements.filter((a) => a.isPinned).slice(0, 3)
const getCategoryColor = (category: string) => { const getCategoryColor = (category: string) => {
const colors: Record<string, string> = { const colors: Record<string, string> = {
@ -41,8 +41,8 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<img <img
src={announcement.author.avatar} src={announcement.employee.avatar}
alt={announcement.author.fullName} alt={announcement.employee.fullName}
className="w-10 h-10 rounded-full" className="w-10 h-10 rounded-full"
/> />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@ -53,18 +53,14 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
<span <span
className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`} className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}
> >
{announcement.category === 'general' && 'Genel'} {announcement.category}
{announcement.category === 'hr' && 'İK'}
{announcement.category === 'it' && 'IT'}
{announcement.category === 'event' && 'Etkinlik'}
{announcement.category === 'urgent' && 'Acil'}
</span> </span>
</div> </div>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2"> <p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{announcement.excerpt} {announcement.excerpt}
</p> </p>
<div className="flex items-center gap-4 mt-3 text-xs text-gray-500 dark:text-gray-400"> <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></span>
<span>{dayjs(announcement.publishDate).fromNow()}</span> <span>{dayjs(announcement.publishDate).fromNow()}</span>
<span></span> <span></span>
@ -77,9 +73,23 @@ const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnou
</div> </div>
</div> </div>
))} ))}
{pinnedAnnouncements.length === 0 && (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
<FaClipboardCheck className="w-8 h-8 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
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>
</div> </div>
) )
} }
export default ImportantAnnouncements export default Announcements

View file

@ -1,76 +1,8 @@
import React from 'react' import React from 'react'
import { import { FaFileAlt, FaDownload } from 'react-icons/fa'
FaFileAlt,
FaDownload,
FaFilePdf,
FaFileWord,
FaFileExcel,
FaFilePowerpoint,
FaFileImage,
FaFileArchive,
FaFileCode,
} from 'react-icons/fa'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { DocumentDto } from '@/proxy/intranet/models' import { DocumentDto } from '@/proxy/intranet/models'
import { getFileIcon, getFileType } from '@/proxy/intranet/utils'
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'
}
}
const formatFileSize = (bytes: number): string => { const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B' if (bytes === 0) return '0 B'