2025-10-18 22:37:20 +00:00
|
|
|
|
import React, { useState } from 'react'
|
|
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
|
|
|
|
import {
|
|
|
|
|
|
HiBell as BellIcon,
|
|
|
|
|
|
HiCalendar as CalendarIcon,
|
|
|
|
|
|
HiDocumentText as DocumentTextIcon,
|
|
|
|
|
|
HiChartBar as ChartBarIcon,
|
|
|
|
|
|
HiUserGroup as UserGroupIcon,
|
|
|
|
|
|
HiClock as ClockIcon,
|
|
|
|
|
|
HiSparkles as SparklesIcon,
|
|
|
|
|
|
HiArrowTrendingUp as ArrowTrendingUpIcon,
|
|
|
|
|
|
HiArrowTrendingDown as ArrowTrendingDownIcon,
|
|
|
|
|
|
HiXMark,
|
|
|
|
|
|
HiEye,
|
2025-10-19 07:39:10 +00:00
|
|
|
|
HiPaperClip,
|
2025-10-18 22:37:20 +00:00
|
|
|
|
} from 'react-icons/hi2'
|
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
import 'dayjs/locale/tr'
|
|
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
|
|
|
|
import isBetween from 'dayjs/plugin/isBetween'
|
|
|
|
|
|
import {
|
|
|
|
|
|
mockAnnouncements,
|
|
|
|
|
|
mockEvents,
|
|
|
|
|
|
mockBirthdays,
|
|
|
|
|
|
mockAnniversaries,
|
|
|
|
|
|
mockQuickLinks,
|
|
|
|
|
|
mockTasks,
|
2025-10-19 07:39:10 +00:00
|
|
|
|
mockDocuments,
|
|
|
|
|
|
Announcement,
|
2025-10-19 20:02:09 +00:00
|
|
|
|
} from '../../mocks/mockIntranetData'
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
|
|
|
|
|
dayjs.locale('tr')
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
|
dayjs.extend(isBetween)
|
|
|
|
|
|
|
|
|
|
|
|
const IntranetDashboard: React.FC = () => {
|
|
|
|
|
|
const [selectedDate] = useState(new Date())
|
|
|
|
|
|
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
// Bugünün etkinlikleri
|
2025-10-19 07:39:10 +00:00
|
|
|
|
const todayEvents = mockEvents.filter(
|
|
|
|
|
|
(event) => event.isPublished && dayjs(event.date).isSame(dayjs(), 'day'),
|
2025-10-18 22:37:20 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Yaklaşan etkinlikler (7 gün içinde)
|
2025-10-19 07:39:10 +00:00
|
|
|
|
const upcomingEvents = mockEvents.filter(
|
|
|
|
|
|
(event) =>
|
|
|
|
|
|
event.isPublished &&
|
|
|
|
|
|
dayjs(event.date).isAfter(dayjs()) &&
|
|
|
|
|
|
dayjs(event.date).isBefore(dayjs().add(7, 'day')),
|
2025-10-18 22:37:20 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
// Bugün doğanlar (sadece ay ve günü karşılaştır)
|
|
|
|
|
|
const todayBirthdays = mockBirthdays.filter((b) => {
|
|
|
|
|
|
const birthDate = dayjs(b.date)
|
|
|
|
|
|
const today = dayjs()
|
|
|
|
|
|
return birthDate.month() === today.month() && birthDate.date() === today.date()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Bu haftaki doğum günleri (sadece ay ve günü karşılaştır)
|
|
|
|
|
|
const weekBirthdays = mockBirthdays.filter((b) => {
|
|
|
|
|
|
const birthDate = dayjs(b.date)
|
|
|
|
|
|
const today = dayjs()
|
|
|
|
|
|
const weekStart = today.startOf('week')
|
|
|
|
|
|
const weekEnd = today.endOf('week')
|
|
|
|
|
|
|
|
|
|
|
|
// Bu yılki doğum gününü oluştur
|
|
|
|
|
|
const thisBirthday = today.year(today.year()).month(birthDate.month()).date(birthDate.date())
|
|
|
|
|
|
return thisBirthday.isBetween(weekStart, weekEnd, null, '[]')
|
|
|
|
|
|
})
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
|
|
|
|
|
// Bu ayki iş yıldönümleri
|
2025-10-19 07:39:10 +00:00
|
|
|
|
const monthAnniversaries = mockAnniversaries.filter(
|
|
|
|
|
|
(a) => dayjs(a.hireDate).month() === dayjs().month(),
|
2025-10-18 22:37:20 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Öncelikli görevler
|
2025-10-19 07:39:10 +00:00
|
|
|
|
const priorityTasks = mockTasks
|
|
|
|
|
|
.filter((t) => t.priority === 'high' || t.priority === 'urgent')
|
|
|
|
|
|
.slice(0, 3)
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
|
|
|
|
|
// Sabitlenmiş duyurular
|
2025-10-19 07:39:10 +00:00
|
|
|
|
const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3)
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
|
|
|
|
|
const getCategoryColor = (category: string) => {
|
|
|
|
|
|
const colors: Record<string, string> = {
|
|
|
|
|
|
general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
|
|
|
|
|
|
hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
|
|
|
|
|
|
it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
|
|
|
|
|
|
event: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
2025-10-19 07:39:10 +00:00
|
|
|
|
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
|
2025-10-18 22:37:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
return colors[category] || colors.general
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getPriorityColor = (priority: string) => {
|
|
|
|
|
|
const colors: Record<string, string> = {
|
|
|
|
|
|
low: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300',
|
|
|
|
|
|
medium: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300',
|
|
|
|
|
|
high: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-300',
|
2025-10-19 07:39:10 +00:00
|
|
|
|
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300',
|
2025-10-18 22:37:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
return colors[priority]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
|
|
|
|
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
|
|
|
|
|
Hoş geldiniz, {dayjs().format('DD MMMM YYYY dddd')}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
|
<button className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg relative">
|
|
|
|
|
|
<BellIcon className="w-6 h-6 text-gray-600 dark:text-gray-400" />
|
|
|
|
|
|
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Main Grid */}
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
|
|
{/* Sol Kolon - Duyurular */}
|
|
|
|
|
|
<div className="lg:col-span-2 space-y-6">
|
|
|
|
|
|
{/* Duyurular */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
|
|
|
|
<BellIcon className="w-5 h-5" />
|
|
|
|
|
|
Önemli Duyurular
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<button className="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
|
|
|
|
|
Tümünü Gör
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
|
|
{pinnedAnnouncements.map((announcement) => (
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<div
|
|
|
|
|
|
key={announcement.id}
|
2025-10-18 22:37:20 +00:00
|
|
|
|
onClick={() => setSelectedAnnouncement(announcement)}
|
|
|
|
|
|
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-start gap-4">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={announcement.author.avatar}
|
|
|
|
|
|
alt={announcement.author.fullName}
|
|
|
|
|
|
className="w-10 h-10 rounded-full"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<div className="flex items-center gap-2 mb-2">
|
|
|
|
|
|
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
|
|
|
|
|
|
{announcement.title}
|
|
|
|
|
|
</h3>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<span
|
|
|
|
|
|
className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}
|
|
|
|
|
|
>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
{announcement.category === 'general' && 'Genel'}
|
|
|
|
|
|
{announcement.category === 'hr' && 'İK'}
|
|
|
|
|
|
{announcement.category === 'it' && 'IT'}
|
|
|
|
|
|
{announcement.category === 'event' && 'Etkinlik'}
|
|
|
|
|
|
{announcement.category === 'urgent' && 'Acil'}
|
|
|
|
|
|
</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>•</span>
|
|
|
|
|
|
<span>{dayjs(announcement.publishDate).fromNow()}</span>
|
|
|
|
|
|
<span>•</span>
|
|
|
|
|
|
<span>{announcement.viewCount} görüntülenme</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Hızlı Erişim */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2 mb-4">
|
|
|
|
|
|
<SparklesIcon className="w-5 h-5" />
|
|
|
|
|
|
Hızlı Erişim
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
|
|
|
|
{mockQuickLinks.map((link) => (
|
|
|
|
|
|
<motion.a
|
|
|
|
|
|
key={link.id}
|
|
|
|
|
|
href={link.url}
|
|
|
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
|
className="flex flex-col items-center gap-2 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors"
|
|
|
|
|
|
style={{ borderColor: link.color + '40' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="text-3xl">{link.icon}</span>
|
|
|
|
|
|
<span className="text-sm font-medium text-gray-900 dark:text-white text-center">
|
|
|
|
|
|
{link.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</motion.a>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Öncelikli Görevler */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
|
|
|
|
<ChartBarIcon className="w-5 h-5" />
|
|
|
|
|
|
Öncelikli Görevler
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
|
|
{priorityTasks.map((task) => (
|
|
|
|
|
|
<div key={task.id} className="p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
|
|
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
className="mt-1 rounded border-gray-300 dark:border-gray-600"
|
|
|
|
|
|
checked={task.status === 'done'}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<div className="flex items-center gap-2 mb-1">
|
|
|
|
|
|
<h3 className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{task.title}
|
|
|
|
|
|
</h3>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<span
|
|
|
|
|
|
className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}
|
|
|
|
|
|
>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
{task.priority === 'urgent' && '🔥 Acil'}
|
|
|
|
|
|
{task.priority === 'high' && 'Yüksek'}
|
|
|
|
|
|
{task.priority === 'medium' && 'Orta'}
|
|
|
|
|
|
{task.priority === 'low' && 'Düşük'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400 mb-2">
|
|
|
|
|
|
{task.description}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div className="flex items-center gap-3 text-xs text-gray-500">
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<ClockIcon className="w-3 h-3" />
|
|
|
|
|
|
{dayjs(task.dueDate).format('DD MMM')}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<UserGroupIcon className="w-3 h-3" />
|
|
|
|
|
|
{task.assignedTo.length} kişi
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sağ Kolon - Etkinlikler & Kutlamalar */}
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
{/* Bugünün Etkinlikleri */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
|
|
|
|
<CalendarIcon className="w-5 h-5" />
|
|
|
|
|
|
Bugün
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 space-y-3">
|
|
|
|
|
|
{todayEvents.length > 0 ? (
|
|
|
|
|
|
todayEvents.map((event) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={event.id}
|
|
|
|
|
|
className="p-3 rounded-lg border-l-4 bg-gray-50 dark:bg-gray-700/50 border-l-blue-500"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{event.title}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
|
|
|
|
{dayjs(event.date).format('DD MMMM YYYY')} - {event.location}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
|
|
|
|
{event.participants} katılımcı
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
|
|
|
|
|
|
Bugün etkinlik yok
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Bugün Doğanlar */}
|
|
|
|
|
|
<div className="bg-gradient-to-br from-pink-50 to-purple-50 dark:from-pink-900/20 dark:to-purple-900/20 rounded-lg shadow-sm border border-pink-200 dark:border-pink-800">
|
2025-10-19 07:49:12 +00:00
|
|
|
|
<div className="p-6 border-b border-pink-200 dark:border-pink-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
2025-10-19 07:39:10 +00:00
|
|
|
|
🎂 Bugün Doğanlar
|
|
|
|
|
|
</h2>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 space-y-3">
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{todayBirthdays.length > 0 ? (
|
2025-10-19 07:49:12 +00:00
|
|
|
|
todayBirthdays.map((birthday, index) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
className="flex items-center gap-3 p-3 bg-white/50 dark:bg-gray-800/50 rounded-lg"
|
|
|
|
|
|
>
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={birthday.employee.avatar}
|
|
|
|
|
|
alt={birthday.employee.fullName}
|
|
|
|
|
|
className="w-12 h-12 rounded-full border-2 border-pink-300 dark:border-pink-700"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<p className="text-sm font-semibold text-gray-900 dark:text-white">
|
|
|
|
|
|
{birthday.employee.fullName}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
{birthday.age} yaşında 🎉
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
|
|
|
|
|
{birthday.employee.department?.name || 'Genel'}
|
|
|
|
|
|
</p>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
</div>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
))
|
2025-10-19 07:39:10 +00:00
|
|
|
|
) : (
|
|
|
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
|
|
|
|
|
|
Bugün doğan yok
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-18 22:37:20 +00:00
|
|
|
|
{/* Yaklaşan Etkinlikler */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
|
|
|
|
<CalendarIcon className="w-5 h-5" />
|
|
|
|
|
|
Yaklaşan Etkinlikler
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 space-y-3">
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{upcomingEvents.length > 0 ? (
|
|
|
|
|
|
upcomingEvents.slice(0, 3).map((event) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={event.id}
|
|
|
|
|
|
className="p-3 rounded-lg border-l-4 bg-gray-50 dark:bg-gray-700/50 border-l-green-500"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{event.title}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
|
|
|
|
{dayjs(event.date).format('DD MMMM YYYY')} - {event.location}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
|
|
|
|
|
|
Yaklaşan etkinlik yok
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Dokümanlar */}
|
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
|
|
|
|
<DocumentTextIcon className="w-5 h-5" />
|
|
|
|
|
|
Son Dokümanlar
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<button className="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
|
|
|
|
|
Tümü
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
|
|
{mockDocuments.slice(0, 5).map((doc) => (
|
2025-10-18 22:37:20 +00:00
|
|
|
|
<div
|
2025-10-19 07:39:10 +00:00
|
|
|
|
key={doc.id}
|
|
|
|
|
|
className="p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors cursor-pointer"
|
2025-10-18 22:37:20 +00:00
|
|
|
|
>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
|
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
|
|
|
|
|
<DocumentTextIcon className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
|
|
|
|
|
{doc.name}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
|
|
|
|
{doc.category === 'policy' && '📋 Politika'}
|
|
|
|
|
|
{doc.category === 'procedure' && '📝 Prosedür'}
|
|
|
|
|
|
{doc.category === 'form' && '📄 Form'}
|
|
|
|
|
|
{doc.category === 'template' && '<27> Şablon'}
|
|
|
|
|
|
{doc.category === 'report' && '📊 Rapor'}
|
|
|
|
|
|
{doc.category === 'other' && '📄 Diğer'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div className="flex items-center gap-2 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
|
<span>{dayjs(doc.uploadDate).fromNow()}</span>
|
|
|
|
|
|
<span>•</span>
|
|
|
|
|
|
<span>{doc.size}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Doğum Günleri */}
|
|
|
|
|
|
{weekBirthdays.length > 0 && (
|
|
|
|
|
|
<div className="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-lg shadow-sm border border-purple-200 dark:border-purple-800">
|
2025-10-19 07:49:12 +00:00
|
|
|
|
<div className="p-6 border-b border-purple-200 dark:border-purple-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
2025-10-18 22:37:20 +00:00
|
|
|
|
🎂 Bu Hafta Doğanlar
|
|
|
|
|
|
</h2>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 space-y-3">
|
|
|
|
|
|
{weekBirthdays.map((birthday, index) => (
|
|
|
|
|
|
<div key={index} className="flex items-center gap-3">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={birthday.employee.avatar}
|
|
|
|
|
|
alt={birthday.employee.fullName}
|
|
|
|
|
|
className="w-10 h-10 rounded-full border-2 border-purple-200 dark:border-purple-700"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{birthday.employee.fullName}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
{dayjs(birthday.date).format('DD MMMM')} • {birthday.age} yaşında
|
|
|
|
|
|
</p>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* İş Yıldönümleri */}
|
|
|
|
|
|
{monthAnniversaries.length > 0 && (
|
|
|
|
|
|
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 rounded-lg shadow-sm border border-blue-200 dark:border-blue-800">
|
2025-10-19 07:49:12 +00:00
|
|
|
|
<div className="p-6 border-b border-blue-200 dark:border-blue-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
2025-10-18 22:37:20 +00:00
|
|
|
|
🎉 İş Yıldönümleri
|
|
|
|
|
|
</h2>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="p-4 space-y-3">
|
|
|
|
|
|
{monthAnniversaries.map((anniversary, index) => (
|
|
|
|
|
|
<div key={index} className="flex items-center gap-3">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={anniversary.employee.avatar}
|
|
|
|
|
|
alt={anniversary.employee.fullName}
|
|
|
|
|
|
className="w-10 h-10 rounded-full border-2 border-blue-200 dark:border-blue-700"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
|
{anniversary.employee.fullName}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
{anniversary.years} yıldır bizimle! 🎊
|
|
|
|
|
|
</p>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
2025-10-19 07:49:12 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Announcement Detail Modal */}
|
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
|
{selectedAnnouncement && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
|
className="fixed inset-0 bg-black/50 z-40"
|
|
|
|
|
|
onClick={() => setSelectedAnnouncement(null)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
|
2025-10-18 22:37:20 +00:00
|
|
|
|
<motion.div
|
2025-10-19 07:39:10 +00:00
|
|
|
|
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
|
|
|
|
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
|
|
|
|
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full"
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
|
<div className="flex items-center gap-3 mb-3">
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{selectedAnnouncement.category === 'general' && '📢 Genel'}
|
|
|
|
|
|
{selectedAnnouncement.category === 'hr' && '👥 İnsan Kaynakları'}
|
|
|
|
|
|
{selectedAnnouncement.category === 'it' && '💻 Bilgi Teknolojileri'}
|
|
|
|
|
|
{selectedAnnouncement.category === 'event' && '🎉 Etkinlik'}
|
|
|
|
|
|
{selectedAnnouncement.category === 'urgent' && '🚨 Acil'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{selectedAnnouncement.isPinned && (
|
|
|
|
|
|
<span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
|
|
|
|
|
|
)}
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
|
|
|
|
{selectedAnnouncement.title}
|
|
|
|
|
|
</h2>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setSelectedAnnouncement(null)}
|
|
|
|
|
|
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<HiXMark className="w-6 h-6 text-gray-500" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Author Info */}
|
|
|
|
|
|
<div className="flex items-center gap-3 mt-4">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={selectedAnnouncement.author.avatar}
|
|
|
|
|
|
alt={selectedAnnouncement.author.fullName}
|
|
|
|
|
|
className="w-12 h-12 rounded-full"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p className="font-semibold text-gray-900 dark:text-white">
|
|
|
|
|
|
{selectedAnnouncement.author.fullName}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
{dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>•</span>
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<HiEye className="w-4 h-4" />
|
|
|
|
|
|
{selectedAnnouncement.viewCount} görüntülenme
|
|
|
|
|
|
</span>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-19 07:39:10 +00:00
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Content */}
|
|
|
|
|
|
<div className="p-6 max-h-[60vh] overflow-y-auto">
|
|
|
|
|
|
{/* Image if exists */}
|
|
|
|
|
|
{selectedAnnouncement.imageUrl && (
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={selectedAnnouncement.imageUrl}
|
|
|
|
|
|
alt={selectedAnnouncement.title}
|
|
|
|
|
|
className="w-full rounded-lg mb-6"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Full Content */}
|
|
|
|
|
|
<div className="prose prose-sm dark:prose-invert max-w-none">
|
|
|
|
|
|
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
|
|
|
|
|
{selectedAnnouncement.content}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Attachments */}
|
|
|
|
|
|
{selectedAnnouncement.attachments &&
|
|
|
|
|
|
selectedAnnouncement.attachments.length > 0 && (
|
2025-10-18 22:37:20 +00:00
|
|
|
|
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
|
|
|
|
|
|
<HiPaperClip className="w-5 h-5" />
|
|
|
|
|
|
Ekler ({selectedAnnouncement.attachments.length})
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{selectedAnnouncement.attachments.map((attachment, idx) => (
|
|
|
|
|
|
<a
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
href={attachment.url}
|
|
|
|
|
|
target="_blank"
|
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
|
className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<HiPaperClip className="w-5 h-5 text-gray-400" />
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
|
|
|
|
|
{attachment.name}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
|
{attachment.size}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-sm text-blue-600 dark:text-blue-400">
|
|
|
|
|
|
İndir
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Departments */}
|
|
|
|
|
|
{selectedAnnouncement.departments &&
|
|
|
|
|
|
selectedAnnouncement.departments.length > 0 && (
|
2025-10-18 22:37:20 +00:00
|
|
|
|
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
|
|
|
|
|
Hedef Departmanlar
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
{selectedAnnouncement.departments.map((dept, idx) => (
|
|
|
|
|
|
<span
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
className="px-3 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-sm rounded-full"
|
|
|
|
|
|
>
|
|
|
|
|
|
{dept}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Expiry Date */}
|
|
|
|
|
|
{selectedAnnouncement.expiryDate && (
|
|
|
|
|
|
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
<span className="font-medium">Son Geçerlilik Tarihi:</span>{' '}
|
|
|
|
|
|
{dayjs(selectedAnnouncement.expiryDate).format('DD MMMM YYYY')}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
|
2025-10-19 07:39:10 +00:00
|
|
|
|
{/* Footer */}
|
|
|
|
|
|
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setSelectedAnnouncement(null)}
|
|
|
|
|
|
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
Kapat
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</AnimatePresence>
|
2025-10-18 22:37:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default IntranetDashboard
|