erp-platform/ui/src/views/intranet/Event.tsx
2025-10-20 14:32:41 +03:00

464 lines
20 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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