İntranet modülündeki Dialoglar düzeltildi.

This commit is contained in:
Sedat Öztürk 2025-10-19 10:39:10 +03:00
parent ddaaa56ea0
commit e8093784e0
10 changed files with 1371 additions and 1087 deletions

View file

@ -8,12 +8,13 @@ import {
HiCalendar, HiCalendar,
HiXMark, HiXMark,
HiChevronLeft, HiChevronLeft,
HiChevronRight HiChevronRight,
} from 'react-icons/hi2' } from 'react-icons/hi2'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import 'dayjs/locale/tr' import 'dayjs/locale/tr'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { mockEvents, CalendarEvent, EventComment } from '../../../mocks/mockIntranetData' import { mockEvents, CalendarEvent, EventComment } from '../../../mocks/mockIntranetData'
import { mockEmployees } from '@/mocks/mockEmployees'
dayjs.locale('tr') dayjs.locale('tr')
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
@ -22,20 +23,21 @@ const EventsModule: React.FC = () => {
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null) const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null)
const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(0) const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(0)
const [showPhotoModal, setShowPhotoModal] = useState(false) const [showPhotoModal, setShowPhotoModal] = useState(false)
const [selectedFilter, setSelectedFilter] = useState<'all' | 'social' | 'training' | 'company' | 'sport' | 'culture'>('all') const [selectedFilter, setSelectedFilter] = useState<
'all' | 'social' | 'training' | 'company' | 'sport' | 'culture'
>('all')
const [newComment, setNewComment] = useState('') const [newComment, setNewComment] = useState('')
const [events, setEvents] = useState<CalendarEvent[]>(mockEvents) const [events, setEvents] = useState<CalendarEvent[]>(mockEvents)
const filteredEvents = selectedFilter === 'all' const filteredEvents =
? events.filter(e => e.isPublished) selectedFilter === 'all'
: events.filter(e => e.isPublished && e.type === selectedFilter) ? events.filter((e) => e.isPublished)
: events.filter((e) => e.isPublished && e.type === selectedFilter)
const handleLikeEvent = (eventId: string) => { const handleLikeEvent = (eventId: string) => {
setEvents(prev => prev.map(e => setEvents((prev) => prev.map((e) => (e.id === eventId ? { ...e, likes: e.likes + 1 } : e)))
e.id === eventId ? { ...e, likes: e.likes + 1 } : e
))
if (selectedEvent?.id === eventId) { if (selectedEvent?.id === eventId) {
setSelectedEvent(prev => prev ? { ...prev, likes: prev.likes + 1 } : null) setSelectedEvent((prev) => (prev ? { ...prev, likes: prev.likes + 1 } : null))
} }
} }
@ -44,22 +46,18 @@ const EventsModule: React.FC = () => {
const comment: EventComment = { const comment: EventComment = {
id: `c${Date.now()}`, id: `c${Date.now()}`,
author: { author: mockEmployees[0],
id: 'current-user',
fullName: 'Sedat Öztürk',
avatar: 'https://ui-avatars.com/api/?name=Sedat+Ozturk&background=3b82f6&color=fff'
},
content: newComment, content: newComment,
createdAt: new Date(), createdAt: new Date(),
likes: 0 likes: 0,
} }
setEvents(prev => prev.map(e => setEvents((prev) =>
e.id === eventId ? { ...e, comments: [...e.comments, comment] } : e prev.map((e) => (e.id === eventId ? { ...e, comments: [...e.comments, comment] } : e)),
)) )
if (selectedEvent?.id === eventId) { if (selectedEvent?.id === eventId) {
setSelectedEvent(prev => prev ? { ...prev, comments: [...prev.comments, comment] } : null) setSelectedEvent((prev) => (prev ? { ...prev, comments: [...prev.comments, comment] } : null))
} }
setNewComment('') setNewComment('')
@ -71,7 +69,7 @@ const EventsModule: React.FC = () => {
training: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-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', 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', 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' culture: 'bg-pink-100 dark:bg-pink-900/30 text-pink-700 dark:text-pink-300',
} }
return colors[type] || colors.social return colors[type] || colors.social
} }
@ -82,7 +80,7 @@ const EventsModule: React.FC = () => {
training: '📚 Eğitim', training: '📚 Eğitim',
company: '🏢 Kurumsal', company: '🏢 Kurumsal',
sport: '⚽ Spor', sport: '⚽ Spor',
culture: '🎨 Kültür' culture: '🎨 Kültür',
} }
return labels[type] || type return labels[type] || type
} }
@ -92,9 +90,7 @@ const EventsModule: React.FC = () => {
<div className="max-w-7xl mx-auto space-y-6"> <div className="max-w-7xl mx-auto space-y-6">
{/* Header */} {/* Header */}
<div> <div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white"> <h1 className="text-2xl font-bold text-gray-900 dark:text-white">🎊 Etkinlikler</h1>
🎊 Etkinlikler
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1"> <p className="text-gray-600 dark:text-gray-400 mt-1">
Şirket etkinlikleri, fotoğraflar ve anılar Şirket etkinlikleri, fotoğraflar ve anılar
</p> </p>
@ -108,7 +104,7 @@ const EventsModule: React.FC = () => {
{ value: 'training' as const, label: '📚 Eğitim' }, { value: 'training' as const, label: '📚 Eğitim' },
{ value: 'company' as const, label: '🏢 Kurumsal' }, { value: 'company' as const, label: '🏢 Kurumsal' },
{ value: 'sport' as const, label: '⚽ Spor' }, { value: 'sport' as const, label: '⚽ Spor' },
{ value: 'culture' as const, label: '🎨 Kültür' } { value: 'culture' as const, label: '🎨 Kültür' },
].map((tab) => ( ].map((tab) => (
<button <button
key={tab.value} key={tab.value}
@ -143,7 +139,9 @@ const EventsModule: React.FC = () => {
className="w-full h-full object-cover hover:scale-110 transition-transform duration-300" className="w-full h-full object-cover hover:scale-110 transition-transform duration-300"
/> />
<div className="absolute top-3 right-3"> <div className="absolute top-3 right-3">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(event.type)}`}> <span
className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(event.type)}`}
>
{getTypeLabel(event.type)} {getTypeLabel(event.type)}
</span> </span>
</div> </div>
@ -207,252 +205,258 @@ const EventsModule: React.FC = () => {
<p className="text-lg">Bu kategoride henüz etkinlik yok</p> <p className="text-lg">Bu kategoride henüz etkinlik yok</p>
</div> </div>
)} )}
</div>
{/* Event Detail Modal */} {/* Event Detail Modal */}
<AnimatePresence> <AnimatePresence>
{selectedEvent && ( {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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" 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={() => setSelectedEvent(null)} onClick={(e) => e.stopPropagation()}
/> >
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto"> {/* Header */}
<motion.div <div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 z-10">
initial={{ opacity: 0, scale: 0.95, y: 20 }} <div className="flex items-start justify-between">
animate={{ opacity: 1, scale: 1, y: 0 }} <div className="flex-1">
exit={{ opacity: 0, scale: 0.95, y: 20 }} <div className="flex items-center gap-3 mb-2">
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto my-8" <span
onClick={(e) => e.stopPropagation()} className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(selectedEvent.type)}`}
> >
{/* Header */} {getTypeLabel(selectedEvent.type)}
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 z-10"> </span>
<div className="flex items-start justify-between"> <span className="text-sm text-gray-600 dark:text-gray-400">
<div className="flex-1"> {dayjs(selectedEvent.date).format('DD MMMM YYYY')}
<div className="flex items-center gap-3 mb-2"> </span>
<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> </div>
<button <h2 className="text-2xl font-bold text-gray-900 dark:text-white">
onClick={() => setSelectedEvent(null)} {selectedEvent.title}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" </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)
}}
> >
<HiXMark className="w-6 h-6 text-gray-500" /> <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> </button>
</div> </div>
</div>
<div className="flex items-center gap-6 mt-4 text-sm text-gray-600 dark:text-gray-400"> {/* Comments List */}
<div className="flex items-center gap-2"> <div className="space-y-4 mb-4">
<HiMapPin className="w-5 h-5" /> {selectedEvent.comments.map((comment) => (
{selectedEvent.location} <div
</div> key={comment.id}
<div className="flex items-center gap-2"> className="flex gap-3 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg"
<HiUsers className="w-5 h-5" /> >
{selectedEvent.participants} katılımcı
</div>
<div className="flex items-center gap-2">
<img <img
src={selectedEvent.organizer.avatar} src={comment.author.avatar}
alt={selectedEvent.organizer.fullName} alt={comment.author.fullName}
className="w-6 h-6 rounded-full" className="w-10 h-10 rounded-full"
/> />
<span>Düzenleyen: {selectedEvent.organizer.fullName}</span> <div className="flex-1">
</div> <div className="flex items-center gap-2 mb-1">
</div> <span className="font-semibold text-gray-900 dark:text-white">
</div> {comment.author.fullName}
</span>
{/* Photo Gallery */} <span className="text-xs text-gray-500 dark:text-gray-400">
<div className="p-6"> {dayjs(comment.createdAt).fromNow()}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4"> </span>
📸 Fotoğraflar ({selectedEvent.photos.length}) </div>
</h3> <p className="text-gray-700 dark:text-gray-300 text-sm">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4"> {comment.content}
{selectedEvent.photos.map((photo, idx) => ( </p>
<motion.div <div className="flex items-center gap-2 mt-2">
key={idx} <button className="text-xs text-gray-500 hover:text-red-500 transition-colors">
whileHover={{ scale: 1.05 }} {comment.likes} beğeni
className="relative aspect-square rounded-lg overflow-hidden cursor-pointer" </button>
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> </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>
{/* 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> </div>
</motion.div> </div>
</div> </motion.div>
</> </div>
)} </>
</AnimatePresence> )}
</AnimatePresence>
{/* Photo Viewer Modal */} {/* Photo Viewer Modal */}
<AnimatePresence> <AnimatePresence>
{showPhotoModal && selectedEvent && ( {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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.9 }}
className="fixed inset-0 bg-black/90 z-50" className="relative max-w-5xl w-full"
onClick={() => setShowPhotoModal(false)} onClick={(e) => e.stopPropagation()}
/> >
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> {/* Close Button */}
<motion.div <button
initial={{ opacity: 0, scale: 0.9 }} onClick={() => setShowPhotoModal(false)}
animate={{ opacity: 1, scale: 1 }} className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white z-10"
exit={{ opacity: 0, scale: 0.9 }}
className="relative max-w-5xl w-full"
onClick={(e) => e.stopPropagation()}
> >
{/* Close Button */} <HiXMark className="w-6 h-6" />
<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 */} {/* Navigation */}
{selectedEvent.photos.length > 1 && ( {selectedEvent.photos.length > 1 && (
<> <>
<button <button
onClick={() => setSelectedPhotoIndex((prev) => onClick={() =>
prev === 0 ? selectedEvent.photos.length - 1 : prev - 1 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" /> className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
</button> >
<button <HiChevronLeft className="w-6 h-6" />
onClick={() => setSelectedPhotoIndex((prev) => </button>
prev === selectedEvent.photos.length - 1 ? 0 : prev + 1 <button
)} onClick={() =>
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white" setSelectedPhotoIndex((prev) =>
> prev === selectedEvent.photos.length - 1 ? 0 : prev + 1,
<HiChevronRight className="w-6 h-6" /> )
</button> }
</> 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 */} {/* Image */}
<img <img
src={selectedEvent.photos[selectedPhotoIndex]} src={selectedEvent.photos[selectedPhotoIndex]}
alt={`${selectedEvent.title} - ${selectedPhotoIndex + 1}`} alt={`${selectedEvent.title} - ${selectedPhotoIndex + 1}`}
className="w-full h-auto max-h-[80vh] object-contain rounded-lg" className="w-full h-auto max-h-[80vh] object-contain rounded-lg"
/> />
{/* Counter */} {/* 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"> <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} {selectedPhotoIndex + 1} / {selectedEvent.photos.length}
</div> </div>
</motion.div> </motion.div>
</div> </div>
</> </>
)} )}
</AnimatePresence> </AnimatePresence>
</div>
</div> </div>
) )
} }

View file

@ -12,7 +12,7 @@ import {
HiArrowTrendingDown as ArrowTrendingDownIcon, HiArrowTrendingDown as ArrowTrendingDownIcon,
HiXMark, HiXMark,
HiEye, HiEye,
HiPaperClip HiPaperClip,
} from 'react-icons/hi2' } from 'react-icons/hi2'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import 'dayjs/locale/tr' import 'dayjs/locale/tr'
@ -25,7 +25,8 @@ import {
mockAnniversaries, mockAnniversaries,
mockQuickLinks, mockQuickLinks,
mockTasks, mockTasks,
Announcement mockDocuments,
Announcement,
} from '../../../mocks/mockIntranetData' } from '../../../mocks/mockIntranetData'
dayjs.locale('tr') dayjs.locale('tr')
@ -37,36 +38,49 @@ const IntranetDashboard: React.FC = () => {
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null) const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null)
// Bugünün etkinlikleri // Bugünün etkinlikleri
const todayEvents = mockEvents.filter(event => const todayEvents = mockEvents.filter(
event.isPublished && dayjs(event.date).isSame(dayjs(), 'day') (event) => event.isPublished && dayjs(event.date).isSame(dayjs(), 'day'),
) )
// Yaklaşan etkinlikler (7 gün içinde) // Yaklaşan etkinlikler (7 gün içinde)
const upcomingEvents = mockEvents.filter(event => const upcomingEvents = mockEvents.filter(
event.isPublished && (event) =>
dayjs(event.date).isAfter(dayjs()) && event.isPublished &&
dayjs(event.date).isBefore(dayjs().add(7, 'day')) dayjs(event.date).isAfter(dayjs()) &&
dayjs(event.date).isBefore(dayjs().add(7, 'day')),
) )
// Bu haftaki doğum günleri // Bugün doğanlar (sadece ay ve günü karşılaştır)
const weekBirthdays = mockBirthdays.filter(b => const todayBirthdays = mockBirthdays.filter((b) => {
dayjs(b.date).isBetween(dayjs().startOf('week'), dayjs().endOf('week')) const birthDate = dayjs(b.date)
) const today = dayjs()
return birthDate.month() === today.month() && birthDate.date() === today.date()
})
// Bu haftaki doğum günleri (sadece ay ve günü karşılaştır)
const weekBirthdays = mockBirthdays.filter((b) => {
const birthDate = dayjs(b.date)
const today = dayjs()
const weekStart = today.startOf('week')
const weekEnd = today.endOf('week')
// Bu yılki doğum gününü oluştur
const thisBirthday = today.year(today.year()).month(birthDate.month()).date(birthDate.date())
return thisBirthday.isBetween(weekStart, weekEnd, null, '[]')
})
// Bu ayki iş yıldönümleri // Bu ayki iş yıldönümleri
const monthAnniversaries = mockAnniversaries.filter(a => const monthAnniversaries = mockAnniversaries.filter(
dayjs(a.hireDate).month() === dayjs().month() (a) => dayjs(a.hireDate).month() === dayjs().month(),
) )
// Öncelikli görevler // Öncelikli görevler
const priorityTasks = mockTasks.filter(t => const priorityTasks = mockTasks
t.priority === 'high' || t.priority === 'urgent' .filter((t) => t.priority === 'high' || t.priority === 'urgent')
).slice(0, 3) .slice(0, 3)
// Sabitlenmiş duyurular // Sabitlenmiş duyurular
const pinnedAnnouncements = mockAnnouncements const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3)
.filter(a => a.isPinned)
.slice(0, 3)
const getCategoryColor = (category: string) => { const getCategoryColor = (category: string) => {
const colors: Record<string, string> = { const colors: Record<string, string> = {
@ -74,7 +88,7 @@ const IntranetDashboard: React.FC = () => {
hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-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', 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', 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' urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
} }
return colors[category] || colors.general return colors[category] || colors.general
} }
@ -84,7 +98,7 @@ const IntranetDashboard: React.FC = () => {
low: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300', 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', 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', 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' urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300',
} }
return colors[priority] return colors[priority]
} }
@ -126,8 +140,8 @@ const IntranetDashboard: React.FC = () => {
</div> </div>
<div className="divide-y divide-gray-200 dark:divide-gray-700"> <div className="divide-y divide-gray-200 dark:divide-gray-700">
{pinnedAnnouncements.map((announcement) => ( {pinnedAnnouncements.map((announcement) => (
<div <div
key={announcement.id} key={announcement.id}
onClick={() => setSelectedAnnouncement(announcement)} onClick={() => setSelectedAnnouncement(announcement)}
className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors" className="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors"
> >
@ -142,7 +156,9 @@ const IntranetDashboard: React.FC = () => {
<h3 className="text-base font-semibold text-gray-900 dark:text-white"> <h3 className="text-base font-semibold text-gray-900 dark:text-white">
{announcement.title} {announcement.title}
</h3> </h3>
<span className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}> <span
className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}
>
{announcement.category === 'general' && 'Genel'} {announcement.category === 'general' && 'Genel'}
{announcement.category === 'hr' && 'İK'} {announcement.category === 'hr' && 'İK'}
{announcement.category === 'it' && 'IT'} {announcement.category === 'it' && 'IT'}
@ -214,7 +230,9 @@ const IntranetDashboard: React.FC = () => {
<h3 className="text-sm font-medium text-gray-900 dark:text-white"> <h3 className="text-sm font-medium text-gray-900 dark:text-white">
{task.title} {task.title}
</h3> </h3>
<span className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}> <span
className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}
>
{task.priority === 'urgent' && '🔥 Acil'} {task.priority === 'urgent' && '🔥 Acil'}
{task.priority === 'high' && 'Yüksek'} {task.priority === 'high' && 'Yüksek'}
{task.priority === 'medium' && 'Orta'} {task.priority === 'medium' && 'Orta'}
@ -278,6 +296,46 @@ const IntranetDashboard: React.FC = () => {
</div> </div>
</div> </div>
{/* Bugün Doğanlar */}
<div className="bg-gradient-to-br from-pink-50 to-purple-50 dark:from-pink-900/20 dark:to-purple-900/20 rounded-lg shadow-sm border border-pink-200 dark:border-pink-800">
<div className="p-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2 mb-4">
🎂 Bugün Doğanlar
</h2>
{todayBirthdays.length > 0 ? (
<div className="space-y-3">
{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>
))}
</div>
) : (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Bugün doğan yok
</p>
)}
</div>
</div>
{/* Yaklaşan Etkinlikler */} {/* Yaklaşan Etkinlikler */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700"> <div className="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="p-6 border-b border-gray-200 dark:border-gray-700">
@ -287,17 +345,70 @@ const IntranetDashboard: React.FC = () => {
</h2> </h2>
</div> </div>
<div className="p-4 space-y-3"> <div className="p-4 space-y-3">
{upcomingEvents.slice(0, 3).map((event) => ( {upcomingEvents.length > 0 ? (
upcomingEvents.slice(0, 3).map((event) => (
<div
key={event.id}
className="p-3 rounded-lg border-l-4 bg-gray-50 dark:bg-gray-700/50 border-l-green-500"
>
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{event.title}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
{dayjs(event.date).format('DD MMMM YYYY')} - {event.location}
</p>
</div>
))
) : (
<p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
Yaklaşan etkinlik yok
</p>
)}
</div>
</div>
{/* Dokümanlar */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<DocumentTextIcon className="w-5 h-5" />
Son Dokümanlar
</h2>
<button className="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
Tümü
</button>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{mockDocuments.slice(0, 5).map((doc) => (
<div <div
key={event.id} key={doc.id}
className="p-3 rounded-lg border-l-4 bg-gray-50 dark:bg-gray-700/50 border-l-green-500" className="p-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors cursor-pointer"
> >
<h4 className="text-sm font-medium text-gray-900 dark:text-white"> <div className="flex items-start gap-3">
{event.title} <div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
</h4> <DocumentTextIcon className="w-5 h-5 text-blue-600 dark:text-blue-400" />
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1"> </div>
{dayjs(event.date).format('DD MMMM YYYY')} - {event.location} <div className="flex-1 min-w-0">
</p> <h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
{doc.name}
</h4>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
{doc.category === 'policy' && '📋 Politika'}
{doc.category === 'procedure' && '📝 Prosedür'}
{doc.category === 'form' && '📄 Form'}
{doc.category === 'template' && '<27> Şablon'}
{doc.category === 'report' && '📊 Rapor'}
{doc.category === 'other' && '📄 Diğer'}
</p>
<div className="flex items-center gap-2 mt-2 text-xs text-gray-500 dark:text-gray-400">
<span>{dayjs(doc.uploadDate).fromNow()}</span>
<span></span>
<span>{doc.size}</span>
</div>
</div>
</div>
</div> </div>
))} ))}
</div> </div>
@ -364,97 +475,103 @@ const IntranetDashboard: React.FC = () => {
)} )}
</div> </div>
</div> </div>
</div>
{/* Announcement Detail Modal */} {/* Announcement Detail Modal */}
<AnimatePresence> <AnimatePresence>
{selectedAnnouncement && ( {selectedAnnouncement && (
<> <>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setSelectedAnnouncement(null)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full"
onClick={() => setSelectedAnnouncement(null)} onClick={(e) => e.stopPropagation()}
/> >
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto"> {/* Header */}
<motion.div <div className="p-6 border-b border-gray-200 dark:border-gray-700">
initial={{ opacity: 0, scale: 0.95, y: 20 }} <div className="flex items-start justify-between">
animate={{ opacity: 1, scale: 1, y: 0 }} <div className="flex-1">
exit={{ opacity: 0, scale: 0.95, y: 20 }} <div className="flex items-center gap-3 mb-3">
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full my-8" <span
onClick={(e) => e.stopPropagation()} className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}
> >
{/* Header */} {selectedAnnouncement.category === 'general' && '📢 Genel'}
<div className="p-6 border-b border-gray-200 dark:border-gray-700"> {selectedAnnouncement.category === 'hr' && '👥 İnsan Kaynakları'}
<div className="flex items-start justify-between"> {selectedAnnouncement.category === 'it' && '💻 Bilgi Teknolojileri'}
<div className="flex-1"> {selectedAnnouncement.category === 'event' && '🎉 Etkinlik'}
<div className="flex items-center gap-3 mb-3"> {selectedAnnouncement.category === 'urgent' && '🚨 Acil'}
<span className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}> </span>
{selectedAnnouncement.category === 'general' && '📢 Genel'} {selectedAnnouncement.isPinned && (
{selectedAnnouncement.category === 'hr' && '👥 İnsan Kaynakları'} <span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
{selectedAnnouncement.category === 'it' && '💻 Bilgi Teknolojileri'} )}
{selectedAnnouncement.category === 'event' && '🎉 Etkinlik'}
{selectedAnnouncement.category === 'urgent' && '🚨 Acil'}
</span>
{selectedAnnouncement.isPinned && (
<span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
)}
</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
{selectedAnnouncement.title}
</h2>
</div> </div>
<button <h2 className="text-2xl font-bold text-gray-900 dark:text-white">
onClick={() => setSelectedAnnouncement(null)} {selectedAnnouncement.title}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" </h2>
>
<HiXMark className="w-6 h-6 text-gray-500" />
</button>
</div> </div>
<button
onClick={() => setSelectedAnnouncement(null)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-6 h-6 text-gray-500" />
</button>
</div>
{/* Author Info */} {/* Author Info */}
<div className="flex items-center gap-3 mt-4"> <div className="flex items-center gap-3 mt-4">
<img <img
src={selectedAnnouncement.author.avatar} src={selectedAnnouncement.author.avatar}
alt={selectedAnnouncement.author.fullName} alt={selectedAnnouncement.author.fullName}
className="w-12 h-12 rounded-full" className="w-12 h-12 rounded-full"
/> />
<div> <div>
<p className="font-semibold text-gray-900 dark:text-white"> <p className="font-semibold text-gray-900 dark:text-white">
{selectedAnnouncement.author.fullName} {selectedAnnouncement.author.fullName}
</p> </p>
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400"> <div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
<span>{dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}</span> <span>
<span></span> {dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}
<span className="flex items-center gap-1"> </span>
<HiEye className="w-4 h-4" /> <span></span>
{selectedAnnouncement.viewCount} görüntülenme <span className="flex items-center gap-1">
</span> <HiEye className="w-4 h-4" />
</div> {selectedAnnouncement.viewCount} görüntülenme
</span>
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Content */} {/* Content */}
<div className="p-6 max-h-[60vh] overflow-y-auto"> <div className="p-6 max-h-[60vh] overflow-y-auto">
{/* Image if exists */} {/* Image if exists */}
{selectedAnnouncement.imageUrl && ( {selectedAnnouncement.imageUrl && (
<img <img
src={selectedAnnouncement.imageUrl} src={selectedAnnouncement.imageUrl}
alt={selectedAnnouncement.title} alt={selectedAnnouncement.title}
className="w-full rounded-lg mb-6" className="w-full rounded-lg mb-6"
/> />
)} )}
{/* Full Content */} {/* Full Content */}
<div className="prose prose-sm dark:prose-invert max-w-none"> <div className="prose prose-sm dark:prose-invert max-w-none">
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line"> <p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
{selectedAnnouncement.content} {selectedAnnouncement.content}
</p> </p>
</div> </div>
{/* Attachments */} {/* Attachments */}
{selectedAnnouncement.attachments && selectedAnnouncement.attachments.length > 0 && ( {selectedAnnouncement.attachments &&
selectedAnnouncement.attachments.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700"> <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"> <h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
<HiPaperClip className="w-5 h-5" /> <HiPaperClip className="w-5 h-5" />
@ -487,8 +604,9 @@ const IntranetDashboard: React.FC = () => {
</div> </div>
)} )}
{/* Departments */} {/* Departments */}
{selectedAnnouncement.departments && selectedAnnouncement.departments.length > 0 && ( {selectedAnnouncement.departments &&
selectedAnnouncement.departments.length > 0 && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700"> <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"> <h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
Hedef Departmanlar Hedef Departmanlar
@ -506,32 +624,31 @@ const IntranetDashboard: React.FC = () => {
</div> </div>
)} )}
{/* Expiry Date */} {/* Expiry Date */}
{selectedAnnouncement.expiryDate && ( {selectedAnnouncement.expiryDate && (
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700"> <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"> <p className="text-sm text-gray-600 dark:text-gray-400">
<span className="font-medium">Son Geçerlilik Tarihi:</span>{' '} <span className="font-medium">Son Geçerlilik Tarihi:</span>{' '}
{dayjs(selectedAnnouncement.expiryDate).format('DD MMMM YYYY')} {dayjs(selectedAnnouncement.expiryDate).format('DD MMMM YYYY')}
</p> </p>
</div> </div>
)} )}
</div> </div>
{/* Footer */} {/* Footer */}
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50"> <div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
<button <button
onClick={() => setSelectedAnnouncement(null)} onClick={() => setSelectedAnnouncement(null)}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors" className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
> >
Kapat Kapat
</button> </button>
</div> </div>
</motion.div> </motion.div>
</div> </div>
</> </>
)} )}
</AnimatePresence> </AnimatePresence>
</div>
</div> </div>
) )
} }

View file

@ -13,6 +13,9 @@ import {
HiBuildingOffice2, HiBuildingOffice2,
HiClipboardDocumentCheck, HiClipboardDocumentCheck,
HiUserPlus, HiUserPlus,
HiBars3,
HiXMark,
HiChevronLeft,
} from 'react-icons/hi2' } from 'react-icons/hi2'
import { import {
mockTasks, mockTasks,
@ -127,6 +130,8 @@ interface IntranetSidebarProps {
const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigate }) => { const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigate }) => {
const [expandedMenus, setExpandedMenus] = useState<string[]>(['hr']) const [expandedMenus, setExpandedMenus] = useState<string[]>(['hr'])
const [isCollapsed, setIsCollapsed] = useState(false)
const [isMobileOpen, setIsMobileOpen] = useState(false)
// Dinamik badge sayılarını hesapla // Dinamik badge sayılarını hesapla
const badgeCounts = useMemo(() => { const badgeCounts = useMemo(() => {
@ -180,6 +185,7 @@ const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigat
toggleMenu(item.id) toggleMenu(item.id)
} else if (item.path) { } else if (item.path) {
onNavigate(item.path) onNavigate(item.path)
setIsMobileOpen(false)
} }
}} }}
className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg transition-colors ${ className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg transition-colors ${
@ -187,28 +193,34 @@ const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigat
? 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300' ? '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' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
} ${level > 0 ? 'ml-6' : ''}`} } ${level > 0 ? 'ml-6' : ''}`}
title={isCollapsed ? item.label : undefined}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 min-w-0">
<item.icon className="w-5 h-5" /> <item.icon className="w-5 h-5 flex-shrink-0" />
<span className="font-medium text-sm">{item.label}</span> {!isCollapsed && <span className="font-medium text-sm truncate">{item.label}</span>}
</div>
<div className="flex items-center gap-2">
{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 && (
<motion.div animate={{ rotate: isExpanded ? 90 : 0 }} transition={{ duration: 0.2 }}>
<HiChevronRight className="w-4 h-4" />
</motion.div>
)}
</div> </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 && (
<motion.div
animate={{ rotate: isExpanded ? 90 : 0 }}
transition={{ duration: 0.2 }}
>
<HiChevronRight className="w-4 h-4" />
</motion.div>
)}
</div>
)}
</button> </button>
{hasChildren && ( {hasChildren && (
<AnimatePresence> <AnimatePresence>
{isExpanded && ( {isExpanded && !isCollapsed && (
<motion.div <motion.div
initial={{ height: 0, opacity: 0 }} initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }} animate={{ height: 'auto', opacity: 1 }}
@ -228,13 +240,74 @@ const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigat
} }
return ( return (
<div className="w-64 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-6 border-b border-gray-200 dark:border-gray-700"> {/* Mobile Toggle Button */}
<h2 className="text-xl font-bold text-gray-900 dark:text-white">İntranet Portal</h2> <button
</div> onClick={() => setIsMobileOpen(!isMobileOpen)}
className="lg:hidden fixed top-4 left-4 z-50 p-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700"
>
{isMobileOpen ? (
<HiXMark className="w-6 h-6 text-gray-700 dark:text-gray-300" />
) : (
<HiBars3 className="w-6 h-6 text-gray-700 dark:text-gray-300" />
)}
</button>
<nav className="p-4 space-y-1">{menuItems.map((item) => renderMenuItem(item))}</nav> {/* Mobile Overlay */}
</div> <AnimatePresence>
{isMobileOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setIsMobileOpen(false)}
className="lg:hidden fixed inset-0 bg-black/50 z-40"
/>
)}
</AnimatePresence>
{/* Sidebar */}
<motion.div
initial={false}
animate={{
width: isCollapsed ? '80px' : '256px',
x: isMobileOpen ? 0 : undefined,
}}
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
${isMobileOpen ? 'fixed left-0 top-0 z-40 lg:relative' : 'hidden lg:block'}
`}
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
{!isCollapsed ? (
<>
<h2 className="text-xl font-bold text-gray-900 dark:text-white truncate">
İntranet Portal
</h2>
<button
onClick={() => setIsCollapsed(true)}
className="hidden lg:block p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Daralt"
>
<HiChevronLeft className="w-5 h-5 text-gray-600 dark:text-gray-400" />
</button>
</>
) : (
<button
onClick={() => setIsCollapsed(false)}
className="w-full flex justify-center p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
title="Genişlet"
>
<HiBars3 className="w-6 h-6 text-gray-600 dark:text-gray-400" />
</button>
)}
</div>
<nav className="p-4 space-y-1">{menuItems.map((item) => renderMenuItem(item))}</nav>
</motion.div>
</>
) )
} }

View file

@ -1,13 +1,6 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { import { HiKey, HiCalendar, HiTruck, HiCog, HiPlus, HiXMark } from 'react-icons/hi2'
HiKey,
HiCalendar,
HiTruck,
HiCog,
HiPlus,
HiXMark
} from 'react-icons/hi2'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { mockReservations, Reservation } from '../../../mocks/mockIntranetData' import { mockReservations, Reservation } from '../../../mocks/mockIntranetData'
@ -15,9 +8,10 @@ const ReservationsModule: React.FC = () => {
const [selectedType, setSelectedType] = useState<'all' | 'room' | 'vehicle' | 'equipment'>('all') const [selectedType, setSelectedType] = useState<'all' | 'room' | 'vehicle' | 'equipment'>('all')
const [showNewReservation, setShowNewReservation] = useState(false) const [showNewReservation, setShowNewReservation] = useState(false)
const filteredReservations = selectedType === 'all' const filteredReservations =
? mockReservations selectedType === 'all'
: mockReservations.filter(r => r.type === selectedType) ? mockReservations
: mockReservations.filter((r) => r.type === selectedType)
const getTypeIcon = (type: string) => { const getTypeIcon = (type: string) => {
switch (type) { switch (type) {
@ -36,7 +30,7 @@ const ReservationsModule: React.FC = () => {
const labels: Record<string, string> = { const labels: Record<string, string> = {
room: 'Toplantı Salonu', room: 'Toplantı Salonu',
vehicle: 'Araç', vehicle: 'Araç',
equipment: 'Ekipman' equipment: 'Ekipman',
} }
return labels[type] || type return labels[type] || type
} }
@ -46,7 +40,7 @@ const ReservationsModule: React.FC = () => {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300', 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', 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', 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' completed: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
} }
return colors[status] || colors.pending return colors[status] || colors.pending
} }
@ -56,7 +50,7 @@ const ReservationsModule: React.FC = () => {
pending: 'Bekliyor', pending: 'Bekliyor',
approved: 'Onaylandı', approved: 'Onaylandı',
rejected: 'Reddedildi', rejected: 'Reddedildi',
completed: 'Tamamlandı' completed: 'Tamamlandı',
} }
return labels[status] || status return labels[status] || status
} }
@ -67,9 +61,7 @@ const ReservationsModule: React.FC = () => {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white"> <h1 className="text-2xl font-bold text-gray-900 dark:text-white">🔑 Rezervasyonlar</h1>
🔑 Rezervasyonlar
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1"> <p className="text-gray-600 dark:text-gray-400 mt-1">
Oda, araç ve ekipman rezervasyonları Oda, araç ve ekipman rezervasyonları
</p> </p>
@ -89,7 +81,7 @@ const ReservationsModule: React.FC = () => {
{ value: 'all' as const, label: 'Tümü', icon: HiCalendar }, { value: 'all' as const, label: 'Tümü', icon: HiCalendar },
{ value: 'room' as const, label: 'Toplantı Salonu', icon: HiKey }, { value: 'room' as const, label: 'Toplantı Salonu', icon: HiKey },
{ value: 'vehicle' as const, label: 'Araç', icon: HiTruck }, { value: 'vehicle' as const, label: 'Araç', icon: HiTruck },
{ value: 'equipment' as const, label: 'Ekipman', icon: HiCog } { value: 'equipment' as const, label: 'Ekipman', icon: HiCog },
].map((type) => ( ].map((type) => (
<button <button
key={type.value} key={type.value}
@ -131,7 +123,9 @@ const ReservationsModule: React.FC = () => {
{getTypeLabel(reservation.type)} {getTypeLabel(reservation.type)}
</p> </p>
</div> </div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(reservation.status)}`}> <span
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(reservation.status)}`}
>
{getStatusLabel(reservation.status)} {getStatusLabel(reservation.status)}
</span> </span>
</div> </div>
@ -197,121 +191,121 @@ const ReservationsModule: React.FC = () => {
</div> </div>
)} )}
</div> </div>
</div>
{/* New Reservation Modal */}
<AnimatePresence> {/* New Reservation Modal */}
{showNewReservation && ( <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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full"
onClick={() => setShowNewReservation(false)} >
/> <div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
<motion.div Yeni Rezervasyon Oluştur
initial={{ opacity: 0, scale: 0.95, y: 20 }} </h2>
animate={{ opacity: 1, scale: 1, y: 0 }} <button
exit={{ opacity: 0, scale: 0.95, y: 20 }} onClick={() => setShowNewReservation(false)}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full" className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
> >
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between"> <HiXMark className="w-5 h-5 text-gray-500" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"> </button>
Yeni Rezervasyon Oluştur </div>
</h2>
<button <div className="p-6 space-y-4">
onClick={() => setShowNewReservation(false)} <div>
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
> Rezervasyon Tipi
<HiXMark className="w-5 h-5 text-gray-500" /> </label>
</button> <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>
<div className="p-6 space-y-4"> <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> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Rezervasyon Tipi Başlangıç Tarihi
</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> </label>
<input <input
type="text" type="datetime-local"
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" 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> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Notlar (Opsiyonel) Bitiş Tarihi
</label> </label>
<textarea <input
rows={3} type="datetime-local"
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" 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="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> </div>
</motion.div>
</div> <div>
</> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
)} Amaç
</AnimatePresence> </label>
</div> <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> </div>
) )
} }

View file

@ -162,12 +162,6 @@ const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
value={content} value={content}
onChange={(e) => setContent(e.target.value)} onChange={(e) => setContent(e.target.value)}
onFocus={() => setIsExpanded(true)} onFocus={() => setIsExpanded(true)}
onBlur={() => {
// Eğer içerik, medya veya konum yoksa küçült
if (!content.trim() && mediaItems.length === 0 && !location && !mediaType) {
setIsExpanded(false)
}
}}
placeholder="Ne düşünüyorsunuz?" placeholder="Ne düşünüyorsunuz?"
className={classNames( className={classNames(
'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-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none transition-all', '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-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none transition-all',

View file

@ -9,9 +9,8 @@ const SurveysModule: React.FC = () => {
const [showSurveyModal, setShowSurveyModal] = useState(false) const [showSurveyModal, setShowSurveyModal] = useState(false)
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null) const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
const filteredSurveys = selectedStatus === 'all' const filteredSurveys =
? mockSurveys selectedStatus === 'all' ? mockSurveys : mockSurveys.filter((s) => s.status === selectedStatus)
: mockSurveys.filter(s => s.status === selectedStatus)
const handleTakeSurvey = (survey: Survey) => { const handleTakeSurvey = (survey: Survey) => {
setSelectedSurvey(survey) setSelectedSurvey(survey)
@ -29,7 +28,7 @@ const SurveysModule: React.FC = () => {
const colors: Record<string, string> = { const colors: Record<string, string> = {
draft: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300', 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', 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' closed: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
} }
return colors[status] || colors.draft return colors[status] || colors.draft
} }
@ -53,7 +52,7 @@ const SurveysModule: React.FC = () => {
{ value: 'all' as const, label: 'Tümü' }, { value: 'all' as const, label: 'Tümü' },
{ value: 'active' as const, label: 'Aktif' }, { value: 'active' as const, label: 'Aktif' },
{ value: 'draft' as const, label: 'Taslak' }, { value: 'draft' as const, label: 'Taslak' },
{ value: 'closed' as const, label: 'Kapalı' } { value: 'closed' as const, label: 'Kapalı' },
].map((tab) => ( ].map((tab) => (
<button <button
key={tab.value} key={tab.value}
@ -83,23 +82,31 @@ const SurveysModule: React.FC = () => {
<h3 className="text-lg font-semibold text-gray-900 dark:text-white pr-4"> <h3 className="text-lg font-semibold text-gray-900 dark:text-white pr-4">
{survey.title} {survey.title}
</h3> </h3>
<span className={`px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(survey.status)}`}> <span
{survey.status === 'active' ? 'Aktif' : survey.status === 'draft' ? 'Taslak' : 'Kapalı'} 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> </span>
</div> </div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{survey.description}</p>
{survey.description}
</p>
<div className="grid grid-cols-2 gap-4 mb-4"> <div className="grid grid-cols-2 gap-4 mb-4">
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400">Soru Sayısı</p> <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> <p className="text-sm font-medium text-gray-900 dark:text-white">
{survey.totalQuestions} soru
</p>
</div> </div>
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400">Yanıt</p> <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> <p className="text-sm font-medium text-gray-900 dark:text-white">
{survey.responses} kişi
</p>
</div> </div>
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400">Son Tarih</p> <p className="text-xs text-gray-500 dark:text-gray-400">Son Tarih</p>
@ -130,7 +137,7 @@ const SurveysModule: React.FC = () => {
</div> </div>
{survey.status === 'active' && ( {survey.status === 'active' && (
<button <button
onClick={() => handleTakeSurvey(survey)} onClick={() => handleTakeSurvey(survey)}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors" className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
> >
@ -154,133 +161,136 @@ const SurveysModule: React.FC = () => {
<p>Anket bulunamadı</p> <p>Anket bulunamadı</p>
</div> </div>
)} )}
</div>
{/* Survey Modal */} {/* Survey Modal */}
<AnimatePresence> <AnimatePresence>
{showSurveyModal && selectedSurvey && ( {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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={() => setShowSurveyModal(false)} onClick={(e) => e.stopPropagation()}
/> >
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> <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">
<motion.div <div>
initial={{ opacity: 0, scale: 0.95, y: 20 }} <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
animate={{ opacity: 1, scale: 1, y: 0 }} {selectedSurvey.title}
exit={{ opacity: 0, scale: 0.95, y: 20 }} </h2>
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto" <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
onClick={(e) => e.stopPropagation()} {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"
> >
<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"> {/* Örnek Anket Soruları */}
<div className="space-y-4">
<div> <div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{selectedSurvey.title} 1. Genel memnuniyet düzeyiniz nedir? *
</h2> </label>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1"> <div className="flex gap-2">
{selectedSurvey.description} {[1, 2, 3, 4, 5].map((rating) => (
</p> <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>
<button
onClick={() => setShowSurveyModal(false)} <div>
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
> 2. Hangi departmanda çalışıyorsunuz? *
<HiXMark className="w-5 h-5 text-gray-500" /> </label>
</button> <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>
<form <div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
onSubmit={(e) => { <button
e.preventDefault() type="button"
handleSubmitSurvey() 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"
className="p-6 space-y-6" >
> İptal
{/* Örnek Anket Soruları */} </button>
<div className="space-y-4"> <button
<div> type="submit"
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
1. Genel memnuniyet düzeyiniz nedir? * >
</label> Anketi Gönder
<div className="flex gap-2"> </button>
{[1, 2, 3, 4, 5].map((rating) => ( </div>
<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"> </form>
<input type="radio" name="rating" value={rating} required /> </motion.div>
<span className="text-sm text-gray-900 dark:text-white">{rating}</span> </div>
</label> </>
))} )}
</div> </AnimatePresence>
</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>
</div> </div>
) )
} }

View file

@ -9,7 +9,7 @@ import {
useSensor, useSensor,
useSensors, useSensors,
closestCorners, closestCorners,
useDroppable useDroppable,
} from '@dnd-kit/core' } from '@dnd-kit/core'
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { useSortable } from '@dnd-kit/sortable' import { useSortable } from '@dnd-kit/sortable'
@ -20,7 +20,7 @@ import {
HiClock, HiClock,
HiChatBubbleLeftRight, HiChatBubbleLeftRight,
HiPaperClip, HiPaperClip,
HiTrash HiTrash,
} from 'react-icons/hi2' } from 'react-icons/hi2'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import 'dayjs/locale/tr' import 'dayjs/locale/tr'
@ -40,11 +40,7 @@ interface DroppableColumnProps {
const DroppableColumn: React.FC<DroppableColumnProps> = ({ id, children }) => { const DroppableColumn: React.FC<DroppableColumnProps> = ({ id, children }) => {
const { setNodeRef } = useDroppable({ id }) const { setNodeRef } = useDroppable({ id })
return ( return <div ref={setNodeRef}>{children}</div>
<div ref={setNodeRef} >
{children}
</div>
)
} }
// Sortable Task Card Component // Sortable Task Card Component
@ -56,26 +52,21 @@ interface SortableTaskCardProps {
isOverdue: (date: Date | string) => boolean isOverdue: (date: Date | string) => boolean
} }
const SortableTaskCard: React.FC<SortableTaskCardProps> = ({ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
task, task,
onTaskClick, onTaskClick,
getPriorityColor, getPriorityColor,
getPriorityLabel, getPriorityLabel,
isOverdue isOverdue,
}) => { }) => {
const { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
attributes, id: task.id,
listeners, })
setNodeRef,
transform,
transition,
isDragging
} = useSortable({ id: task.id })
const style = { const style = {
transform: CSS.Transform.toString(transform), transform: CSS.Transform.toString(transform),
transition, transition,
opacity: isDragging ? 0.5 : 1 opacity: isDragging ? 0.5 : 1,
} }
const overdue = isOverdue(task.dueDate) && task.status !== 'done' const overdue = isOverdue(task.dueDate) && task.status !== 'done'
@ -87,9 +78,7 @@ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
{...attributes} {...attributes}
{...listeners} {...listeners}
className={`bg-white dark:bg-gray-800 rounded-lg p-3 sm:p-4 border-2 cursor-move hover:shadow-lg transition-all ${ className={`bg-white dark:bg-gray-800 rounded-lg p-3 sm:p-4 border-2 cursor-move hover:shadow-lg transition-all ${
overdue overdue ? 'border-red-300 dark:border-red-700' : 'border-gray-200 dark:border-gray-700'
? 'border-red-300 dark:border-red-700'
: 'border-gray-200 dark:border-gray-700'
} ${isDragging ? 'shadow-2xl ring-4 ring-blue-500/50' : ''}`} } ${isDragging ? 'shadow-2xl ring-4 ring-blue-500/50' : ''}`}
onClick={(e) => { onClick={(e) => {
if (!(e.target as HTMLElement).closest('[data-no-click]')) { if (!(e.target as HTMLElement).closest('[data-no-click]')) {
@ -98,19 +87,17 @@ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
}} }}
> >
<div className="flex items-start justify-between mb-2 sm:mb-3"> <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)}`}> <span
className={`px-2 py-1 text-xs font-medium rounded border ${getPriorityColor(task.priority)}`}
>
{getPriorityLabel(task.priority)} {getPriorityLabel(task.priority)}
</span> </span>
{overdue && ( {overdue && (
<span className="text-xs text-red-600 dark:text-red-400 font-medium"> <span className="text-xs text-red-600 dark:text-red-400 font-medium"> Gecikmiş</span>
Gecikmiş
</span>
)} )}
</div> </div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2"> <h4 className="font-semibold text-gray-900 dark:text-white mb-2">{task.title}</h4>
{task.title}
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
{task.description} {task.description}
@ -180,6 +167,7 @@ const TasksModule: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>(mockTasks) const [tasks, setTasks] = useState<Task[]>(mockTasks)
const [selectedTask, setSelectedTask] = useState<Task | null>(null) const [selectedTask, setSelectedTask] = useState<Task | null>(null)
const [activeId, setActiveId] = useState<string | null>(null) const [activeId, setActiveId] = useState<string | null>(null)
const [dragOverColumn, setDragOverColumn] = useState<TaskStatus | null>(null)
const [showNewTaskModal, setShowNewTaskModal] = useState(false) const [showNewTaskModal, setShowNewTaskModal] = useState(false)
const [newTaskColumn, setNewTaskColumn] = useState<TaskStatus>('todo') const [newTaskColumn, setNewTaskColumn] = useState<TaskStatus>('todo')
@ -188,64 +176,82 @@ const TasksModule: React.FC = () => {
activationConstraint: { activationConstraint: {
distance: 8, distance: 8,
}, },
}) }),
) )
const columns: { id: TaskStatus; title: string; icon: string; color: string }[] = [ const columns: { id: TaskStatus; title: string; icon: string; color: string }[] = [
{ id: 'todo', title: 'Yapılacak', icon: '📋', color: 'gray' }, { id: 'todo', title: 'Yapılacak', icon: '📋', color: 'gray' },
{ id: 'in-progress', title: 'Devam Ediyor', icon: '⚙️', color: 'blue' }, { id: 'in-progress', title: 'Devam Ediyor', icon: '⚙️', color: 'blue' },
{ id: 'review', title: 'İncelemede', icon: '👀', color: 'yellow' }, { id: 'review', title: 'İncelemede', icon: '👀', color: 'yellow' },
{ id: 'done', title: 'Tamamlandı', icon: '✅', color: 'green' } { id: 'done', title: 'Tamamlandı', icon: '✅', color: 'green' },
] ]
const handleDragStart = (event: DragStartEvent) => { const handleDragStart = (event: DragStartEvent) => {
setActiveId(event.active.id as string) 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 handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event const { active, over } = event
setActiveId(null) setActiveId(null)
setDragOverColumn(null)
if (!over) return if (!over) return
const taskId = active.id as string const taskId = active.id as string
// over.id could be either a column id or a task id // 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 column id (from DroppableColumn), use it directly
// If it's a task id, find that task's column // If it's a task id, find that task's column
let newStatus: TaskStatus let newStatus: TaskStatus
const overColumn = columns.find(col => col.id === over.id) const overColumn = columns.find((col) => col.id === over.id)
if (overColumn) { if (overColumn) {
newStatus = overColumn.id newStatus = overColumn.id
} else { } else {
// over.id is a task, find its column // over.id is a task, find its column
const overTask = tasks.find(t => t.id === over.id) const overTask = tasks.find((t) => t.id === over.id)
if (!overTask) return if (!overTask) return
newStatus = overTask.status newStatus = overTask.status
} }
// Update task status // Update task status
setTasks(prevTasks => setTasks((prevTasks) =>
prevTasks.map(task => prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
task.id === taskId ? { ...task, status: newStatus } : task
)
) )
if (selectedTask?.id === taskId) { if (selectedTask?.id === taskId) {
setSelectedTask(prev => prev ? { ...prev, status: newStatus } : null) setSelectedTask((prev) => (prev ? { ...prev, status: newStatus } : null))
} }
} }
const handleStatusChange = (taskId: string, newStatus: TaskStatus) => { const handleStatusChange = (taskId: string, newStatus: TaskStatus) => {
setTasks(prevTasks => setTasks((prevTasks) =>
prevTasks.map(task => prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
task.id === taskId ? { ...task, status: newStatus } : task
)
) )
if (selectedTask?.id === taskId) { if (selectedTask?.id === taskId) {
setSelectedTask(prev => prev ? { ...prev, status: newStatus } : null) setSelectedTask((prev) => (prev ? { ...prev, status: newStatus } : null))
} }
} }
@ -267,16 +273,16 @@ const TasksModule: React.FC = () => {
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
createdAt: new Date(), createdAt: new Date(),
labels: [], labels: [],
comments: 0 comments: 0,
} }
setTasks(prev => [...prev, newTask]) setTasks((prev) => [...prev, newTask])
setShowNewTaskModal(false) setShowNewTaskModal(false)
} }
const handleDeleteTask = (taskId: string) => { const handleDeleteTask = (taskId: string) => {
if (window.confirm('Bu görevi silmek istediğinizden emin misiniz?')) { if (window.confirm('Bu görevi silmek istediğinizden emin misiniz?')) {
setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId)) setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId))
setSelectedTask(null) setSelectedTask(null)
} }
} }
@ -288,9 +294,11 @@ const TasksModule: React.FC = () => {
const getPriorityColor = (priority: string) => { const getPriorityColor = (priority: string) => {
const colors = { const colors = {
low: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600', 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', 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', 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' 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] return colors[priority as keyof typeof colors]
} }
@ -300,7 +308,7 @@ const TasksModule: React.FC = () => {
low: 'Düşük', low: 'Düşük',
medium: 'Orta', medium: 'Orta',
high: 'Yüksek', high: 'Yüksek',
urgent: '🔥 Acil' urgent: '🔥 Acil',
} }
return labels[priority as keyof typeof labels] return labels[priority as keyof typeof labels]
} }
@ -314,6 +322,7 @@ const TasksModule: React.FC = () => {
sensors={sensors} sensors={sensors}
collisionDetection={closestCorners} collisionDetection={closestCorners}
onDragStart={handleDragStart} onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
> >
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-3 sm:p-4 md:p-6"> <div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-3 sm:p-4 md:p-6">
@ -343,79 +352,83 @@ const TasksModule: React.FC = () => {
{/* Kanban Board */} {/* 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="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"> <div className="kanban-container sm:contents">
{columns.map(column => { {columns.map((column) => {
const columnTasks = getTasksByStatus(column.id) const columnTasks = getTasksByStatus(column.id)
return ( return (
<DroppableColumn key={column.id} id={column.id}> <DroppableColumn key={column.id} id={column.id}>
<SortableContext <SortableContext
id={column.id} id={column.id}
items={columnTasks.map(t => t.id)} items={columnTasks.map((t) => t.id)}
strategy={verticalListSortingStrategy} 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
<div className="flex items-center justify-between mb-2"> 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]"
<div className="flex items-center gap-2"> data-status={column.id}
<span className="text-lg sm:text-xl">{column.icon}</span> >
<h3 className="font-semibold text-gray-900 dark:text-white text-sm sm:text-base"> {/* Column Header */}
{column.title} <div className="flex items-center justify-between mb-2">
</h3> <div className="flex items-center gap-2">
</div> <span className="text-lg sm:text-xl">{column.icon}</span>
<h3
className={`font-semibold text-sm sm:text-base transition-colors duration-200 ${
dragOverColumn === column.id
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-900 dark:text-white'
}`}
>
{column.title}
</h3>
</div>
<Badge content={columnTasks.length}></Badge> <Badge content={columnTasks.length}></Badge>
</div> </div>
{/* Tasks */} {/* Tasks */}
<div className="space-y-3 flex-1"> <div className="space-y-3 flex-1">
{columnTasks.map(task => ( {columnTasks.map((task) => (
<SortableTaskCard <SortableTaskCard
key={task.id} key={task.id}
task={task} task={task}
onTaskClick={setSelectedTask} onTaskClick={setSelectedTask}
getPriorityColor={getPriorityColor} getPriorityColor={getPriorityColor}
getPriorityLabel={getPriorityLabel} getPriorityLabel={getPriorityLabel}
isOverdue={isOverdue} isOverdue={isOverdue}
/> />
))} ))}
</div> </div>
{/* Add Task Button */} {/* Add Task Button */}
<button <button
onClick={() => handleAddTask(column.id)} 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" 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" /> <HiPlus className="w-4 h-4 sm:w-5 sm:h-5 mx-auto" />
</button> </button>
</div> </div>
</SortableContext> </SortableContext>
</DroppableColumn> </DroppableColumn>
) )
})} })}
</div> </div>
</div> </div>
{/* DragOverlay */} {/* DragOverlay */}
<DragOverlay> <DragOverlay>
{activeId ? ( {activeId ? (
<div className="opacity-50"> <div className="opacity-50">
{(() => { {(() => {
const task = tasks.find(t => t.id === activeId) const task = tasks.find((t) => t.id === activeId)
if (!task) return null if (!task) return null
return ( return (
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 border-2 border-blue-500 shadow-2xl"> <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"> <h4 className="font-semibold text-gray-900 dark:text-white">{task.title}</h4>
{task.title} </div>
</h4> )
</div> })()}
) </div>
})()} ) : null}
</div> </DragOverlay>
) : null} </div>
</DragOverlay>
{/* New Task Modal */} {/* New Task Modal */}
<AnimatePresence> <AnimatePresence>
@ -488,7 +501,9 @@ const TasksModule: React.FC = () => {
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3"> <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"> <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 📋 Görev{' '}
<strong>{columns.find((c) => c.id === newTaskColumn)?.title}</strong>{' '}
kolonuna eklenecek
</p> </p>
</div> </div>
@ -534,7 +549,9 @@ const TasksModule: React.FC = () => {
> >
<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="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"> <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)}`}> <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)} {getPriorityLabel(selectedTask.priority)}
</span> </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"> <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">
@ -565,7 +582,9 @@ const TasksModule: React.FC = () => {
</p> </p>
<select <select
value={selectedTask.status} value={selectedTask.status}
onChange={(e) => handleStatusChange(selectedTask.id, e.target.value as TaskStatus)} 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" 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="todo">📋 Yapılacak</option>
@ -630,7 +649,8 @@ const TasksModule: React.FC = () => {
<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"> <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"> <p className="text-xs text-gray-500 dark:text-gray-400">
Oluşturan: {selectedTask.assignedBy.fullName} {dayjs(selectedTask.createdAt).format('DD MMMM YYYY')} Oluşturan: {selectedTask.assignedBy.fullName} {' '}
{dayjs(selectedTask.createdAt).format('DD MMMM YYYY')}
</p> </p>
<button <button
onClick={() => handleDeleteTask(selectedTask.id)} onClick={() => handleDeleteTask(selectedTask.id)}
@ -646,7 +666,6 @@ const TasksModule: React.FC = () => {
</> </>
)} )}
</AnimatePresence> </AnimatePresence>
</div>
</div> </div>
</DndContext> </DndContext>
) )

View file

@ -7,33 +7,39 @@ import {
HiMapPin, HiMapPin,
HiXMark, HiXMark,
HiCheckBadge, HiCheckBadge,
HiCalendar HiCalendar,
} from 'react-icons/hi2' } from 'react-icons/hi2'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { mockTrainings, mockCertificates, Training } from '../../../mocks/mockIntranetData' import { mockTrainings, mockCertificates, Training } from '../../../mocks/mockIntranetData'
const TrainingModule: React.FC = () => { const TrainingModule: React.FC = () => {
const [selectedTraining, setSelectedTraining] = useState<Training | null>(null) const [selectedTraining, setSelectedTraining] = useState<Training | null>(null)
const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>('all') const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>(
'all',
)
const [trainings, setTrainings] = useState<Training[]>(mockTrainings) const [trainings, setTrainings] = useState<Training[]>(mockTrainings)
const [showEnrollSuccess, setShowEnrollSuccess] = useState(false) const [showEnrollSuccess, setShowEnrollSuccess] = useState(false)
const filteredTrainings = selectedTab === 'all' const filteredTrainings =
? trainings selectedTab === 'all' ? trainings : trainings.filter((t) => t.status === selectedTab)
: trainings.filter(t => t.status === selectedTab)
const handleEnroll = (trainingId: string) => { const handleEnroll = (trainingId: string) => {
setTrainings(prev => prev.map(t => setTrainings((prev) =>
t.id === trainingId && t.enrolled < t.maxParticipants prev.map((t) =>
? { ...t, enrolled: t.enrolled + 1 } t.id === trainingId && t.enrolled < t.maxParticipants
: t ? { ...t, enrolled: t.enrolled + 1 }
)) : t,
),
)
// Seçili eğitimi de güncelle // Seçili eğitimi de güncelle
if (selectedTraining?.id === trainingId && selectedTraining.enrolled < selectedTraining.maxParticipants) { if (
setSelectedTraining(prev => prev ? { ...prev, enrolled: prev.enrolled + 1 } : null) selectedTraining?.id === trainingId &&
selectedTraining.enrolled < selectedTraining.maxParticipants
) {
setSelectedTraining((prev) => (prev ? { ...prev, enrolled: prev.enrolled + 1 } : null))
} }
setShowEnrollSuccess(true) setShowEnrollSuccess(true)
setTimeout(() => setShowEnrollSuccess(false), 3000) setTimeout(() => setShowEnrollSuccess(false), 3000)
} }
@ -44,7 +50,7 @@ const TrainingModule: React.FC = () => {
'soft-skills': 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-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', 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', 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' other: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
} }
return colors[category] || colors.other return colors[category] || colors.other
} }
@ -52,8 +58,11 @@ const TrainingModule: React.FC = () => {
const getStatusBadge = (status: string) => { const getStatusBadge = (status: string) => {
const badges: Record<string, { bg: string; text: 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' }, 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' }, ongoing: {
completed: { bg: 'bg-gray-100 dark:bg-gray-700', text: 'text-gray-700 dark:text-gray-300' } 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 badges[status] || badges.upcoming
} }
@ -77,9 +86,21 @@ const TrainingModule: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{[ {[
{ label: 'Tümü', value: mockTrainings.length, tab: 'all' as const }, { 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: 'Yaklaşan',
{ label: 'Tamamlanan', value: mockTrainings.filter(t => t.status === 'completed').length, tab: 'completed' as const } 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) => ( ].map((stat, idx) => (
<motion.button <motion.button
key={idx} key={idx}
@ -119,14 +140,22 @@ const TrainingModule: React.FC = () => {
className="w-full h-48 object-cover" className="w-full h-48 object-cover"
/> />
)} )}
<div className="p-6"> <div className="p-6">
<div className="flex items-start justify-between mb-3"> <div className="flex items-start justify-between mb-3">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}> <span
className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}
>
{training.category} {training.category}
</span> </span>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`}> <span
{training.status === 'upcoming' ? 'Yaklaşan' : training.status === 'ongoing' ? 'Devam Ediyor' : 'Tamamlandı'} 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> </span>
</div> </div>
@ -153,18 +182,21 @@ const TrainingModule: React.FC = () => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<HiUsers className="w-4 h-4" /> <HiUsers className="w-4 h-4" />
<span>{training.enrolled} / {training.maxParticipants} katılımcı</span> <span>
{training.enrolled} / {training.maxParticipants} katılımcı
</span>
</div> </div>
</div> </div>
{training.enrolled < training.maxParticipants && training.status === 'upcoming' && ( {training.enrolled < training.maxParticipants &&
<button training.status === 'upcoming' && (
onClick={() => handleEnroll(training.id)} <button
className="mt-4 w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors" 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> Kayıt Ol
)} </button>
)}
</div> </div>
</motion.div> </motion.div>
) )
@ -177,7 +209,7 @@ const TrainingModule: React.FC = () => {
<HiCheckBadge className="w-6 h-6 text-blue-500" /> <HiCheckBadge className="w-6 h-6 text-blue-500" />
Sertifikalarım Sertifikalarım
</h2> </h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{mockCertificates.map((cert, idx) => ( {mockCertificates.map((cert, idx) => (
<motion.div <motion.div
@ -195,15 +227,15 @@ const TrainingModule: React.FC = () => {
</span> </span>
)} )}
</div> </div>
<h3 className="font-semibold text-gray-900 dark:text-white mb-2"> <h3 className="font-semibold text-gray-900 dark:text-white mb-2">
{cert.trainingTitle} {cert.trainingTitle}
</h3> </h3>
<p className="text-sm text-gray-600 dark:text-gray-400"> <p className="text-sm text-gray-600 dark:text-gray-400">
{dayjs(cert.issueDate).format('DD MMMM YYYY')} {dayjs(cert.issueDate).format('DD MMMM YYYY')}
</p> </p>
{cert.expiryDate && ( {cert.expiryDate && (
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1"> <p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
Geçerlilik: {dayjs(cert.expiryDate).format('DD/MM/YYYY')} Geçerlilik: {dayjs(cert.expiryDate).format('DD/MM/YYYY')}
@ -217,136 +249,149 @@ const TrainingModule: React.FC = () => {
))} ))}
</div> </div>
</div> </div>
</div>
{/* Training Detail Modal */} {/* Training Detail Modal */}
<AnimatePresence> <AnimatePresence>
{selectedTraining && ( {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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={() => setSelectedTraining(null)} >
/> <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">
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
<motion.div Eğitim Detayları
initial={{ opacity: 0, scale: 0.95, y: 20 }} </h2>
animate={{ opacity: 1, scale: 1, y: 0 }} <button
exit={{ opacity: 0, scale: 0.95, y: 20 }} onClick={() => setSelectedTraining(null)}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto" className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
> >
<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"> <HiXMark className="w-5 h-5 text-gray-500" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"> </button>
Eğitim Detayları </div>
</h2>
<button <div className="p-6 space-y-6">
onClick={() => setSelectedTraining(null)} {selectedTraining.thumbnail && (
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" <img
> src={selectedTraining.thumbnail}
<HiXMark className="w-5 h-5 text-gray-500" /> alt={selectedTraining.title}
</button> 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>
<div className="p-6 space-y-6"> <div className="grid grid-cols-2 gap-4">
{selectedTraining.thumbnail && (
<img
src={selectedTraining.thumbnail}
alt={selectedTraining.title}
className="w-full h-64 object-cover rounded-lg"
/>
)}
<div> <div>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p>
{selectedTraining.title} <p className="font-medium text-gray-900 dark:text-white">
</h3> {selectedTraining.instructor}
<p className="text-gray-700 dark:text-gray-300">
{selectedTraining.description}
</p> </p>
</div> </div>
<div>
<div className="grid grid-cols-2 gap-4"> <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> <div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p> <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.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"> <p className="font-medium text-gray-900 dark:text-white">
{dayjs(selectedTraining.startDate).format('DD MMM')} - {dayjs(selectedTraining.endDate).format('DD MMM YYYY')} {selectedTraining.location}
</p> </p>
</div> </div>
<div> )}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Süre</p> </div>
<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> <div>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Doluluk Oranı</p> <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 items-center gap-3">
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3"> <div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3">
<div <div
className="bg-blue-600 h-3 rounded-full transition-all" className="bg-blue-600 h-3 rounded-full transition-all"
style={{ width: `${(selectedTraining.enrolled / selectedTraining.maxParticipants) * 100}%` }} 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>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{selectedTraining.enrolled} / {selectedTraining.maxParticipants}
</span>
</div> </div>
</div>
{selectedTraining.enrolled < selectedTraining.maxParticipants && selectedTraining.status === 'upcoming' && ( {selectedTraining.enrolled < selectedTraining.maxParticipants &&
<button selectedTraining.status === 'upcoming' && (
<button
onClick={() => handleEnroll(selectedTraining.id)} 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" 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 Eğitime Kayıt Ol
</button> </button>
)} )}
</div> </div>
</motion.div> </motion.div>
</div> </div>
</> </>
)} )}
</AnimatePresence> </AnimatePresence>
{/* Success Notification */} {/* Success Notification */}
<AnimatePresence> <AnimatePresence>
{showEnrollSuccess && ( {showEnrollSuccess && (
<motion.div <motion.div
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }} 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" 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" /> <HiCheckBadge className="w-6 h-6" />
<div> <div>
<p className="font-semibold">Kayıt Başarılı!</p> <p className="font-semibold">Kayıt Başarılı!</p>
<p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p> <p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p>
</div> </div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</div>
</div> </div>
) )
} }

View file

@ -5,36 +5,33 @@ import dayjs from 'dayjs'
import { mockVisitors, Visitor } from '../../../mocks/mockIntranetData' import { mockVisitors, Visitor } from '../../../mocks/mockIntranetData'
const VisitorsModule: React.FC = () => { const VisitorsModule: React.FC = () => {
const [selectedStatus, setSelectedStatus] = useState<'all' | 'scheduled' | 'checked-in' | 'checked-out' | 'cancelled'>('all') const [selectedStatus, setSelectedStatus] = useState<
'all' | 'scheduled' | 'checked-in' | 'checked-out' | 'cancelled'
>('all')
const [showNewVisitor, setShowNewVisitor] = useState(false) const [showNewVisitor, setShowNewVisitor] = useState(false)
const [visitors, setVisitors] = useState<Visitor[]>(mockVisitors) const [visitors, setVisitors] = useState<Visitor[]>(mockVisitors)
const filteredVisitors = selectedStatus === 'all' const filteredVisitors =
? visitors selectedStatus === 'all' ? visitors : visitors.filter((v) => v.status === selectedStatus)
: visitors.filter(v => v.status === selectedStatus)
const handleCheckIn = (visitorId: string) => { const handleCheckIn = (visitorId: string) => {
setVisitors(prev => prev.map(v => setVisitors((prev) =>
v.id === visitorId prev.map((v) =>
? { ...v, status: 'checked-in', checkIn: new Date() } v.id === visitorId ? { ...v, status: 'checked-in', checkIn: new Date() } : v,
: v ),
)) )
} }
const handleCheckOut = (visitorId: string) => { const handleCheckOut = (visitorId: string) => {
setVisitors(prev => prev.map(v => setVisitors((prev) =>
v.id === visitorId prev.map((v) =>
? { ...v, status: 'checked-out', checkOut: new Date() } v.id === visitorId ? { ...v, status: 'checked-out', checkOut: new Date() } : v,
: v ),
)) )
} }
const handleCancel = (visitorId: string) => { const handleCancel = (visitorId: string) => {
setVisitors(prev => prev.map(v => setVisitors((prev) => prev.map((v) => (v.id === visitorId ? { ...v, status: 'cancelled' } : v)))
v.id === visitorId
? { ...v, status: 'cancelled' }
: v
))
} }
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
@ -42,7 +39,7 @@ const VisitorsModule: React.FC = () => {
scheduled: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300', 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-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', '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' cancelled: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
} }
return colors[status] || colors.scheduled return colors[status] || colors.scheduled
} }
@ -52,7 +49,7 @@ const VisitorsModule: React.FC = () => {
scheduled: '📅 Planlandı', scheduled: '📅 Planlandı',
'checked-in': '✅ Giriş Yaptı', 'checked-in': '✅ Giriş Yaptı',
'checked-out': '🚪 Çıkış Yaptı', 'checked-out': '🚪 Çıkış Yaptı',
cancelled: '❌ İptal Edildi' cancelled: '❌ İptal Edildi',
} }
return labels[status] || status return labels[status] || status
} }
@ -83,9 +80,21 @@ const VisitorsModule: React.FC = () => {
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[ {[
{ label: 'Tümü', value: mockVisitors.length, status: 'all' as const }, { 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: 'Planlandı',
{ label: ıkış Yaptı', value: mockVisitors.filter(v => v.status === 'checked-out').length, status: 'checked-out' as const } 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) => ( ].map((stat, idx) => (
<motion.button <motion.button
key={idx} key={idx}
@ -132,14 +141,18 @@ const VisitorsModule: React.FC = () => {
</h3> </h3>
<p className="text-sm text-gray-600 dark:text-gray-400">{visitor.company}</p> <p className="text-sm text-gray-600 dark:text-gray-400">{visitor.company}</p>
</div> </div>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(visitor.status)}`}> <span
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(visitor.status)}`}
>
{getStatusLabel(visitor.status)} {getStatusLabel(visitor.status)}
</span> </span>
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div> <div>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Ziyaret Tarihi</p> <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"> <div className="flex items-center gap-1 text-sm text-gray-900 dark:text-white">
<HiClock className="w-4 h-4" /> <HiClock className="w-4 h-4" />
{dayjs(visitor.visitDate).format('DD MMM, HH:mm')} {dayjs(visitor.visitDate).format('DD MMM, HH:mm')}
@ -202,7 +215,7 @@ const VisitorsModule: React.FC = () => {
{visitor.host.fullName} {visitor.host.fullName}
</p> </p>
<p className="text-xs text-gray-600 dark:text-gray-400"> <p className="text-xs text-gray-600 dark:text-gray-400">
{visitor.host.department} {visitor.host.department?.name}
</p> </p>
</div> </div>
</div> </div>
@ -216,14 +229,14 @@ const VisitorsModule: React.FC = () => {
{visitor.status === 'scheduled' && ( {visitor.status === 'scheduled' && (
<div className="flex gap-2 mt-4"> <div className="flex gap-2 mt-4">
<button <button
onClick={() => handleCheckIn(visitor.id)} onClick={() => handleCheckIn(visitor.id)}
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors text-sm" 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" /> <HiCheckCircle className="w-4 h-4 inline mr-1" />
Giriş Yaptır Giriş Yaptır
</button> </button>
<button <button
onClick={() => handleCancel(visitor.id)} 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" 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"
> >
@ -233,7 +246,7 @@ const VisitorsModule: React.FC = () => {
)} )}
{visitor.status === 'checked-in' && ( {visitor.status === 'checked-in' && (
<button <button
onClick={() => handleCheckOut(visitor.id)} 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" className="mt-4 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors text-sm"
> >
@ -245,134 +258,134 @@ const VisitorsModule: React.FC = () => {
</motion.div> </motion.div>
))} ))}
</div> </div>
</div>
{/* New Visitor Modal */} {/* New Visitor Modal */}
<AnimatePresence> <AnimatePresence>
{showNewVisitor && ( {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 <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed inset-0 bg-black/50 z-40" className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={() => setShowNewVisitor(false)} >
/> <div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
<motion.div Yeni Ziyaretçi Kaydı
initial={{ opacity: 0, scale: 0.95, y: 20 }} </h2>
animate={{ opacity: 1, scale: 1, y: 0 }} <button
exit={{ opacity: 0, scale: 0.95, y: 20 }} onClick={() => setShowNewVisitor(false)}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto" className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
> >
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between"> <HiXMark className="w-5 h-5 text-gray-500" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"> </button>
Yeni Ziyaretçi Kaydı </div>
</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 className="p-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ziyaret Tarihi ve Saati * Ad Soyad *
</label> </label>
<input <input
type="datetime-local" 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" 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> <div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Ev Sahibi (Personel) * Şirket *
</label> </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"> <input
<option>Ahmet Yılmaz - Yazılım Geliştirme</option> type="text"
<option>Zeynep Kaya - İnsan Kaynakları</option> placeholder="Şirket adı"
<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" 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="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> </div>
</motion.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">
</AnimatePresence> E-posta *
</div> </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> </div>
) )
} }

View file

@ -606,25 +606,40 @@ export const mockDocuments: Document[] = [
export const mockBirthdays: Birthday[] = [ export const mockBirthdays: Birthday[] = [
{ {
employee: mockEmployees[0], employee: mockEmployees[0],
date: new Date('2024-10-20'), date: new Date('1990-10-19'), // Bugün
age: 34, age: 35,
}, },
{ {
employee: mockEmployees[1], employee: mockEmployees[1],
date: new Date('2024-10-25'), date: new Date('1992-10-21'), // Bu hafta içinde
age: 32, age: 33,
},
{
employee: mockEmployees[2],
date: new Date('1988-10-23'), // Bu hafta içinde
age: 37,
},
{
employee: mockEmployees[3],
date: new Date('1995-10-28'), // Bu ay içinde
age: 30,
}, },
] ]
export const mockAnniversaries: WorkAnniversary[] = [ export const mockAnniversaries: WorkAnniversary[] = [
{ {
employee: mockEmployees[2], employee: mockEmployees[2],
hireDate: new Date('2019-09-10'), hireDate: new Date('2019-10-10'), // Bu ay (Ekim)
years: 6,
},
{
employee: mockEmployees[4],
hireDate: new Date('2020-10-15'), // Bu ay (Ekim)
years: 5, years: 5,
}, },
{ {
employee: mockEmployees[0], employee: mockEmployees[0],
hireDate: new Date('2020-03-15'), hireDate: new Date('2021-10-20'), // Bu ay (Ekim)
years: 4, years: 4,
}, },
] ]