İntranet modülündeki Dialoglar düzeltildi.
This commit is contained in:
parent
ddaaa56ea0
commit
e8093784e0
10 changed files with 1371 additions and 1087 deletions
|
|
@ -8,12 +8,13 @@ import {
|
|||
HiCalendar,
|
||||
HiXMark,
|
||||
HiChevronLeft,
|
||||
HiChevronRight
|
||||
HiChevronRight,
|
||||
} from 'react-icons/hi2'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/tr'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { mockEvents, CalendarEvent, EventComment } from '../../../mocks/mockIntranetData'
|
||||
import { mockEmployees } from '@/mocks/mockEmployees'
|
||||
|
||||
dayjs.locale('tr')
|
||||
dayjs.extend(relativeTime)
|
||||
|
|
@ -22,20 +23,21 @@ const EventsModule: React.FC = () => {
|
|||
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null)
|
||||
const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(0)
|
||||
const [showPhotoModal, setShowPhotoModal] = useState(false)
|
||||
const [selectedFilter, setSelectedFilter] = useState<'all' | 'social' | 'training' | 'company' | 'sport' | 'culture'>('all')
|
||||
const [selectedFilter, setSelectedFilter] = useState<
|
||||
'all' | 'social' | 'training' | 'company' | 'sport' | 'culture'
|
||||
>('all')
|
||||
const [newComment, setNewComment] = useState('')
|
||||
const [events, setEvents] = useState<CalendarEvent[]>(mockEvents)
|
||||
|
||||
const filteredEvents = selectedFilter === 'all'
|
||||
? events.filter(e => e.isPublished)
|
||||
: events.filter(e => e.isPublished && e.type === selectedFilter)
|
||||
const filteredEvents =
|
||||
selectedFilter === 'all'
|
||||
? events.filter((e) => e.isPublished)
|
||||
: events.filter((e) => e.isPublished && e.type === selectedFilter)
|
||||
|
||||
const handleLikeEvent = (eventId: string) => {
|
||||
setEvents(prev => prev.map(e =>
|
||||
e.id === eventId ? { ...e, likes: e.likes + 1 } : e
|
||||
))
|
||||
setEvents((prev) => prev.map((e) => (e.id === eventId ? { ...e, likes: e.likes + 1 } : e)))
|
||||
if (selectedEvent?.id === eventId) {
|
||||
setSelectedEvent(prev => prev ? { ...prev, likes: prev.likes + 1 } : null)
|
||||
setSelectedEvent((prev) => (prev ? { ...prev, likes: prev.likes + 1 } : null))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,22 +46,18 @@ const EventsModule: React.FC = () => {
|
|||
|
||||
const comment: EventComment = {
|
||||
id: `c${Date.now()}`,
|
||||
author: {
|
||||
id: 'current-user',
|
||||
fullName: 'Sedat Öztürk',
|
||||
avatar: 'https://ui-avatars.com/api/?name=Sedat+Ozturk&background=3b82f6&color=fff'
|
||||
},
|
||||
author: mockEmployees[0],
|
||||
content: newComment,
|
||||
createdAt: new Date(),
|
||||
likes: 0
|
||||
likes: 0,
|
||||
}
|
||||
|
||||
setEvents(prev => prev.map(e =>
|
||||
e.id === eventId ? { ...e, comments: [...e.comments, comment] } : e
|
||||
))
|
||||
setEvents((prev) =>
|
||||
prev.map((e) => (e.id === eventId ? { ...e, comments: [...e.comments, comment] } : e)),
|
||||
)
|
||||
|
||||
if (selectedEvent?.id === eventId) {
|
||||
setSelectedEvent(prev => prev ? { ...prev, comments: [...prev.comments, comment] } : null)
|
||||
setSelectedEvent((prev) => (prev ? { ...prev, comments: [...prev.comments, comment] } : null))
|
||||
}
|
||||
|
||||
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',
|
||||
company: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
sport: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
|
||||
culture: 'bg-pink-100 dark:bg-pink-900/30 text-pink-700 dark:text-pink-300'
|
||||
culture: 'bg-pink-100 dark:bg-pink-900/30 text-pink-700 dark:text-pink-300',
|
||||
}
|
||||
return colors[type] || colors.social
|
||||
}
|
||||
|
|
@ -82,7 +80,7 @@ const EventsModule: React.FC = () => {
|
|||
training: '📚 Eğitim',
|
||||
company: '🏢 Kurumsal',
|
||||
sport: '⚽ Spor',
|
||||
culture: '🎨 Kültür'
|
||||
culture: '🎨 Kültür',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
|
@ -92,9 +90,7 @@ const EventsModule: React.FC = () => {
|
|||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
🎊 Etkinlikler
|
||||
</h1>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">🎊 Etkinlikler</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Şirket etkinlikleri, fotoğraflar ve anılar
|
||||
</p>
|
||||
|
|
@ -108,7 +104,7 @@ const EventsModule: React.FC = () => {
|
|||
{ value: 'training' as const, label: '📚 Eğitim' },
|
||||
{ value: 'company' as const, label: '🏢 Kurumsal' },
|
||||
{ value: 'sport' as const, label: '⚽ Spor' },
|
||||
{ value: 'culture' as const, label: '🎨 Kültür' }
|
||||
{ value: 'culture' as const, label: '🎨 Kültür' },
|
||||
].map((tab) => (
|
||||
<button
|
||||
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"
|
||||
/>
|
||||
<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)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -207,252 +205,258 @@ const EventsModule: React.FC = () => {
|
|||
<p className="text-lg">Bu kategoride henüz etkinlik yok</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Event Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedEvent && (
|
||||
<>
|
||||
{/* Event Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedEvent && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto my-8"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 z-10">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(selectedEvent.type)}`}>
|
||||
{getTypeLabel(selectedEvent.type)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{dayjs(selectedEvent.date).format('DD MMMM YYYY')}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedEvent.title}
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-2">
|
||||
{selectedEvent.description}
|
||||
</p>
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto my-8"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 z-10">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${getTypeColor(selectedEvent.type)}`}
|
||||
>
|
||||
{getTypeLabel(selectedEvent.type)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{dayjs(selectedEvent.date).format('DD MMMM YYYY')}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedEvent.title}
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-2">
|
||||
{selectedEvent.description}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-6 h-6 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6 mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className="flex items-center gap-2">
|
||||
<HiMapPin className="w-5 h-5" />
|
||||
{selectedEvent.location}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<HiUsers className="w-5 h-5" />
|
||||
{selectedEvent.participants} katılımcı
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src={selectedEvent.organizer.avatar}
|
||||
alt={selectedEvent.organizer.fullName}
|
||||
className="w-6 h-6 rounded-full"
|
||||
/>
|
||||
<span>Düzenleyen: {selectedEvent.organizer.fullName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Photo Gallery */}
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
📸 Fotoğraflar ({selectedEvent.photos.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{selectedEvent.photos.map((photo, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="relative aspect-square rounded-lg overflow-hidden cursor-pointer"
|
||||
onClick={() => {
|
||||
setSelectedPhotoIndex(idx)
|
||||
setShowPhotoModal(true)
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</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">
|
||||
{/* 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={selectedEvent.organizer.avatar}
|
||||
alt={selectedEvent.organizer.fullName}
|
||||
className="w-6 h-6 rounded-full"
|
||||
src={comment.author.avatar}
|
||||
alt={comment.author.fullName}
|
||||
className="w-10 h-10 rounded-full"
|
||||
/>
|
||||
<span>Düzenleyen: {selectedEvent.organizer.fullName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Photo Gallery */}
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
📸 Fotoğraflar ({selectedEvent.photos.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{selectedEvent.photos.map((photo, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="relative aspect-square rounded-lg overflow-hidden cursor-pointer"
|
||||
onClick={() => {
|
||||
setSelectedPhotoIndex(idx)
|
||||
setShowPhotoModal(true)
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={photo}
|
||||
alt={`${selectedEvent.title} - ${idx + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comments Section */}
|
||||
<div className="p-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
💬 Yorumlar ({selectedEvent.comments.length})
|
||||
</h3>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<button
|
||||
onClick={() => handleLikeEvent(selectedEvent.id)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 transition-colors"
|
||||
>
|
||||
<HiHeart className="w-5 h-5 text-red-500" />
|
||||
{selectedEvent.likes} beğeni
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comments List */}
|
||||
<div className="space-y-4 mb-4">
|
||||
{selectedEvent.comments.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex gap-3 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg"
|
||||
>
|
||||
<img
|
||||
src={comment.author.avatar}
|
||||
alt={comment.author.fullName}
|
||||
className="w-10 h-10 rounded-full"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-semibold text-gray-900 dark:text-white">
|
||||
{comment.author.fullName}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{dayjs(comment.createdAt).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm">
|
||||
{comment.content}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<button className="text-xs text-gray-500 hover:text-red-500 transition-colors">
|
||||
❤️ {comment.likes} beğeni
|
||||
</button>
|
||||
</div>
|
||||
<div 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>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* 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>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Photo Viewer Modal */}
|
||||
<AnimatePresence>
|
||||
{showPhotoModal && selectedEvent && (
|
||||
<>
|
||||
{/* Photo Viewer Modal */}
|
||||
<AnimatePresence>
|
||||
{showPhotoModal && selectedEvent && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/90 z-50"
|
||||
onClick={() => setShowPhotoModal(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/90 z-50"
|
||||
onClick={() => setShowPhotoModal(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="relative max-w-5xl w-full"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="relative max-w-5xl w-full"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={() => setShowPhotoModal(false)}
|
||||
className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white z-10"
|
||||
>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={() => setShowPhotoModal(false)}
|
||||
className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white z-10"
|
||||
>
|
||||
<HiXMark className="w-6 h-6" />
|
||||
</button>
|
||||
<HiXMark className="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
{/* Navigation */}
|
||||
{selectedEvent.photos.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setSelectedPhotoIndex((prev) =>
|
||||
prev === 0 ? selectedEvent.photos.length - 1 : prev - 1
|
||||
)}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
|
||||
>
|
||||
<HiChevronLeft className="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedPhotoIndex((prev) =>
|
||||
prev === selectedEvent.photos.length - 1 ? 0 : prev + 1
|
||||
)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
|
||||
>
|
||||
<HiChevronRight className="w-6 h-6" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{/* Navigation */}
|
||||
{selectedEvent.photos.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
setSelectedPhotoIndex((prev) =>
|
||||
prev === 0 ? selectedEvent.photos.length - 1 : prev - 1,
|
||||
)
|
||||
}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
|
||||
>
|
||||
<HiChevronLeft className="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setSelectedPhotoIndex((prev) =>
|
||||
prev === selectedEvent.photos.length - 1 ? 0 : prev + 1,
|
||||
)
|
||||
}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white"
|
||||
>
|
||||
<HiChevronRight className="w-6 h-6" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Image */}
|
||||
<img
|
||||
src={selectedEvent.photos[selectedPhotoIndex]}
|
||||
alt={`${selectedEvent.title} - ${selectedPhotoIndex + 1}`}
|
||||
className="w-full h-auto max-h-[80vh] object-contain rounded-lg"
|
||||
/>
|
||||
{/* Image */}
|
||||
<img
|
||||
src={selectedEvent.photos[selectedPhotoIndex]}
|
||||
alt={`${selectedEvent.title} - ${selectedPhotoIndex + 1}`}
|
||||
className="w-full h-auto max-h-[80vh] object-contain rounded-lg"
|
||||
/>
|
||||
|
||||
{/* Counter */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 bg-black/70 text-white rounded-full text-sm">
|
||||
{selectedPhotoIndex + 1} / {selectedEvent.photos.length}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{/* Counter */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 bg-black/70 text-white rounded-full text-sm">
|
||||
{selectedPhotoIndex + 1} / {selectedEvent.photos.length}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
HiArrowTrendingDown as ArrowTrendingDownIcon,
|
||||
HiXMark,
|
||||
HiEye,
|
||||
HiPaperClip
|
||||
HiPaperClip,
|
||||
} from 'react-icons/hi2'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/tr'
|
||||
|
|
@ -25,7 +25,8 @@ import {
|
|||
mockAnniversaries,
|
||||
mockQuickLinks,
|
||||
mockTasks,
|
||||
Announcement
|
||||
mockDocuments,
|
||||
Announcement,
|
||||
} from '../../../mocks/mockIntranetData'
|
||||
|
||||
dayjs.locale('tr')
|
||||
|
|
@ -37,36 +38,49 @@ const IntranetDashboard: React.FC = () => {
|
|||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<Announcement | null>(null)
|
||||
|
||||
// Bugünün etkinlikleri
|
||||
const todayEvents = mockEvents.filter(event =>
|
||||
event.isPublished && dayjs(event.date).isSame(dayjs(), 'day')
|
||||
const todayEvents = mockEvents.filter(
|
||||
(event) => event.isPublished && dayjs(event.date).isSame(dayjs(), 'day'),
|
||||
)
|
||||
|
||||
// Yaklaşan etkinlikler (7 gün içinde)
|
||||
const upcomingEvents = mockEvents.filter(event =>
|
||||
event.isPublished &&
|
||||
dayjs(event.date).isAfter(dayjs()) &&
|
||||
dayjs(event.date).isBefore(dayjs().add(7, 'day'))
|
||||
const upcomingEvents = mockEvents.filter(
|
||||
(event) =>
|
||||
event.isPublished &&
|
||||
dayjs(event.date).isAfter(dayjs()) &&
|
||||
dayjs(event.date).isBefore(dayjs().add(7, 'day')),
|
||||
)
|
||||
|
||||
// Bu haftaki doğum günleri
|
||||
const weekBirthdays = mockBirthdays.filter(b =>
|
||||
dayjs(b.date).isBetween(dayjs().startOf('week'), dayjs().endOf('week'))
|
||||
)
|
||||
// Bugün doğanlar (sadece ay ve günü karşılaştır)
|
||||
const todayBirthdays = mockBirthdays.filter((b) => {
|
||||
const birthDate = dayjs(b.date)
|
||||
const today = dayjs()
|
||||
return birthDate.month() === today.month() && birthDate.date() === today.date()
|
||||
})
|
||||
|
||||
// Bu haftaki doğum günleri (sadece ay ve günü karşılaştır)
|
||||
const weekBirthdays = mockBirthdays.filter((b) => {
|
||||
const birthDate = dayjs(b.date)
|
||||
const today = dayjs()
|
||||
const weekStart = today.startOf('week')
|
||||
const weekEnd = today.endOf('week')
|
||||
|
||||
// Bu yılki doğum gününü oluştur
|
||||
const thisBirthday = today.year(today.year()).month(birthDate.month()).date(birthDate.date())
|
||||
return thisBirthday.isBetween(weekStart, weekEnd, null, '[]')
|
||||
})
|
||||
|
||||
// Bu ayki iş yıldönümleri
|
||||
const monthAnniversaries = mockAnniversaries.filter(a =>
|
||||
dayjs(a.hireDate).month() === dayjs().month()
|
||||
const monthAnniversaries = mockAnniversaries.filter(
|
||||
(a) => dayjs(a.hireDate).month() === dayjs().month(),
|
||||
)
|
||||
|
||||
// Öncelikli görevler
|
||||
const priorityTasks = mockTasks.filter(t =>
|
||||
t.priority === 'high' || t.priority === 'urgent'
|
||||
).slice(0, 3)
|
||||
const priorityTasks = mockTasks
|
||||
.filter((t) => t.priority === 'high' || t.priority === 'urgent')
|
||||
.slice(0, 3)
|
||||
|
||||
// Sabitlenmiş duyurular
|
||||
const pinnedAnnouncements = mockAnnouncements
|
||||
.filter(a => a.isPinned)
|
||||
.slice(0, 3)
|
||||
const pinnedAnnouncements = mockAnnouncements.filter((a) => a.isPinned).slice(0, 3)
|
||||
|
||||
const getCategoryColor = (category: 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',
|
||||
it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
|
||||
event: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
|
||||
}
|
||||
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',
|
||||
medium: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300',
|
||||
high: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-300',
|
||||
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300'
|
||||
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300',
|
||||
}
|
||||
return colors[priority]
|
||||
}
|
||||
|
|
@ -142,7 +156,9 @@ const IntranetDashboard: React.FC = () => {
|
|||
<h3 className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
{announcement.title}
|
||||
</h3>
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs rounded-full ${getCategoryColor(announcement.category)}`}
|
||||
>
|
||||
{announcement.category === 'general' && 'Genel'}
|
||||
{announcement.category === 'hr' && 'İK'}
|
||||
{announcement.category === 'it' && 'IT'}
|
||||
|
|
@ -214,7 +230,9 @@ const IntranetDashboard: React.FC = () => {
|
|||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{task.title}
|
||||
</h3>
|
||||
<span className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded ${getPriorityColor(task.priority)}`}
|
||||
>
|
||||
{task.priority === 'urgent' && '🔥 Acil'}
|
||||
{task.priority === 'high' && 'Yüksek'}
|
||||
{task.priority === 'medium' && 'Orta'}
|
||||
|
|
@ -278,6 +296,46 @@ const IntranetDashboard: React.FC = () => {
|
|||
</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 */}
|
||||
<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">
|
||||
|
|
@ -287,17 +345,70 @@ const IntranetDashboard: React.FC = () => {
|
|||
</h2>
|
||||
</div>
|
||||
<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
|
||||
key={event.id}
|
||||
className="p-3 rounded-lg border-l-4 bg-gray-50 dark:bg-gray-700/50 border-l-green-500"
|
||||
key={doc.id}
|
||||
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">
|
||||
{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 className="flex items-start gap-3">
|
||||
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
||||
<DocumentTextIcon className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{doc.name}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
{doc.category === 'policy' && '📋 Politika'}
|
||||
{doc.category === 'procedure' && '📝 Prosedür'}
|
||||
{doc.category === 'form' && '📄 Form'}
|
||||
{doc.category === 'template' && '<27> Şablon'}
|
||||
{doc.category === 'report' && '📊 Rapor'}
|
||||
{doc.category === 'other' && '📄 Diğer'}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>{dayjs(doc.uploadDate).fromNow()}</span>
|
||||
<span>•</span>
|
||||
<span>{doc.size}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -364,97 +475,103 @@ const IntranetDashboard: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Announcement Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedAnnouncement && (
|
||||
<>
|
||||
{/* Announcement Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedAnnouncement && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setSelectedAnnouncement(null)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
|
||||
<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
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full my-8"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}>
|
||||
{selectedAnnouncement.category === 'general' && '📢 Genel'}
|
||||
{selectedAnnouncement.category === 'hr' && '👥 İnsan Kaynakları'}
|
||||
{selectedAnnouncement.category === 'it' && '💻 Bilgi Teknolojileri'}
|
||||
{selectedAnnouncement.category === 'event' && '🎉 Etkinlik'}
|
||||
{selectedAnnouncement.category === 'urgent' && '🚨 Acil'}
|
||||
</span>
|
||||
{selectedAnnouncement.isPinned && (
|
||||
<span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedAnnouncement.title}
|
||||
</h2>
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span
|
||||
className={`px-3 py-1 text-xs font-medium rounded-full ${getCategoryColor(selectedAnnouncement.category)}`}
|
||||
>
|
||||
{selectedAnnouncement.category === 'general' && '📢 Genel'}
|
||||
{selectedAnnouncement.category === 'hr' && '👥 İnsan Kaynakları'}
|
||||
{selectedAnnouncement.category === 'it' && '💻 Bilgi Teknolojileri'}
|
||||
{selectedAnnouncement.category === 'event' && '🎉 Etkinlik'}
|
||||
{selectedAnnouncement.category === 'urgent' && '🚨 Acil'}
|
||||
</span>
|
||||
{selectedAnnouncement.isPinned && (
|
||||
<span className="text-yellow-500 text-sm">📌 Sabitlenmiş</span>
|
||||
)}
|
||||
</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>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedAnnouncement.title}
|
||||
</h2>
|
||||
</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 */}
|
||||
<div className="flex items-center gap-3 mt-4">
|
||||
<img
|
||||
src={selectedAnnouncement.author.avatar}
|
||||
alt={selectedAnnouncement.author.fullName}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">
|
||||
{selectedAnnouncement.author.fullName}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>{dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}</span>
|
||||
<span>•</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<HiEye className="w-4 h-4" />
|
||||
{selectedAnnouncement.viewCount} görüntülenme
|
||||
</span>
|
||||
</div>
|
||||
{/* Author Info */}
|
||||
<div className="flex items-center gap-3 mt-4">
|
||||
<img
|
||||
src={selectedAnnouncement.author.avatar}
|
||||
alt={selectedAnnouncement.author.fullName}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">
|
||||
{selectedAnnouncement.author.fullName}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>
|
||||
{dayjs(selectedAnnouncement.publishDate).format('DD MMMM YYYY, HH:mm')}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<HiEye className="w-4 h-4" />
|
||||
{selectedAnnouncement.viewCount} görüntülenme
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 max-h-[60vh] overflow-y-auto">
|
||||
{/* Image if exists */}
|
||||
{selectedAnnouncement.imageUrl && (
|
||||
<img
|
||||
src={selectedAnnouncement.imageUrl}
|
||||
alt={selectedAnnouncement.title}
|
||||
className="w-full rounded-lg mb-6"
|
||||
/>
|
||||
)}
|
||||
{/* Content */}
|
||||
<div className="p-6 max-h-[60vh] overflow-y-auto">
|
||||
{/* Image if exists */}
|
||||
{selectedAnnouncement.imageUrl && (
|
||||
<img
|
||||
src={selectedAnnouncement.imageUrl}
|
||||
alt={selectedAnnouncement.title}
|
||||
className="w-full rounded-lg mb-6"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Full Content */}
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
||||
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
||||
{selectedAnnouncement.content}
|
||||
</p>
|
||||
</div>
|
||||
{/* Full Content */}
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
||||
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
||||
{selectedAnnouncement.content}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Attachments */}
|
||||
{selectedAnnouncement.attachments && selectedAnnouncement.attachments.length > 0 && (
|
||||
{/* Attachments */}
|
||||
{selectedAnnouncement.attachments &&
|
||||
selectedAnnouncement.attachments.length > 0 && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
|
||||
<HiPaperClip className="w-5 h-5" />
|
||||
|
|
@ -487,8 +604,9 @@ const IntranetDashboard: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Departments */}
|
||||
{selectedAnnouncement.departments && selectedAnnouncement.departments.length > 0 && (
|
||||
{/* Departments */}
|
||||
{selectedAnnouncement.departments &&
|
||||
selectedAnnouncement.departments.length > 0 && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Hedef Departmanlar
|
||||
|
|
@ -506,32 +624,31 @@ const IntranetDashboard: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Expiry Date */}
|
||||
{selectedAnnouncement.expiryDate && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<span className="font-medium">Son Geçerlilik Tarihi:</span>{' '}
|
||||
{dayjs(selectedAnnouncement.expiryDate).format('DD MMMM YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Expiry Date */}
|
||||
{selectedAnnouncement.expiryDate && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<span className="font-medium">Son Geçerlilik Tarihi:</span>{' '}
|
||||
{dayjs(selectedAnnouncement.expiryDate).format('DD MMMM YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
|
||||
<button
|
||||
onClick={() => setSelectedAnnouncement(null)}
|
||||
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Kapat
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{/* Footer */}
|
||||
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
|
||||
<button
|
||||
onClick={() => setSelectedAnnouncement(null)}
|
||||
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Kapat
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import {
|
|||
HiBuildingOffice2,
|
||||
HiClipboardDocumentCheck,
|
||||
HiUserPlus,
|
||||
HiBars3,
|
||||
HiXMark,
|
||||
HiChevronLeft,
|
||||
} from 'react-icons/hi2'
|
||||
import {
|
||||
mockTasks,
|
||||
|
|
@ -127,6 +130,8 @@ interface IntranetSidebarProps {
|
|||
|
||||
const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigate }) => {
|
||||
const [expandedMenus, setExpandedMenus] = useState<string[]>(['hr'])
|
||||
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||
const [isMobileOpen, setIsMobileOpen] = useState(false)
|
||||
|
||||
// Dinamik badge sayılarını hesapla
|
||||
const badgeCounts = useMemo(() => {
|
||||
|
|
@ -180,6 +185,7 @@ const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigat
|
|||
toggleMenu(item.id)
|
||||
} else if (item.path) {
|
||||
onNavigate(item.path)
|
||||
setIsMobileOpen(false)
|
||||
}
|
||||
}}
|
||||
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'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
} ${level > 0 ? 'ml-6' : ''}`}
|
||||
title={isCollapsed ? item.label : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<item.icon className="w-5 h-5" />
|
||||
<span className="font-medium text-sm">{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 className="flex items-center gap-3 min-w-0">
|
||||
<item.icon className="w-5 h-5 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="font-medium text-sm truncate">{item.label}</span>}
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{item.badge && item.badge > 0 && (
|
||||
<span className="px-2 py-0.5 bg-red-500 text-white text-xs rounded-full">
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
{hasChildren && (
|
||||
<motion.div
|
||||
animate={{ rotate: isExpanded ? 90 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<HiChevronRight className="w-4 h-4" />
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{hasChildren && (
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
{isExpanded && !isCollapsed && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
|
|
@ -228,13 +240,74 @@ const IntranetSidebar: React.FC<IntranetSidebarProps> = ({ activePath, onNavigat
|
|||
}
|
||||
|
||||
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">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white">İntranet Portal</h2>
|
||||
</div>
|
||||
<>
|
||||
{/* Mobile Toggle Button */}
|
||||
<button
|
||||
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>
|
||||
</div>
|
||||
{/* Mobile Overlay */}
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import React, { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import {
|
||||
HiKey,
|
||||
HiCalendar,
|
||||
HiTruck,
|
||||
HiCog,
|
||||
HiPlus,
|
||||
HiXMark
|
||||
} from 'react-icons/hi2'
|
||||
import { HiKey, HiCalendar, HiTruck, HiCog, HiPlus, HiXMark } from 'react-icons/hi2'
|
||||
import dayjs from 'dayjs'
|
||||
import { mockReservations, Reservation } from '../../../mocks/mockIntranetData'
|
||||
|
||||
|
|
@ -15,9 +8,10 @@ const ReservationsModule: React.FC = () => {
|
|||
const [selectedType, setSelectedType] = useState<'all' | 'room' | 'vehicle' | 'equipment'>('all')
|
||||
const [showNewReservation, setShowNewReservation] = useState(false)
|
||||
|
||||
const filteredReservations = selectedType === 'all'
|
||||
? mockReservations
|
||||
: mockReservations.filter(r => r.type === selectedType)
|
||||
const filteredReservations =
|
||||
selectedType === 'all'
|
||||
? mockReservations
|
||||
: mockReservations.filter((r) => r.type === selectedType)
|
||||
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
|
|
@ -36,7 +30,7 @@ const ReservationsModule: React.FC = () => {
|
|||
const labels: Record<string, string> = {
|
||||
room: 'Toplantı Salonu',
|
||||
vehicle: 'Araç',
|
||||
equipment: 'Ekipman'
|
||||
equipment: 'Ekipman',
|
||||
}
|
||||
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',
|
||||
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
|
||||
completed: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
|
||||
completed: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
|
||||
}
|
||||
return colors[status] || colors.pending
|
||||
}
|
||||
|
|
@ -56,7 +50,7 @@ const ReservationsModule: React.FC = () => {
|
|||
pending: 'Bekliyor',
|
||||
approved: 'Onaylandı',
|
||||
rejected: 'Reddedildi',
|
||||
completed: 'Tamamlandı'
|
||||
completed: 'Tamamlandı',
|
||||
}
|
||||
return labels[status] || status
|
||||
}
|
||||
|
|
@ -67,9 +61,7 @@ const ReservationsModule: React.FC = () => {
|
|||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
🔑 Rezervasyonlar
|
||||
</h1>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">🔑 Rezervasyonlar</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Oda, araç ve ekipman rezervasyonları
|
||||
</p>
|
||||
|
|
@ -89,7 +81,7 @@ const ReservationsModule: React.FC = () => {
|
|||
{ value: 'all' as const, label: 'Tümü', icon: HiCalendar },
|
||||
{ value: 'room' as const, label: 'Toplantı Salonu', icon: HiKey },
|
||||
{ value: 'vehicle' as const, label: 'Araç', icon: HiTruck },
|
||||
{ value: 'equipment' as const, label: 'Ekipman', icon: HiCog }
|
||||
{ value: 'equipment' as const, label: 'Ekipman', icon: HiCog },
|
||||
].map((type) => (
|
||||
<button
|
||||
key={type.value}
|
||||
|
|
@ -131,7 +123,9 @@ const ReservationsModule: React.FC = () => {
|
|||
{getTypeLabel(reservation.type)}
|
||||
</p>
|
||||
</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)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -197,121 +191,121 @@ const ReservationsModule: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Reservation Modal */}
|
||||
<AnimatePresence>
|
||||
{showNewReservation && (
|
||||
<>
|
||||
{/* New Reservation Modal */}
|
||||
<AnimatePresence>
|
||||
{showNewReservation && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowNewReservation(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowNewReservation(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full"
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Yeni Rezervasyon Oluştur
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowNewReservation(false)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full"
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Yeni Rezervasyon Oluştur
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowNewReservation(false)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Rezervasyon Tipi
|
||||
</label>
|
||||
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option>Toplantı Salonu</option>
|
||||
<option>Araç</option>
|
||||
<option>Ekipman</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div 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>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Rezervasyon Tipi
|
||||
</label>
|
||||
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option>Toplantı Salonu</option>
|
||||
<option>Araç</option>
|
||||
<option>Ekipman</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Kaynak Seçin
|
||||
</label>
|
||||
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option>Toplantı Salonu A</option>
|
||||
<option>Toplantı Salonu B</option>
|
||||
<option>Eğitim Salonu</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Başlangıç Tarihi
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Bitiş Tarihi
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Amaç
|
||||
Başlangıç Tarihi
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rezervasyon amacını yazın"
|
||||
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">
|
||||
Notlar (Opsiyonel)
|
||||
Bitiş Tarihi
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Ek notlarınızı yazın"
|
||||
<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 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>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Amaç
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Rezervasyon amacını yazın"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Notlar (Opsiyonel)
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Ek notlarınızı yazın"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
onClick={() => setShowNewReservation(false)}
|
||||
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
İptal
|
||||
</button>
|
||||
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
|
||||
Rezervasyon Oluştur
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,12 +162,6 @@ const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
|
|||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
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?"
|
||||
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',
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ const SurveysModule: React.FC = () => {
|
|||
const [showSurveyModal, setShowSurveyModal] = useState(false)
|
||||
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
|
||||
|
||||
const filteredSurveys = selectedStatus === 'all'
|
||||
? mockSurveys
|
||||
: mockSurveys.filter(s => s.status === selectedStatus)
|
||||
const filteredSurveys =
|
||||
selectedStatus === 'all' ? mockSurveys : mockSurveys.filter((s) => s.status === selectedStatus)
|
||||
|
||||
const handleTakeSurvey = (survey: Survey) => {
|
||||
setSelectedSurvey(survey)
|
||||
|
|
@ -29,7 +28,7 @@ const SurveysModule: React.FC = () => {
|
|||
const colors: Record<string, string> = {
|
||||
draft: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
|
||||
active: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
closed: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||
closed: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
|
||||
}
|
||||
return colors[status] || colors.draft
|
||||
}
|
||||
|
|
@ -53,7 +52,7 @@ const SurveysModule: React.FC = () => {
|
|||
{ value: 'all' as const, label: 'Tümü' },
|
||||
{ value: 'active' as const, label: 'Aktif' },
|
||||
{ value: 'draft' as const, label: 'Taslak' },
|
||||
{ value: 'closed' as const, label: 'Kapalı' }
|
||||
{ value: 'closed' as const, label: 'Kapalı' },
|
||||
].map((tab) => (
|
||||
<button
|
||||
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">
|
||||
{survey.title}
|
||||
</h3>
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(survey.status)}`}>
|
||||
{survey.status === 'active' ? 'Aktif' : survey.status === 'draft' ? 'Taslak' : 'Kapalı'}
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap ${getStatusColor(survey.status)}`}
|
||||
>
|
||||
{survey.status === 'active'
|
||||
? 'Aktif'
|
||||
: survey.status === 'draft'
|
||||
? 'Taslak'
|
||||
: 'Kapalı'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{survey.description}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{survey.description}</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Soru Sayısı</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">{survey.totalQuestions} soru</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{survey.totalQuestions} soru
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Yanıt</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">{survey.responses} kişi</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{survey.responses} kişi
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">Son Tarih</p>
|
||||
|
|
@ -154,133 +161,136 @@ const SurveysModule: React.FC = () => {
|
|||
<p>Anket bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Survey Modal */}
|
||||
<AnimatePresence>
|
||||
{showSurveyModal && selectedSurvey && (
|
||||
<>
|
||||
</div>
|
||||
{/* Survey Modal */}
|
||||
<AnimatePresence>
|
||||
{showSurveyModal && selectedSurvey && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowSurveyModal(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowSurveyModal(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between sticky top-0 bg-white dark:bg-gray-800 z-10">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{selectedSurvey.title}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{selectedSurvey.description}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowSurveyModal(false)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
handleSubmitSurvey()
|
||||
}}
|
||||
className="p-6 space-y-6"
|
||||
>
|
||||
<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>
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{selectedSurvey.title}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{selectedSurvey.description}
|
||||
</p>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
1. Genel memnuniyet düzeyiniz nedir? *
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{[1, 2, 3, 4, 5].map((rating) => (
|
||||
<label
|
||||
key={rating}
|
||||
className="flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
<input type="radio" name="rating" value={rating} required />
|
||||
<span className="text-sm text-gray-900 dark:text-white">{rating}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
handleSubmitSurvey()
|
||||
}}
|
||||
className="p-6 space-y-6"
|
||||
>
|
||||
{/* Örnek Anket Soruları */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
1. Genel memnuniyet düzeyiniz nedir? *
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{[1, 2, 3, 4, 5].map((rating) => (
|
||||
<label key={rating} className="flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<input type="radio" name="rating" value={rating} required />
|
||||
<span className="text-sm text-gray-900 dark:text-white">{rating}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
2. Hangi departmanda çalışıyorsunuz? *
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Seçiniz</option>
|
||||
<option value="it">Bilgi Teknolojileri</option>
|
||||
<option value="hr">İnsan Kaynakları</option>
|
||||
<option value="finance">Finans</option>
|
||||
<option value="sales">Satış</option>
|
||||
<option value="marketing">Pazarlama</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
3. Görüş ve önerileriniz
|
||||
</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
placeholder="Yorumlarınızı buraya yazabilirsiniz..."
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!selectedSurvey.isAnonymous && (
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
|
||||
<p className="text-sm text-blue-700 dark:text-blue-300">
|
||||
ℹ️ Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız kaydedilecektir.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedSurvey.isAnonymous && (
|
||||
<div className="bg-green-50 dark:bg-green-900/20 rounded-lg p-3">
|
||||
<p className="text-sm text-green-700 dark:text-green-300">
|
||||
✅ Bu anket anonimdir. Kimlik bilgileriniz kaydedilmeyecektir.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSurveyModal(false)}
|
||||
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
İptal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Anketi Gönder
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
useSensor,
|
||||
useSensors,
|
||||
closestCorners,
|
||||
useDroppable
|
||||
useDroppable,
|
||||
} from '@dnd-kit/core'
|
||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
|
|
@ -20,7 +20,7 @@ import {
|
|||
HiClock,
|
||||
HiChatBubbleLeftRight,
|
||||
HiPaperClip,
|
||||
HiTrash
|
||||
HiTrash,
|
||||
} from 'react-icons/hi2'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/tr'
|
||||
|
|
@ -40,11 +40,7 @@ interface DroppableColumnProps {
|
|||
const DroppableColumn: React.FC<DroppableColumnProps> = ({ id, children }) => {
|
||||
const { setNodeRef } = useDroppable({ id })
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} >
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
return <div ref={setNodeRef}>{children}</div>
|
||||
}
|
||||
|
||||
// Sortable Task Card Component
|
||||
|
|
@ -61,21 +57,16 @@ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
|
|||
onTaskClick,
|
||||
getPriorityColor,
|
||||
getPriorityLabel,
|
||||
isOverdue
|
||||
isOverdue,
|
||||
}) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging
|
||||
} = useSortable({ id: task.id })
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: task.id,
|
||||
})
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
}
|
||||
|
||||
const overdue = isOverdue(task.dueDate) && task.status !== 'done'
|
||||
|
|
@ -87,9 +78,7 @@ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
|
|||
{...attributes}
|
||||
{...listeners}
|
||||
className={`bg-white dark:bg-gray-800 rounded-lg p-3 sm:p-4 border-2 cursor-move hover:shadow-lg transition-all ${
|
||||
overdue
|
||||
? 'border-red-300 dark:border-red-700'
|
||||
: 'border-gray-200 dark:border-gray-700'
|
||||
overdue ? 'border-red-300 dark:border-red-700' : 'border-gray-200 dark:border-gray-700'
|
||||
} ${isDragging ? 'shadow-2xl ring-4 ring-blue-500/50' : ''}`}
|
||||
onClick={(e) => {
|
||||
if (!(e.target as HTMLElement).closest('[data-no-click]')) {
|
||||
|
|
@ -98,19 +87,17 @@ const SortableTaskCard: React.FC<SortableTaskCardProps> = ({
|
|||
}}
|
||||
>
|
||||
<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)}
|
||||
</span>
|
||||
{overdue && (
|
||||
<span className="text-xs text-red-600 dark:text-red-400 font-medium">
|
||||
⚠️ Gecikmiş
|
||||
</span>
|
||||
<span className="text-xs text-red-600 dark:text-red-400 font-medium">⚠️ Gecikmiş</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
|
||||
{task.title}
|
||||
</h4>
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">{task.title}</h4>
|
||||
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
|
||||
{task.description}
|
||||
|
|
@ -180,6 +167,7 @@ const TasksModule: React.FC = () => {
|
|||
const [tasks, setTasks] = useState<Task[]>(mockTasks)
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null)
|
||||
const [activeId, setActiveId] = useState<string | null>(null)
|
||||
const [dragOverColumn, setDragOverColumn] = useState<TaskStatus | null>(null)
|
||||
const [showNewTaskModal, setShowNewTaskModal] = useState(false)
|
||||
const [newTaskColumn, setNewTaskColumn] = useState<TaskStatus>('todo')
|
||||
|
||||
|
|
@ -188,23 +176,45 @@ const TasksModule: React.FC = () => {
|
|||
activationConstraint: {
|
||||
distance: 8,
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const columns: { id: TaskStatus; title: string; icon: string; color: string }[] = [
|
||||
{ id: 'todo', title: 'Yapılacak', icon: '📋', color: 'gray' },
|
||||
{ id: 'in-progress', title: 'Devam Ediyor', icon: '⚙️', color: 'blue' },
|
||||
{ id: 'review', title: 'İncelemede', icon: '👀', color: 'yellow' },
|
||||
{ id: 'done', title: 'Tamamlandı', icon: '✅', color: 'green' }
|
||||
{ id: 'done', title: 'Tamamlandı', icon: '✅', color: 'green' },
|
||||
]
|
||||
|
||||
const handleDragStart = (event: DragStartEvent) => {
|
||||
setActiveId(event.active.id as string)
|
||||
}
|
||||
|
||||
const handleDragOver = (event: any) => {
|
||||
const { over } = event
|
||||
|
||||
if (!over) {
|
||||
setDragOverColumn(null)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if over a column or a task
|
||||
const overColumn = columns.find((col) => col.id === over.id)
|
||||
if (overColumn) {
|
||||
setDragOverColumn(overColumn.id)
|
||||
} else {
|
||||
// over.id is a task, find its column
|
||||
const overTask = tasks.find((t) => t.id === over.id)
|
||||
if (overTask) {
|
||||
setDragOverColumn(overTask.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
setActiveId(null)
|
||||
setDragOverColumn(null)
|
||||
|
||||
if (!over) return
|
||||
|
||||
|
|
@ -215,37 +225,33 @@ const TasksModule: React.FC = () => {
|
|||
// If it's a task id, find that task's column
|
||||
let newStatus: TaskStatus
|
||||
|
||||
const overColumn = columns.find(col => col.id === over.id)
|
||||
const overColumn = columns.find((col) => col.id === over.id)
|
||||
if (overColumn) {
|
||||
newStatus = overColumn.id
|
||||
} else {
|
||||
// over.id is a task, find its column
|
||||
const overTask = tasks.find(t => t.id === over.id)
|
||||
const overTask = tasks.find((t) => t.id === over.id)
|
||||
if (!overTask) return
|
||||
newStatus = overTask.status
|
||||
}
|
||||
|
||||
// Update task status
|
||||
setTasks(prevTasks =>
|
||||
prevTasks.map(task =>
|
||||
task.id === taskId ? { ...task, status: newStatus } : task
|
||||
)
|
||||
setTasks((prevTasks) =>
|
||||
prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
|
||||
)
|
||||
|
||||
if (selectedTask?.id === taskId) {
|
||||
setSelectedTask(prev => prev ? { ...prev, status: newStatus } : null)
|
||||
setSelectedTask((prev) => (prev ? { ...prev, status: newStatus } : null))
|
||||
}
|
||||
}
|
||||
|
||||
const handleStatusChange = (taskId: string, newStatus: TaskStatus) => {
|
||||
setTasks(prevTasks =>
|
||||
prevTasks.map(task =>
|
||||
task.id === taskId ? { ...task, status: newStatus } : task
|
||||
)
|
||||
setTasks((prevTasks) =>
|
||||
prevTasks.map((task) => (task.id === taskId ? { ...task, status: newStatus } : task)),
|
||||
)
|
||||
|
||||
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
|
||||
createdAt: new Date(),
|
||||
labels: [],
|
||||
comments: 0
|
||||
comments: 0,
|
||||
}
|
||||
|
||||
setTasks(prev => [...prev, newTask])
|
||||
setTasks((prev) => [...prev, newTask])
|
||||
setShowNewTaskModal(false)
|
||||
}
|
||||
|
||||
const handleDeleteTask = (taskId: string) => {
|
||||
if (window.confirm('Bu görevi silmek istediğinizden emin misiniz?')) {
|
||||
setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId))
|
||||
setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId))
|
||||
setSelectedTask(null)
|
||||
}
|
||||
}
|
||||
|
|
@ -288,9 +294,11 @@ const TasksModule: React.FC = () => {
|
|||
const getPriorityColor = (priority: string) => {
|
||||
const colors = {
|
||||
low: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600',
|
||||
medium: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300 border-blue-200 dark:border-blue-700',
|
||||
medium:
|
||||
'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-300 border-blue-200 dark:border-blue-700',
|
||||
high: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-300 border-orange-200 dark:border-orange-700',
|
||||
urgent: 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300 border-red-200 dark:border-red-700'
|
||||
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]
|
||||
}
|
||||
|
|
@ -300,7 +308,7 @@ const TasksModule: React.FC = () => {
|
|||
low: 'Düşük',
|
||||
medium: 'Orta',
|
||||
high: 'Yüksek',
|
||||
urgent: '🔥 Acil'
|
||||
urgent: '🔥 Acil',
|
||||
}
|
||||
return labels[priority as keyof typeof labels]
|
||||
}
|
||||
|
|
@ -314,6 +322,7 @@ const TasksModule: React.FC = () => {
|
|||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-3 sm:p-4 md:p-6">
|
||||
|
|
@ -343,79 +352,83 @@ const TasksModule: React.FC = () => {
|
|||
|
||||
{/* Kanban Board */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-4 overflow-x-auto pb-4">
|
||||
|
||||
<div className="kanban-container sm:contents">
|
||||
{columns.map(column => {
|
||||
const columnTasks = getTasksByStatus(column.id)
|
||||
return (
|
||||
<DroppableColumn key={column.id} id={column.id}>
|
||||
<SortableContext
|
||||
id={column.id}
|
||||
items={columnTasks.map(t => t.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div
|
||||
className="kanban-column flex flex-col gap-2 sm:gap-3 bg-gray-100 dark:bg-gray-800/50 rounded-lg p-3 sm:p-4 min-h-[400px] sm:min-h-[500px] lg:min-h-[600px]"
|
||||
data-status={column.id}
|
||||
{columns.map((column) => {
|
||||
const columnTasks = getTasksByStatus(column.id)
|
||||
return (
|
||||
<DroppableColumn key={column.id} id={column.id}>
|
||||
<SortableContext
|
||||
id={column.id}
|
||||
items={columnTasks.map((t) => t.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{/* Column Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg sm:text-xl">{column.icon}</span>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white text-sm sm:text-base">
|
||||
{column.title}
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
className="kanban-column flex flex-col gap-2 sm:gap-3 bg-gray-100 dark:bg-gray-800/50 rounded-lg p-3 sm:p-4 min-h-[400px] sm:min-h-[500px] lg:min-h-[600px]"
|
||||
data-status={column.id}
|
||||
>
|
||||
{/* Column Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg sm:text-xl">{column.icon}</span>
|
||||
<h3
|
||||
className={`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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tasks */}
|
||||
<div className="space-y-3 flex-1">
|
||||
{columnTasks.map(task => (
|
||||
<SortableTaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onTaskClick={setSelectedTask}
|
||||
getPriorityColor={getPriorityColor}
|
||||
getPriorityLabel={getPriorityLabel}
|
||||
isOverdue={isOverdue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Tasks */}
|
||||
<div className="space-y-3 flex-1">
|
||||
{columnTasks.map((task) => (
|
||||
<SortableTaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onTaskClick={setSelectedTask}
|
||||
getPriorityColor={getPriorityColor}
|
||||
getPriorityLabel={getPriorityLabel}
|
||||
isOverdue={isOverdue}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Task Button */}
|
||||
<button
|
||||
onClick={() => handleAddTask(column.id)}
|
||||
className="w-full p-3 sm:p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-blue-500 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-colors text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
<HiPlus className="w-4 h-4 sm:w-5 sm:h-5 mx-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DroppableColumn>
|
||||
)
|
||||
})}
|
||||
{/* Add Task Button */}
|
||||
<button
|
||||
onClick={() => handleAddTask(column.id)}
|
||||
className="w-full p-3 sm:p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg hover:border-blue-500 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/10 transition-colors text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
<HiPlus className="w-4 h-4 sm:w-5 sm:h-5 mx-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DroppableColumn>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DragOverlay */}
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
<div className="opacity-50">
|
||||
{(() => {
|
||||
const task = tasks.find(t => t.id === activeId)
|
||||
if (!task) return null
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 border-2 border-blue-500 shadow-2xl">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white">
|
||||
{task.title}
|
||||
</h4>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
{/* DragOverlay */}
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
<div className="opacity-50">
|
||||
{(() => {
|
||||
const task = tasks.find((t) => t.id === activeId)
|
||||
if (!task) return null
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 border-2 border-blue-500 shadow-2xl">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white">{task.title}</h4>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</div>
|
||||
|
||||
{/* New Task Modal */}
|
||||
<AnimatePresence>
|
||||
|
|
@ -488,7 +501,9 @@ const TasksModule: React.FC = () => {
|
|||
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
|
||||
<p className="text-xs sm:text-sm text-blue-700 dark:text-blue-300">
|
||||
📋 Görev <strong>{columns.find(c => c.id === newTaskColumn)?.title}</strong> kolonuna eklenecek
|
||||
📋 Görev{' '}
|
||||
<strong>{columns.find((c) => c.id === newTaskColumn)?.title}</strong>{' '}
|
||||
kolonuna eklenecek
|
||||
</p>
|
||||
</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="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)}
|
||||
</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">
|
||||
|
|
@ -565,7 +582,9 @@ const TasksModule: React.FC = () => {
|
|||
</p>
|
||||
<select
|
||||
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"
|
||||
>
|
||||
<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">
|
||||
<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>
|
||||
<button
|
||||
onClick={() => handleDeleteTask(selectedTask.id)}
|
||||
|
|
@ -646,7 +666,6 @@ const TasksModule: React.FC = () => {
|
|||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</DndContext>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,31 +7,37 @@ import {
|
|||
HiMapPin,
|
||||
HiXMark,
|
||||
HiCheckBadge,
|
||||
HiCalendar
|
||||
HiCalendar,
|
||||
} from 'react-icons/hi2'
|
||||
import dayjs from 'dayjs'
|
||||
import { mockTrainings, mockCertificates, Training } from '../../../mocks/mockIntranetData'
|
||||
|
||||
const TrainingModule: React.FC = () => {
|
||||
const [selectedTraining, setSelectedTraining] = useState<Training | null>(null)
|
||||
const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>('all')
|
||||
const [selectedTab, setSelectedTab] = useState<'all' | 'upcoming' | 'ongoing' | 'completed'>(
|
||||
'all',
|
||||
)
|
||||
const [trainings, setTrainings] = useState<Training[]>(mockTrainings)
|
||||
const [showEnrollSuccess, setShowEnrollSuccess] = useState(false)
|
||||
|
||||
const filteredTrainings = selectedTab === 'all'
|
||||
? trainings
|
||||
: trainings.filter(t => t.status === selectedTab)
|
||||
const filteredTrainings =
|
||||
selectedTab === 'all' ? trainings : trainings.filter((t) => t.status === selectedTab)
|
||||
|
||||
const handleEnroll = (trainingId: string) => {
|
||||
setTrainings(prev => prev.map(t =>
|
||||
t.id === trainingId && t.enrolled < t.maxParticipants
|
||||
? { ...t, enrolled: t.enrolled + 1 }
|
||||
: t
|
||||
))
|
||||
setTrainings((prev) =>
|
||||
prev.map((t) =>
|
||||
t.id === trainingId && t.enrolled < t.maxParticipants
|
||||
? { ...t, enrolled: t.enrolled + 1 }
|
||||
: t,
|
||||
),
|
||||
)
|
||||
|
||||
// Seçili eğitimi de güncelle
|
||||
if (selectedTraining?.id === trainingId && selectedTraining.enrolled < selectedTraining.maxParticipants) {
|
||||
setSelectedTraining(prev => prev ? { ...prev, enrolled: prev.enrolled + 1 } : null)
|
||||
if (
|
||||
selectedTraining?.id === trainingId &&
|
||||
selectedTraining.enrolled < selectedTraining.maxParticipants
|
||||
) {
|
||||
setSelectedTraining((prev) => (prev ? { ...prev, enrolled: prev.enrolled + 1 } : null))
|
||||
}
|
||||
|
||||
setShowEnrollSuccess(true)
|
||||
|
|
@ -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',
|
||||
management: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
compliance: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
|
||||
other: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
|
||||
other: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
|
||||
}
|
||||
return colors[category] || colors.other
|
||||
}
|
||||
|
|
@ -52,8 +58,11 @@ const TrainingModule: React.FC = () => {
|
|||
const getStatusBadge = (status: string) => {
|
||||
const badges: Record<string, { bg: string; text: string }> = {
|
||||
upcoming: { bg: 'bg-blue-100 dark:bg-blue-900/30', text: 'text-blue-700 dark:text-blue-300' },
|
||||
ongoing: { bg: 'bg-green-100 dark:bg-green-900/30', text: 'text-green-700 dark:text-green-300' },
|
||||
completed: { bg: 'bg-gray-100 dark:bg-gray-700', text: 'text-gray-700 dark:text-gray-300' }
|
||||
ongoing: {
|
||||
bg: 'bg-green-100 dark:bg-green-900/30',
|
||||
text: 'text-green-700 dark:text-green-300',
|
||||
},
|
||||
completed: { bg: 'bg-gray-100 dark:bg-gray-700', text: 'text-gray-700 dark:text-gray-300' },
|
||||
}
|
||||
return badges[status] || badges.upcoming
|
||||
}
|
||||
|
|
@ -77,9 +86,21 @@ const TrainingModule: React.FC = () => {
|
|||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
{[
|
||||
{ label: 'Tümü', value: mockTrainings.length, tab: 'all' as const },
|
||||
{ label: 'Yaklaşan', value: mockTrainings.filter(t => t.status === 'upcoming').length, tab: 'upcoming' as const },
|
||||
{ label: 'Devam Eden', value: mockTrainings.filter(t => t.status === 'ongoing').length, tab: 'ongoing' as const },
|
||||
{ label: 'Tamamlanan', value: mockTrainings.filter(t => t.status === 'completed').length, tab: 'completed' as const }
|
||||
{
|
||||
label: 'Yaklaşan',
|
||||
value: mockTrainings.filter((t) => t.status === 'upcoming').length,
|
||||
tab: 'upcoming' as const,
|
||||
},
|
||||
{
|
||||
label: 'Devam Eden',
|
||||
value: mockTrainings.filter((t) => t.status === 'ongoing').length,
|
||||
tab: 'ongoing' as const,
|
||||
},
|
||||
{
|
||||
label: 'Tamamlanan',
|
||||
value: mockTrainings.filter((t) => t.status === 'completed').length,
|
||||
tab: 'completed' as const,
|
||||
},
|
||||
].map((stat, idx) => (
|
||||
<motion.button
|
||||
key={idx}
|
||||
|
|
@ -122,11 +143,19 @@ const TrainingModule: React.FC = () => {
|
|||
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}>
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(training.category)}`}
|
||||
>
|
||||
{training.category}
|
||||
</span>
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`}>
|
||||
{training.status === 'upcoming' ? 'Yaklaşan' : training.status === 'ongoing' ? 'Devam Ediyor' : 'Tamamlandı'}
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`}
|
||||
>
|
||||
{training.status === 'upcoming'
|
||||
? 'Yaklaşan'
|
||||
: training.status === 'ongoing'
|
||||
? 'Devam Ediyor'
|
||||
: 'Tamamlandı'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -153,18 +182,21 @@ const TrainingModule: React.FC = () => {
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
|
||||
{training.enrolled < training.maxParticipants && training.status === 'upcoming' && (
|
||||
<button
|
||||
onClick={() => handleEnroll(training.id)}
|
||||
className="mt-4 w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Kayıt Ol
|
||||
</button>
|
||||
)}
|
||||
{training.enrolled < training.maxParticipants &&
|
||||
training.status === 'upcoming' && (
|
||||
<button
|
||||
onClick={() => handleEnroll(training.id)}
|
||||
className="mt-4 w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Kayıt Ol
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
|
|
@ -217,104 +249,118 @@ const TrainingModule: React.FC = () => {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Training Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedTraining && (
|
||||
<>
|
||||
</div>
|
||||
{/* Training Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedTraining && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setSelectedTraining(null)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setSelectedTraining(null)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 flex items-center justify-between z-10">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Eğitim Detayları
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setSelectedTraining(null)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-6 flex items-center justify-between z-10">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Eğitim Detayları
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setSelectedTraining(null)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{selectedTraining.thumbnail && (
|
||||
<img
|
||||
src={selectedTraining.thumbnail}
|
||||
alt={selectedTraining.title}
|
||||
className="w-full h-64 object-cover rounded-lg"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
{selectedTraining.title}
|
||||
</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">
|
||||
{selectedTraining.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
{selectedTraining.thumbnail && (
|
||||
<img
|
||||
src={selectedTraining.thumbnail}
|
||||
alt={selectedTraining.title}
|
||||
className="w-full h-64 object-cover rounded-lg"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<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 className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">
|
||||
{selectedTraining.instructor}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Kategori</p>
|
||||
<span
|
||||
className={`inline-block px-3 py-1 rounded-full text-sm ${getCategoryColor(selectedTraining.category)}`}
|
||||
>
|
||||
{selectedTraining.category}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tarih</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">
|
||||
{dayjs(selectedTraining.startDate).format('DD MMM')} -{' '}
|
||||
{dayjs(selectedTraining.endDate).format('DD MMM YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Süre</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">
|
||||
{selectedTraining.duration} saat
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tip</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white capitalize">
|
||||
{selectedTraining.type}
|
||||
</p>
|
||||
</div>
|
||||
{selectedTraining.location && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Eğitmen</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.instructor}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Kategori</p>
|
||||
<span className={`inline-block px-3 py-1 rounded-full text-sm ${getCategoryColor(selectedTraining.category)}`}>
|
||||
{selectedTraining.category}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tarih</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Konum</p>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Süre</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.duration} saat</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Tip</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white capitalize">{selectedTraining.type}</p>
|
||||
</div>
|
||||
{selectedTraining.location && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Konum</p>
|
||||
<p className="font-medium text-gray-900 dark:text-white">{selectedTraining.location}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Doluluk Oranı</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3">
|
||||
<div
|
||||
className="bg-blue-600 h-3 rounded-full transition-all"
|
||||
style={{ width: `${(selectedTraining.enrolled / selectedTraining.maxParticipants) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{selectedTraining.enrolled} / {selectedTraining.maxParticipants}
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">Doluluk Oranı</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3">
|
||||
<div
|
||||
className="bg-blue-600 h-3 rounded-full transition-all"
|
||||
style={{
|
||||
width: `${(selectedTraining.enrolled / selectedTraining.maxParticipants) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{selectedTraining.enrolled} / {selectedTraining.maxParticipants}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedTraining.enrolled < selectedTraining.maxParticipants && selectedTraining.status === 'upcoming' && (
|
||||
{selectedTraining.enrolled < selectedTraining.maxParticipants &&
|
||||
selectedTraining.status === 'upcoming' && (
|
||||
<button
|
||||
onClick={() => handleEnroll(selectedTraining.id)}
|
||||
className="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
|
||||
|
|
@ -322,31 +368,30 @@ const TrainingModule: React.FC = () => {
|
|||
Eğitime Kayıt Ol
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Success Notification */}
|
||||
<AnimatePresence>
|
||||
{showEnrollSuccess && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 50 }}
|
||||
className="fixed bottom-6 right-6 bg-green-600 text-white px-6 py-4 rounded-lg shadow-xl flex items-center gap-3 z-50"
|
||||
>
|
||||
<HiCheckBadge className="w-6 h-6" />
|
||||
<div>
|
||||
<p className="font-semibold">Kayıt Başarılı!</p>
|
||||
<p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{/* Success Notification */}
|
||||
<AnimatePresence>
|
||||
{showEnrollSuccess && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 50 }}
|
||||
className="fixed bottom-6 right-6 bg-green-600 text-white px-6 py-4 rounded-lg shadow-xl flex items-center gap-3 z-50"
|
||||
>
|
||||
<HiCheckBadge className="w-6 h-6" />
|
||||
<div>
|
||||
<p className="font-semibold">Kayıt Başarılı!</p>
|
||||
<p className="text-sm text-green-100">Eğitime kayıt oldunuz.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,36 +5,33 @@ import dayjs from 'dayjs'
|
|||
import { mockVisitors, Visitor } from '../../../mocks/mockIntranetData'
|
||||
|
||||
const VisitorsModule: React.FC = () => {
|
||||
const [selectedStatus, setSelectedStatus] = useState<'all' | 'scheduled' | 'checked-in' | 'checked-out' | 'cancelled'>('all')
|
||||
const [selectedStatus, setSelectedStatus] = useState<
|
||||
'all' | 'scheduled' | 'checked-in' | 'checked-out' | 'cancelled'
|
||||
>('all')
|
||||
const [showNewVisitor, setShowNewVisitor] = useState(false)
|
||||
const [visitors, setVisitors] = useState<Visitor[]>(mockVisitors)
|
||||
|
||||
const filteredVisitors = selectedStatus === 'all'
|
||||
? visitors
|
||||
: visitors.filter(v => v.status === selectedStatus)
|
||||
const filteredVisitors =
|
||||
selectedStatus === 'all' ? visitors : visitors.filter((v) => v.status === selectedStatus)
|
||||
|
||||
const handleCheckIn = (visitorId: string) => {
|
||||
setVisitors(prev => prev.map(v =>
|
||||
v.id === visitorId
|
||||
? { ...v, status: 'checked-in', checkIn: new Date() }
|
||||
: v
|
||||
))
|
||||
setVisitors((prev) =>
|
||||
prev.map((v) =>
|
||||
v.id === visitorId ? { ...v, status: 'checked-in', checkIn: new Date() } : v,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const handleCheckOut = (visitorId: string) => {
|
||||
setVisitors(prev => prev.map(v =>
|
||||
v.id === visitorId
|
||||
? { ...v, status: 'checked-out', checkOut: new Date() }
|
||||
: v
|
||||
))
|
||||
setVisitors((prev) =>
|
||||
prev.map((v) =>
|
||||
v.id === visitorId ? { ...v, status: 'checked-out', checkOut: new Date() } : v,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const handleCancel = (visitorId: string) => {
|
||||
setVisitors(prev => prev.map(v =>
|
||||
v.id === visitorId
|
||||
? { ...v, status: 'cancelled' }
|
||||
: v
|
||||
))
|
||||
setVisitors((prev) => prev.map((v) => (v.id === visitorId ? { ...v, status: 'cancelled' } : v)))
|
||||
}
|
||||
|
||||
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',
|
||||
'checked-in': 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
'checked-out': 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
|
||||
cancelled: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||
cancelled: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
|
||||
}
|
||||
return colors[status] || colors.scheduled
|
||||
}
|
||||
|
|
@ -52,7 +49,7 @@ const VisitorsModule: React.FC = () => {
|
|||
scheduled: '📅 Planlandı',
|
||||
'checked-in': '✅ Giriş Yaptı',
|
||||
'checked-out': '🚪 Çıkış Yaptı',
|
||||
cancelled: '❌ İptal Edildi'
|
||||
cancelled: '❌ İptal Edildi',
|
||||
}
|
||||
return labels[status] || status
|
||||
}
|
||||
|
|
@ -83,9 +80,21 @@ const VisitorsModule: React.FC = () => {
|
|||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: 'Tümü', value: mockVisitors.length, status: 'all' as const },
|
||||
{ label: 'Planlandı', value: mockVisitors.filter(v => v.status === 'scheduled').length, status: 'scheduled' as const },
|
||||
{ label: 'İçeride', value: mockVisitors.filter(v => v.status === 'checked-in').length, status: 'checked-in' as const },
|
||||
{ label: 'Çıkış Yaptı', value: mockVisitors.filter(v => v.status === 'checked-out').length, status: 'checked-out' as const }
|
||||
{
|
||||
label: 'Planlandı',
|
||||
value: mockVisitors.filter((v) => v.status === 'scheduled').length,
|
||||
status: 'scheduled' as const,
|
||||
},
|
||||
{
|
||||
label: 'İçeride',
|
||||
value: mockVisitors.filter((v) => v.status === 'checked-in').length,
|
||||
status: 'checked-in' as const,
|
||||
},
|
||||
{
|
||||
label: 'Çıkış Yaptı',
|
||||
value: mockVisitors.filter((v) => v.status === 'checked-out').length,
|
||||
status: 'checked-out' as const,
|
||||
},
|
||||
].map((stat, idx) => (
|
||||
<motion.button
|
||||
key={idx}
|
||||
|
|
@ -132,14 +141,18 @@ const VisitorsModule: React.FC = () => {
|
|||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{visitor.company}</p>
|
||||
</div>
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(visitor.status)}`}>
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(visitor.status)}`}
|
||||
>
|
||||
{getStatusLabel(visitor.status)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Ziyaret Tarihi</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||||
Ziyaret Tarihi
|
||||
</p>
|
||||
<div className="flex items-center gap-1 text-sm text-gray-900 dark:text-white">
|
||||
<HiClock className="w-4 h-4" />
|
||||
{dayjs(visitor.visitDate).format('DD MMM, HH:mm')}
|
||||
|
|
@ -202,7 +215,7 @@ const VisitorsModule: React.FC = () => {
|
|||
{visitor.host.fullName}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
{visitor.host.department}
|
||||
{visitor.host.department?.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -245,134 +258,134 @@ const VisitorsModule: React.FC = () => {
|
|||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Visitor Modal */}
|
||||
<AnimatePresence>
|
||||
{showNewVisitor && (
|
||||
<>
|
||||
{/* New Visitor Modal */}
|
||||
<AnimatePresence>
|
||||
{showNewVisitor && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowNewVisitor(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setShowNewVisitor(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Yeni Ziyaretçi Kaydı
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowNewVisitor(false)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ad Soyad *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Ziyaretçi adı"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Şirket *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Şirket adı"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
E-posta *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="email@example.com"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Telefon *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="+90 5XX XXX XX XX"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Yeni Ziyaretçi Kaydı
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowNewVisitor(false)}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
<HiXMark className="w-5 h-5 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ziyaret Tarihi ve Saati *
|
||||
Ad Soyad *
|
||||
</label>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ev Sahibi (Personel) *
|
||||
Şirket *
|
||||
</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ı açıklayın"
|
||||
<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 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 className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
E-posta *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="email@example.com"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Telefon *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="+90 5XX XXX XX XX"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ziyaret Tarihi ve Saati *
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ev Sahibi (Personel) *
|
||||
</label>
|
||||
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option>Ahmet Yılmaz - Yazılım Geliştirme</option>
|
||||
<option>Zeynep Kaya - İnsan Kaynakları</option>
|
||||
<option>Mehmet Demir - Yazılım Geliştirme</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Ziyaret Amacı *
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Ziyaret amacını açı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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -606,25 +606,40 @@ export const mockDocuments: Document[] = [
|
|||
export const mockBirthdays: Birthday[] = [
|
||||
{
|
||||
employee: mockEmployees[0],
|
||||
date: new Date('2024-10-20'),
|
||||
age: 34,
|
||||
date: new Date('1990-10-19'), // Bugün
|
||||
age: 35,
|
||||
},
|
||||
{
|
||||
employee: mockEmployees[1],
|
||||
date: new Date('2024-10-25'),
|
||||
age: 32,
|
||||
date: new Date('1992-10-21'), // Bu hafta içinde
|
||||
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[] = [
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
employee: mockEmployees[0],
|
||||
hireDate: new Date('2020-03-15'),
|
||||
hireDate: new Date('2021-10-20'), // Bu ay (Ekim)
|
||||
years: 4,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue