2026-05-07 14:25:06 +00:00
|
|
|
|
import React, { useState, useEffect, useRef } from 'react'
|
2026-05-25 20:00:57 +00:00
|
|
|
|
import { createPortal } from 'react-dom'
|
2026-05-07 14:25:06 +00:00
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
|
|
|
|
import {
|
|
|
|
|
|
FaTimes,
|
|
|
|
|
|
FaChevronLeft,
|
|
|
|
|
|
FaChevronRight,
|
|
|
|
|
|
FaMapMarkerAlt,
|
|
|
|
|
|
FaCalendarAlt,
|
|
|
|
|
|
FaUsers,
|
|
|
|
|
|
FaExpand,
|
|
|
|
|
|
FaCommentAlt,
|
|
|
|
|
|
FaPaperPlane,
|
2026-05-08 18:11:56 +00:00
|
|
|
|
FaHeart,
|
2026-05-07 14:25:06 +00:00
|
|
|
|
} from 'react-icons/fa'
|
2026-05-25 20:00:57 +00:00
|
|
|
|
import { Dialog } from '@/components/ui'
|
|
|
|
|
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
2026-05-07 14:25:06 +00:00
|
|
|
|
import { EventCommentDto, EventDto } from '@/proxy/intranet/models'
|
|
|
|
|
|
import useLocale from '@/utils/hooks/useLocale'
|
|
|
|
|
|
import { currentLocalDate } from '@/utils/dateUtils'
|
|
|
|
|
|
import Avatar from '@/components/ui/Avatar/Avatar'
|
|
|
|
|
|
import { AVATAR_URL } from '@/constants/app.constant'
|
|
|
|
|
|
import { intranetService } from '@/services/intranet.service'
|
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
2026-05-08 08:38:57 +00:00
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
|
|
|
|
|
|
|
interface EventModalProps {
|
|
|
|
|
|
event: EventDto
|
|
|
|
|
|
onClose: () => void
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const imgSrc = (img: string) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
img.startsWith('data:') ||
|
|
|
|
|
|
img.startsWith('http://') ||
|
|
|
|
|
|
img.startsWith('https://') ||
|
|
|
|
|
|
img.startsWith('/')
|
|
|
|
|
|
)
|
|
|
|
|
|
return img
|
|
|
|
|
|
return `data:image/jpeg;base64,${img}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
function EventModalContent({ event, onClose }: EventModalProps) {
|
2026-05-07 14:25:06 +00:00
|
|
|
|
const currentLocale = useLocale()
|
|
|
|
|
|
const photos = (event.photos || '').split('|').filter(Boolean)
|
2026-05-08 08:38:57 +00:00
|
|
|
|
const { translate } = useLocalization()
|
2026-05-25 20:00:57 +00:00
|
|
|
|
const { isMaximized } = useDialogContext()
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
|
|
|
|
|
// Photo slider state
|
|
|
|
|
|
const [activePhoto, setActivePhoto] = useState(0)
|
|
|
|
|
|
const [lightboxOpen, setLightboxOpen] = useState(false)
|
|
|
|
|
|
const [lightboxIndex, setLightboxIndex] = useState(0)
|
|
|
|
|
|
|
|
|
|
|
|
// Comments state
|
|
|
|
|
|
const [comments, setComments] = useState<EventCommentDto[]>(event.comments || [])
|
|
|
|
|
|
const [commentText, setCommentText] = useState('')
|
|
|
|
|
|
const [submitting, setSubmitting] = useState(false)
|
|
|
|
|
|
const commentInputRef = useRef<HTMLTextAreaElement>(null)
|
|
|
|
|
|
const commentsEndRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
|
|
2026-05-08 18:11:56 +00:00
|
|
|
|
// Likes state
|
|
|
|
|
|
const [likes, setLikes] = useState(event.likes)
|
|
|
|
|
|
const [isLiked, setIsLiked] = useState(event.isLiked)
|
|
|
|
|
|
const [liking, setLiking] = useState(false)
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// Refresh comments from API on open
|
|
|
|
|
|
intranetService.getEventComments(event.id).then((res) => {
|
|
|
|
|
|
if (res.data) setComments(res.data)
|
|
|
|
|
|
})
|
|
|
|
|
|
}, [event.id])
|
|
|
|
|
|
|
|
|
|
|
|
const scrollToBottom = () => {
|
|
|
|
|
|
commentsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmitComment = async () => {
|
|
|
|
|
|
if (!commentText.trim() || submitting) return
|
|
|
|
|
|
setSubmitting(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await intranetService.createEventComment(event.id, commentText.trim())
|
|
|
|
|
|
if (res.data) {
|
|
|
|
|
|
setComments((prev) => [...prev, res.data!])
|
|
|
|
|
|
setCommentText('')
|
|
|
|
|
|
setTimeout(scrollToBottom, 100)
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSubmitting(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
|
|
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
handleSubmitComment()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 18:11:56 +00:00
|
|
|
|
const handleLike = async () => {
|
|
|
|
|
|
if (liking) return
|
|
|
|
|
|
setLiking(true)
|
|
|
|
|
|
// Optimistic update
|
|
|
|
|
|
setIsLiked((prev) => !prev)
|
|
|
|
|
|
setLikes((prev) => (isLiked ? Math.max(0, prev - 1) : prev + 1))
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await intranetService.likeEvent(event.id)
|
|
|
|
|
|
if (res.data) {
|
|
|
|
|
|
setLikes(res.data.likes)
|
|
|
|
|
|
setIsLiked(res.data.isLiked)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// Revert on error
|
|
|
|
|
|
setIsLiked((prev) => !prev)
|
|
|
|
|
|
setLikes((prev) => (isLiked ? prev + 1 : Math.max(0, prev - 1)))
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLiking(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
const openLightbox = (idx: number) => {
|
|
|
|
|
|
setLightboxIndex(idx)
|
|
|
|
|
|
setLightboxOpen(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const closeLightbox = () => setLightboxOpen(false)
|
|
|
|
|
|
|
|
|
|
|
|
const prevLightbox = (e: React.MouseEvent) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
setLightboxIndex((i) => (i - 1 + photos.length) % photos.length)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const nextLightbox = (e: React.MouseEvent) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
setLightboxIndex((i) => (i + 1) % photos.length)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<Dialog.Body className="flex flex-col min-h-0">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="pb-3 pr-16 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
|
|
|
|
|
|
<div className="flex items-start justify-between gap-4">
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<h2 className="text-xl font-bold text-gray-900 dark:text-white leading-tight">
|
|
|
|
|
|
{event.name}
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="flex flex-wrap items-center gap-4 mt-2 text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<FaCalendarAlt className="w-3.5 h-3.5 text-green-500" />
|
|
|
|
|
|
{currentLocalDate(event.date, currentLocale || 'tr')}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{event.place && (
|
2026-05-07 14:25:06 +00:00
|
|
|
|
<span className="flex items-center gap-1">
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<FaMapMarkerAlt className="w-3.5 h-3.5 text-red-500" />
|
|
|
|
|
|
{event.place}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{event.participantsCount > 0 && (
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<FaUsers className="w-3.5 h-3.5 text-blue-500" />
|
|
|
|
|
|
{event.participantsCount}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
<button
|
2026-05-25 20:00:57 +00:00
|
|
|
|
onClick={handleLike}
|
|
|
|
|
|
disabled={liking}
|
|
|
|
|
|
className={`flex items-center gap-2 border-0 transition-colors text-xs font-xs border ${
|
|
|
|
|
|
isLiked
|
|
|
|
|
|
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/40'
|
|
|
|
|
|
: 'bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600'
|
|
|
|
|
|
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<FaHeart className={`w-4 h-4 ${isLiked ? 'text-red-500' : ''}`} />
|
|
|
|
|
|
{likes > 0 && likes}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{event.user && (
|
|
|
|
|
|
<div className="flex items-center gap-2 mt-3">
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
size={28}
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
src={AVATAR_URL(event.user.id, event.user.tenantId)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
|
|
|
|
{event.user.fullName}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Scrollable body */}
|
|
|
|
|
|
<div className={`overflow-y-auto ${isMaximized ? 'flex-1 min-h-0' : 'max-h-[62vh]'}`}>
|
|
|
|
|
|
{/* Photo Gallery */}
|
|
|
|
|
|
{photos.length > 0 && (
|
|
|
|
|
|
<div className="m-3 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
{/* Main photo */}
|
|
|
|
|
|
<div className="relative w-full rounded-lg overflow-hidden dark:bg-gray-900 mb-2 mx-auto">
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={imgSrc(photos[activePhoto])}
|
|
|
|
|
|
alt={`${event.name} ${activePhoto + 1}`}
|
|
|
|
|
|
className="w-full h-56 object-contain cursor-zoom-in"
|
|
|
|
|
|
onClick={() => openLightbox(activePhoto)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => openLightbox(activePhoto)}
|
|
|
|
|
|
className="absolute top-2 right-2 p-1.5 bg-black/50 hover:bg-black/70 rounded-lg text-white transition-colors"
|
|
|
|
|
|
title="Tam ekran"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaExpand className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
{photos.length > 1 && (
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
setActivePhoto((i) => (i - 1 + photos.length) % photos.length)
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaChevronLeft className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
setActivePhoto((i) => (i + 1) % photos.length)
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaChevronRight className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{/* Thumbnail strip */}
|
|
|
|
|
|
{photos.length > 1 && (
|
|
|
|
|
|
<div className="flex gap-2 overflow-x-auto pb-1">
|
|
|
|
|
|
{photos.map((photo, idx) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
onClick={() => setActivePhoto(idx)}
|
|
|
|
|
|
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-all ${
|
|
|
|
|
|
idx === activePhoto
|
|
|
|
|
|
? 'border-green-500 opacity-100'
|
|
|
|
|
|
: 'border-transparent opacity-60 hover:opacity-90'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<img
|
|
|
|
|
|
src={imgSrc(photo)}
|
|
|
|
|
|
alt={`Thumbnail ${idx + 1}`}
|
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{/* Description */}
|
|
|
|
|
|
{event.description && (
|
|
|
|
|
|
<div className="pb-2 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
|
|
|
|
|
{event.description}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{/* Comments Section */}
|
|
|
|
|
|
<div className="py-3">
|
|
|
|
|
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-2 mb-2">
|
|
|
|
|
|
<FaCommentAlt className="w-4 h-4 text-green-500" />
|
|
|
|
|
|
{translate('::App.Intranet.Events.Comments')} ({comments.length})
|
|
|
|
|
|
</h3>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{/* Comment List */}
|
|
|
|
|
|
<div className="max-h-64 overflow-y-auto">
|
|
|
|
|
|
{comments.length === 0 ? (
|
|
|
|
|
|
<p className="text-sm text-gray-400 dark:text-gray-500 text-center">
|
2026-06-15 13:08:54 +00:00
|
|
|
|
{translate('::App.Intranet.Events.NoComment')}
|
2026-05-25 20:00:57 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
comments.map((comment) => (
|
|
|
|
|
|
<div key={comment.id} className="flex gap-3">
|
|
|
|
|
|
{comment.user && (
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
size={32}
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
src={AVATAR_URL(comment.user.id, comment.user.tenantId)}
|
|
|
|
|
|
className="flex-shrink-0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
|
<div className="bg-gray-50 dark:bg-gray-700/60 rounded-xl px-3 py-2">
|
|
|
|
|
|
<p className="text-xs font-semibold text-gray-800 dark:text-gray-200">
|
|
|
|
|
|
{comment.user?.fullName ?? 'Kullanıcı'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p className="text-sm text-gray-700 dark:text-gray-300 mt-0.5 whitespace-pre-line">
|
|
|
|
|
|
{comment.content}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1 px-1">
|
|
|
|
|
|
{dayjs(comment.creationTime).fromNow()}
|
|
|
|
|
|
</p>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</div>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div ref={commentsEndRef} />
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</Dialog.Body>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<Dialog.Footer className="p-1 pt-2 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 flex items-end gap-3">
|
|
|
|
|
|
<div className="flex flex-1 gap-2 items-end">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
ref={commentInputRef}
|
|
|
|
|
|
value={commentText}
|
|
|
|
|
|
autoFocus
|
|
|
|
|
|
onChange={(e) => setCommentText(e.target.value)}
|
|
|
|
|
|
onKeyDown={handleKeyDown}
|
|
|
|
|
|
placeholder={translate('::App.Intranet.Events.EventAttendance')}
|
|
|
|
|
|
rows={1}
|
|
|
|
|
|
className="flex-1 resize-none px-3 py-2 text-sm rounded-xl border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 transition-colors"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={handleSubmitComment}
|
|
|
|
|
|
disabled={!commentText.trim() || submitting}
|
|
|
|
|
|
className="p-2.5 bg-green-500 hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl transition-colors flex-shrink-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaPaperPlane className="w-4 h-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="px-2 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors text-sm font-medium flex-shrink-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
{translate('::App.Platform.Close')}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</Dialog.Footer>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
|
|
|
|
|
|
{/* Lightbox */}
|
2026-05-25 20:00:57 +00:00
|
|
|
|
{createPortal(
|
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
|
{lightboxOpen && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
|
animate={{ opacity: 1 }}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
exit={{ opacity: 0 }}
|
2026-05-25 20:00:57 +00:00
|
|
|
|
className="fixed inset-0 bg-black/95 z-[1100]"
|
|
|
|
|
|
onClick={closeLightbox}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
/>
|
2026-05-25 20:00:57 +00:00
|
|
|
|
<div className="fixed inset-0 z-[1110] flex items-center justify-center">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={closeLightbox}
|
|
|
|
|
|
className="absolute top-4 right-4 p-2 text-white hover:text-gray-300 transition-colors z-10"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaTimes className="w-8 h-8" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{photos.length > 1 && (
|
|
|
|
|
|
<>
|
2026-05-07 14:25:06 +00:00
|
|
|
|
<button
|
2026-05-25 20:00:57 +00:00
|
|
|
|
onClick={prevLightbox}
|
|
|
|
|
|
className="absolute left-4 top-1/2 -translate-y-1/2 p-3 bg-black/50 hover:bg-black/70 text-white rounded-full transition-colors z-10"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaChevronLeft className="w-6 h-6" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={nextLightbox}
|
|
|
|
|
|
className="absolute right-4 top-1/2 -translate-y-1/2 p-3 bg-black/50 hover:bg-black/70 text-white rounded-full transition-colors z-10"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FaChevronRight className="w-6 h-6" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<motion.img
|
|
|
|
|
|
key={lightboxIndex}
|
|
|
|
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
|
|
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
|
src={imgSrc(photos[lightboxIndex])}
|
|
|
|
|
|
alt={`${event.name} ${lightboxIndex + 1}`}
|
|
|
|
|
|
className="max-w-[90vw] max-h-[90vh] object-contain rounded-lg"
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{photos.length > 1 && (
|
|
|
|
|
|
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
|
|
|
|
|
{photos.map((_, idx) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
setLightboxIndex(idx)
|
|
|
|
|
|
}}
|
|
|
|
|
|
className={`w-2.5 h-2.5 rounded-full transition-all ${
|
|
|
|
|
|
idx === lightboxIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/70'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</AnimatePresence>,
|
|
|
|
|
|
document.body,
|
|
|
|
|
|
)}
|
2026-05-07 14:25:06 +00:00
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 20:00:57 +00:00
|
|
|
|
const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Dialog isOpen onClose={onClose} onRequestClose={onClose} width={768}>
|
|
|
|
|
|
<EventModalContent event={event} onClose={onClose} />
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 14:25:06 +00:00
|
|
|
|
export default EventModal
|