Intranet stil düzenlemeleri
This commit is contained in:
parent
8e61351ac0
commit
277dbd907f
4 changed files with 621 additions and 606 deletions
|
|
@ -299,7 +299,7 @@ const PostItem: React.FC<PostItemProps> = ({ post, onLike, onComment, onDelete,
|
||||||
className="p-2 text-gray-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-full transition-colors"
|
className="p-2 text-gray-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-full transition-colors"
|
||||||
title={translate('::App.Platform.Intranet.SocialWall.PostItem.DeletePost')}
|
title={translate('::App.Platform.Intranet.SocialWall.PostItem.DeletePost')}
|
||||||
>
|
>
|
||||||
<FaTrash className="w-5 h-5" />
|
<FaTrash className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { FaTimes, FaEye, FaClipboard, FaChevronLeft, FaChevronRight } from 'react-icons/fa'
|
import {
|
||||||
|
FaTimes,
|
||||||
|
FaEye,
|
||||||
|
FaClipboard,
|
||||||
|
FaChevronLeft,
|
||||||
|
FaChevronRight,
|
||||||
|
FaExpand,
|
||||||
|
} from 'react-icons/fa'
|
||||||
|
import { Dialog } from '@/components/ui'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import { AnnouncementDto } from '@/proxy/intranet/models'
|
import { AnnouncementDto } from '@/proxy/intranet/models'
|
||||||
import useLocale from '@/utils/hooks/useLocale'
|
import useLocale from '@/utils/hooks/useLocale'
|
||||||
import { currentLocalDate } from '@/utils/dateUtils'
|
import { currentLocalDate } from '@/utils/dateUtils'
|
||||||
|
|
@ -14,9 +24,11 @@ interface AnnouncementModalProps {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onClose }) => {
|
function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalProps) {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const currentLocale = useLocale()
|
const currentLocale = useLocale()
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
const [activePhoto, setActivePhoto] = useState(0)
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false)
|
const [lightboxOpen, setLightboxOpen] = useState(false)
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0)
|
const [lightboxIndex, setLightboxIndex] = useState(0)
|
||||||
|
|
||||||
|
|
@ -48,7 +60,10 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
const category = announcement.category || 'general'
|
const category = announcement.category || 'general'
|
||||||
|
|
||||||
const imgSrc = (img: string) =>
|
const imgSrc = (img: string) =>
|
||||||
img.startsWith('data:') || img.startsWith('http://') || img.startsWith('https://') || img.startsWith('/')
|
img.startsWith('data:') ||
|
||||||
|
img.startsWith('http://') ||
|
||||||
|
img.startsWith('https://') ||
|
||||||
|
img.startsWith('/')
|
||||||
? img
|
? img
|
||||||
: `data:image/jpeg;base64,${img}`
|
: `data:image/jpeg;base64,${img}`
|
||||||
|
|
||||||
|
|
@ -65,23 +80,9 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<Dialog.Body className="flex flex-col min-h-0">
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className="fixed inset-0 bg-black/50 z-40"
|
|
||||||
onClick={onClose}
|
|
||||||
/>
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-3xl w-full"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="pb-4 pr-16 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
|
@ -109,12 +110,6 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
{announcement.title}
|
{announcement.title}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<FaTimes className="w-6 h-6 text-gray-500" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Author Info */}
|
{/* Author Info */}
|
||||||
|
|
@ -125,9 +120,7 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
src={AVATAR_URL(announcementUser.id ?? '', announcementUser.tenantId ?? '')}
|
src={AVATAR_URL(announcementUser.id ?? '', announcementUser.tenantId ?? '')}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-900 dark:text-white">
|
<p className="font-semibold text-gray-900 dark:text-white">{announcementUserName}</p>
|
||||||
{announcementUserName}
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
<div className="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<span>{currentLocalDate(announcement.publishDate, currentLocale || 'tr')}</span>
|
<span>{currentLocalDate(announcement.publishDate, currentLocale || 'tr')}</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
|
|
@ -142,44 +135,92 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="p-6 max-h-[60vh] overflow-y-auto">
|
<div className={`py-2 overflow-y-auto ${isMaximized ? 'flex-1 min-h-0' : 'max-h-[60vh]'}`}>
|
||||||
{/* Images if exist */}
|
|
||||||
{images.length > 0 &&
|
|
||||||
(() => {
|
|
||||||
const getGridClass = (count: number) => {
|
|
||||||
if (count === 1) return ''
|
|
||||||
if (count === 2) return 'grid grid-cols-2 gap-2'
|
|
||||||
if (count === 3) return 'grid grid-cols-3 gap-2'
|
|
||||||
return 'grid grid-cols-2 gap-2'
|
|
||||||
}
|
|
||||||
const getImgClass = (count: number, idx: number) => {
|
|
||||||
if (count === 1) return 'w-full rounded-lg object-cover max-h-96'
|
|
||||||
if (count === 3 && idx === 0) return 'col-span-3 w-full rounded-lg object-cover max-h-64'
|
|
||||||
if (count >= 4 && idx === 0) return 'col-span-2 w-full rounded-lg object-cover max-h-64'
|
|
||||||
return 'w-full rounded-lg object-cover h-40'
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={`mb-6 ${getGridClass(images.length)}`}>
|
|
||||||
{images.map((img, idx) => (
|
|
||||||
<img
|
|
||||||
key={idx}
|
|
||||||
src={imgSrc(img)}
|
|
||||||
alt={`${announcement.title} ${images.length > 1 ? idx + 1 : ''}`.trim()}
|
|
||||||
className={`${getImgClass(images.length, idx)} cursor-zoom-in hover:opacity-90 transition-opacity`}
|
|
||||||
onClick={() => openLightbox(idx)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{/* Full Content */}
|
{/* Full Content */}
|
||||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
<div className="max-w-none">
|
||||||
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
{/* Expiry Date */}
|
||||||
|
{announcement.expiryDate && (
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400 py-1">
|
||||||
|
<span className="font-medium">
|
||||||
|
{translate('::App.Platform.Intranet.AnnouncementDetailModal.ExpiryDate')}:
|
||||||
|
</span>{' '}
|
||||||
|
{currentLocalDate(announcement.expiryDate, currentLocale || 'tr')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-line py-1">
|
||||||
{announcement.content}
|
{announcement.content}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Images if exist */}
|
||||||
|
{images.length > 0 && (
|
||||||
|
<div className="mx-auto border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="mt-3 w-full relative rounded-lg overflow-hidden dark:bg-gray-900 mb-2">
|
||||||
|
<img
|
||||||
|
src={imgSrc(images[activePhoto])}
|
||||||
|
alt={`${announcement.title} ${activePhoto + 1}`}
|
||||||
|
className="w-full h-56 object-contain cursor-zoom-in"
|
||||||
|
onClick={() => openLightbox(activePhoto)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="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>
|
||||||
|
{images.length > 1 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setActivePhoto((i) => (i - 1 + images.length) % images.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
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setActivePhoto((i) => (i + 1) % images.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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{images.length > 1 && (
|
||||||
|
<div className="flex gap-2 overflow-x-auto pb-1">
|
||||||
|
{images.map((img, idx) => (
|
||||||
|
<button
|
||||||
|
key={idx}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setActivePhoto(idx)}
|
||||||
|
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-all ${
|
||||||
|
idx === activePhoto
|
||||||
|
? 'border-blue-500 opacity-100'
|
||||||
|
: 'border-transparent opacity-60 hover:opacity-90'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={imgSrc(img)}
|
||||||
|
alt={`${announcement.title} thumbnail ${idx + 1}`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Attachments */}
|
{/* Attachments */}
|
||||||
{attachments.length > 0 && (
|
{attachments.length > 0 && (
|
||||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
|
@ -202,9 +243,7 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||||
{attachment.name}
|
{attachment.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-gray-500 dark:text-gray-400">{attachment.size}</p>
|
||||||
{attachment.size}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-blue-600 dark:text-blue-400">
|
<span className="text-sm text-blue-600 dark:text-blue-400">
|
||||||
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Download')}
|
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Download')}
|
||||||
|
|
@ -233,33 +272,20 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Expiry Date */}
|
|
||||||
{announcement.expiryDate && (
|
|
||||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
<span className="font-medium">
|
|
||||||
{translate('::App.Platform.Intranet.AnnouncementDetailModal.ExpiryDate')}:
|
|
||||||
</span>{' '}
|
|
||||||
{currentLocalDate(announcement.expiryDate, currentLocale || 'tr')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
</Dialog.Body>
|
||||||
|
|
||||||
{/* Footer */}
|
<Dialog.Footer className="pt-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
|
||||||
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50">
|
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{translate('::App.Platform.Close')}
|
{translate('::App.Platform.Close')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</Dialog.Footer>
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Lightbox */}
|
{/* Lightbox */}
|
||||||
|
{createPortal(
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{lightboxOpen && (
|
{lightboxOpen && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -267,10 +293,10 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 bg-black/95 z-[60]"
|
className="fixed inset-0 bg-black/95 z-[1100]"
|
||||||
onClick={closeLightbox}
|
onClick={closeLightbox}
|
||||||
/>
|
/>
|
||||||
<div className="fixed inset-0 z-[70] flex items-center justify-center">
|
<div className="fixed inset-0 z-[1110] flex items-center justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={closeLightbox}
|
onClick={closeLightbox}
|
||||||
className="absolute top-4 right-4 p-2 text-white hover:text-gray-300 transition-colors z-10"
|
className="absolute top-4 right-4 p-2 text-white hover:text-gray-300 transition-colors z-10"
|
||||||
|
|
@ -322,9 +348,19 @@ const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onC
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AnnouncementModal: React.FC<AnnouncementModalProps> = ({ announcement, onClose }) => {
|
||||||
|
return (
|
||||||
|
<Dialog isOpen onClose={onClose} onRequestClose={onClose} width={768}>
|
||||||
|
<AnnouncementModalContent announcement={announcement} onClose={onClose} />
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default AnnouncementModal
|
export default AnnouncementModal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import {
|
import {
|
||||||
FaTimes,
|
FaTimes,
|
||||||
|
|
@ -12,6 +13,8 @@ import {
|
||||||
FaPaperPlane,
|
FaPaperPlane,
|
||||||
FaHeart,
|
FaHeart,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
|
import { Dialog } from '@/components/ui'
|
||||||
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import { EventCommentDto, EventDto } from '@/proxy/intranet/models'
|
import { EventCommentDto, EventDto } from '@/proxy/intranet/models'
|
||||||
import useLocale from '@/utils/hooks/useLocale'
|
import useLocale from '@/utils/hooks/useLocale'
|
||||||
import { currentLocalDate } from '@/utils/dateUtils'
|
import { currentLocalDate } from '@/utils/dateUtils'
|
||||||
|
|
@ -40,10 +43,11 @@ const imgSrc = (img: string) => {
|
||||||
return `data:image/jpeg;base64,${img}`
|
return `data:image/jpeg;base64,${img}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
function EventModalContent({ event, onClose }: EventModalProps) {
|
||||||
const currentLocale = useLocale()
|
const currentLocale = useLocale()
|
||||||
const photos = (event.photos || '').split('|').filter(Boolean)
|
const photos = (event.photos || '').split('|').filter(Boolean)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
|
|
||||||
// Photo slider state
|
// Photo slider state
|
||||||
const [activePhoto, setActivePhoto] = useState(0)
|
const [activePhoto, setActivePhoto] = useState(0)
|
||||||
|
|
@ -135,27 +139,9 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
<Dialog.Body className="flex flex-col min-h-0">
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className="fixed inset-0 bg-black/60 z-40"
|
|
||||||
onClick={onClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Modal */}
|
|
||||||
<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-xl shadow-2xl w-full max-w-3xl flex flex-col"
|
|
||||||
style={{ maxHeight: '90vh' }}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="p-5 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
|
<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 items-start justify-between gap-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white leading-tight">
|
<h2 className="text-xl font-bold text-gray-900 dark:text-white leading-tight">
|
||||||
|
|
@ -207,27 +193,20 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors flex-shrink-0"
|
|
||||||
>
|
|
||||||
<FaTimes className="w-5 h-5 text-gray-500" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scrollable body */}
|
{/* Scrollable body */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className={`overflow-y-auto ${isMaximized ? 'flex-1 min-h-0' : 'max-h-[62vh]'}`}>
|
||||||
{/* Photo Gallery */}
|
{/* Photo Gallery */}
|
||||||
{photos.length > 0 && (
|
{photos.length > 0 && (
|
||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="m-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
{/* Main photo */}
|
{/* Main photo */}
|
||||||
<div className="relative rounded-lg overflow-hidden bg-gray-900 mb-2">
|
<div className="relative w-full rounded-lg overflow-hidden dark:bg-gray-900 mb-2 mx-auto">
|
||||||
<img
|
<img
|
||||||
src={imgSrc(photos[activePhoto])}
|
src={imgSrc(photos[activePhoto])}
|
||||||
alt={`${event.name} ${activePhoto + 1}`}
|
alt={`${event.name} ${activePhoto + 1}`}
|
||||||
className="w-full object-cover cursor-pointer"
|
className="w-full h-56 object-contain cursor-zoom-in"
|
||||||
style={{ maxHeight: '320px' }}
|
|
||||||
onClick={() => openLightbox(activePhoto)}
|
onClick={() => openLightbox(activePhoto)}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|
@ -287,7 +266,7 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
{event.description && (
|
{event.description && (
|
||||||
<div className="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
|
<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">
|
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line">
|
||||||
{event.description}
|
{event.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -295,16 +274,16 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Comments Section */}
|
{/* Comments Section */}
|
||||||
<div className="p-5">
|
<div className="py-3">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-2 mb-4">
|
<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" />
|
<FaCommentAlt className="w-4 h-4 text-green-500" />
|
||||||
{translate('::App.Intranet.Events.Comments')} ({comments.length})
|
{translate('::App.Intranet.Events.Comments')} ({comments.length})
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Comment List */}
|
{/* Comment List */}
|
||||||
<div className="space-y-4 mb-4 max-h-64 overflow-y-auto">
|
<div className="max-h-64 overflow-y-auto">
|
||||||
{comments.length === 0 ? (
|
{comments.length === 0 ? (
|
||||||
<p className="text-sm text-gray-400 dark:text-gray-500 text-center py-6">
|
<p className="text-sm text-gray-400 dark:text-gray-500 text-center">
|
||||||
{translate('::App.Intranet.Events.EventComment')}
|
{translate('::App.Intranet.Events.EventComment')}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -336,9 +315,12 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
)}
|
)}
|
||||||
<div ref={commentsEndRef} />
|
<div ref={commentsEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Body>
|
||||||
|
|
||||||
{/* Comment Input */}
|
<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 gap-2 items-end">
|
<div className="flex flex-1 gap-2 items-end">
|
||||||
<textarea
|
<textarea
|
||||||
ref={commentInputRef}
|
ref={commentInputRef}
|
||||||
value={commentText}
|
value={commentText}
|
||||||
|
|
@ -346,10 +328,11 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
onChange={(e) => setCommentText(e.target.value)}
|
onChange={(e) => setCommentText(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={translate('::App.Intranet.Events.EventAttendance')}
|
placeholder={translate('::App.Intranet.Events.EventAttendance')}
|
||||||
rows={2}
|
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"
|
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
|
<button
|
||||||
|
type="button"
|
||||||
onClick={handleSubmitComment}
|
onClick={handleSubmitComment}
|
||||||
disabled={!commentText.trim() || submitting}
|
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"
|
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"
|
||||||
|
|
@ -357,22 +340,17 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
<FaPaperPlane className="w-4 h-4" />
|
<FaPaperPlane className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50 flex-shrink-0 flex items-center gap-3">
|
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="flex-1 px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors text-sm font-medium"
|
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')}
|
{translate('::App.Platform.Close')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</Dialog.Footer>
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Lightbox */}
|
{/* Lightbox */}
|
||||||
|
{createPortal(
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{lightboxOpen && (
|
{lightboxOpen && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -380,10 +358,10 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 bg-black/95 z-[60]"
|
className="fixed inset-0 bg-black/95 z-[1100]"
|
||||||
onClick={closeLightbox}
|
onClick={closeLightbox}
|
||||||
/>
|
/>
|
||||||
<div className="fixed inset-0 z-[70] flex items-center justify-center">
|
<div className="fixed inset-0 z-[1110] flex items-center justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={closeLightbox}
|
onClick={closeLightbox}
|
||||||
className="absolute top-4 right-4 p-2 text-white hover:text-gray-300 transition-colors z-10"
|
className="absolute top-4 right-4 p-2 text-white hover:text-gray-300 transition-colors z-10"
|
||||||
|
|
@ -435,9 +413,19 @@ const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EventModal: React.FC<EventModalProps> = ({ event, onClose }) => {
|
||||||
|
return (
|
||||||
|
<Dialog isOpen onClose={onClose} onRequestClose={onClose} width={768}>
|
||||||
|
<EventModalContent event={event} onClose={onClose} />
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default EventModal
|
export default EventModal
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { motion } from 'framer-motion'
|
import { Dialog } from '@/components/ui'
|
||||||
import { FaTimes } from 'react-icons/fa'
|
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
|
||||||
import { SurveyAnswerDto, SurveyDto, SurveyQuestionDto } from '@/proxy/intranet/models'
|
import { SurveyAnswerDto, SurveyDto, SurveyQuestionDto } from '@/proxy/intranet/models'
|
||||||
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
interface SurveyModalProps {
|
interface SurveyModalProps {
|
||||||
survey: SurveyDto
|
survey: SurveyDto
|
||||||
|
|
@ -9,9 +10,9 @@ interface SurveyModalProps {
|
||||||
onSubmit: (answers: SurveyAnswerDto[]) => void
|
onSubmit: (answers: SurveyAnswerDto[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
function SurveyModalContent({ survey, onClose, onSubmit }: SurveyModalProps) {
|
||||||
const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit }) => {
|
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const { isMaximized } = useDialogContext()
|
||||||
const isUpdate = !!survey.myResponse
|
const isUpdate = !!survey.myResponse
|
||||||
|
|
||||||
const [answers, setAnswers] = useState<{ [questionId: string]: any }>(() => {
|
const [answers, setAnswers] = useState<{ [questionId: string]: any }>(() => {
|
||||||
|
|
@ -232,38 +233,21 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<form
|
||||||
<motion.div
|
onSubmit={handleSubmit}
|
||||||
initial={{ opacity: 0 }}
|
className={`flex flex-col ${isMaximized ? 'flex-1 min-h-0' : ''}`}
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className="fixed inset-0 bg-black/50 z-40"
|
|
||||||
onClick={onClose}
|
|
||||||
/>
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<div className="p-4 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">
|
<Dialog.Body className="flex flex-col min-h-0">
|
||||||
<div>
|
<div className="pb-4 pr-16 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{survey.title}</h2>
|
||||||
{survey.title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{survey.description}</p>
|
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{survey.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<FaTimes className="w-5 h-5 text-gray-500" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
<div
|
||||||
|
className={`py-6 space-y-6 overflow-y-auto ${
|
||||||
|
isMaximized ? 'flex-1 min-h-0' : 'max-h-[65vh]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{survey.questions
|
{survey.questions
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
|
|
@ -285,8 +269,10 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</Dialog.Body>
|
||||||
|
|
||||||
<div className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<Dialog.Footer className="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|
@ -302,11 +288,16 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
|
||||||
? translate('::App.Platform.Intranet.SurveyModal.Update')
|
? translate('::App.Platform.Intranet.SurveyModal.Update')
|
||||||
: translate('::App.Platform.Intranet.SurveyModal.Submit')}
|
: translate('::App.Platform.Intranet.SurveyModal.Submit')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</Dialog.Footer>
|
||||||
</form>
|
</form>
|
||||||
</motion.div>
|
)
|
||||||
</div>
|
}
|
||||||
</>
|
|
||||||
|
const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit }) => {
|
||||||
|
return (
|
||||||
|
<Dialog isOpen onClose={onClose} onRequestClose={onClose} width={672}>
|
||||||
|
<SurveyModalContent survey={survey} onClose={onClose} onSubmit={onSubmit} />
|
||||||
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue