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 ExpensesDto Expenses { get; set; } = new ExpensesDto();
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<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,7 +41,9 @@ 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;
@ -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);
// Departments string'ini array'e çevir (pipe ile ayrılmış ID'ler)
if (!string.IsNullOrEmpty(announcement.Departments))
{
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);
}
}
}
var tenantId = _currentTenant.Id?.ToString() ?? "host";
var fullPath = Path.Combine(cdnBasePath, tenantId);
if (!string.IsNullOrEmpty(folderPath))
dto.Departments = departmentNames.ToArray();
}
else
{
fullPath = Path.Combine(fullPath, folderPath);
dto.Departments = [];
}
if (!Directory.Exists(fullPath))
{
Logger.LogWarning($"Directory not found: {fullPath}");
return items;
announcementDtos.Add(dto);
}
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"
};
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"
};
}
}

View file

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

View file

@ -1,6 +1,4 @@
// Domain/Entities/Announcement.cs
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
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 {
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ıı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ıı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[]
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 }[]
}

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"
// 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

View file

@ -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':

View file

@ -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"

View file

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

View file

@ -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'