intranet içn komponentler

This commit is contained in:
Sedat ÖZTÜRK 2025-10-20 16:41:31 +03:00
parent 84be7170ec
commit ee7114a2dc
35 changed files with 1897 additions and 7198 deletions

View file

@ -1,291 +0,0 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import {
HiBell,
HiMagnifyingGlass,
HiFunnel,
HiEye,
HiCalendar,
HiUser
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockAnnouncements, Announcement } from '../../mocks/mockIntranetData'
dayjs.locale('tr')
const AnnouncementsModule: React.FC = () => {
const [announcements] = useState<Announcement[]>(mockAnnouncements)
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<string>('all')
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null)
const categories = [
{ id: 'all', label: 'Tümü', color: 'blue' },
{ id: 'general', label: 'Genel', color: 'blue' },
{ id: 'hr', label: 'İK', color: 'purple' },
{ id: 'it', label: 'IT', color: 'orange' },
{ id: 'event', label: 'Etkinlik', color: 'green' },
{ id: 'urgent', label: 'Acil', color: 'red' }
]
const filteredAnnouncements = announcements.filter(announcement => {
const matchesSearch = announcement.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
announcement.content.toLowerCase().includes(searchQuery.toLowerCase())
const matchesCategory = selectedCategory === 'all' || announcement.category === selectedCategory
return matchesSearch && matchesCategory
})
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 border-blue-200 dark:border-blue-800',
hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 border-purple-200 dark:border-purple-800',
it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 border-orange-200 dark:border-orange-800',
event: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 border-green-200 dark:border-green-800',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 border-red-200 dark:border-red-800'
}
return colors[category] || colors.general
}
const getCategoryBadge = (category: string) => {
const labels: Record<string, string> = {
general: 'Genel',
hr: 'İK',
it: 'IT',
event: 'Etkinlik',
urgent: 'Acil'
}
return labels[category]
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Duyurular & Haberler
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Şirket duyurularını ve haberleri takip edin
</p>
</div>
{/* Arama ve Filtreler */}
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1 relative">
<HiMagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
placeholder="Duyuru ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex items-center gap-2">
<HiFunnel className="w-5 h-5 text-gray-400" />
{categories.map(cat => (
<button
key={cat.id}
onClick={() => setSelectedCategory(cat.id)}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedCategory === cat.id
? `bg-${cat.color}-100 dark:bg-${cat.color}-900/30 text-${cat.color}-700 dark:text-${cat.color}-300`
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
{cat.label}
</button>
))}
</div>
</div>
{/* Duyuru Listesi */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Sol Kolon - Liste */}
<div className="lg:col-span-1 space-y-4">
{filteredAnnouncements.map(announcement => (
<motion.div
key={announcement.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className={`bg-white dark:bg-gray-800 rounded-lg border-2 shadow-sm cursor-pointer hover:shadow-md transition-all ${
selectedAnnouncement?.id === announcement.id
? getCategoryColor(announcement.category)
: 'border-gray-200 dark:border-gray-700'
}`}
onClick={() => setSelectedAnnouncement(announcement)}
>
{announcement.imageUrl && (
<div className="relative h-48 overflow-hidden rounded-t-lg">
<img
src={announcement.imageUrl}
alt={announcement.title}
className="w-full h-full object-cover"
/>
{announcement.isPinned && (
<div className="absolute top-3 right-3 px-2.5 py-1 bg-yellow-500 text-white text-xs font-bold rounded-full shadow-lg">
📌 SABİTLENDİ
</div>
)}
</div>
)}
<div className="p-6">
<div className="flex items-start justify-between gap-3 mb-3">
<h3 className="text-lg font-bold text-gray-900 dark:text-white flex-1">
{announcement.title}
</h3>
<span className={`px-2.5 py-1 text-xs font-medium rounded-full whitespace-nowrap ${getCategoryColor(announcement.category)}`}>
{getCategoryBadge(announcement.category)}
</span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
{announcement.excerpt}
</p>
<div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400">
<div className="flex items-center gap-2">
<img
src={announcement.author.avatar}
alt={announcement.author.fullName}
className="w-6 h-6 rounded-full"
/>
<span>{announcement.author.fullName}</span>
</div>
<div className="flex items-center gap-1">
<HiCalendar className="w-4 h-4" />
{dayjs(announcement.publishDate).format('DD MMMM YYYY')}
</div>
<div className="flex items-center gap-1">
<HiEye className="w-4 h-4" />
{announcement.viewCount}
</div>
</div>
</div>
</motion.div>
))}
{filteredAnnouncements.length === 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg p-12 text-center border border-gray-200 dark:border-gray-700">
<HiBell className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 dark:text-gray-400">
{searchQuery ? 'Arama kriterlerine uygun duyuru bulunamadı' : 'Henüz duyuru bulunmuyor'}
</p>
</div>
)}
</div>
{/* Sağ Kolon - Detay */}
<div className="lg:col-span-1">
{selectedAnnouncement ? (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm sticky top-6">
{selectedAnnouncement.imageUrl && (
<div className="relative h-64 overflow-hidden rounded-t-lg">
<img
src={selectedAnnouncement.imageUrl}
alt={selectedAnnouncement.title}
className="w-full h-full object-cover"
/>
</div>
)}
<div className="p-6">
<div className="mb-4">
<span className={`px-3 py-1.5 text-sm font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}>
{getCategoryBadge(selectedAnnouncement.category)}
</span>
{selectedAnnouncement.isPinned && (
<span className="ml-2 px-3 py-1.5 bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300 text-sm font-medium rounded-full">
📌 Sabitlendi
</span>
)}
</div>
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
{selectedAnnouncement.title}
</h2>
<div className="flex items-center gap-3 mb-4 pb-4 border-b border-gray-200 dark:border-gray-700">
<img
src={selectedAnnouncement.author.avatar}
alt={selectedAnnouncement.author.fullName}
className="w-10 h-10 rounded-full"
/>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{selectedAnnouncement.author.fullName}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}
</p>
</div>
</div>
<div className="prose prose-sm dark:prose-invert max-w-none mb-4">
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
{selectedAnnouncement.content}
</p>
</div>
{selectedAnnouncement.attachments && selectedAnnouncement.attachments.length > 0 && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-900 dark:text-white mb-2">
📎 Ekler
</h4>
<div className="space-y-2">
{selectedAnnouncement.attachments.map((attachment, idx) => (
<a
key={idx}
href={attachment.url}
className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-700 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
>
<div className="w-8 h-8 bg-blue-100 dark:bg-blue-900/30 rounded flex items-center justify-center">
<span className="text-xs">📄</span>
</div>
<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>
</a>
))}
</div>
</div>
)}
{selectedAnnouncement.departments && selectedAnnouncement.departments.length > 0 && (
<div className="pt-4 border-t border-gray-200 dark:border-gray-700">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
Hedef Departmanlar:
</p>
<div className="flex flex-wrap gap-2">
{selectedAnnouncement.departments.map((dept, idx) => (
<span
key={idx}
className="px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded"
>
{dept}
</span>
))}
</div>
</div>
)}
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-500 dark:text-gray-400">
<HiEye className="w-4 h-4" />
<span>{selectedAnnouncement.viewCount} kez görüntülendi</span>
</div>
</div>
</div>
) : (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-12 text-center sticky top-6">
<HiBell className="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" />
<p className="text-gray-500 dark:text-gray-400">
Detayları görüntülemek için bir duyuru seçin
</p>
</div>
)}
</div>
</div>
</div>
</div>
)
}
export default AnnouncementsModule

View file

@ -1,279 +0,0 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { HiCake, HiGift, HiCalendar } from 'react-icons/hi2'
import dayjs from 'dayjs'
import isBetween from 'dayjs/plugin/isBetween'
import { mockBirthdays, mockAnniversaries, Birthday, WorkAnniversary } from '../../mocks/mockIntranetData'
dayjs.extend(isBetween)
const BirthdaysModule: React.FC = () => {
const [selectedMonth, setSelectedMonth] = useState(dayjs().month())
const months = [
'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran',
'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'
]
// Bugünün doğum günleri
const todayBirthdays = mockBirthdays.filter(b =>
dayjs(b.date).month() === dayjs().month() &&
dayjs(b.date).date() === dayjs().date()
)
// Bu haftanın doğum günleri
const thisWeekBirthdays = mockBirthdays.filter(b => {
const birthDate = dayjs().year(dayjs().year()).month(dayjs(b.date).month()).date(dayjs(b.date).date())
return birthDate.isBetween(dayjs().startOf('week'), dayjs().endOf('week'), null, '[]')
})
// Seçilen aydaki doğum günleri
const monthBirthdays = mockBirthdays.filter(b => dayjs(b.date).month() === selectedMonth)
.sort((a, b) => dayjs(a.date).date() - dayjs(b.date).date())
// Bu ayki iş yıldönümleri
const thisMonthAnniversaries = mockAnniversaries.filter((a: WorkAnniversary) => dayjs(a.hireDate).month() === dayjs().month())
const getBirthdayMessage = (birthday: Birthday) => {
const age = birthday.age || dayjs().year() - dayjs(birthday.date).year()
return `${age}. yaş günü kutlu olsun! 🎉`
}
const getAnniversaryMessage = (anniversary: WorkAnniversary) => {
return `${anniversary.years} yıllık iş birliğimiz için teşekkürler! 🎊`
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
🎂 Doğum Günleri & Yıldönümleri
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Ekip üyelerimizin özel günlerini kutlayalım
</p>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Bugün */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-gradient-to-br from-pink-500 to-purple-600 rounded-lg p-6 text-white"
>
<div className="flex items-center justify-between mb-4">
<HiCake className="w-8 h-8" />
<span className="text-3xl font-bold">{todayBirthdays.length}</span>
</div>
<h3 className="font-semibold text-lg mb-1">Bugün Doğanlar</h3>
<p className="text-pink-100 text-sm">
{todayBirthdays.length > 0 ? 'Kutlama zamanı!' : 'Bugün doğum günü yok'}
</p>
</motion.div>
{/* Bu Hafta */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="bg-gradient-to-br from-blue-500 to-cyan-600 rounded-lg p-6 text-white"
>
<div className="flex items-center justify-between mb-4">
<HiGift className="w-8 h-8" />
<span className="text-3xl font-bold">{thisWeekBirthdays.length}</span>
</div>
<h3 className="font-semibold text-lg mb-1">Bu Hafta</h3>
<p className="text-blue-100 text-sm">
{dayjs().startOf('week').format('DD MMM')} - {dayjs().endOf('week').format('DD MMM')}
</p>
</motion.div>
{/* İş Yıldönümleri */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-gradient-to-br from-orange-500 to-red-600 rounded-lg p-6 text-white"
>
<div className="flex items-center justify-between mb-4">
<HiCalendar className="w-8 h-8" />
<span className="text-3xl font-bold">{thisMonthAnniversaries.length}</span>
</div>
<h3 className="font-semibold text-lg mb-1">Bu Ay Yıldönümü</h3>
<p className="text-orange-100 text-sm">
{dayjs().format('MMMM')} ayında
</p>
</motion.div>
</div>
{/* Bugünün Doğum Günleri */}
{todayBirthdays.length > 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 border-2 border-pink-300 dark:border-pink-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
🎉 Bugün Doğum Günü Olanlar
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{todayBirthdays.map((birthday, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="bg-gradient-to-r from-pink-50 to-purple-50 dark:from-pink-900/20 dark:to-purple-900/20 rounded-lg p-4 border border-pink-200 dark:border-pink-800"
>
<div className="flex items-center gap-4">
<img
src={birthday.employee.avatar}
alt={birthday.employee.fullName}
className="w-16 h-16 rounded-full border-4 border-white dark:border-gray-800 shadow-lg"
/>
<div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white">
{birthday.employee.fullName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{birthday.employee.department?.name || 'Genel'}
</p>
<p className="text-sm font-medium text-pink-600 dark:text-pink-400 mt-1">
{getBirthdayMessage(birthday)}
</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
)}
{/* Ay Seçici */}
<div className="bg-white dark:bg-gray-800 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
📅 Aylık Doğum Günleri
</h2>
<select
value={selectedMonth}
onChange={(e) => setSelectedMonth(Number(e.target.value))}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
{months.map((month, idx) => (
<option key={idx} value={idx}>
{month}
</option>
))}
</select>
</div>
{/* Doğum Günleri Listesi */}
<div className="space-y-3">
{monthBirthdays.map((birthday, idx) => {
const isToday = dayjs(birthday.date).month() === dayjs().month() &&
dayjs(birthday.date).date() === dayjs().date()
return (
<motion.div
key={idx}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.05 }}
className={`flex items-center gap-4 p-4 rounded-lg border ${
isToday
? 'bg-pink-50 dark:bg-pink-900/20 border-pink-300 dark:border-pink-700'
: 'bg-gray-50 dark:bg-gray-700/50 border-gray-200 dark:border-gray-700'
}`}
>
<div className={`w-16 h-16 rounded-lg flex flex-col items-center justify-center ${
isToday
? 'bg-pink-500 text-white'
: 'bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300'
}`}>
<span className="text-2xl font-bold">{dayjs(birthday.date).date()}</span>
<span className="text-xs">{months[selectedMonth].substring(0, 3)}</span>
</div>
<img
src={birthday.employee.avatar}
alt={birthday.employee.fullName}
className="w-12 h-12 rounded-full"
/>
<div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white">
{birthday.employee.fullName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{birthday.employee.department?.name || 'Genel'}
</p>
</div>
<div className="text-right">
<p className="text-sm font-medium text-gray-900 dark:text-white">
{birthday.age || dayjs().year() - dayjs(birthday.date).year()} yaşında
</p>
{isToday && (
<span className="text-xs text-pink-600 dark:text-pink-400 font-medium">
🎂 Bugün!
</span>
)}
</div>
</motion.div>
)
})}
{monthBirthdays.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<HiCake className="w-16 h-16 mx-auto mb-4 opacity-20" />
<p>{months[selectedMonth]} ayında doğum günü yok</p>
</div>
)}
</div>
</div>
{/* İş Yıldönümleri */}
{thisMonthAnniversaries.length > 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
🎊 Bu Ayki İş Yıldönümleri
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{thisMonthAnniversaries.map((anniversary: WorkAnniversary, idx: number) => (
<motion.div
key={idx}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-gradient-to-r from-orange-50 to-amber-50 dark:from-orange-900/20 dark:to-amber-900/20 rounded-lg p-4 border border-orange-200 dark:border-orange-800"
>
<div className="flex items-center gap-4">
<img
src={anniversary.employee.avatar}
alt={anniversary.employee.fullName}
className="w-16 h-16 rounded-full border-4 border-white dark:border-gray-800 shadow-lg"
/>
<div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white">
{anniversary.employee.fullName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{anniversary.employee.department?.name || 'Genel'}
</p>
<p className="text-sm font-medium text-orange-600 dark:text-orange-400 mt-1">
{getAnniversaryMessage(anniversary)}
</p>
</div>
<div className="text-center bg-orange-500 text-white rounded-full w-16 h-16 flex flex-col items-center justify-center">
<span className="text-2xl font-bold">{anniversary.years}</span>
<span className="text-xs">YIL</span>
</div>
</div>
</motion.div>
))}
</div>
</div>
)}
</div>
</div>
)
}
export default BirthdaysModule

View file

@ -1,207 +0,0 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { HiClock, HiMapPin } from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockMealMenus, mockShuttleRoutes } from '../../mocks/mockIntranetData'
const CafeteriaModule: React.FC = () => {
const [selectedView, setSelectedView] = useState<'menu' | 'shuttle'>('menu')
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
🍽 Kafeterya & Servis
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Yemek menüsü ve servis saatleri
</p>
</div>
{/* Tabs */}
<div className="flex gap-2 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setSelectedView('menu')}
className={`px-6 py-3 font-medium transition-colors ${
selectedView === 'menu'
? 'text-blue-600 dark:text-blue-400 border-b-2 border-blue-600'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
📅 Haftalık Menü
</button>
<button
onClick={() => setSelectedView('shuttle')}
className={`px-6 py-3 font-medium transition-colors ${
selectedView === 'shuttle'
? 'text-blue-600 dark:text-blue-400 border-b-2 border-blue-600'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
🚌 Servis Saatleri
</button>
</div>
{/* Menu View */}
{selectedView === 'menu' && (
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{mockMealMenus.map((menu, idx) => (
<motion.div
key={menu.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700"
>
<div className="text-center mb-4">
<div className="text-sm text-gray-600 dark:text-gray-400">{dayjs(menu.date).format('DD MMM')}</div>
<h3 className="text-lg font-bold text-gray-900 dark:text-white">{menu.dayOfWeek}</h3>
</div>
{menu.meals.map((meal, mealIdx) => (
<div key={mealIdx} className="space-y-2">
<div className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
<span className="font-medium">
{meal.type === 'lunch' ? '🍽️ Öğle Yemeği' : meal.type}
</span>
{meal.calories && <span>{meal.calories} kcal</span>}
</div>
<ul className="space-y-1">
{meal.items.map((item, itemIdx) => (
<li key={itemIdx} className="text-sm text-gray-700 dark:text-gray-300 flex items-start">
<span className="text-green-500 mr-2"></span>
{item}
</li>
))}
</ul>
</div>
))}
</motion.div>
))}
</div>
)}
{/* Shuttle View */}
{selectedView === 'shuttle' && (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">🌅 Sabah Servisleri</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{mockShuttleRoutes.filter(s => s.type === 'morning').map((shuttle, idx) => (
<motion.div
key={shuttle.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700"
>
<div className="flex items-start justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{shuttle.name}</h3>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
shuttle.available > 5
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300'
}`}>
{shuttle.available} Koltuk
</span>
</div>
<div className="space-y-3 mb-4">
<div className="flex items-center gap-2 text-sm">
<HiClock className="w-4 h-4 text-gray-400" />
<span className="text-gray-700 dark:text-gray-300">
{shuttle.departureTime} - {shuttle.arrivalTime}
</span>
</div>
<div className="flex items-start gap-2 text-sm">
<HiMapPin className="w-4 h-4 text-gray-400 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
{shuttle.route.map((stop, idx) => (
<span key={idx}>
{stop}
{idx < shuttle.route.length - 1 && ' → '}
</span>
))}
</div>
</div>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${((shuttle.capacity - shuttle.available) / shuttle.capacity) * 100}%` }}
/>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
{shuttle.capacity - shuttle.available} / {shuttle.capacity} dolu
</p>
</motion.div>
))}
</div>
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">🌆 Akşam Servisleri</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{mockShuttleRoutes.filter(s => s.type === 'evening').map((shuttle, idx) => (
<motion.div
key={shuttle.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700"
>
<div className="flex items-start justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{shuttle.name}</h3>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
shuttle.available > 5
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300'
}`}>
{shuttle.available} Koltuk
</span>
</div>
<div className="space-y-3 mb-4">
<div className="flex items-center gap-2 text-sm">
<HiClock className="w-4 h-4 text-gray-400" />
<span className="text-gray-700 dark:text-gray-300">
{shuttle.departureTime} - {shuttle.arrivalTime}
</span>
</div>
<div className="flex items-start gap-2 text-sm">
<HiMapPin className="w-4 h-4 text-gray-400 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
{shuttle.route.map((stop, idx) => (
<span key={idx}>
{stop}
{idx < shuttle.route.length - 1 && ' → '}
</span>
))}
</div>
</div>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${((shuttle.capacity - shuttle.available) / shuttle.capacity) * 100}%` }}
/>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
{shuttle.capacity - shuttle.available} / {shuttle.capacity} dolu
</p>
</motion.div>
))}
</div>
</div>
</div>
)}
</div>
</div>
)
}
export default CafeteriaModule

File diff suppressed because it is too large Load diff

View file

@ -1,458 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiMagnifyingGlass,
HiFunnel,
HiArrowDownTray,
HiEye,
HiFolder,
HiDocument,
HiXMark
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockDocuments, Document } from '../../mocks/mockIntranetData'
dayjs.locale('tr')
const DocumentsModule: React.FC = () => {
const [documents] = useState<Document[]>(mockDocuments)
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<string>('all')
const [previewDocument, setPreviewDocument] = useState<Document | null>(null)
const categories = [
{ id: 'all', label: 'Tümü' },
{ id: 'policy', label: 'Politika' },
{ id: 'procedure', label: 'Prosedür' },
{ id: 'form', label: 'Form' },
{ id: 'template', label: 'Şablon' },
{ id: 'report', label: 'Rapor' },
{ id: 'other', label: 'Diğer' }
]
const handleDownload = (doc: Document) => {
// Gerçek indirme işlemi
const link = document.createElement('a')
link.href = doc.url
link.download = doc.name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
const filteredDocuments = documents.filter(doc => {
const matchesSearch = doc.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
doc.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
doc.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
const matchesCategory = selectedCategory === 'all' || doc.category === selectedCategory
return matchesSearch && matchesCategory
})
const getFileIcon = (type: string) => {
const icons = {
pdf: '📄',
doc: '📝',
xls: '📊',
ppt: '📊',
other: '📎'
}
return icons[type as keyof typeof icons] || icons.other
}
const getCategoryLabel = (category: string) => {
const labels = {
policy: 'Politika',
procedure: 'Prosedür',
form: 'Form',
template: 'Şablon',
report: 'Rapor',
other: 'Diğer'
}
return labels[category as keyof typeof labels]
}
const groupedDocuments = filteredDocuments.reduce((acc, doc) => {
if (!acc[doc.category]) {
acc[doc.category] = []
}
acc[doc.category].push(doc)
return acc
}, {} as Record<string, Document[]>)
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Doküman Yönetimi
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Şirket dokümanlarına erişin ve yönetin
</p>
</div>
{/* Arama ve Filtreler */}
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1 relative">
<HiMagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
placeholder="Doküman ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex items-center gap-2">
<HiFunnel className="w-5 h-5 text-gray-400" />
{categories.map(cat => (
<button
key={cat.id}
onClick={() => setSelectedCategory(cat.id)}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedCategory === cat.id
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
{cat.label}
</button>
))}
</div>
</div>
{/* İstatistikler */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Toplam Doküman
</h3>
<span className="text-2xl">📁</span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{documents.length}
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Kategoriler
</h3>
<span className="text-2xl">🗂</span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{Object.keys(groupedDocuments).length}
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Toplam İndirme
</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{documents.reduce((sum, doc) => sum + doc.downloadCount, 0)}
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Bu Ay Yeni
</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{documents.filter(d => dayjs(d.uploadDate).isAfter(dayjs().subtract(30, 'day'))).length}
</p>
</div>
</div>
{/* Doküman Listesi */}
{Object.entries(groupedDocuments).map(([category, categoryDocs]) => (
<div key={category} className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center gap-3">
<HiFolder className="w-6 h-6 text-blue-600 dark:text-blue-400" />
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
{getCategoryLabel(category)}
</h2>
<span className="text-sm text-gray-500 dark:text-gray-400">
({categoryDocs.length} doküman)
</span>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{categoryDocs.map(doc => (
<motion.div
key={doc.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center text-2xl">
{getFileIcon(doc.type)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-3 mb-2">
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
{doc.name}
</h3>
<div className="flex items-center gap-2">
<button
onClick={() => setPreviewDocument(doc)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
title="Önizle"
>
<HiEye className="w-5 h-5 text-gray-600 dark:text-gray-400" />
</button>
<button
onClick={() => handleDownload(doc)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
title="İndir"
>
<HiArrowDownTray className="w-5 h-5 text-gray-600 dark:text-gray-400" />
</button>
</div>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
{doc.description}
</p>
<div className="flex flex-wrap items-center gap-4 text-xs text-gray-500 dark:text-gray-400">
<div className="flex items-center gap-2">
<img
src={doc.uploadedBy.avatar}
alt={doc.uploadedBy.fullName}
className="w-5 h-5 rounded-full"
/>
<span>{doc.uploadedBy.fullName}</span>
</div>
<span></span>
<span>{dayjs(doc.uploadDate).format('DD MMMM YYYY')}</span>
<span></span>
<span>{doc.size}</span>
<span></span>
<span>v{doc.version}</span>
<span></span>
<span>{doc.downloadCount} indirme</span>
</div>
{doc.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-3">
{doc.tags.map((tag, idx) => (
<span
key={idx}
className="px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded"
>
#{tag}
</span>
))}
</div>
)}
</div>
</div>
</motion.div>
))}
</div>
</div>
))}
{filteredDocuments.length === 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg p-12 text-center border border-gray-200 dark:border-gray-700">
<HiDocument className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 dark:text-gray-400">
{searchQuery ? 'Arama kriterlerine uygun doküman bulunamadı' : 'Henüz doküman bulunmuyor'}
</p>
</div>
)}
</div>
{/* Doküman Önizleme Modal */}
<AnimatePresence>
{previewDocument && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setPreviewDocument(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-4xl w-full max-h-[90vh] overflow-hidden flex flex-col"
>
{/* Header */}
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center text-2xl">
{getFileIcon(previewDocument.type)}
</div>
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{previewDocument.name}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
{getCategoryLabel(previewDocument.category)} {previewDocument.size} v{previewDocument.version}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handleDownload(previewDocument)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiArrowDownTray className="w-5 h-5" />
İndir
</button>
<button
onClick={() => setPreviewDocument(null)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6">
<div className="space-y-6">
{/* Açıklama */}
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-2">
ıklama
</h3>
<p className="text-gray-700 dark:text-gray-300">
{previewDocument.description}
</p>
</div>
{/* Dosya Önizleme Alanı */}
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Önizleme
</h3>
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-12 text-center bg-gray-50 dark:bg-gray-900/50">
<div className="text-6xl mb-4">{getFileIcon(previewDocument.type)}</div>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{previewDocument.type.toUpperCase()} dosyası önizlemesi desteklenmemektedir
</p>
<button
onClick={() => handleDownload(previewDocument)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg inline-flex items-center gap-2 transition-colors"
>
<HiArrowDownTray className="w-5 h-5" />
Dosyayı İndir
</button>
</div>
</div>
{/* Metadata */}
<div className="grid grid-cols-2 gap-6">
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
Dosya Bilgileri
</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Dosya Tipi:</span>
<span className="text-gray-900 dark:text-white font-medium">
{previewDocument.type.toUpperCase()}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Boyut:</span>
<span className="text-gray-900 dark:text-white font-medium">
{previewDocument.size}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Versiyon:</span>
<span className="text-gray-900 dark:text-white font-medium">
v{previewDocument.version}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">İndirme:</span>
<span className="text-gray-900 dark:text-white font-medium">
{previewDocument.downloadCount} kez
</span>
</div>
</div>
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
Yükleyen
</h3>
<div className="flex items-center gap-3 mb-4">
<img
src={previewDocument.uploadedBy.avatar}
alt={previewDocument.uploadedBy.fullName}
className="w-10 h-10 rounded-full"
/>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{previewDocument.uploadedBy.fullName}
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
{dayjs(previewDocument.uploadDate).format('DD MMMM YYYY, HH:mm')}
</p>
</div>
</div>
</div>
</div>
{/* Etiketler */}
{previewDocument.tags.length > 0 && (
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Etiketler
</h3>
<div className="flex flex-wrap gap-2">
{previewDocument.tags.map((tag, idx) => (
<span
key={idx}
className="px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-sm rounded"
>
#{tag}
</span>
))}
</div>
</div>
)}
{/* Departmanlar */}
{previewDocument.departments.length > 0 && (
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-2">
Yetkili Departmanlar
</h3>
<div className="flex flex-wrap gap-2">
{previewDocument.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"
>
{dept}
</span>
))}
</div>
</div>
)}
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default DocumentsModule

View file

@ -1,464 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiHeart,
HiChatBubbleLeft,
HiMapPin,
HiUsers,
HiCalendar,
HiXMark,
HiChevronLeft,
HiChevronRight,
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import relativeTime from 'dayjs/plugin/relativeTime'
import { mockEvents, CalendarEvent, EventComment } from '../../mocks/mockIntranetData'
import { mockEmployees } from '@/mocks/mockEmployees'
dayjs.locale('tr')
dayjs.extend(relativeTime)
const EventsModule: React.FC = () => {
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null)
const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(0)
const [showPhotoModal, setShowPhotoModal] = useState(false)
const [selectedFilter, setSelectedFilter] = useState<
'all' | 'social' | 'training' | 'company' | 'sport' | 'culture'
>('all')
const [newComment, setNewComment] = useState('')
const [events, setEvents] = useState<CalendarEvent[]>(mockEvents)
const filteredEvents =
selectedFilter === 'all'
? events.filter((e) => e.isPublished)
: events.filter((e) => e.isPublished && e.type === selectedFilter)
const handleLikeEvent = (eventId: string) => {
setEvents((prev) => prev.map((e) => (e.id === eventId ? { ...e, likes: e.likes + 1 } : e)))
if (selectedEvent?.id === eventId) {
setSelectedEvent((prev) => (prev ? { ...prev, likes: prev.likes + 1 } : null))
}
}
const handleAddComment = (eventId: string) => {
if (!newComment.trim()) return
const comment: EventComment = {
id: `c${Date.now()}`,
author: mockEmployees[0],
content: newComment,
createdAt: new Date(),
likes: 0,
}
setEvents((prev) =>
prev.map((e) => (e.id === eventId ? { ...e, comments: [...e.comments, comment] } : e)),
)
if (selectedEvent?.id === eventId) {
setSelectedEvent((prev) => (prev ? { ...prev, comments: [...prev.comments, comment] } : null))
}
setNewComment('')
}
const getTypeColor = (type: string) => {
const colors: Record<string, string> = {
social: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
training: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
company: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
sport: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
culture: 'bg-pink-100 dark:bg-pink-900/30 text-pink-700 dark:text-pink-300',
}
return colors[type] || colors.social
}
const getTypeLabel = (type: string) => {
const labels: Record<string, string> = {
social: '🎉 Sosyal',
training: '📚 Eğitim',
company: '🏢 Kurumsal',
sport: '⚽ Spor',
culture: '🎨 Kültür',
}
return labels[type] || type
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">🎊 Etkinlikler</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Şirket etkinlikleri, fotoğraflar ve anılar
</p>
</div>
{/* Filter Tabs */}
<div className="flex gap-3 overflow-x-auto pb-2">
{[
{ value: 'all' as const, label: '🌟 Tümü' },
{ value: 'social' as const, label: '🎉 Sosyal' },
{ value: 'training' as const, label: '📚 Eğitim' },
{ value: 'company' as const, label: '🏢 Kurumsal' },
{ value: 'sport' as const, label: '⚽ Spor' },
{ value: 'culture' as const, label: '🎨 Kültür' },
].map((tab) => (
<button
key={tab.value}
onClick={() => setSelectedFilter(tab.value)}
className={`px-4 py-2 rounded-lg border-2 transition-all whitespace-nowrap ${
selectedFilter === tab.value
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Events Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEvents.map((event, idx) => (
<motion.div
key={event.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:shadow-xl transition-all cursor-pointer"
onClick={() => setSelectedEvent(event)}
>
{/* Cover Photo */}
<div className="relative h-48 overflow-hidden">
<img
src={event.photos[0]}
alt={event.title}
className="w-full h-full object-cover hover:scale-110 transition-transform duration-300"
/>
<div className="absolute top-3 right-3">
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(event.type)}`}
>
{getTypeLabel(event.type)}
</span>
</div>
{event.photos.length > 1 && (
<div className="absolute bottom-3 right-3 bg-black/70 text-white px-2 py-1 rounded text-xs">
+{event.photos.length - 1} fotoğraf
</div>
)}
</div>
{/* Content */}
<div className="p-4 space-y-3">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white line-clamp-2">
{event.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
{event.description}
</p>
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1">
<HiCalendar className="w-4 h-4" />
{dayjs(event.date).format('DD MMMM YYYY')}
</div>
<div className="flex items-center gap-1">
<HiMapPin className="w-4 h-4" />
{event.location}
</div>
</div>
<div className="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-4 text-sm">
<button
onClick={(e) => {
e.stopPropagation()
handleLikeEvent(event.id)
}}
className="flex items-center gap-1 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors"
>
<HiHeart className="w-5 h-5" />
{event.likes}
</button>
<div className="flex items-center gap-1 text-gray-600 dark:text-gray-400">
<HiChatBubbleLeft className="w-5 h-5" />
{event.comments.length}
</div>
</div>
<div className="flex items-center gap-1 text-sm text-gray-600 dark:text-gray-400">
<HiUsers className="w-4 h-4" />
{event.participants} kişi
</div>
</div>
</div>
</motion.div>
))}
</div>
{filteredEvents.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<p className="text-lg">Bu kategoride henüz etkinlik yok</p>
</div>
)}
</div>
{/* Event Detail Modal */}
<AnimatePresence>
{selectedEvent && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setSelectedEvent(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
<motion.div
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-4xl w-full max-h-[90vh] overflow-y-auto my-8"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 z-10">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(selectedEvent.type)}`}
>
{getTypeLabel(selectedEvent.type)}
</span>
<span className="text-sm text-gray-600 dark:text-gray-400">
{dayjs(selectedEvent.date).format('DD MMMM YYYY')}
</span>
</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{selectedEvent.title}
</h2>
<p className="text-gray-600 dark:text-gray-400 mt-2">
{selectedEvent.description}
</p>
</div>
<button
onClick={() => setSelectedEvent(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>
<div className="flex items-center gap-6 mt-4 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-2">
<HiMapPin className="w-5 h-5" />
{selectedEvent.location}
</div>
<div className="flex items-center gap-2">
<HiUsers className="w-5 h-5" />
{selectedEvent.participants} katılımcı
</div>
<div className="flex items-center gap-2">
<img
src={selectedEvent.organizer.avatar}
alt={selectedEvent.organizer.fullName}
className="w-6 h-6 rounded-full"
/>
<span>Düzenleyen: {selectedEvent.organizer.fullName}</span>
</div>
</div>
</div>
{/* Photo Gallery */}
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
📸 Fotoğraflar ({selectedEvent.photos.length})
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{selectedEvent.photos.map((photo, idx) => (
<motion.div
key={idx}
whileHover={{ scale: 1.05 }}
className="relative aspect-square rounded-lg overflow-hidden cursor-pointer"
onClick={() => {
setSelectedPhotoIndex(idx)
setShowPhotoModal(true)
}}
>
<img
src={photo}
alt={`${selectedEvent.title} - ${idx + 1}`}
className="w-full h-full object-cover"
/>
</motion.div>
))}
</div>
</div>
{/* Comments Section */}
<div className="p-6 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
💬 Yorumlar ({selectedEvent.comments.length})
</h3>
<div className="flex items-center gap-4 text-sm">
<button
onClick={() => handleLikeEvent(selectedEvent.id)}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 transition-colors"
>
<HiHeart className="w-5 h-5 text-red-500" />
{selectedEvent.likes} beğeni
</button>
</div>
</div>
{/* Comments List */}
<div className="space-y-4 mb-4">
{selectedEvent.comments.map((comment) => (
<div
key={comment.id}
className="flex gap-3 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg"
>
<img
src={comment.author.avatar}
alt={comment.author.fullName}
className="w-10 h-10 rounded-full"
/>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-semibold text-gray-900 dark:text-white">
{comment.author.fullName}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
{dayjs(comment.createdAt).fromNow()}
</span>
</div>
<p className="text-gray-700 dark:text-gray-300 text-sm">
{comment.content}
</p>
<div className="flex items-center gap-2 mt-2">
<button className="text-xs text-gray-500 hover:text-red-500 transition-colors">
{comment.likes} beğeni
</button>
</div>
</div>
</div>
))}
</div>
{/* Add Comment */}
<div className="flex gap-3">
<img
src="https://ui-avatars.com/api/?name=Sedat+Ozturk&background=3b82f6&color=fff"
alt="You"
className="w-10 h-10 rounded-full"
/>
<div className="flex-1 flex gap-2">
<input
type="text"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleAddComment(selectedEvent.id)
}
}}
placeholder="Yorumunuzu yazın..."
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={() => handleAddComment(selectedEvent.id)}
disabled={!newComment.trim()}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-lg transition-colors"
>
Gönder
</button>
</div>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
{/* Photo Viewer Modal */}
<AnimatePresence>
{showPhotoModal && selectedEvent && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/90 z-50"
onClick={() => setShowPhotoModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="relative max-w-5xl w-full"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={() => setShowPhotoModal(false)}
className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white z-10"
>
<HiXMark className="w-6 h-6" />
</button>
{/* Navigation */}
{selectedEvent.photos.length > 1 && (
<>
<button
onClick={() =>
setSelectedPhotoIndex((prev) =>
prev === 0 ? selectedEvent.photos.length - 1 : prev - 1,
)
}
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
>
<HiChevronLeft className="w-6 h-6" />
</button>
<button
onClick={() =>
setSelectedPhotoIndex((prev) =>
prev === selectedEvent.photos.length - 1 ? 0 : prev + 1,
)
}
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
>
<HiChevronRight className="w-6 h-6" />
</button>
</>
)}
{/* Image */}
<img
src={selectedEvent.photos[selectedPhotoIndex]}
alt={`${selectedEvent.title} - ${selectedPhotoIndex + 1}`}
className="w-full h-auto max-h-[80vh] object-contain rounded-lg"
/>
{/* Counter */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 bg-black/70 text-white rounded-full text-sm">
{selectedPhotoIndex + 1} / {selectedEvent.photos.length}
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default EventsModule

View file

@ -1,480 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiCurrencyDollar,
HiCheckCircle,
HiPlus,
HiFunnel,
HiXMark,
HiPaperClip,
HiArrowUpTray
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockExpenseRequests, ExpenseRequest } from '../../../mocks/mockIntranetData'
dayjs.locale('tr')
const ExpenseManagement: React.FC = () => {
const [requests, setRequests] = useState<ExpenseRequest[]>(mockExpenseRequests)
const [showCreateModal, setShowCreateModal] = useState(false)
const [filterStatus, setFilterStatus] = useState<'all' | 'pending' | 'approved' | 'rejected'>('all')
// Harcama istatistikleri (mock)
const expenseStats = {
thisMonth: { total: 2370, approved: 2050, pending: 320, rejected: 0 },
thisYear: { total: 28450, approved: 26100, pending: 1800, rejected: 550 },
byCategory: {
travel: 12300,
meal: 6800,
accommodation: 5400,
transport: 2950,
other: 1000
}
}
const filteredRequests = requests.filter(req => {
if (filterStatus === 'all') return true
return req.status === filterStatus
})
const getStatusColor = (status: string) => {
const colors = {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}
return colors[status as keyof typeof colors]
}
const getCategoryLabel = (category: string) => {
const labels = {
travel: 'Seyahat',
meal: 'Yemek',
accommodation: 'Konaklama',
transport: 'Ulaşım',
other: 'Diğer'
}
return labels[category as keyof typeof labels]
}
const getCategoryIcon = (category: string) => {
const icons = {
travel: '✈️',
meal: '🍽️',
accommodation: '🏨',
transport: '🚗',
other: '📋'
}
return icons[category as keyof typeof icons]
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Harcama Yönetimi
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Harcama taleplerinizi oluşturun ve takip edin
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiPlus className="w-5 h-5" />
Yeni Harcama Talebi
</button>
</div>
{/* İstatistikler */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Bu Ay Toplam
</h3>
<span className="text-2xl">💰</span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{expenseStats.thisMonth.total.toLocaleString()}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
toplam harcama
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Onaylanan
</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-green-600 dark:text-green-400">
{expenseStats.thisMonth.approved.toLocaleString()}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
bu ay onaylandı
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Bekleyen
</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-yellow-600 dark:text-yellow-400">
{expenseStats.thisMonth.pending.toLocaleString()}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
onay bekliyor
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Yıllık Toplam
</h3>
<span className="text-2xl">📊</span>
</div>
<p className="text-3xl font-bold text-blue-600 dark:text-blue-400">
{expenseStats.thisYear.total.toLocaleString()}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
bu yıl toplam
</p>
</div>
</div>
{/* Kategori Dağılımı */}
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Kategori Bazlı Dağılım (Yıllık)
</h3>
<div className="space-y-3">
{Object.entries(expenseStats.byCategory).map(([category, amount]) => {
const percentage = (amount / expenseStats.thisYear.total) * 100
return (
<div key={category}>
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 flex items-center gap-2">
{getCategoryIcon(category)} {getCategoryLabel(category)}
</span>
<span className="text-sm font-semibold text-gray-900 dark:text-white">
{amount.toLocaleString()} ({percentage.toFixed(1)}%)
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{ width: `${percentage}%` }}
></div>
</div>
</div>
)
})}
</div>
</div>
{/* Filtreler */}
<div className="flex items-center gap-2">
<HiFunnel className="w-5 h-5 text-gray-400" />
<button
onClick={() => setFilterStatus('all')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'all'
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Tümü
</button>
<button
onClick={() => setFilterStatus('pending')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'pending'
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Bekleyen
</button>
<button
onClick={() => setFilterStatus('approved')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'approved'
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Onaylanan
</button>
<button
onClick={() => setFilterStatus('rejected')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'rejected'
? 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Reddedilen
</button>
</div>
{/* Harcama Talepleri Listesi */}
<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">
Harcama Talepleri
</h2>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{filteredRequests.length > 0 ? (
filteredRequests.map((request) => (
<div
key={request.id}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-4">
<span className="text-3xl">{getCategoryIcon(request.category)}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
{getCategoryLabel(request.category)}
</h3>
<span className={`px-2.5 py-1 text-xs rounded-full ${getStatusColor(request.status)}`}>
{request.status === 'pending' && '⏳ Beklemede'}
{request.status === 'approved' && '✅ Onaylandı'}
{request.status === 'rejected' && '❌ Reddedildi'}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Tutar</p>
<p className="text-lg font-bold text-green-600 dark:text-green-400">
{request.amount.toLocaleString()} {request.currency}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Tarih</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.date).format('DD MMMM YYYY')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Makbuzlar</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{request.receipts.length} dosya
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Talep Tarihi</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.createdAt).format('DD MMM')}
</p>
</div>
</div>
{request.project && (
<div className="mb-2">
<span className="inline-flex items-center px-2.5 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-xs font-medium">
📁 {request.project}
</span>
</div>
)}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
<span className="font-medium">ıklama:</span> {request.description}
</p>
{request.receipts.length > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{request.receipts.map((receipt, idx) => (
<a
key={idx}
href={receipt.url}
className="inline-flex items-center gap-2 px-3 py-1.5 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
<HiPaperClip className="w-4 h-4 text-gray-500 dark:text-gray-400" />
<span className="text-xs text-gray-700 dark:text-gray-300">
{receipt.name}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
({receipt.size})
</span>
</a>
))}
</div>
)}
{request.approver && (
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<HiCheckCircle className="w-4 h-4" />
<span>
{request.approver.fullName} tarafından{' '}
{dayjs(request.approvalDate).format('DD MMMM YYYY')} tarihinde{' '}
{request.status === 'approved' ? 'onaylandı' : 'reddedildi'}
</span>
</div>
)}
{request.notes && (
<div className="mt-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<p className="text-xs text-gray-700 dark:text-gray-300">
<span className="font-medium">Not:</span> {request.notes}
</p>
</div>
)}
</div>
</div>
</div>
))
) : (
<div className="p-12 text-center">
<HiCurrencyDollar className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 dark:text-gray-400">
{filterStatus === 'all'
? 'Henüz harcama talebi bulunmuyor'
: `${filterStatus === 'pending' ? 'Bekleyen' : filterStatus === 'approved' ? 'Onaylanan' : 'Reddedilen'} harcama talebi bulunmuyor`}
</p>
</div>
)}
</div>
</div>
</div>
{/* Yeni Harcama Talebi Modal */}
<AnimatePresence>
{showCreateModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowCreateModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Harcama Talebi
</h2>
<button
onClick={() => setShowCreateModal(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Harcama Kategorisi
</label>
<select className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Seyahat</option>
<option>Yemek</option>
<option>Konaklama</option>
<option>Ulaşım</option>
<option>Diğer</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tutar
</label>
<input
type="number"
placeholder="0.00"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Para Birimi
</label>
<select className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>TRY</option>
<option>USD</option>
<option>EUR</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Harcama Tarihi
</label>
<input
type="date"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Proje (Opsiyonel)
</label>
<input
type="text"
placeholder="Proje adı"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama
</label>
<textarea
rows={3}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white resize-none"
placeholder="Harcama detaylarınııklayın..."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Makbuz / Fatura
</label>
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-6 text-center hover:border-blue-500 dark:hover:border-blue-500 transition-colors cursor-pointer">
<HiArrowUpTray className="w-8 h-8 text-gray-400 mx-auto mb-2" />
<p className="text-sm text-gray-600 dark:text-gray-400">
Dosyaları sürükle-bırak veya <span className="text-blue-600 dark:text-blue-400">tıkla</span>
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
PDF, JPG, PNG (max 5MB)
</p>
</div>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
İptal
</button>
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Talep Oluştur
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default ExpenseManagement

View file

@ -1,426 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiCalendar,
HiClock,
HiCheckCircle,
HiXCircle,
HiPlus,
HiFunnel,
HiXMark
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockLeaveRequests } from '../../../mocks/mockIntranetData'
import { HrLeave, LeaveStatusEnum } from '@/types/hr'
dayjs.locale('tr')
const LeaveManagement: React.FC = () => {
const [requests, setRequests] = useState<HrLeave[]>(mockLeaveRequests)
const [showCreateModal, setShowCreateModal] = useState(false)
const [filterStatus, setFilterStatus] = useState<'all' | 'pending' | 'approved' | 'rejected'>('all')
// İzin bakiyeleri (mock)
const leaveBalance = {
annual: { total: 20, used: 8, remaining: 12 },
sick: { total: 10, used: 2, remaining: 8 },
unpaid: { used: 0 }
}
const filteredRequests = requests.filter(req => {
if (filterStatus === 'all') return true
return req.status === filterStatus
})
const getStatusColor = (status: string) => {
const colors = {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}
return colors[status as keyof typeof colors]
}
const getTypeLabel = (type: string) => {
const labels = {
annual: 'Yıllık İzin',
sick: 'Hastalık İzni',
unpaid: 'Ücretsiz İzin',
maternity: 'Doğum İzni',
other: 'Diğer'
}
return labels[type as keyof typeof labels]
}
const getTypeIcon = (type: string) => {
const icons = {
annual: '🏖️',
sick: '🏥',
unpaid: '💼',
maternity: '👶',
other: '📋'
}
return icons[type as keyof typeof icons]
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
İzin Yönetimi
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
İzin taleplerinizi oluşturun ve takip edin
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiPlus className="w-5 h-5" />
Yeni İzin Talebi
</button>
</div>
{/* İzin Bakiyeleri */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Yıllık İzin
</h3>
<span className="text-2xl">🏖</span>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Toplam</span>
<span className="font-semibold text-gray-900 dark:text-white">
{leaveBalance.annual.total} gün
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Kullanılan</span>
<span className="font-semibold text-orange-600 dark:text-orange-400">
{leaveBalance.annual.used} gün
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Kalan</span>
<span className="font-semibold text-green-600 dark:text-green-400">
{leaveBalance.annual.remaining} gün
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-3">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{
width: `${(leaveBalance.annual.used / leaveBalance.annual.total) * 100}%`
}}
></div>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Hastalık İzni
</h3>
<span className="text-2xl">🏥</span>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Toplam</span>
<span className="font-semibold text-gray-900 dark:text-white">
{leaveBalance.sick.total} gün
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Kullanılan</span>
<span className="font-semibold text-orange-600 dark:text-orange-400">
{leaveBalance.sick.used} gün
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Kalan</span>
<span className="font-semibold text-green-600 dark:text-green-400">
{leaveBalance.sick.remaining} gün
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-3">
<div
className="bg-red-600 h-2 rounded-full transition-all"
style={{
width: `${(leaveBalance.sick.used / leaveBalance.sick.total) * 100}%`
}}
></div>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Ücretsiz İzin
</h3>
<span className="text-2xl">💼</span>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Toplam</span>
<span className="font-semibold text-gray-900 dark:text-white">
Sınırsız
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Bu yıl kullanılan</span>
<span className="font-semibold text-orange-600 dark:text-orange-400">
{leaveBalance.unpaid.used} gün
</span>
</div>
<div className="mt-8 text-center">
<p className="text-xs text-gray-500 dark:text-gray-400">
Onay sürecinden geçer
</p>
</div>
</div>
</div>
</div>
{/* Filtreler */}
<div className="flex items-center gap-2">
<HiFunnel className="w-5 h-5 text-gray-400" />
<button
onClick={() => setFilterStatus('all')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'all'
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Tümü
</button>
<button
onClick={() => setFilterStatus('pending')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'pending'
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Bekleyen
</button>
<button
onClick={() => setFilterStatus('approved')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'approved'
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Onaylanan
</button>
<button
onClick={() => setFilterStatus('rejected')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'rejected'
? 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Reddedilen
</button>
</div>
{/* İzin Talepleri Listesi */}
<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">
İzin Talepleri
</h2>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{filteredRequests.length > 0 ? (
filteredRequests.map((request) => (
<div
key={request.id}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-4">
<span className="text-3xl">{getTypeIcon(request.leaveType)}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
{getTypeLabel(request.leaveType)}
</h3>
<span className={`px-2.5 py-1 text-xs rounded-full ${getStatusColor(request.status)}`}>
{request.status === LeaveStatusEnum.Pending && '⏳ Beklemede'}
{request.status === LeaveStatusEnum.Approved && '✅ Onaylandı'}
{request.status === LeaveStatusEnum.Rejected && '❌ Reddedildi'}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Başlangıç</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.startDate).format('DD MMMM YYYY')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Bitiş</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.endDate).format('DD MMMM YYYY')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Süre</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{request.days} gün
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Talep Tarihi</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.creationTime).format('DD MMM YYYY')}
</p>
</div>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
<span className="font-medium">ıklama:</span> {request.reason}
</p>
{request.approver && (
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<HiCheckCircle className="w-4 h-4" />
<span>
{request.approver.fullName} tarafından{' '}
{dayjs(request.approvalDate).format('DD MMMM YYYY')} tarihinde{' '}
{request.status === 'approved' ? 'onaylandı' : 'reddedildi'}
</span>
</div>
)}
{request.notes && (
<div className="mt-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<p className="text-xs text-gray-700 dark:text-gray-300">
<span className="font-medium">Not:</span> {request.notes}
</p>
</div>
)}
</div>
</div>
</div>
))
) : (
<div className="p-12 text-center">
<HiCalendar className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 dark:text-gray-400">
{filterStatus === 'all'
? 'Henüz izin talebi bulunmuyor'
: `${filterStatus === 'pending' ? 'Bekleyen' : filterStatus === 'approved' ? 'Onaylanan' : 'Reddedilen'} izin talebi bulunmuyor`}
</p>
</div>
)}
</div>
</div>
</div>
{/* Yeni İzin Talebi Modal */}
<AnimatePresence>
{showCreateModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowCreateModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni İzin Talebi
</h2>
<button
onClick={() => setShowCreateModal(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
İzin Türü
</label>
<select className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Yıllık İzin</option>
<option>Hastalık İzni</option>
<option>Ücretsiz İzin</option>
<option>Doğum İzni</option>
<option>Diğer</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Tarihi
</label>
<input
type="date"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Tarihi
</label>
<input
type="date"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama
</label>
<textarea
rows={4}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white resize-none"
placeholder="İzin sebebinizi açıklayın..."
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
İptal
</button>
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Talep Oluştur
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default LeaveManagement

View file

@ -1,356 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { HiClock, HiCheckCircle, HiPlus, HiXMark, HiFunnel } from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockOvertimeRequests } from '../../../mocks/mockIntranetData'
import { HrOvertime, LeaveStatusEnum } from '@/types/hr'
dayjs.locale('tr')
const OvertimeManagement: React.FC = () => {
const [requests, setRequests] = useState<HrOvertime[]>(mockOvertimeRequests)
const [showCreateModal, setShowCreateModal] = useState(false)
const [filterStatus, setFilterStatus] = useState<'all' | LeaveStatusEnum>('all')
// Mesai istatistikleri (mock)
const overtimeStats = {
thisMonth: { total: 24, approved: 20, pending: 4, rejected: 0 },
thisYear: { total: 156, approved: 142, pending: 8, rejected: 6 },
averagePerMonth: 13,
}
const filteredRequests = requests.filter((req) => {
if (filterStatus === 'all') return true
return req.status === filterStatus
})
const getStatusColor = (status: string) => {
const colors = {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[status as keyof typeof colors]
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Mesai Yönetimi</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Fazla mesai taleplerinizi oluşturun ve takip edin
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiPlus className="w-5 h-5" />
Yeni Mesai Talebi
</button>
</div>
{/* İstatistikler */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">Bu Ay Toplam</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{overtimeStats.thisMonth.total}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">saat fazla mesai</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">Onaylanan</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-green-600 dark:text-green-400">
{overtimeStats.thisMonth.approved}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">bu ay onaylandı</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">Bekleyen</h3>
<span className="text-2xl"></span>
</div>
<p className="text-3xl font-bold text-yellow-600 dark:text-yellow-400">
{overtimeStats.thisMonth.pending}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">onay bekliyor</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">
Yıllık Toplam
</h3>
<span className="text-2xl">📊</span>
</div>
<p className="text-3xl font-bold text-blue-600 dark:text-blue-400">
{overtimeStats.thisYear.total}
</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">saat bu yıl</p>
</div>
</div>
{/* Filtreler */}
<div className="flex items-center gap-2">
<HiFunnel className="w-5 h-5 text-gray-400" />
<button
onClick={() => setFilterStatus('all')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === 'all'
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Tümü
</button>
<button
onClick={() => setFilterStatus(LeaveStatusEnum.Pending)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === LeaveStatusEnum.Pending
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Bekleyen
</button>
<button
onClick={() => setFilterStatus(LeaveStatusEnum.Approved)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === LeaveStatusEnum.Approved
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Onaylanan
</button>
<button
onClick={() => setFilterStatus(LeaveStatusEnum.Rejected)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
filterStatus === LeaveStatusEnum.Rejected
? 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
Reddedilen
</button>
</div>
{/* Mesai Talepleri Listesi */}
<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">Mesai Talepleri</h2>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{filteredRequests.length > 0 ? (
filteredRequests.map((request) => (
<div
key={request.id}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
<HiClock className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
{dayjs(request.date).format('DD MMMM YYYY dddd')}
</h3>
<span
className={`px-2.5 py-1 text-xs rounded-full ${getStatusColor(request.status)}`}
>
{request.status === LeaveStatusEnum.Pending && '⏳ Beklemede'}
{request.status === LeaveStatusEnum.Approved && '✅ Onaylandı'}
{request.status === LeaveStatusEnum.Rejected && '❌ Reddedildi'}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Başlangıç</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{request.startTime}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Bitiş</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{request.endTime}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Süre</p>
<p className="text-sm font-medium text-blue-600 dark:text-blue-400">
{request.duration} saat
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Talep Tarihi</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(request.creationTime).format('DD MMM')}
</p>
</div>
</div>
{request.project && (
<div className="mb-2">
<span className="inline-flex items-center px-2.5 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-xs font-medium">
📁 {request.project}
</span>
</div>
)}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
<span className="font-medium">ıklama:</span> {request.reason}
</p>
{request.approver && (
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<HiCheckCircle className="w-4 h-4" />
<span>
{request.approver.fullName} tarafından{' '}
{dayjs(request.approvalDate).format('DD MMMM YYYY')} tarihinde{' '}
{request.status === LeaveStatusEnum.Approved
? 'onaylandı'
: 'reddedildi'}
</span>
</div>
)}
{request.notes && (
<div className="mt-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<p className="text-xs text-gray-700 dark:text-gray-300">
<span className="font-medium">Not:</span> {request.notes}
</p>
</div>
)}
</div>
</div>
</div>
))
) : (
<div className="p-12 text-center">
<HiClock className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 dark:text-gray-400">
{filterStatus === 'all'
? 'Henüz mesai talebi bulunmuyor'
: `${filterStatus === LeaveStatusEnum.Pending ? 'Bekleyen' : filterStatus === LeaveStatusEnum.Approved ? 'Onaylanan' : 'Reddedilen'} mesai talebi bulunmuyor`}
</p>
</div>
)}
</div>
</div>
</div>
{/* Yeni Mesai Talebi Modal */}
<AnimatePresence>
{showCreateModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowCreateModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Mesai Talebi
</h2>
<button
onClick={() => setShowCreateModal(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tarih
</label>
<input
type="date"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Saati
</label>
<input
type="time"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Saati
</label>
<input
type="time"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Proje (Opsiyonel)
</label>
<input
type="text"
placeholder="Proje adı"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama
</label>
<textarea
rows={4}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white resize-none"
placeholder="Mesai yapma sebebini açıklayın..."
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
İptal
</button>
<button
onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Talep Oluştur
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default OvertimeManagement

View file

@ -1,313 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { HiKey, HiCalendar, HiTruck, HiCog, HiPlus, HiXMark } from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockReservations, Reservation } from '../../mocks/mockIntranetData'
const ReservationsModule: React.FC = () => {
const [selectedType, setSelectedType] = useState<'all' | 'room' | 'vehicle' | 'equipment'>('all')
const [showNewReservation, setShowNewReservation] = useState(false)
const filteredReservations =
selectedType === 'all'
? mockReservations
: mockReservations.filter((r) => r.type === selectedType)
const getTypeIcon = (type: string) => {
switch (type) {
case 'room':
return <HiKey className="w-5 h-5" />
case 'vehicle':
return <HiTruck className="w-5 h-5" />
case 'equipment':
return <HiCog className="w-5 h-5" />
default:
return <HiKey className="w-5 h-5" />
}
}
const getTypeLabel = (type: string) => {
const labels: Record<string, string> = {
room: 'Toplantı Salonu',
vehicle: 'Araç',
equipment: 'Ekipman',
}
return labels[type] || type
}
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
completed: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
}
return colors[status] || colors.pending
}
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
pending: 'Bekliyor',
approved: 'Onaylandı',
rejected: 'Reddedildi',
completed: 'Tamamlandı',
}
return labels[status] || status
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">🔑 Rezervasyonlar</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Oda, araç ve ekipman rezervasyonları
</p>
</div>
<button
onClick={() => setShowNewReservation(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiPlus className="w-5 h-5" />
Yeni Rezervasyon
</button>
</div>
{/* Type Filter */}
<div className="flex gap-3">
{[
{ value: 'all' as const, label: 'Tümü', icon: HiCalendar },
{ value: 'room' as const, label: 'Toplantı Salonu', icon: HiKey },
{ value: 'vehicle' as const, label: 'Araç', icon: HiTruck },
{ value: 'equipment' as const, label: 'Ekipman', icon: HiCog },
].map((type) => (
<button
key={type.value}
onClick={() => setSelectedType(type.value)}
className={`flex items-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
selectedType === type.value
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
}`}
>
<type.icon className="w-5 h-5" />
{type.label}
</button>
))}
</div>
{/* Reservations List */}
<div className="space-y-4">
{filteredReservations.map((reservation: Reservation, idx: number) => (
<motion.div
key={reservation.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all"
>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center text-blue-600 dark:text-blue-400">
{getTypeIcon(reservation.type)}
</div>
<div className="flex-1">
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{reservation.resourceName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{getTypeLabel(reservation.type)}
</p>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(reservation.status)}`}
>
{getStatusLabel(reservation.status)}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Başlangıç</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(reservation.startDate).format('DD MMM, HH:mm')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Bitiş</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(reservation.endDate).format('DD MMM, HH:mm')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Rezerve Eden</p>
<div className="flex items-center gap-2 mt-1">
<img
src={reservation.bookedBy.avatar}
alt={reservation.bookedBy.fullName}
className="w-5 h-5 rounded-full"
/>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{reservation.bookedBy.fullName}
</p>
</div>
</div>
{reservation.participants && (
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Katılımcı</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{reservation.participants} kişi
</p>
</div>
)}
</div>
<div className="mb-3">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
Amaç: {reservation.purpose}
</p>
</div>
{reservation.notes && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<p className="text-xs text-gray-600 dark:text-gray-400">
<strong>Not:</strong> {reservation.notes}
</p>
</div>
)}
</div>
</div>
</motion.div>
))}
{filteredReservations.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<HiCalendar className="w-16 h-16 mx-auto mb-4 opacity-20" />
<p>Rezervasyon bulunamadı</p>
</div>
)}
</div>
</div>
{/* New Reservation Modal */}
<AnimatePresence>
{showNewReservation && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowNewReservation(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Rezervasyon Oluştur
</h2>
<button
onClick={() => setShowNewReservation(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Rezervasyon Tipi
</label>
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Toplantı Salonu</option>
<option>Araç</option>
<option>Ekipman</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Kaynak Seçin
</label>
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Toplantı Salonu A</option>
<option>Toplantı Salonu B</option>
<option>Eğitim Salonu</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Tarihi
</label>
<input
type="datetime-local"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Tarihi
</label>
<input
type="datetime-local"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Amaç
</label>
<input
type="text"
placeholder="Rezervasyon amacını yazın"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Notlar (Opsiyonel)
</label>
<textarea
rows={3}
placeholder="Ek notlarınızı yazın"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowNewReservation(false)}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
Rezervasyon Oluştur
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default ReservationsModule

View file

@ -1,464 +0,0 @@
import React, { useState, useMemo, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiHome,
HiCalendar,
HiFolder,
HiClipboardDocumentList,
HiChevronRight,
HiChatBubbleLeftRight,
HiCake,
HiAcademicCap,
HiKey,
HiBuildingOffice2,
HiClipboardDocumentCheck,
HiUserPlus,
HiBars3,
HiChevronLeft,
HiCog6Tooth,
HiArrowsUpDown,
HiSquares2X2,
} from 'react-icons/hi2'
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from '@dnd-kit/core'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import {
mockTasks,
mockEvents,
mockDocuments,
mockBirthdays,
mockTrainings,
mockReservations,
mockSurveys,
mockVisitors,
} from '../../mocks/mockIntranetData'
import dayjs from 'dayjs'
interface MenuItem {
id: string
label: string
icon: React.ElementType
path?: string
badge?: number
children?: MenuItem[]
}
const getMenuItems = (badgeCounts: any): MenuItem[] => [
{
id: 'dashboard',
label: 'Ana Sayfa',
icon: HiHome,
path: '/intranet/dashboard',
},
{
id: 'social',
label: 'Sosyal Akış',
icon: HiChatBubbleLeftRight,
path: '/intranet/social',
},
{
id: 'events',
label: 'Etkinlikler',
icon: HiCalendar,
path: '/intranet/events',
badge: badgeCounts.events || undefined,
},
{
id: 'documents',
label: 'Dokümanlar',
icon: HiFolder,
path: '/intranet/documents',
badge: badgeCounts.documents || undefined,
},
{
id: 'tasks',
label: 'Görevler',
icon: HiClipboardDocumentList,
path: '/intranet/tasks',
badge: badgeCounts.tasks || undefined,
},
{
id: 'birthdays',
label: 'Doğum Günleri',
icon: HiCake,
path: '/intranet/birthdays',
badge: badgeCounts.birthdays || undefined,
},
{
id: 'training',
label: 'Eğitimler',
icon: HiAcademicCap,
path: '/intranet/training',
badge: badgeCounts.training || undefined,
},
{
id: 'reservations',
label: 'Rezervasyonlar',
icon: HiKey,
path: '/intranet/reservations',
badge: badgeCounts.reservations || undefined,
},
{ id: 'hr-leave', label: 'İzin Yönetimi', icon: HiCalendar, path: '/intranet/hr/leave' },
{
id: 'hr-overtime',
label: 'Mesai Yönetimi',
icon: HiClipboardDocumentList,
path: '/intranet/hr/overtime',
},
{ id: 'hr-expense', label: 'Harcama Yönetimi', icon: HiFolder, path: '/intranet/hr/expense' },
{
id: 'cafeteria',
label: 'Kafeterya & Servis',
icon: HiBuildingOffice2,
path: '/intranet/cafeteria/shuttle',
},
{
id: 'surveys',
label: 'Anketler',
icon: HiClipboardDocumentCheck,
path: '/intranet/surveys',
badge: badgeCounts.surveys || undefined,
},
{
id: 'visitors',
label: 'Ziyaretçi Yönetimi',
icon: HiUserPlus,
path: '/intranet/visitors',
badge: badgeCounts.visitors || undefined,
},
]
interface IntranetSidebarProps {
activePath: string
onNavigate: (path: string) => void
}
const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigate }) => {
const [expandedMenus, setExpandedMenus] = useState<string[]>(['hr'])
const [isCollapsed, setIsCollapsed] = useState(false)
const [isDesignMode, setIsDesignMode] = useState(false)
const [customOrder, setCustomOrder] = useState<string[]>([])
// Mobil ekranlarda otomatik daralt
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 1024) {
// lg breakpoint (1024px)
setIsCollapsed(true)
}
}
// İlk yükleme
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// localStorage'dan özel sıralamayı yükle
useEffect(() => {
const savedOrder = localStorage.getItem('intranet-menu-order')
if (savedOrder) {
try {
setCustomOrder(JSON.parse(savedOrder))
} catch (e) {
console.error('Failed to load menu order:', e)
}
}
}, [])
// Dinamik badge sayılarını hesapla
const badgeCounts = useMemo(() => {
const today = dayjs()
return {
tasks: mockTasks.filter((t) => t.status !== 'done').length,
events: mockEvents.filter(
(e) =>
e.isPublished && (dayjs(e.date).isAfter(today) || dayjs(e.date).isSame(today, 'day')),
).length,
documents: mockDocuments.length,
birthdays: mockBirthdays.filter((b) => {
const birthMonth = dayjs(b.date).month()
const currentMonth = today.month()
return birthMonth === currentMonth
}).length,
training: mockTrainings.filter((t) => t.status === 'upcoming').length,
reservations: mockReservations.filter(
(r) => r.status === 'approved' || r.status === 'pending',
).length,
surveys: mockSurveys.filter((s) => s.status === 'active').length,
visitors: mockVisitors.filter((v) => v.status === 'scheduled' || v.status === 'checked-in')
.length,
}
}, [])
const toggleMenu = (id: string) => {
setExpandedMenus((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
)
}
const isActive = (path?: string) => {
if (!path) return false
return activePath === path || activePath.startsWith(path + '/')
}
const menuItems = useMemo(() => getMenuItems(badgeCounts), [badgeCounts])
// Menü sıralamasını customOrder'a göre düzenle
const orderedMenuItems = useMemo(() => {
if (customOrder.length === 0) {
return menuItems
}
const ordered = [...menuItems].sort((a, b) => {
const indexA = customOrder.indexOf(a.id)
const indexB = customOrder.indexOf(b.id)
// Eğer her ikisi de customOrder'da varsa, sıralarına göre sırala
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB
}
// Sadece a customOrder'da varsa, a önce gelsin
if (indexA !== -1) return -1
// Sadece b customOrder'da varsa, b önce gelsin
if (indexB !== -1) return 1
// İkisi de yoksa, orijinal sıraları koru
return 0
})
return ordered
}, [menuItems, customOrder])
// Drag & Drop sensörleri
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
)
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (over && active.id !== over.id) {
const oldIndex = orderedMenuItems.findIndex((item) => item.id === active.id)
const newIndex = orderedMenuItems.findIndex((item) => item.id === over.id)
const newOrder = arrayMove(orderedMenuItems, oldIndex, newIndex)
const newOrderIds = newOrder.map((item) => item.id)
setCustomOrder(newOrderIds)
localStorage.setItem('intranet-menu-order', JSON.stringify(newOrderIds))
}
}
const toggleDesignMode = () => {
setIsDesignMode(!isDesignMode)
}
const resetMenuOrder = () => {
setCustomOrder([])
localStorage.removeItem('intranet-menu-order')
}
// Sortable MenuItem komponenti
const SortableMenuItem = ({ item, level = 0 }: { item: MenuItem; level?: number }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: item.id,
disabled: !isDesignMode,
})
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
}
const hasChildren = item.children && item.children.length > 0
const isExpanded = expandedMenus.includes(item.id)
const active = isActive(item.path)
return (
<div ref={setNodeRef} style={style} key={item.id}>
<button
{...(isDesignMode ? { ...attributes, ...listeners } : {})}
onClick={() => {
if (isDesignMode) return // Tasarım modunda navigasyon engellenir
if (hasChildren) {
toggleMenu(item.id)
} else if (item.path) {
onNavigate(item.path)
}
}}
className={`w-full flex items-center ${
isCollapsed ? 'justify-center' : 'justify-between'
} px-3 py-2.5 rounded-lg transition-colors ${
active
? 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
} ${level > 0 ? 'ml-6' : ''} ${
isDesignMode
? 'cursor-move border-2 border-dashed border-blue-400 dark:border-blue-500'
: 'cursor-pointer'
}`}
title={isCollapsed ? item.label : isDesignMode ? 'Sürükle' : undefined}
>
<div className="flex items-center gap-3 min-w-0">
{isDesignMode && !isCollapsed && (
<HiArrowsUpDown className="w-4 h-4 flex-shrink-0 text-blue-500" />
)}
<item.icon className="w-5 h-5 flex-shrink-0" />
{!isCollapsed && <span className="font-medium text-sm truncate">{item.label}</span>}
</div>
{!isCollapsed && (
<div className="flex items-center gap-2 flex-shrink-0">
{item.badge && item.badge > 0 && (
<span className="px-2 py-0.5 bg-red-500 text-white text-xs rounded-full">
{item.badge}
</span>
)}
{hasChildren && !isDesignMode && (
<motion.div
animate={{ rotate: isExpanded ? 90 : 0 }}
transition={{ duration: 0.2 }}
>
<HiChevronRight className="w-4 h-4" />
</motion.div>
)}
</div>
)}
</button>
{hasChildren && (
<AnimatePresence>
{isExpanded && !isCollapsed && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden"
>
<div className="mt-1 space-y-1">
{item.children!.map((child) => (
<SortableMenuItem key={child.id} item={child} level={level + 1} />
))}
</div>
</motion.div>
)}
</AnimatePresence>
)}
</div>
)
}
const renderMenuItem = (item: MenuItem, level: number = 0) => {
return <SortableMenuItem key={item.id} item={item} level={level} />
}
return (
<>
{/* Sidebar */}
<motion.div
initial={false}
animate={{
width: isCollapsed ? '60px' : '256px',
}}
transition={{ duration: 0.3 }}
className="bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 h-screen sticky top-0 overflow-y-auto"
>
<div className="p-2 border-b border-gray-200 dark:border-gray-700">
{!isCollapsed ? (
<div className="flex items-center justify-between px-3 py-2.5">
<div className="flex items-center gap-3 min-w-0">
<HiSquares2X2 className="w-5 h-5 flex-shrink-0 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<button
onClick={toggleDesignMode}
className={`p-1.5 rounded transition-colors ${
isDesignMode
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400'
: 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400'
}`}
title={isDesignMode ? 'Düzenlemeyi Bitir' : 'Menü Düzenle'}
>
<HiCog6Tooth className="w-4 h-4" />
</button>
<button
onClick={() => setIsCollapsed(true)}
className="p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Daralt"
>
<HiChevronLeft className="w-4 h-4 text-gray-600 dark:text-gray-400" />
</button>
</div>
</div>
) : (
<button
onClick={() => setIsCollapsed(false)}
className="w-full flex justify-center p-2.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
title="Genişlet"
>
<HiBars3 className="w-5 h-5 text-gray-600 dark:text-gray-400" />
</button>
)}
</div>
{/* Tasarım Modu Bilgi Paneli */}
{!isCollapsed && isDesignMode && (
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-blue-50 dark:bg-blue-900/20">
<div className="flex items-center justify-between gap-2">
<p className="text-xs text-blue-700 dark:text-blue-300 flex items-center gap-2">
<HiArrowsUpDown className="w-4 h-4" />
Menü öğelerini sürükleyerek sıralayın
</p>
{customOrder.length > 0 && (
<button
onClick={resetMenuOrder}
className="px-2 py-1 text-xs text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 rounded transition-colors"
title="Varsayılana Sıfırla"
>
Sıfırla
</button>
)}
</div>
</div>
)}
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext
items={orderedMenuItems.map((item) => item.id)}
strategy={verticalListSortingStrategy}
>
<nav className="p-2 space-y-1">
{orderedMenuItems.map((item) => renderMenuItem(item))}
</nav>
</SortableContext>
</DndContext>
</motion.div>
</>
)
}
export default IntranetSidebar

View file

@ -1,298 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { HiClipboardDocumentCheck, HiClock, HiCheckCircle, HiXMark } from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockSurveys, Survey } from '../../mocks/mockIntranetData'
const SurveysModule: React.FC = () => {
const [selectedStatus, setSelectedStatus] = useState<'all' | 'active' | 'draft' | 'closed'>('all')
const [showSurveyModal, setShowSurveyModal] = useState(false)
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
const filteredSurveys =
selectedStatus === 'all' ? mockSurveys : mockSurveys.filter((s) => s.status === selectedStatus)
const handleTakeSurvey = (survey: Survey) => {
setSelectedSurvey(survey)
setShowSurveyModal(true)
}
const handleSubmitSurvey = () => {
// Anket gönderildi
setShowSurveyModal(false)
setSelectedSurvey(null)
// Başarı mesajı gösterilebilir
}
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
draft: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
active: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
closed: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[status] || colors.draft
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
📊 Anketler & Memnuniyet Formları
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Geri bildirim ve değerlendirme anketleri
</p>
</div>
{/* Filter */}
<div className="flex gap-3">
{[
{ value: 'all' as const, label: 'Tümü' },
{ value: 'active' as const, label: 'Aktif' },
{ value: 'draft' as const, label: 'Taslak' },
{ value: 'closed' as const, label: 'Kapalı' },
].map((tab) => (
<button
key={tab.value}
onClick={() => setSelectedStatus(tab.value)}
className={`px-4 py-2 rounded-lg border-2 transition-all ${
selectedStatus === tab.value
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Surveys List */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredSurveys.map((survey: Survey, idx: number) => (
<motion.div
key={survey.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all"
>
<div className="flex items-start justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white pr-4">
{survey.title}
</h3>
<span
className={`px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(survey.status)}`}
>
{survey.status === 'active'
? 'Aktif'
: survey.status === 'draft'
? 'Taslak'
: 'Kapalı'}
</span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{survey.description}</p>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Soru Sayısı</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{survey.totalQuestions} soru
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Yanıt</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{survey.responses} kişi
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Son Tarih</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(survey.deadline).format('DD MMM YYYY')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Gizlilik</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{survey.isAnonymous ? '🔒 Anonim' : '👤 İsimli'}
</p>
</div>
</div>
<div className="mb-4">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">Hedef Kitle</p>
<div className="flex flex-wrap gap-2">
{survey.targetAudience.map((audience, idx) => (
<span
key={idx}
className="px-2 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-xs rounded"
>
{audience}
</span>
))}
</div>
</div>
{survey.status === 'active' && (
<button
onClick={() => handleTakeSurvey(survey)}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Anketi Doldur
</button>
)}
{survey.status === 'closed' && (
<div className="flex items-center justify-center gap-2 py-2 text-green-600 dark:text-green-400">
<HiCheckCircle className="w-5 h-5" />
<span className="text-sm font-medium">Tamamlandı</span>
</div>
)}
</motion.div>
))}
</div>
{filteredSurveys.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<HiClipboardDocumentCheck className="w-16 h-16 mx-auto mb-4 opacity-20" />
<p>Anket bulunamadı</p>
</div>
)}
</div>
{/* Survey Modal */}
<AnimatePresence>
{showSurveyModal && selectedSurvey && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowSurveyModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{selectedSurvey.title}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
{selectedSurvey.description}
</p>
</div>
<button
onClick={() => setShowSurveyModal(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
handleSubmitSurvey()
}}
className="p-6 space-y-6"
>
{/* Örnek Anket Soruları */}
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
1. Genel memnuniyet düzeyiniz nedir? *
</label>
<div className="flex gap-2">
{[1, 2, 3, 4, 5].map((rating) => (
<label
key={rating}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
>
<input type="radio" name="rating" value={rating} required />
<span className="text-sm text-gray-900 dark:text-white">{rating}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
2. Hangi departmanda çalışıyorsunuz? *
</label>
<select
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
<option value="">Seçiniz</option>
<option value="it">Bilgi Teknolojileri</option>
<option value="hr">İnsan Kaynakları</option>
<option value="finance">Finans</option>
<option value="sales">Satış</option>
<option value="marketing">Pazarlama</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
3. Görüş ve önerileriniz
</label>
<textarea
rows={4}
placeholder="Yorumlarınızı buraya yazabilirsiniz..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
{!selectedSurvey.isAnonymous && (
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-sm text-blue-700 dark:text-blue-300">
Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız
kaydedilecektir.
</p>
</div>
)}
{selectedSurvey.isAnonymous && (
<div className="bg-green-50 dark:bg-green-900/20 rounded-lg p-3">
<p className="text-sm text-green-700 dark:text-green-300">
Bu anket anonimdir. Kimlik bilgileriniz kaydedilmeyecektir.
</p>
</div>
)}
</div>
<div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={() => setShowSurveyModal(false)}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Anketi Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default SurveysModule

View file

@ -1,674 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
useSensor,
useSensors,
closestCorners,
useDroppable,
} from '@dnd-kit/core'
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import {
HiPlus,
HiXMark,
HiClock,
HiChatBubbleLeftRight,
HiPaperClip,
HiTrash,
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { mockTasks, Task } from '../../mocks/mockIntranetData'
import { Badge } from '@/components/ui'
dayjs.locale('tr')
type TaskStatus = 'todo' | 'in-progress' | 'review' | 'done'
// Droppable Column Component
interface DroppableColumnProps {
id: TaskStatus
children: React.ReactNode
}
const DroppableColumn: React.FC<DroppableColumnProps> = ({ id, children }) => {
const { setNodeRef } = useDroppable({ id })
return <div ref={setNodeRef}>{children}</div>
}
// Sortable Task Card Component
interface SortableTaskCardProps {
task: Task
onTaskClick: (task: Task) => void
getPriorityColor: (priority: string) => string
getPriorityLabel: (priority: string) => string
isOverdue: (date: Date | string) => boolean
}
const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
task,
onTaskClick,
getPriorityColor,
getPriorityLabel,
isOverdue,
}) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: task.id,
})
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
}
const overdue = isOverdue(task.dueDate) && task.status !== 'done'
return (
<div
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className={`bg-white dark:bg-gray-800 rounded-lg p-3 sm:p-4 border-2 cursor-move hover:shadow-lg transition-all ${
overdue ? 'border-red-300 dark:border-red-700' : 'border-gray-200 dark:border-gray-700'
} ${isDragging ? 'shadow-2xl ring-4 ring-blue-500/50' : ''}`}
onClick={(e) => {
if (!(e.target as HTMLElement).closest('[data-no-click]')) {
onTaskClick(task)
}
}}
>
<div className="flex items-start justify-between mb-2 sm:mb-3">
<span
className={`px-2 py-1 text-xs font-medium rounded border ${getPriorityColor(task.priority)}`}
>
{getPriorityLabel(task.priority)}
</span>
{overdue && (
<span className="text-xs text-red-600 dark:text-red-400 font-medium"> Gecikmiş</span>
)}
</div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">{task.title}</h4>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
{task.description}
</p>
<div className="mb-3">
<span className="inline-flex items-center px-2 py-1 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-xs">
📁 {task.project}
</span>
</div>
{task.labels.length > 0 && (
<div className="flex flex-wrap gap-1 mb-3">
{task.labels.map((label, idx) => (
<span
key={idx}
className="px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded"
>
{label}
</span>
))}
</div>
)}
<div className="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
<div className="flex items-center gap-1">
<HiClock className="w-4 h-4" />
{dayjs(task.dueDate).format('DD MMM')}
</div>
{task.comments > 0 && (
<div className="flex items-center gap-1">
<HiChatBubbleLeftRight className="w-4 h-4" />
{task.comments}
</div>
)}
{task.attachments && task.attachments.length > 0 && (
<div className="flex items-center gap-1">
<HiPaperClip className="w-4 h-4" />
{task.attachments.length}
</div>
)}
</div>
<div className="flex -space-x-2">
{task.assignedTo.slice(0, 3).map((assignee, idx) => (
<img
key={idx}
src={assignee.avatar}
alt={assignee.fullName}
className="w-6 h-6 rounded-full border-2 border-white dark:border-gray-800"
title={assignee.fullName}
/>
))}
{task.assignedTo.length > 3 && (
<div className="w-6 h-6 rounded-full border-2 border-white dark:border-gray-800 bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-xs font-medium text-gray-700 dark:text-gray-300">
+{task.assignedTo.length - 3}
</div>
)}
</div>
</div>
</div>
)
}
const TasksModule: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>(mockTasks)
const [selectedTask, setSelectedTask] = useState<Task | null>(null)
const [activeId, setActiveId] = useState<string | null>(null)
const [dragOverColumn, setDragOverColumn] = useState<TaskStatus | null>(null)
const [showNewTaskModal, setShowNewTaskModal] = useState(false)
const [newTaskColumn, setNewTaskColumn] = useState<TaskStatus>('todo')
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
}),
)
const columns: { id: TaskStatus; title: string; icon: string; color: string }[] = [
{ id: 'todo', title: 'Yapılacak', icon: '📋', color: 'gray' },
{ id: 'in-progress', title: 'Devam Ediyor', icon: '⚙️', color: 'blue' },
{ id: 'review', title: 'İncelemede', icon: '👀', color: 'yellow' },
{ id: 'done', title: 'Tamamlandı', icon: '✅', color: 'green' },
]
const handleDragStart = (event: DragStartEvent) => {
setActiveId(event.active.id as string)
}
const handleDragOver = (event: any) => {
const { over } = event
if (!over) {
setDragOverColumn(null)
return
}
// Check if over a column or a task
const overColumn = columns.find((col) => col.id === over.id)
if (overColumn) {
setDragOverColumn(overColumn.id)
} else {
// over.id is a task, find its column
const overTask = tasks.find((t) => t.id === over.id)
if (overTask) {
setDragOverColumn(overTask.status)
}
}
}
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
setActiveId(null)
setDragOverColumn(null)
if (!over) return
const taskId = active.id as string
// over.id could be either a column id or a task id
// If it's a column id (from DroppableColumn), use it directly
// If it's a task id, find that task's column
let newStatus: TaskStatus
const overColumn = columns.find((col) => col.id === over.id)
if (overColumn) {
newStatus = overColumn.id
} else {
// over.id is a task, find its column
const overTask = tasks.find((t) => t.id === over.id)
if (!overTask) return
newStatus = overTask.status
}
// Update task status
setTasks((prevTasks) =>
prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
)
if (selectedTask?.id === taskId) {
setSelectedTask((prev) => (prev ? { ...prev, status: newStatus } : null))
}
}
const handleStatusChange = (taskId: string, newStatus: TaskStatus) => {
setTasks((prevTasks) =>
prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
)
if (selectedTask?.id === taskId) {
setSelectedTask((prev) => (prev ? { ...prev, status: newStatus } : null))
}
}
const handleAddTask = (status: TaskStatus) => {
setNewTaskColumn(status)
setShowNewTaskModal(true)
}
const handleCreateTask = (title: string, description: string) => {
const newTask: Task = {
id: `task-${Date.now()}`,
title,
description,
project: 'Genel',
assignedTo: [mockTasks[0].assignedTo[0]], // Default assignee
assignedBy: mockTasks[0].assignedBy,
priority: 'medium',
status: newTaskColumn,
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
createdAt: new Date(),
labels: [],
comments: 0,
}
setTasks((prev) => [...prev, newTask])
setShowNewTaskModal(false)
}
const handleDeleteTask = (taskId: string) => {
if (window.confirm('Bu görevi silmek istediğinizden emin misiniz?')) {
setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId))
setSelectedTask(null)
}
}
const getTasksByStatus = (status: TaskStatus) => {
return tasks.filter((task: Task) => task.status === status)
}
const getPriorityColor = (priority: string) => {
const colors = {
low: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600',
medium:
'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300 border-blue-200 dark:border-blue-700',
high: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-300 border-orange-200 dark:border-orange-700',
urgent:
'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300 border-red-200 dark:border-red-700',
}
return colors[priority as keyof typeof colors]
}
const getPriorityLabel = (priority: string) => {
const labels = {
low: 'Düşük',
medium: 'Orta',
high: 'Yüksek',
urgent: '🔥 Acil',
}
return labels[priority as keyof typeof labels]
}
const isOverdue = (dueDate: Date | string) => {
return dayjs(dueDate).isBefore(dayjs(), 'day')
}
return (
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-3 sm:p-4 md:p-6">
<div className="max-w-[1600px] mx-auto space-y-4 md:space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white">
Görev & Proje Yönetimi
</h1>
<p className="text-sm sm:text-base text-gray-600 dark:text-gray-400 mt-1">
Görevleri Kanban board ile yönetin
</p>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-xs sm:text-sm text-gray-600 dark:text-gray-400">
<span className="font-medium">Toplam:</span>
<span>{tasks.length} görev</span>
</div>
<button className="px-3 sm:px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors text-sm sm:text-base">
<HiPlus className="w-4 h-4 sm:w-5 sm:h-5" />
<span className="hidden sm:inline">Yeni Görev</span>
<span className="sm:hidden">Yeni</span>
</button>
</div>
</div>
{/* Kanban Board */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-4 overflow-x-auto pb-4">
<div className="kanban-container sm:contents">
{columns.map((column) => {
const columnTasks = getTasksByStatus(column.id)
return (
<DroppableColumn key={column.id} id={column.id}>
<SortableContext
id={column.id}
items={columnTasks.map((t) => t.id)}
strategy={verticalListSortingStrategy}
>
<div
className="kanban-column flex flex-col gap-2 sm:gap-3 bg-gray-100 dark:bg-gray-800/50 rounded-lg p-3 sm:p-4 min-h-[400px] sm:min-h-[500px] lg:min-h-[600px]"
data-status={column.id}
>
{/* Column Header */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl">{column.icon}</span>
<h3
className={`text-sm sm:text-base transition-colors duration-200 ${
dragOverColumn === column.id
? 'text-red-600 font-bold dark:text-blue-400'
: 'text-gray-900 font-semibold dark:text-white'
}`}
>
{column.title}
</h3>
</div>
<Badge content={columnTasks.length}></Badge>
</div>
{/* Tasks */}
<div className="space-y-3 flex-1">
{columnTasks.map((task) => (
<SortableTaskCard
key={task.id}
task={task}
onTaskClick={setSelectedTask}
getPriorityColor={getPriorityColor}
getPriorityLabel={getPriorityLabel}
isOverdue={isOverdue}
/>
))}
</div>
{/* Add Task Button */}
<button
onClick={() => handleAddTask(column.id)}
className="w-full p-3 sm:p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-blue-500 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-colors text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
>
<HiPlus className="w-4 h-4 sm:w-5 sm:h-5 mx-auto" />
</button>
</div>
</SortableContext>
</DroppableColumn>
)
})}
</div>
</div>
{/* DragOverlay */}
<DragOverlay>
{activeId ? (
<div className="opacity-50">
{(() => {
const task = tasks.find((t) => t.id === activeId)
if (!task) return null
return (
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 border-2 border-blue-500 shadow-2xl">
<h4 className="font-semibold text-gray-900 dark:text-white">{task.title}</h4>
</div>
)
})()}
</div>
) : null}
</DragOverlay>
</div>
{/* New Task Modal */}
<AnimatePresence>
{showNewTaskModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowNewTaskModal(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4">
<motion.div
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-lg w-full"
onClick={(e) => e.stopPropagation()}
>
<div className="p-4 sm:p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 className="text-lg sm:text-xl font-semibold text-gray-900 dark:text-white">
Yeni Görev Oluştur
</h2>
<button
onClick={() => setShowNewTaskModal(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const title = formData.get('title') as string
const description = formData.get('description') as string
if (title && description) {
handleCreateTask(title, description)
}
}}
className="p-4 sm:p-6 space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Görev Başlığı *
</label>
<input
type="text"
name="title"
required
placeholder="Görev başlığını yazın"
className="w-full px-3 sm:px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 text-sm sm:text-base"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama *
</label>
<textarea
name="description"
required
rows={4}
placeholder="Görev detaylarını yazın"
className="w-full px-3 sm:px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 text-sm sm:text-base"
/>
</div>
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-xs sm:text-sm text-blue-700 dark:text-blue-300">
📋 Görev{' '}
<strong>{columns.find((c) => c.id === newTaskColumn)?.title}</strong>{' '}
kolonuna eklenecek
</p>
</div>
<div className="flex gap-2 sm:gap-3 pt-4">
<button
type="button"
onClick={() => setShowNewTaskModal(false)}
className="flex-1 px-3 sm:px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm sm:text-base"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-3 sm:px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm sm:text-base"
>
Oluştur
</button>
</div>
</form>
</motion.div>
</div>
</>
)}
</AnimatePresence>
{/* Task Detail Modal */}
<AnimatePresence>
{selectedTask && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setSelectedTask(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4">
<motion.div
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 max-h-[90vh] overflow-y-auto"
>
<div className="p-4 sm:p-6 border-b border-gray-200 dark:border-gray-700 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
<span
className={`px-2 sm:px-3 py-1 sm:py-1.5 text-xs sm:text-sm font-medium rounded border ${getPriorityColor(selectedTask.priority)}`}
>
{getPriorityLabel(selectedTask.priority)}
</span>
<span className="px-2 sm:px-3 py-1 sm:py-1.5 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs sm:text-sm font-medium rounded">
📁 {selectedTask.project}
</span>
</div>
<button
onClick={() => setSelectedTask(null)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-4 sm:p-6 space-y-4 sm:space-y-6">
<div>
<h2 className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white mb-2">
{selectedTask.title}
</h2>
<p className="text-sm sm:text-base text-gray-700 dark:text-gray-300">
{selectedTask.description}
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
Durum
</p>
<select
value={selectedTask.status}
onChange={(e) =>
handleStatusChange(selectedTask.id, e.target.value as TaskStatus)
}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 cursor-pointer"
>
<option value="todo">📋 Yapılacak</option>
<option value="in-progress"> Devam Ediyor</option>
<option value="review">👀 İncelemede</option>
<option value="done"> Tamamlandı</option>
</select>
</div>
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
Son Tarih
</p>
<div className="flex items-center gap-2 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg">
<HiClock className="w-5 h-5 text-gray-400" />
<span className="text-gray-900 dark:text-white">
{dayjs(selectedTask.dueDate).format('DD MMMM YYYY')}
</span>
</div>
</div>
</div>
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2 sm:mb-3">
Atananlar
</p>
<div className="flex flex-wrap gap-2 sm:gap-3">
{selectedTask.assignedTo.map((user, idx) => (
<div
key={idx}
className="flex items-center gap-2 px-2 sm:px-3 py-1.5 sm:py-2 bg-gray-100 dark:bg-gray-700 rounded-lg"
>
<img
src={user.avatar}
alt={user.fullName}
className="w-6 h-6 sm:w-8 sm:h-8 rounded-full"
/>
<span className="text-xs sm:text-sm text-gray-900 dark:text-white">
{user.fullName}
</span>
</div>
))}
</div>
</div>
{selectedTask.labels.length > 0 && (
<div>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">
Etiketler
</p>
<div className="flex flex-wrap gap-2">
{selectedTask.labels.map((label, idx) => (
<span
key={idx}
className="px-2 sm:px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs sm:text-sm rounded"
>
{label}
</span>
))}
</div>
</div>
)}
<div className="pt-6 border-t border-gray-200 dark:border-gray-700 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<p className="text-xs text-gray-500 dark:text-gray-400">
Oluşturan: {selectedTask.assignedBy.fullName} {' '}
{dayjs(selectedTask.createdAt).format('DD MMMM YYYY')}
</p>
<button
onClick={() => handleDeleteTask(selectedTask.id)}
className="px-3 sm:px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center gap-2 transition-colors text-sm"
>
<HiTrash className="w-4 h-4" />
Görevi Sil
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
</DndContext>
)
}
export default TasksModule

View file

@ -1,399 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
HiAcademicCap,
HiClock,
HiUsers,
HiMapPin,
HiXMark,
HiCheckBadge,
HiCalendar,
} from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockTrainings, mockCertificates, Training } from '../../mocks/mockIntranetData'
const TrainingModule: React.FC = () => {
const [selectedTraining, setSelectedTraining] = useState<Training | null>(null)
const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>(
'all',
)
const [trainings, setTrainings] = useState<Training[]>(mockTrainings)
const [showEnrollSuccess, setShowEnrollSuccess] = useState(false)
const filteredTrainings =
selectedTab === 'all' ? trainings : trainings.filter((t) => t.status === selectedTab)
const handleEnroll = (trainingId: string) => {
setTrainings((prev) =>
prev.map((t) =>
t.id === trainingId && t.enrolled < t.maxParticipants
? { ...t, enrolled: t.enrolled + 1 }
: t,
),
)
// Seçili eğitimi de güncelle
if (
selectedTraining?.id === trainingId &&
selectedTraining.enrolled < selectedTraining.maxParticipants
) {
setSelectedTraining((prev) => (prev ? { ...prev, enrolled: prev.enrolled + 1 } : null))
}
setShowEnrollSuccess(true)
setTimeout(() => setShowEnrollSuccess(false), 3000)
}
const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
technical: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
'soft-skills': 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
management: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
compliance: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
other: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
}
return colors[category] || colors.other
}
const getStatusBadge = (status: string) => {
const badges: Record<string, { bg: string; text: string }> = {
upcoming: { bg: 'bg-blue-100 dark:bg-blue-900/30', text: 'text-blue-700 dark:text-blue-300' },
ongoing: {
bg: 'bg-green-100 dark:bg-green-900/30',
text: 'text-green-700 dark:text-green-300',
},
completed: { bg: 'bg-gray-100 dark:bg-gray-700', text: 'text-gray-700 dark:text-gray-300' },
}
return badges[status] || badges.upcoming
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
🎓 Eğitimler & Sertifikalar
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Kişisel gelişim ve eğitim kayıtları
</p>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{[
{ label: 'Tümü', value: mockTrainings.length, tab: 'all' as const },
{
label: 'Yaklaşan',
value: mockTrainings.filter((t) => t.status === 'upcoming').length,
tab: 'upcoming' as const,
},
{
label: 'Devam Eden',
value: mockTrainings.filter((t) => t.status === 'ongoing').length,
tab: 'ongoing' as const,
},
{
label: 'Tamamlanan',
value: mockTrainings.filter((t) => t.status === 'completed').length,
tab: 'completed' as const,
},
].map((stat, idx) => (
<motion.button
key={idx}
onClick={() => setSelectedTab(stat.tab)}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className={`p-4 rounded-lg border-2 transition-all ${
selectedTab === stat.tab
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-blue-300'
}`}
>
<div className="text-3xl font-bold text-gray-900 dark:text-white">{stat.value}</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">{stat.label}</div>
</motion.button>
))}
</div>
{/* Eğitimler */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredTrainings.map((training, idx) => {
const badge = getStatusBadge(training.status)
return (
<motion.div
key={training.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
onClick={() => setSelectedTraining(training)}
className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all cursor-pointer"
>
{training.thumbnail && (
<img
src={training.thumbnail}
alt={training.title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<div className="flex items-start justify-between mb-3">
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}
>
{training.category}
</span>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`}
>
{training.status === 'upcoming'
? 'Yaklaşan'
: training.status === 'ongoing'
? 'Devam Ediyor'
: 'Tamamlandı'}
</span>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
{training.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
{training.description}
</p>
<div className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
<div className="flex items-center gap-2">
<HiAcademicCap className="w-4 h-4" />
<span>{training.instructor}</span>
</div>
<div className="flex items-center gap-2">
<HiCalendar className="w-4 h-4" />
<span>{dayjs(training.startDate).format('DD MMM YYYY')}</span>
</div>
<div className="flex items-center gap-2">
<HiClock className="w-4 h-4" />
<span>{training.duration} saat</span>
</div>
<div className="flex items-center gap-2">
<HiUsers className="w-4 h-4" />
<span>
{training.enrolled} / {training.maxParticipants} katılımcı
</span>
</div>
</div>
{training.enrolled < training.maxParticipants &&
training.status === 'upcoming' && (
<button
onClick={() => handleEnroll(training.id)}
className="mt-4 w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Kayıt Ol
</button>
)}
</div>
</motion.div>
)
})}
</div>
{/* Sertifikalar */}
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<HiCheckBadge className="w-6 h-6 text-blue-500" />
Sertifikalarım
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{mockCertificates.map((cert, idx) => (
<motion.div
key={cert.id}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: idx * 0.1 }}
className="p-4 rounded-lg border-2 border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20"
>
<div className="flex items-start justify-between mb-3">
<HiCheckBadge className="w-8 h-8 text-blue-600 dark:text-blue-400" />
{cert.score && (
<span className="px-2 py-1 bg-blue-600 text-white rounded text-xs font-bold">
{cert.score}
</span>
)}
</div>
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
{cert.trainingTitle}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{dayjs(cert.issueDate).format('DD MMMM YYYY')}
</p>
{cert.expiryDate && (
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
Geçerlilik: {dayjs(cert.expiryDate).format('DD/MM/YYYY')}
</p>
)}
<button className="mt-3 w-full px-3 py-1 text-sm text-blue-600 dark:text-blue-400 border border-blue-600 dark:border-blue-400 rounded hover:bg-blue-600 hover:text-white dark:hover:bg-blue-600 transition-colors">
Görüntüle
</button>
</motion.div>
))}
</div>
</div>
</div>
{/* Training Detail Modal */}
<AnimatePresence>
{selectedTraining && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setSelectedTraining(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 flex items-center justify-between z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Eğitim Detayları
</h2>
<button
onClick={() => setSelectedTraining(null)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-6">
{selectedTraining.thumbnail && (
<img
src={selectedTraining.thumbnail}
alt={selectedTraining.title}
className="w-full h-64 object-cover rounded-lg"
/>
)}
<div>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
{selectedTraining.title}
</h3>
<p className="text-gray-700 dark:text-gray-300">
{selectedTraining.description}
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p>
<p className="font-medium text-gray-900 dark:text-white">
{selectedTraining.instructor}
</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Kategori</p>
<span
className={`inline-block px-3 py-1 rounded-full text-sm ${getCategoryColor(selectedTraining.category)}`}
>
{selectedTraining.category}
</span>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tarih</p>
<p className="font-medium text-gray-900 dark:text-white">
{dayjs(selectedTraining.startDate).format('DD MMM')} -{' '}
{dayjs(selectedTraining.endDate).format('DD MMM YYYY')}
</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Süre</p>
<p className="font-medium text-gray-900 dark:text-white">
{selectedTraining.duration} saat
</p>
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tip</p>
<p className="font-medium text-gray-900 dark:text-white capitalize">
{selectedTraining.type}
</p>
</div>
{selectedTraining.location && (
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Konum</p>
<p className="font-medium text-gray-900 dark:text-white">
{selectedTraining.location}
</p>
</div>
)}
</div>
<div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Doluluk Oranı</p>
<div className="flex items-center gap-3">
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3">
<div
className="bg-blue-600 h-3 rounded-full transition-all"
style={{
width: `${(selectedTraining.enrolled / selectedTraining.maxParticipants) * 100}%`,
}}
/>
</div>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{selectedTraining.enrolled} / {selectedTraining.maxParticipants}
</span>
</div>
</div>
{selectedTraining.enrolled < selectedTraining.maxParticipants &&
selectedTraining.status === 'upcoming' && (
<button
onClick={() => handleEnroll(selectedTraining.id)}
className="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
>
Eğitime Kayıt Ol
</button>
)}
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
{/* Success Notification */}
<AnimatePresence>
{showEnrollSuccess && (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
className="fixed bottom-6 right-6 bg-green-600 text-white px-6 py-4 rounded-lg shadow-xl flex items-center gap-3 z-50"
>
<HiCheckBadge className="w-6 h-6" />
<div>
<p className="font-semibold">Kayıt Başarılı!</p>
<p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}
export default TrainingModule

View file

@ -1,393 +0,0 @@
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { HiUserPlus, HiXMark, HiCheckCircle, HiClock, HiPhone, HiEnvelope } from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockVisitors, Visitor } from '../../mocks/mockIntranetData'
const VisitorsModule: React.FC = () => {
const [selectedStatus, setSelectedStatus] = useState<
'all' | 'scheduled' | 'checked-in' | 'checked-out' | 'cancelled'
>('all')
const [showNewVisitor, setShowNewVisitor] = useState(false)
const [visitors, setVisitors] = useState<Visitor[]>(mockVisitors)
const filteredVisitors =
selectedStatus === 'all' ? visitors : visitors.filter((v) => v.status === selectedStatus)
const handleCheckIn = (visitorId: string) => {
setVisitors((prev) =>
prev.map((v) =>
v.id === visitorId ? { ...v, status: 'checked-in', checkIn: new Date() } : v,
),
)
}
const handleCheckOut = (visitorId: string) => {
setVisitors((prev) =>
prev.map((v) =>
v.id === visitorId ? { ...v, status: 'checked-out', checkOut: new Date() } : v,
),
)
}
const handleCancel = (visitorId: string) => {
setVisitors((prev) => prev.map((v) => (v.id === visitorId ? { ...v, status: 'cancelled' } : v)))
}
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
scheduled: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
'checked-in': 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
'checked-out': 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
cancelled: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[status] || colors.scheduled
}
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
scheduled: '📅 Planlandı',
'checked-in': '✅ Giriş Yaptı',
'checked-out': '🚪 Çıkış Yaptı',
cancelled: '❌ İptal Edildi',
}
return labels[status] || status
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
👥 Ziyaretçi Yönetimi
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Ziyaretçi kayıtları ve giriş-çıkış takibi
</p>
</div>
<button
onClick={() => setShowNewVisitor(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiUserPlus className="w-5 h-5" />
Yeni Ziyaretçi
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[
{ label: 'Tümü', value: mockVisitors.length, status: 'all' as const },
{
label: 'Planlandı',
value: mockVisitors.filter((v) => v.status === 'scheduled').length,
status: 'scheduled' as const,
},
{
label: 'İçeride',
value: mockVisitors.filter((v) => v.status === 'checked-in').length,
status: 'checked-in' as const,
},
{
label: ıkış Yaptı',
value: mockVisitors.filter((v) => v.status === 'checked-out').length,
status: 'checked-out' as const,
},
].map((stat, idx) => (
<motion.button
key={idx}
onClick={() => setSelectedStatus(stat.status)}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className={`p-4 rounded-lg border-2 transition-all ${
selectedStatus === stat.status
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-blue-300'
}`}
>
<div className="text-3xl font-bold text-gray-900 dark:text-white">{stat.value}</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">{stat.label}</div>
</motion.button>
))}
</div>
{/* Visitors List */}
<div className="space-y-4">
{filteredVisitors.map((visitor: Visitor, idx: number) => (
<motion.div
key={visitor.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all"
>
<div className="flex items-start gap-4">
{visitor.photo && (
<img
src={visitor.photo}
alt={visitor.fullName}
className="w-16 h-16 rounded-full border-4 border-gray-100 dark:border-gray-700"
/>
)}
<div className="flex-1">
<div className="flex items-start justify-between mb-3">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{visitor.fullName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">{visitor.company}</p>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(visitor.status)}`}
>
{getStatusLabel(visitor.status)}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
Ziyaret Tarihi
</p>
<div className="flex items-center gap-1 text-sm text-gray-900 dark:text-white">
<HiClock className="w-4 h-4" />
{dayjs(visitor.visitDate).format('DD MMM, HH:mm')}
</div>
</div>
{visitor.checkIn && (
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Giriş</p>
<p className="text-sm font-medium text-green-600 dark:text-green-400">
{dayjs(visitor.checkIn).format('HH:mm')}
</p>
</div>
)}
{visitor.checkOut && (
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Çıkış</p>
<p className="text-sm font-medium text-gray-600 dark:text-gray-400">
{dayjs(visitor.checkOut).format('HH:mm')}
</p>
</div>
)}
{visitor.badgeNumber && (
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Rozet No</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{visitor.badgeNumber}
</p>
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">İletişim</p>
<div className="space-y-1">
<div className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<HiEnvelope className="w-4 h-4" />
{visitor.email}
</div>
<div className="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<HiPhone className="w-4 h-4" />
{visitor.phone}
</div>
</div>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Ev Sahibi</p>
<div className="flex items-center gap-2">
<img
src={visitor.host.avatar}
alt={visitor.host.fullName}
className="w-8 h-8 rounded-full"
/>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{visitor.host.fullName}
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
{visitor.host.department?.name}
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<p className="text-xs text-gray-600 dark:text-gray-400 mb-1">Ziyaret Amacı</p>
<p className="text-sm text-gray-900 dark:text-white">{visitor.purpose}</p>
</div>
{visitor.status === 'scheduled' && (
<div className="flex gap-2 mt-4">
<button
onClick={() => handleCheckIn(visitor.id)}
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors text-sm"
>
<HiCheckCircle className="w-4 h-4 inline mr-1" />
Giriş Yaptır
</button>
<button
onClick={() => handleCancel(visitor.id)}
className="px-4 py-2 border border-red-600 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors text-sm"
>
İptal Et
</button>
</div>
)}
{visitor.status === 'checked-in' && (
<button
onClick={() => handleCheckOut(visitor.id)}
className="mt-4 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors text-sm"
>
Çıkış Yaptır
</button>
)}
</div>
</div>
</motion.div>
))}
</div>
</div>
{/* New Visitor Modal */}
<AnimatePresence>
{showNewVisitor && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowNewVisitor(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Ziyaretçi Kaydı
</h2>
<button
onClick={() => setShowNewVisitor(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ad Soyad *
</label>
<input
type="text"
placeholder="Ziyaretçi adı"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Şirket *
</label>
<input
type="text"
placeholder="Şirket adı"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
E-posta *
</label>
<input
type="email"
placeholder="email@example.com"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Telefon *
</label>
<input
type="tel"
placeholder="+90 5XX XXX XX XX"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ziyaret Tarihi ve Saati *
</label>
<input
type="datetime-local"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ev Sahibi (Personel) *
</label>
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Ahmet Yılmaz - Yazılım Geliştirme</option>
<option>Zeynep Kaya - İnsan Kaynakları</option>
<option>Mehmet Demir - Yazılım Geliştirme</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ziyaret Amacı *
</label>
<textarea
rows={3}
placeholder="Ziyaret amacınııklayın"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowNewVisitor(false)}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
Ziyaretçi Kaydet
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default VisitorsModule

View file

@ -0,0 +1,195 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes, FaEye, FaClipboard } from 'react-icons/fa'
import dayjs from 'dayjs'
import { Announcement } from '../../../mocks/mockIntranetData'
interface AnnouncementDetailModalProps {
announcement: Announcement
onClose: () => void
}
const AnnouncementDetailModal: React.FC<AnnouncementDetailModalProps> = ({ announcement, onClose }) => {
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',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[category] || colors.general
}
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
<motion.div
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(announcement.category)}`}
>
{announcement.category === 'general' && '📢 Genel'}
{announcement.category === 'hr' && '👥 İnsan Kaynakları'}
{announcement.category === 'it' && '💻 Bilgi Teknolojileri'}
{announcement.category === 'event' && '🎉 Etkinlik'}
{announcement.category === 'urgent' && '🚨 Acil'}
</span>
{announcement.isPinned && (
<span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
)}
</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{announcement.title}
</h2>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-6 h-6 text-gray-500" />
</button>
</div>
{/* Author Info */}
<div className="flex items-center gap-3 mt-4">
<img
src={announcement.author.avatar}
alt={announcement.author.fullName}
className="w-12 h-12 rounded-full"
/>
<div>
<p className="font-semibold text-gray-900 dark:text-white">
{announcement.author.fullName}
</p>
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
<span>
{dayjs(announcement.publishDate).format('DD MMMM YYYY, HH:mm')}
</span>
<span></span>
<span className="flex items-center gap-1">
<FaEye className="w-4 h-4" />
{announcement.viewCount} görüntülenme
</span>
</div>
</div>
</div>
</div>
{/* Content */}
<div className="p-6 max-h-[60vh] overflow-y-auto">
{/* Image if exists */}
{announcement.imageUrl && (
<img
src={announcement.imageUrl}
alt={announcement.title}
className="w-full rounded-lg mb-6"
/>
)}
{/* 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">
{announcement.content}
</p>
</div>
{/* Attachments */}
{announcement.attachments &&
announcement.attachments.length > 0 && (
<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">
<FaClipboard className="w-5 h-5" />
Ekler ({announcement.attachments.length})
</h3>
<div className="space-y-2">
{announcement.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"
>
<FaClipboard 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>
)}
{/* Departments */}
{announcement.departments &&
announcement.departments.length > 0 && (
<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">
{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"
>
{dept}
</span>
))}
</div>
</div>
)}
{/* Expiry Date */}
{announcement.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(announcement.expiryDate).format('DD MMMM YYYY')}
</p>
</div>
)}
</div>
{/* Footer */}
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
<button
onClick={onClose}
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>
</>
)
}
export default AnnouncementDetailModal

View file

@ -0,0 +1,135 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
interface ExpenseRequestModalProps {
onClose: () => void
onSubmit: () => void
}
const ExpenseRequestModal: React.FC<ExpenseRequestModalProps> = ({ onClose, onSubmit }) => {
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-lg w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Harcama Talebi
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
onSubmit()
}}
className="p-6 space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Harcama Kategorisi *
</label>
<select
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
<option value="">Seçiniz</option>
<option value="travel"> Seyahat</option>
<option value="meal">🍽 Yemek</option>
<option value="accommodation">🏨 Konaklama</option>
<option value="transport">🚗 Ulaşım</option>
<option value="other">📋 Diğer</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama *
</label>
<input
type="text"
required
placeholder="Harcama açıklaması..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tutar () *
</label>
<input
type="number"
required
min="0"
step="0.01"
placeholder="0.00"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tarih *
</label>
<input
type="date"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Not
</label>
<textarea
rows={3}
placeholder="Ek bilgi (opsiyonel)..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg transition-colors"
>
Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default ExpenseRequestModal

View file

@ -0,0 +1,120 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
interface LeaveRequestModalProps {
onClose: () => void
onSubmit: () => void
}
const LeaveRequestModal: React.FC<LeaveRequestModalProps> = ({ onClose, onSubmit }) => {
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-lg w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni İzin Talebi
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
onSubmit()
}}
className="p-6 space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
İzin Türü *
</label>
<select
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
<option value="">Seçiniz</option>
<option value="annual">🏖 Yıllık İzin</option>
<option value="sick">🏥 Hastalık İzni</option>
<option value="unpaid">💼 Ücretsiz İzin</option>
<option value="other">📋 Diğer</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Tarihi *
</label>
<input
type="date"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Tarihi *
</label>
<input
type="date"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama *
</label>
<textarea
required
rows={3}
placeholder="İzin sebebinizi yazınız..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default LeaveRequestModal

View file

@ -0,0 +1,115 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
interface OvertimeRequestModalProps {
onClose: () => void
onSubmit: () => void
}
const OvertimeRequestModal: React.FC<OvertimeRequestModalProps> = ({ onClose, onSubmit }) => {
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-lg w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Mesai Talebi
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
onSubmit()
}}
className="p-6 space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Mesai Tarihi *
</label>
<input
type="date"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Saati *
</label>
<input
type="time"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Saati *
</label>
<input
type="time"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
ıklama *
</label>
<textarea
required
rows={3}
placeholder="Mesai sebebinizi yazınız..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-lg transition-colors"
>
Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default OvertimeRequestModal

View file

@ -0,0 +1,160 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
interface ReservationRequestModalProps {
onClose: () => void
onSubmit: () => void
}
const ReservationRequestModal: React.FC<ReservationRequestModalProps> = ({ onClose, onSubmit }) => {
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-lg w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Rezervasyon Talebi
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
onSubmit()
}}
className="p-6 space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Rezervasyon Türü *
</label>
<select
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
<option value="">Seçiniz</option>
<option value="room">🏢 Toplantı Odası</option>
<option value="vehicle">🚗 Şirket Aracı</option>
<option value="equipment"> Ekipman</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Kaynak Adı *
</label>
<input
type="text"
required
placeholder="Örn: Toplantı Salonu A, Şirket Aracı - Plaka, Kamera Seti..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Tarihi *
</label>
<input
type="datetime-local"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Tarihi *
</label>
<input
type="datetime-local"
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Amaç *
</label>
<input
type="text"
required
placeholder="Rezervasyon amacını belirtiniz..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Katılımcı Sayısı
</label>
<input
type="number"
min="1"
placeholder="Toplantı odası için katılımcı sayısı (opsiyonel)"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Notlar
</label>
<textarea
rows={3}
placeholder="Ek bilgi veya özel istekler (opsiyonel)..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-sm text-blue-700 dark:text-blue-300">
Rezervasyon talebiniz yönetici onayına sunulacaktır.
</p>
</div>
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
>
Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default ReservationRequestModal

View file

@ -0,0 +1,141 @@
import React from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
import { Survey } from '../../../mocks/mockIntranetData'
interface SurveyModalProps {
survey: Survey
onClose: () => void
onSubmit: () => void
}
const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit }) => {
return (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={onClose}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
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-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{survey.title}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
{survey.description}
</p>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<FaTimes className="w-5 h-5 text-gray-500" />
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault()
onSubmit()
}}
className="p-6 space-y-6"
>
{/* Örnek Anket Soruları */}
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
1. Genel memnuniyet düzeyiniz nedir? *
</label>
<div className="flex gap-2">
{[1, 2, 3, 4, 5].map((rating) => (
<label
key={rating}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
>
<input type="radio" name="rating" value={rating} required />
<span className="text-sm text-gray-900 dark:text-white">{rating}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
2. Hangi departmanda çalışıyorsunuz? *
</label>
<select
required
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
>
<option value="">Seçiniz</option>
<option value="it">Bilgi Teknolojileri</option>
<option value="hr">İnsan Kaynakları</option>
<option value="finance">Finans</option>
<option value="sales">Satış</option>
<option value="marketing">Pazarlama</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
3. Görüş ve önerileriniz
</label>
<textarea
rows={4}
placeholder="Yorumlarınızı buraya yazabilirsiniz..."
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
/>
</div>
{!survey.isAnonymous && (
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-sm text-blue-700 dark:text-blue-300">
Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız
kaydedilecektir.
</p>
</div>
)}
{survey.isAnonymous && (
<div className="bg-green-50 dark:bg-green-900/20 rounded-lg p-3">
<p className="text-sm text-green-700 dark:text-green-300">
Bu anket anonimdir. Kimlik bilgileriniz kaydedilmeyecektir.
</p>
</div>
)}
</div>
<div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button
type="submit"
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Anketi Gönder
</button>
</div>
</form>
</motion.div>
</div>
</>
)
}
export default SurveyModal

View file

@ -0,0 +1,59 @@
import React from 'react'
import { FaKey, FaPlus } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockReservations } from '../../../mocks/mockIntranetData'
interface ActiveReservationsProps {
onNewReservation: () => void
}
const ActiveReservations: React.FC<ActiveReservationsProps> = ({ onNewReservation }) => {
return (
<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">
<FaKey className="w-5 h-5" />
Aktif Rezervasyonlar
</h2>
</div>
<div className="p-4 space-y-3">
{mockReservations
.filter((r) => r.status === 'approved')
.slice(0, 3)
.map((reservation) => (
<div
key={reservation.id}
className="p-3 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800"
>
<div className="flex items-start justify-between mb-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{reservation.resourceName}
</h4>
<span className="text-xs px-2 py-1 bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 rounded-full">
{reservation.type === 'room' ? '🏢' : reservation.type === 'vehicle' ? '🚗' : '⚙️'}
</span>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
{dayjs(reservation.startDate).format('DD MMM HH:mm')} - {dayjs(reservation.endDate).format('HH:mm')}
</p>
</div>
))}
{mockReservations.filter((r) => r.status === 'approved').length === 0 && (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Aktif rezervasyon yok
</p>
)}
<button
onClick={onNewReservation}
className="w-full mt-3 px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm rounded-lg transition-colors flex items-center justify-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Rezervasyon
</button>
</div>
</div>
)
}
export default ActiveReservations

View file

@ -0,0 +1,58 @@
import React from 'react'
import { FaClipboardCheck } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockSurveys, Survey } from '../../../mocks/mockIntranetData'
interface ActiveSurveysProps {
onTakeSurvey: (survey: Survey) => void
}
const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => {
return (
<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">
<FaClipboardCheck className="w-5 h-5" />
Aktif Anketler
</h2>
</div>
<div className="p-4 space-y-3">
{mockSurveys
.filter((s) => s.status === 'active')
.slice(0, 3)
.map((survey) => (
<div
key={survey.id}
onClick={() => onTakeSurvey(survey)}
className="p-3 rounded-lg bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 hover:bg-purple-100 dark:hover:bg-purple-900/30 cursor-pointer transition-colors"
>
<h4 className="text-sm font-medium text-gray-900 dark:text-white mb-1">
{survey.title}
</h4>
<div className="flex items-center justify-between text-xs">
<span className="text-gray-600 dark:text-gray-400">
{survey.totalQuestions} soru
</span>
<span className="text-purple-600 dark:text-purple-400 font-medium">
{survey.responses} yanıt
</span>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Son tarih: {dayjs(survey.deadline).format('DD MMM')}
</p>
<button className="mt-2 w-full px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs rounded-md transition-colors">
Anketi Doldur
</button>
</div>
))}
{mockSurveys.filter((s) => s.status === 'active').length === 0 && (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Aktif anket yok
</p>
)}
</div>
</div>
)
}
export default ActiveSurveys

View file

@ -0,0 +1,74 @@
import React from 'react'
import { FaDollarSign, FaPlus } from 'react-icons/fa'
import { mockExpenseRequests } from '../../../mocks/mockIntranetData'
interface ExpenseManagementProps {
onNewExpense: () => void
}
const ExpenseManagement: React.FC<ExpenseManagementProps> = ({ onNewExpense }) => {
return (
<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">
<FaDollarSign className="w-5 h-5" />
Harcama Yönetimi
</h2>
</div>
<div className="p-4 space-y-3">
{/* Harcama özeti */}
<div className="p-3 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg border border-emerald-200 dark:border-emerald-800 mb-4">
<p className="text-xs text-gray-600 dark:text-gray-400">Bu Ay Toplam</p>
<p className="text-2xl font-bold text-emerald-600 dark:text-emerald-400">2,370</p>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">2,050 onaylandı</p>
</div>
{/* Son harcama talepleri */}
<div className="space-y-2">
{mockExpenseRequests.slice(0, 3).map((expense) => (
<div
key={expense.id}
className="p-3 rounded-lg bg-gray-50 dark:bg-gray-900/20 border border-gray-200 dark:border-gray-700"
>
<div className="flex items-start justify-between mb-1">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{expense.category === 'travel' ? '✈️' :
expense.category === 'meal' ? '🍽️' :
expense.category === 'accommodation' ? '🏨' :
expense.category === 'transport' ? '🚗' : '📋'} {expense.description}
</h4>
<p className="text-xs font-semibold text-emerald-600 dark:text-emerald-400 mt-1">
{expense.amount.toLocaleString('tr-TR')}
</p>
</div>
<span
className={`text-xs px-2 py-1 rounded-full ${
expense.status === 'approved'
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: expense.status === 'pending'
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}`}
>
{expense.status === 'approved' ? 'Onaylandı' :
expense.status === 'pending' ? 'Bekliyor' : 'Reddedildi'}
</span>
</div>
</div>
))}
</div>
<button
onClick={onNewExpense}
className="w-full mt-3 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm rounded-lg transition-colors flex items-center justify-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Harcama Talebi
</button>
</div>
</div>
)
}
export default ExpenseManagement

View file

@ -0,0 +1,84 @@
import React from 'react'
import { FaBell, FaEye } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockAnnouncements, Announcement } from '../../../mocks/mockIntranetData'
interface ImportantAnnouncementsProps {
onAnnouncementClick: (announcement: Announcement) => void
}
const ImportantAnnouncements: React.FC<ImportantAnnouncementsProps> = ({ onAnnouncementClick }) => {
const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3)
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',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
}
return colors[category] || colors.general
}
return (
<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">
<FaBell className="w-5 h-5" />
Önemli Duyurular
</h2>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{pinnedAnnouncements.map((announcement) => (
<div
key={announcement.id}
onClick={() => onAnnouncementClick(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>
<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'}
</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 className="flex items-center gap-1">
<FaEye className="w-3 h-3" />
{announcement.viewCount}
</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
)
}
export default ImportantAnnouncements

View file

@ -0,0 +1,78 @@
import React from 'react'
import { FaCalendarAlt, FaPlus } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockLeaveRequests } from '../../../mocks/mockIntranetData'
import { LeaveStatusEnum, LeaveTypeEnum } from '../../../types/hr'
interface LeaveManagementProps {
onNewLeave: () => void
}
const LeaveManagement: React.FC<LeaveManagementProps> = ({ onNewLeave }) => {
return (
<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">
<FaCalendarAlt className="w-5 h-5" />
İzin Yönetimi
</h2>
</div>
<div className="p-4 space-y-3">
{/* İzin bakiye özeti */}
<div className="grid grid-cols-2 gap-2 mb-4">
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<p className="text-xs text-gray-600 dark:text-gray-400">Yıllık İzin</p>
<p className="text-lg font-bold text-blue-600 dark:text-blue-400">12 gün</p>
</div>
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<p className="text-xs text-gray-600 dark:text-gray-400">Hastalık İzni</p>
<p className="text-lg font-bold text-green-600 dark:text-green-400">8 gün</p>
</div>
</div>
{/* Son izin talepleri */}
<div className="space-y-2">
{mockLeaveRequests.slice(0, 3).map((leave) => (
<div
key={leave.id}
className="p-3 rounded-lg bg-gray-50 dark:bg-gray-900/20 border border-gray-200 dark:border-gray-700"
>
<div className="flex items-start justify-between mb-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{leave.leaveType === LeaveTypeEnum.Annual ? '🏖️ Yıllık' :
leave.leaveType === LeaveTypeEnum.Sick ? '🏥 Hastalık' :
leave.leaveType === LeaveTypeEnum.Unpaid ? '💼 Ücretsiz' : '📋 Diğer'} İzin
</h4>
<span
className={`text-xs px-2 py-1 rounded-full ${
leave.status === LeaveStatusEnum.Approved
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: leave.status === LeaveStatusEnum.Pending
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}`}
>
{leave.status === LeaveStatusEnum.Approved ? 'Onaylandı' :
leave.status === LeaveStatusEnum.Pending ? 'Bekliyor' : 'Reddedildi'}
</span>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
{dayjs(leave.startDate).format('DD MMM')} - {dayjs(leave.endDate).format('DD MMM')} ({leave.totalDays} gün)
</p>
</div>
))}
</div>
<button
onClick={onNewLeave}
className="w-full mt-3 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-lg transition-colors flex items-center justify-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni İzin Talebi
</button>
</div>
</div>
)
}
export default LeaveManagement

View file

@ -0,0 +1,71 @@
import React from 'react'
import { FaClock, FaPlus } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockOvertimeRequests } from '../../../mocks/mockIntranetData'
import { LeaveStatusEnum } from '../../../types/hr'
interface OvertimeManagementProps {
onNewOvertime: () => void
}
const OvertimeManagement: React.FC<OvertimeManagementProps> = ({ onNewOvertime }) => {
return (
<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">
<FaClock className="w-5 h-5" />
Mesai Yönetimi
</h2>
</div>
<div className="p-4 space-y-3">
{/* Mesai özeti */}
<div className="p-3 bg-orange-50 dark:bg-orange-900/20 rounded-lg border border-orange-200 dark:border-orange-800 mb-4">
<p className="text-xs text-gray-600 dark:text-gray-400">Bu Ay Toplam</p>
<p className="text-2xl font-bold text-orange-600 dark:text-orange-400">24 saat</p>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">20 onaylandı, 4 bekliyor</p>
</div>
{/* Son mesai talepleri */}
<div className="space-y-2">
{mockOvertimeRequests.slice(0, 3).map((overtime) => (
<div
key={overtime.id}
className="p-3 rounded-lg bg-gray-50 dark:bg-gray-900/20 border border-gray-200 dark:border-gray-700"
>
<div className="flex items-start justify-between mb-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{overtime.totalHours} saat
</h4>
<span
className={`text-xs px-2 py-1 rounded-full ${
overtime.status === LeaveStatusEnum.Approved
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: overtime.status === LeaveStatusEnum.Pending
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300'
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}`}
>
{overtime.status === LeaveStatusEnum.Approved ? 'Onaylandı' :
overtime.status === LeaveStatusEnum.Pending ? 'Bekliyor' : 'Reddedildi'}
</span>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
{dayjs(overtime.date).format('DD MMM YYYY')}
</p>
</div>
))}
</div>
<button
onClick={onNewOvertime}
className="w-full mt-3 px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white text-sm rounded-lg transition-colors flex items-center justify-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Mesai Talebi
</button>
</div>
</div>
)
}
export default OvertimeManagement

View file

@ -0,0 +1,75 @@
import React from 'react'
import { FaChartBar, FaClock, FaUsers } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockTasks } from '../../../mocks/mockIntranetData'
const PriorityTasks: React.FC = () => {
const priorityTasks = mockTasks
.filter((t) => t.priority === 'high' || t.priority === 'urgent')
.slice(0, 3)
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',
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300',
}
return colors[priority]
}
return (
<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">
<FaChartBar 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'}
readOnly
/>
<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>
<span
className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}
>
{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">
<FaClock className="w-3 h-3" />
{dayjs(task.dueDate).format('DD MMM')}
</span>
<span className="flex items-center gap-1">
<FaUsers className="w-3 h-3" />
{task.assignedTo.length} kişi
</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
)
}
export default PriorityTasks

View file

@ -0,0 +1,65 @@
import React from 'react'
import { FaFileAlt, FaDownload } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockDocuments } from '../../../mocks/mockIntranetData'
const RecentDocuments: React.FC = () => {
return (
<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">
<FaFileAlt className="w-5 h-5" />
Son Dokümanlar
</h2>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{mockDocuments.slice(0, 3).map((doc) => (
<div
key={doc.id}
className="p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<div className="flex items-start gap-3">
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<FaFileAlt className="w-4 h-4 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' && '📋 Şablon'}
{doc.category === 'report' && '📊 Rapor'}
{doc.category === 'other' && '📄 Diğer'}
<span className="mx-1"></span>
{doc.size}
</p>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2">
<span>{dayjs(doc.uploadDate).fromNow()}</span>
<span></span>
<span>{doc.downloadCount} indirme</span>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation()
window.open(doc.url, '_blank')
}}
className="p-2 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-lg transition-colors group"
title="İndir"
>
<FaDownload className="w-5 h-5 text-gray-600 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors" />
</button>
</div>
</div>
))}
</div>
</div>
)
}
export default RecentDocuments

View file

@ -0,0 +1,116 @@
import React from 'react'
import { FaTruck } from 'react-icons/fa'
import { mockShuttleRoutes } from '../../../mocks/mockIntranetData'
const ShuttleSchedule: React.FC = () => {
const morningShuttles = mockShuttleRoutes.filter((s) => s.type === 'morning')
const eveningShuttles = mockShuttleRoutes.filter((s) => s.type === 'evening')
return (
<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">
<FaTruck className="w-5 h-5" />
Servis Saatleri
</h2>
</div>
<div className="p-4 space-y-4">
{/* Sabah Servisleri */}
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
🌅 Sabah Servisleri
</h3>
<div className="space-y-2">
{morningShuttles.slice(0, 2).map((shuttle) => (
<div
key={shuttle.id}
className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800"
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{shuttle.name}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
{shuttle.route[0]} {shuttle.route[shuttle.route.length - 1]}
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-blue-600 dark:text-blue-400">
{shuttle.departureTime}
</p>
</div>
</div>
<div className="flex items-center justify-between text-xs">
<span className="text-gray-600 dark:text-gray-400">
{shuttle.arrivalTime}
</span>
<span
className={`px-2 py-1 rounded-full ${
shuttle.available > 5
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: shuttle.available > 0
? 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300'
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}`}
>
{shuttle.available} yer
</span>
</div>
</div>
))}
</div>
</div>
{/* Akşam Servisleri */}
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
🌆 Akşam Servisleri
</h3>
<div className="space-y-2">
{eveningShuttles.slice(0, 2).map((shuttle) => (
<div
key={shuttle.id}
className="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg border border-purple-200 dark:border-purple-800"
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{shuttle.name}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
{shuttle.route[0]} {shuttle.route[shuttle.route.length - 1]}
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-purple-600 dark:text-purple-400">
{shuttle.departureTime}
</p>
</div>
</div>
<div className="flex items-center justify-between text-xs">
<span className="text-gray-600 dark:text-gray-400">
{shuttle.arrivalTime}
</span>
<span
className={`px-2 py-1 rounded-full ${
shuttle.available > 5
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: shuttle.available > 0
? 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300'
: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
}`}
>
{shuttle.available} yer
</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default ShuttleSchedule

View file

@ -0,0 +1,54 @@
import React from 'react'
import dayjs from 'dayjs'
import { mockBirthdays } from '../../../mocks/mockIntranetData'
const TodayBirthdays: React.FC = () => {
const todayBirthdays = mockBirthdays.filter((b) => {
const birthDate = dayjs(b.date)
const today = dayjs()
return birthDate.month() === today.month() && birthDate.date() === today.date()
})
return (
<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">
<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">
🎂 Bugün Doğanlar
</h2>
</div>
<div className="p-4 space-y-3">
{todayBirthdays.length > 0 ? (
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>
</div>
</div>
))
) : (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Bugün doğan yok
</p>
)}
</div>
</div>
)
}
export default TodayBirthdays

View file

@ -0,0 +1,47 @@
import React from 'react'
import { FaCalendarAlt } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockEvents } from '../../../mocks/mockIntranetData'
const TodayEvents: React.FC = () => {
const todayEvents = mockEvents.filter(
(event) => event.isPublished && dayjs(event.date).isSame(dayjs(), 'day'),
)
return (
<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">
<FaCalendarAlt 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>
)
}
export default TodayEvents

View file

@ -0,0 +1,47 @@
import React from 'react'
import { FaCalendarAlt } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockEvents } from '../../../mocks/mockIntranetData'
const UpcomingEvents: React.FC = () => {
const upcomingEvents = mockEvents.filter(
(event) =>
event.isPublished &&
dayjs(event.date).isAfter(dayjs()) &&
dayjs(event.date).isBefore(dayjs().add(7, 'day')),
)
return (
<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">
<FaCalendarAlt className="w-5 h-5" />
Yaklaşan Etkinlikler
</h2>
</div>
<div className="p-4 space-y-3">
{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>
)
}
export default UpcomingEvents

View file

@ -0,0 +1,50 @@
import React from 'react'
import { FaGraduationCap } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockTrainings } from '../../../mocks/mockIntranetData'
const UpcomingTrainings: React.FC = () => {
return (
<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">
<FaGraduationCap className="w-5 h-5" />
Yaklaşan Eğitimler
</h2>
</div>
<div className="p-4 space-y-3">
{mockTrainings
.filter((t) => t.status === 'upcoming')
.slice(0, 3)
.map((training) => (
<div
key={training.id}
className="p-3 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800"
>
<h4 className="text-sm font-medium text-gray-900 dark:text-white mb-1">
{training.title}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mb-2">
{training.instructor} {training.duration}
</p>
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500 dark:text-gray-400">
{dayjs(training.startDate).format('DD MMM')} - {dayjs(training.endDate).format('DD MMM')}
</span>
<span className="text-blue-600 dark:text-blue-400 font-medium">
{training.enrolled}/{training.maxParticipants}
</span>
</div>
</div>
))}
{mockTrainings.filter((t) => t.status === 'upcoming').length === 0 && (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Yaklaşan eğitim yok
</p>
)}
</div>
</div>
)
}
export default UpcomingTrainings

View file

@ -0,0 +1,73 @@
import React from 'react'
import { FaUtensils } from 'react-icons/fa'
import dayjs from 'dayjs'
import isBetween from 'dayjs/plugin/isBetween'
import { mockMealMenus } from '../../../mocks/mockIntranetData'
dayjs.extend(isBetween)
const WeeklyMenu: React.FC = () => {
const weekMenus = mockMealMenus.filter((menu) => {
const menuDate = dayjs(menu.date)
const today = dayjs()
const weekStart = today.startOf('week')
const weekEnd = today.endOf('week')
return menuDate.isBetween(weekStart, weekEnd, null, '[]')
})
return (
<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">
<FaUtensils className="w-5 h-5" />
Haftalık Menü
</h2>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{weekMenus.map((menu) => {
const isToday = dayjs(menu.date).isSame(dayjs(), 'day')
return (
<div
key={menu.id}
className={`p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${
isToday ? 'bg-orange-50 dark:bg-orange-900/20' : ''
}`}
>
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-2">
{menu.dayOfWeek}
{isToday && (
<span className="text-xs bg-orange-500 text-white px-2 py-0.5 rounded-full">
Bugün
</span>
)}
</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
{dayjs(menu.date).format('DD MMMM')}
</p>
</div>
</div>
<div className="space-y-1">
{menu.meals[0]?.items.slice(0, 3).map((item, idx) => (
<p key={idx} className="text-xs text-gray-600 dark:text-gray-400">
{item}
</p>
))}
{menu.meals[0]?.items.length > 3 && (
<p className="text-xs text-gray-500 dark:text-gray-500">
+{menu.meals[0].items.length - 3} daha...
</p>
)}
</div>
</div>
)
})}
</div>
</div>
)
}
export default WeeklyMenu