+
{selectedContacts.length === 1
? selectedContacts[0].fullName
: selectedContacts.length > 1
? `${selectedContacts.length} ${translate('::MessengerWidget.PeopleSelected')}`
: translate('::MessengerWidget.Chat')}
-
+
{selectedContacts.length === 1
? `${selectedContacts[0].isOnline ? translate('::MessengerWidget.Online') : translate('::MessengerWidget.Offline')} - ${
selectedContacts[0].email || selectedContacts[0].userName
@@ -591,7 +592,7 @@ const MessengerWidget = () => {
-
+
{visibleMessages.length === 0 && (
{translate('::MessengerWidget.NoMessagesYet')}
diff --git a/ui/src/views/form/FormButtons.tsx b/ui/src/views/form/FormButtons.tsx
index 2c57f21..ca59476 100644
--- a/ui/src/views/form/FormButtons.tsx
+++ b/ui/src/views/form/FormButtons.tsx
@@ -284,7 +284,7 @@ const FormButtons = (props: {
}
title={translate('::App.Reports.Form')}
onClick={openDynamicGridReport}
diff --git a/ui/src/views/intranet/Dashboard.tsx b/ui/src/views/intranet/Dashboard.tsx
index bf85c60..e92a12f 100644
--- a/ui/src/views/intranet/Dashboard.tsx
+++ b/ui/src/views/intranet/Dashboard.tsx
@@ -32,7 +32,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import useLocale from '@/utils/hooks/useLocale'
import { currentLocalDate } from '@/utils/dateUtils'
import { useStoreState } from '@/store/store'
-import UpcomingEvents from './widgets/UpcomingEvents'
+import Events from './widgets/Events'
import Button from '@/components/ui/Button'
dayjs.extend(relativeTime)
@@ -104,6 +104,38 @@ const IntranetDashboard: React.FC = () => {
setSelectedSurvey(null)
}
+ const handleEventLikeChange = (updatedEvent: EventDto) => {
+ setSelectedEvent((current) => (current ? { ...current, ...updatedEvent } : updatedEvent))
+ setIntranetDashboard((current) =>
+ current
+ ? {
+ ...current,
+ events: current.events.map((event) =>
+ event.id === updatedEvent.id ? { ...event, ...updatedEvent } : event,
+ ),
+ }
+ : current,
+ )
+ }
+
+ const handleAnnouncementLikeChange = (updatedAnnouncement: AnnouncementDto) => {
+ setSelectedAnnouncement((current) =>
+ current ? { ...current, ...updatedAnnouncement } : updatedAnnouncement,
+ )
+ setIntranetDashboard((current) =>
+ current
+ ? {
+ ...current,
+ announcements: current.announcements.map((announcement) =>
+ announcement.id === updatedAnnouncement.id
+ ? { ...announcement, ...updatedAnnouncement }
+ : announcement,
+ ),
+ }
+ : current,
+ )
+ }
+
// Widget metadata (component'lar yerine sadece meta bilgiler)
const widgetMetadata = [
{ id: 'today-birthdays', permission: 'AbpIdentity.Users.Widget', column: 'left' },
@@ -247,7 +279,7 @@ const IntranetDashboard: React.FC = () => {
switch (widgetId) {
case 'upcoming-events':
return (
-
@@ -640,7 +672,11 @@ const IntranetDashboard: React.FC = () => {
{selectedEvent && (
- setSelectedEvent(null)} />
+ setSelectedEvent(null)}
+ onLikeChange={handleEventLikeChange}
+ />
)}
@@ -649,6 +685,7 @@ const IntranetDashboard: React.FC = () => {
setSelectedAnnouncement(null)}
+ onLikeChange={handleAnnouncementLikeChange}
/>
)}
diff --git a/ui/src/views/intranet/SocialWall/MediaManager.tsx b/ui/src/views/intranet/SocialWall/MediaManager.tsx
index 16353e1..25271b5 100644
--- a/ui/src/views/intranet/SocialWall/MediaManager.tsx
+++ b/ui/src/views/intranet/SocialWall/MediaManager.tsx
@@ -17,6 +17,13 @@ const MediaManager: React.FC = ({ media, onChange, onClose })
const [activeTab, setActiveTab] = useState<'upload' | 'url'>('upload')
const [urlInput, setUrlInput] = useState('')
const [mediaType, setMediaType] = useState<'image' | 'video'>('image')
+ const tabClassName = (tab: 'upload' | 'url') =>
+ classNames(
+ '!h-auto !items-center gap-2 !rounded-none border-b-2 !bg-transparent !px-4 !py-3 font-medium transition-colors hover:!bg-gray-50 active:!bg-transparent focus:!bg-transparent dark:hover:!bg-gray-700/50',
+ activeTab === tab
+ ? '!border-blue-600 !text-blue-600 dark:!border-blue-400 dark:!text-blue-300'
+ : '!border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100',
+ )
const handleFileSelect = (e: React.ChangeEvent) => {
const files = e.target.files
@@ -92,12 +99,7 @@ const MediaManager: React.FC = ({ media, onChange, onClose })
variant="plain"
shape="none"
icon={}
- className={classNames(
- '!h-auto !items-center gap-2 !rounded-none border-b-2 !px-4 !py-3 font-medium transition-colors',
- activeTab === 'upload'
- ? 'border-blue-600 !text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200',
- )}
+ className={tabClassName('upload')}
>
{translate('::App.Platform.Intranet.SocialWall.MediaManager.SelectFromComputer')}
@@ -108,12 +110,7 @@ const MediaManager: React.FC = ({ media, onChange, onClose })
variant="plain"
shape="none"
icon={}
- className={classNames(
- '!h-auto !items-center gap-2 !rounded-none border-b-2 !px-4 !py-3 font-medium transition-colors',
- activeTab === 'url'
- ? 'border-blue-600 !text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200',
- )}
+ className={tabClassName('url')}
>
{translate('::App.Platform.Intranet.SocialWall.MediaManager.AddByUrl')}
diff --git a/ui/src/views/intranet/SocialWall/PostItem.tsx b/ui/src/views/intranet/SocialWall/PostItem.tsx
index d29103b..3c8d3f5 100644
--- a/ui/src/views/intranet/SocialWall/PostItem.tsx
+++ b/ui/src/views/intranet/SocialWall/PostItem.tsx
@@ -321,12 +321,15 @@ const PostItem: React.FC = ({ post, onLike, onComment, onDelete,
onClick={() => onLike(post.id)}
variant="plain"
shape="none"
- icon={post.isLiked ? : }
+ icon={
+ post.isLiked ? (
+
+ ) : (
+
+ )
+ }
className={classNames(
- '!h-auto !items-center gap-2 !rounded-none !px-0 !py-0 transition-colors hover:!bg-transparent active:!bg-transparent focus:!bg-transparent',
- post.isLiked
- ? 'text-red-600 hover:text-red-700'
- : 'text-gray-600 dark:text-gray-400 hover:text-red-600',
+ '!h-auto !items-center gap-2 !rounded-none !border-0 !bg-transparent !px-0 !py-0 text-gray-600 transition-colors hover:!bg-transparent active:!bg-transparent focus:!bg-transparent dark:text-gray-400',
)}
>
{postLikeCount}
@@ -337,7 +340,7 @@ const PostItem: React.FC = ({ post, onLike, onComment, onDelete,
variant="plain"
shape="none"
icon={}
- className="!h-auto !items-center gap-2 !rounded-none !px-0 !py-0 text-gray-600 transition-colors hover:!bg-transparent hover:text-blue-600 active:!bg-transparent focus:!bg-transparent dark:text-gray-400"
+ className="!h-auto !items-center gap-2 !rounded-none !border-0 !bg-transparent !px-0 !py-0 text-gray-600 transition-colors hover:!bg-transparent hover:text-blue-600 active:!bg-transparent focus:!bg-transparent dark:text-gray-400"
>
{postComments.length}
diff --git a/ui/src/views/intranet/widgets/AnnouncementModal.tsx b/ui/src/views/intranet/widgets/AnnouncementModal.tsx
index e589f3c..0d61dcf 100644
--- a/ui/src/views/intranet/widgets/AnnouncementModal.tsx
+++ b/ui/src/views/intranet/widgets/AnnouncementModal.tsx
@@ -1,9 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'
-import { createPortal } from 'react-dom'
import { useLocalization } from '@/utils/hooks/useLocalization'
-import { motion, AnimatePresence } from 'framer-motion'
import {
- FaTimes,
FaEye,
FaClipboard,
FaChevronLeft,
@@ -11,7 +8,6 @@ import {
FaExpand,
FaCommentAlt,
FaPaperPlane,
- FaHeart,
} from 'react-icons/fa'
import { Button, Dialog } from '@/components/ui'
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
@@ -23,15 +19,19 @@ import { AVATAR_URL } from '@/constants/app.constant'
import { intranetService } from '@/services/intranet.service'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
+import ImageLightbox from './ImageLightbox'
+import LikeButton from './LikeButton'
+import { categoryColor, imageSrc } from './widgetUtils'
dayjs.extend(relativeTime)
interface AnnouncementModalProps {
announcement: AnnouncementDto
onClose: () => void
+ onLikeChange?: (announcement: AnnouncementDto) => void
}
-function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalProps) {
+function AnnouncementModalContent({ announcement, onClose, onLikeChange }: AnnouncementModalProps) {
const { translate } = useLocalization()
const currentLocale = useLocale()
const { isMaximized } = useDialogContext()
@@ -51,14 +51,6 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
setLightboxOpen(true)
}
const closeLightbox = () => setLightboxOpen(false)
- const prevLightbox = (e: React.MouseEvent) => {
- e.stopPropagation()
- setLightboxIndex((i) => (i - 1 + images.length) % images.length)
- }
- const nextLightbox = (e: React.MouseEvent) => {
- e.stopPropagation()
- setLightboxIndex((i) => (i + 1) % images.length)
- }
useEffect(() => {
intranetService.incrementAnnouncementViewCount(announcement.id)
@@ -107,8 +99,14 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
try {
const res = await intranetService.likeAnnouncement(announcement.id)
if (res.data) {
- setLikes(res.data.likes ?? 0)
- setIsLiked(res.data.isLiked ?? false)
+ const updatedAnnouncement = {
+ ...announcement,
+ likes: res.data.likes ?? 0,
+ isLiked: res.data.isLiked ?? false,
+ }
+ setLikes(updatedAnnouncement.likes ?? 0)
+ setIsLiked(updatedAnnouncement.isLiked ?? false)
+ onLikeChange?.(updatedAnnouncement)
}
} catch {
setIsLiked((prev) => !prev)
@@ -127,25 +125,6 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
const attachments = Array.isArray(announcement.attachments) ? announcement.attachments : []
const category = announcement.category || 'general'
- const imgSrc = (img: string) =>
- img.startsWith('data:') ||
- img.startsWith('http://') ||
- img.startsWith('https://') ||
- img.startsWith('/')
- ? img
- : `data:image/jpeg;base64,${img}`
-
- const getCategoryColor = (category: string) => {
- const colors: Record = {
- general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
- hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
- it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
- 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',
- }
- return colors[category] || colors.general
- }
-
return (
<>
@@ -155,7 +134,7 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
{category === 'general' &&
`📢 ${translate('::App.Platform.Intranet.AnnouncementDetailModal.Category.General')}`}
@@ -197,21 +176,12 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
{announcement.viewCount}{' '}
{translate('::App.Platform.Intranet.AnnouncementDetailModal.Views')}
-
+ isLiked={isLiked}
+ likes={likes}
+ />
@@ -241,7 +211,7 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
})
openLightbox(activePhoto)}
@@ -298,7 +268,7 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
}`}
>
})
@@ -416,7 +386,7 @@ function AnnouncementModalContent({ announcement, onClose }: AnnouncementModalPr
'::App.Platform.Intranet.SocialWall.PostItem.CommentPlaceholder',
)}
rows={1}
- className="flex-1 resize-none px-2 py-1 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-blue-500 transition-colors"
+ className="flex-1 resize-none px-2 py-1 text-sm rounded-xl bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
/>
diff --git a/ui/src/views/intranet/widgets/EventModal.tsx b/ui/src/views/intranet/widgets/EventModal.tsx
index aeaad7e..3d2de65 100644
--- a/ui/src/views/intranet/widgets/EventModal.tsx
+++ b/ui/src/views/intranet/widgets/EventModal.tsx
@@ -1,8 +1,5 @@
import React, { useState, useEffect, useRef } from 'react'
-import { createPortal } from 'react-dom'
-import { motion, AnimatePresence } from 'framer-motion'
import {
- FaTimes,
FaChevronLeft,
FaChevronRight,
FaMapMarkerAlt,
@@ -11,7 +8,6 @@ import {
FaExpand,
FaCommentAlt,
FaPaperPlane,
- FaHeart,
} from 'react-icons/fa'
import { Button, Dialog } from '@/components/ui'
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
@@ -24,26 +20,19 @@ import { intranetService } from '@/services/intranet.service'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useLocalization } from '@/utils/hooks/useLocalization'
+import ImageLightbox from './ImageLightbox'
+import LikeButton from './LikeButton'
+import { imageSrc } from './widgetUtils'
dayjs.extend(relativeTime)
interface EventModalProps {
event: EventDto
onClose: () => void
+ onLikeChange?: (event: EventDto) => 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}`
-}
-
-function EventModalContent({ event, onClose }: EventModalProps) {
+function EventModalContent({ event, onClose, onLikeChange }: EventModalProps) {
const currentLocale = useLocale()
const photos = (event.photos || '').split('|').filter(Boolean)
const { translate } = useLocalization()
@@ -108,8 +97,14 @@ function EventModalContent({ event, onClose }: EventModalProps) {
try {
const res = await intranetService.likeEvent(event.id)
if (res.data) {
- setLikes(res.data.likes)
- setIsLiked(res.data.isLiked)
+ const updatedEvent = {
+ ...event,
+ likes: res.data.likes,
+ isLiked: res.data.isLiked,
+ }
+ setLikes(updatedEvent.likes)
+ setIsLiked(updatedEvent.isLiked)
+ onLikeChange?.(updatedEvent)
}
} catch {
// Revert on error
@@ -127,16 +122,6 @@ function EventModalContent({ event, onClose }: EventModalProps) {
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 (
<>
@@ -165,20 +150,12 @@ function EventModalContent({ event, onClose }: EventModalProps) {
)}
-
-
- {likes > 0 && likes}
-
+ isLiked={isLiked}
+ likes={likes}
+ />
@@ -206,7 +183,7 @@ function EventModalContent({ event, onClose }: EventModalProps) {
{/* Main photo */}
})
openLightbox(activePhoto)}
@@ -260,7 +237,7 @@ function EventModalContent({ event, onClose }: EventModalProps) {
}`}
>
})
@@ -336,7 +313,7 @@ function EventModalContent({ event, onClose }: EventModalProps) {
onKeyDown={handleKeyDown}
placeholder={translate('::App.Intranet.Events.EventAttendance')}
rows={1}
- className="flex-1 resize-none px-2 py-1 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-2 py-1 text-sm rounded-xl bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
/>
- {/* Lightbox */}
- {createPortal(
-
- {lightboxOpen && (
- <>
-
-
-
}
- className="absolute top-4 right-4 z-10 !h-12 !w-12 !px-0 text-white transition-colors hover:text-gray-300"
- />
- {photos.length > 1 && (
- <>
-
}
- className="absolute left-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
- />
-
}
- className="absolute right-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
- />
- >
- )}
-
e.stopPropagation()}
- />
- {photos.length > 1 && (
-
- {photos.map((_, idx) => (
- {
- e.stopPropagation()
- setLightboxIndex(idx)
- }}
- variant="plain"
- shape="circle"
- className={`!h-2.5 !w-2.5 !min-w-2.5 !px-0 transition-all ${
- idx === lightboxIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/70'
- }`}
- />
- ))}
-
- )}
-
- >
- )}
- ,
- document.body,
- )}
+
>
)
}
-const EventModal: React.FC = ({ event, onClose }) => {
+const EventModal: React.FC = ({ event, onClose, onLikeChange }) => {
return (
)
}
diff --git a/ui/src/views/intranet/widgets/UpcomingEvents.tsx b/ui/src/views/intranet/widgets/Events.tsx
similarity index 91%
rename from ui/src/views/intranet/widgets/UpcomingEvents.tsx
rename to ui/src/views/intranet/widgets/Events.tsx
index 5e28276..bd62875 100644
--- a/ui/src/views/intranet/widgets/UpcomingEvents.tsx
+++ b/ui/src/views/intranet/widgets/Events.tsx
@@ -7,8 +7,9 @@ import { currentLocalDate } from '@/utils/dateUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Avatar } from '@/components/ui'
import { AVATAR_URL } from '@/constants/app.constant'
+import { imageSrc } from './widgetUtils'
-interface UpcomingEventsProps {
+interface EventsProps {
events: EventDto[]
onEventClick?: (event: EventDto) => void
}
@@ -19,18 +20,7 @@ const getFirstPhoto = (photos?: string): string | null => {
return parts.length > 0 ? parts[0] : null
}
-const photoSrc = (img: string) => {
- if (
- img.startsWith('data:') ||
- img.startsWith('http://') ||
- img.startsWith('https://') ||
- img.startsWith('/')
- )
- return img
- return `data:image/jpeg;base64,${img}`
-}
-
-const UpcomingEvents: React.FC = ({ events, onEventClick }) => {
+const Events: React.FC = ({ events, onEventClick }) => {
const currentLocale = useLocale()
const { translate } = useLocalization()
@@ -96,7 +86,7 @@ const UpcomingEvents: React.FC = ({ events, onEventClick })
{firstPhoto && (
})
@@ -120,5 +110,5 @@ const UpcomingEvents: React.FC
= ({ events, onEventClick })
)
}
-export default UpcomingEvents
+export default Events
diff --git a/ui/src/views/intranet/widgets/ImageLightbox.tsx b/ui/src/views/intranet/widgets/ImageLightbox.tsx
new file mode 100644
index 0000000..7803834
--- /dev/null
+++ b/ui/src/views/intranet/widgets/ImageLightbox.tsx
@@ -0,0 +1,104 @@
+import { createPortal } from 'react-dom'
+import { motion, AnimatePresence } from 'framer-motion'
+import { FaChevronLeft, FaChevronRight, FaTimes } from 'react-icons/fa'
+import { Button } from '@/components/ui'
+import type { MouseEvent } from 'react'
+
+interface ImageLightboxProps {
+ images: string[]
+ activeIndex: number
+ isOpen: boolean
+ altPrefix: string
+ getSrc: (img: string) => string
+ onClose: () => void
+ onChangeIndex: (index: number) => void
+}
+
+const ImageLightbox = ({
+ images,
+ activeIndex,
+ isOpen,
+ altPrefix,
+ getSrc,
+ onClose,
+ onChangeIndex,
+}: ImageLightboxProps) => {
+ const changeBy = (delta: number) => (event: MouseEvent) => {
+ event.stopPropagation()
+ onChangeIndex((activeIndex + delta + images.length) % images.length)
+ }
+
+ return createPortal(
+
+ {isOpen && (
+ <>
+
+
+
}
+ className="absolute top-4 right-4 z-10 !h-12 !w-12 !px-0 text-white transition-colors hover:text-gray-300"
+ />
+ {images.length > 1 && (
+ <>
+
}
+ className="absolute left-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
+ />
+
}
+ className="absolute right-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white transition-colors hover:!bg-black/70"
+ />
+ >
+ )}
+
event.stopPropagation()}
+ />
+ {images.length > 1 && (
+
+ {images.map((_, index) => (
+ {
+ event.stopPropagation()
+ onChangeIndex(index)
+ }}
+ variant="plain"
+ shape="circle"
+ className={`!h-2.5 !w-2.5 !min-w-2.5 !px-0 transition-all ${
+ index === activeIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/70'
+ }`}
+ />
+ ))}
+
+ )}
+
+ >
+ )}
+ ,
+ document.body,
+ )
+}
+
+export default ImageLightbox
diff --git a/ui/src/views/intranet/widgets/LikeButton.tsx b/ui/src/views/intranet/widgets/LikeButton.tsx
new file mode 100644
index 0000000..e752cc1
--- /dev/null
+++ b/ui/src/views/intranet/widgets/LikeButton.tsx
@@ -0,0 +1,25 @@
+import { FaHeart } from 'react-icons/fa'
+import { Button } from '@/components/ui'
+
+interface LikeButtonProps {
+ likes?: number
+ isLiked?: boolean
+ disabled?: boolean
+ onClick: () => void
+}
+
+const LikeButton = ({ likes = 0, isLiked = false, disabled, onClick }: LikeButtonProps) => (
+
+
+ {likes > 0 && {likes}}
+
+)
+
+export default LikeButton
diff --git a/ui/src/views/intranet/widgets/SurveyModal.tsx b/ui/src/views/intranet/widgets/SurveyModal.tsx
index 7b80d7d..3004b87 100644
--- a/ui/src/views/intranet/widgets/SurveyModal.tsx
+++ b/ui/src/views/intranet/widgets/SurveyModal.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { Button, Dialog } from '@/components/ui'
+import { Button, Dialog, Rate } from '@/components/ui'
import { useDialogContext } from '@/components/ui/Dialog/Dialog'
import { SurveyAnswerDto, SurveyDto, SurveyQuestionDto } from '@/proxy/intranet/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
@@ -93,24 +93,14 @@ function SurveyModalContent({ survey, onClose, onSubmit }: SurveyModalProps) {
-
- {[1, 2, 3, 4, 5].map((star) => (
- handleAnswerChange(question.id, star)}
- variant="plain"
- shape="circle"
- className={`!h-8 !w-8 !px-0 text-2xl transition-colors ${
- (answers[question.id] ?? 0) >= star
- ? 'text-yellow-400'
- : 'text-gray-300 dark:text-gray-600 hover:text-yellow-300'
- }`}
- >
- ★
-
- ))}
-
+ handleAnswerChange(question.id, value)}
+ allowClear={false}
+ size={20}
+ gap={8}
+ color="yellow"
+ />
{hasError && (
{errors[question.id]}
)}
@@ -246,7 +236,7 @@ function SurveyModalContent({ survey, onClose, onSubmit }: SurveyModalProps) {
@@ -277,7 +267,7 @@ function SurveyModalContent({ survey, onClose, onSubmit }: SurveyModalProps) {
diff --git a/ui/src/views/intranet/widgets/Surveys.tsx b/ui/src/views/intranet/widgets/Surveys.tsx
index e125436..ccf95d0 100644
--- a/ui/src/views/intranet/widgets/Surveys.tsx
+++ b/ui/src/views/intranet/widgets/Surveys.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState } from 'react'
import { FaClipboardCheck, FaQuestionCircle, FaUsers, FaClock, FaArrowRight } from 'react-icons/fa'
import dayjs from 'dayjs'
import { SurveyDto } from '@/proxy/intranet/models'
@@ -15,6 +15,7 @@ interface SurveysProps {
const Surveys: React.FC = ({ surveys, onTakeSurvey }) => {
const currentLocale = useLocale()
const { translate } = useLocalization()
+ const [hoveredSurveyId, setHoveredSurveyId] = useState(null)
return (
@@ -32,20 +33,31 @@ const Surveys: React.FC
= ({ surveys, onTakeSurvey }) => {
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal'
const isCompleted = !!survey.myResponse
+ const isHovered = hoveredSurveyId === survey.id
return (
onTakeSurvey(survey)}
- className={`group relative p-5 rounded-xl border cursor-pointer transition-all duration-300 hover:shadow-lg hover:-translate-y-1 ${
+ onMouseEnter={() => setHoveredSurveyId(survey.id)}
+ onMouseLeave={() => setHoveredSurveyId(null)}
+ className={`relative p-5 rounded-xl border cursor-pointer transition-all duration-300 ${
+ isHovered ? '-translate-y-1 shadow-lg' : ''
+ } ${
isCompleted
- ? 'bg-green-50 dark:bg-green-900/30 border-green-300 dark:border-green-700 hover:border-green-400 dark:hover:border-green-500'
- : 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-500'
+ ? `bg-green-50 dark:bg-green-900/30 border-green-300 dark:border-green-700 ${
+ isHovered ? 'border-green-400 dark:border-green-500' : ''
+ }`
+ : `bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 ${
+ isHovered ? 'border-purple-300 dark:border-purple-500' : ''
+ }`
}`}
>
{/* Background gradient on hover */}
= ({ surveys, onTakeSurvey }) => {
{survey.title}
@@ -167,9 +183,15 @@ const Surveys: React.FC = ({ surveys, onTakeSurvey }) => {
+
}
- className={`w-full !h-auto !justify-center gap-2 !rounded-lg !px-4 !py-3 text-sm font-medium text-white shadow-sm transition-all duration-300 hover:shadow-md group-hover:scale-[1.02] focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 dark:focus:ring-purple-400 ${
+ className={`w-full !h-auto !justify-center gap-2 !rounded-lg !px-4 !py-3 text-sm font-medium text-white shadow-sm transition-all duration-300 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 dark:focus:ring-purple-400 ${
+ isHovered ? 'scale-[1.02]' : ''
+ } ${
isCompleted
? 'bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 dark:from-green-600 dark:to-emerald-600 dark:hover:from-green-700 dark:hover:to-emerald-700'
: 'bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 dark:from-purple-700 dark:to-pink-700 dark:hover:from-purple-800 dark:hover:to-pink-800'
diff --git a/ui/src/views/intranet/widgets/widgetUtils.ts b/ui/src/views/intranet/widgets/widgetUtils.ts
new file mode 100644
index 0000000..d8356c2
--- /dev/null
+++ b/ui/src/views/intranet/widgets/widgetUtils.ts
@@ -0,0 +1,24 @@
+export const imageSrc = (img: string) => {
+ if (
+ img.startsWith('data:') ||
+ img.startsWith('http://') ||
+ img.startsWith('https://') ||
+ img.startsWith('/')
+ ) {
+ return img
+ }
+
+ return `data:image/jpeg;base64,${img}`
+}
+
+export const categoryColor = (category?: string) => {
+ const colors: Record = {
+ general: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
+ hr: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
+ it: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300',
+ 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',
+ }
+
+ return colors[category || 'general'] || colors.general
+}