Messenger Widget güncellemesi
This commit is contained in:
parent
8118404895
commit
e96498645a
10 changed files with 75 additions and 65 deletions
|
|
@ -7,6 +7,7 @@ import SidePanel from '@/components/template/SidePanel'
|
|||
import UserDropdown from '@/components/template/UserDropdown'
|
||||
import { useStoreState } from '@/store'
|
||||
import LanguageSelector from '../template/LanguageSelector'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
import Search from '../template/Search'
|
||||
import StackedSideNav from '../template/StackedSideNav'
|
||||
import AiAssistant from '../template/AiAssistant'
|
||||
|
|
@ -27,6 +28,7 @@ const HeaderActionsEnd = () => {
|
|||
<>
|
||||
<AiAssistant />
|
||||
<Notification />
|
||||
<MessengerWidget />
|
||||
<LanguageSelector />
|
||||
<SidePanel className="hidden" />
|
||||
<UserDropdown hoverable={false} />
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import MobileNav from '@/components/template/MobileNav'
|
|||
import View from '@/views/Views'
|
||||
import Search from '@/components/template/Search'
|
||||
import LanguageSelector from '../template/LanguageSelector'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
import AiAssistant from '../template/AiAssistant'
|
||||
|
||||
const HeaderActionsStart = () => {
|
||||
|
|
@ -25,6 +26,7 @@ const HeaderActionsEnd = () => {
|
|||
<AiAssistant />
|
||||
<Search />
|
||||
<Notification />
|
||||
<MessengerWidget />
|
||||
<LanguageSelector />
|
||||
<SidePanel className="hidden" />
|
||||
<UserDropdown hoverable={false} />
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import useLocale from '@/utils/hooks/useLocale'
|
|||
import { useDynamicRoutes } from '@/routes/dynamicRoutesContext'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { hasSubdomain } from '@/utils/subdomain'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
|
||||
export type LayoutType =
|
||||
| typeof LAYOUT_TYPE_CLASSIC
|
||||
|
|
@ -103,7 +102,6 @@ const Layout = () => {
|
|||
}
|
||||
>
|
||||
<AppLayout />
|
||||
{authenticated && currentPath.includes('/admin') && <MessengerWidget />}
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import SidePanel from '@/components/template/SidePanel'
|
|||
import UserDropdown from '@/components/template/UserDropdown'
|
||||
import View from '@/views/Views'
|
||||
import LanguageSelector from '../template/LanguageSelector'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
import { useStoreState } from '@/store'
|
||||
import StackedSideNav from '../template/StackedSideNav'
|
||||
import AiAssistant from '../template/AiAssistant'
|
||||
|
|
@ -27,6 +28,7 @@ const HeaderActionsEnd = () => {
|
|||
<>
|
||||
<AiAssistant />
|
||||
<Notification />
|
||||
<MessengerWidget />
|
||||
<LanguageSelector />
|
||||
<SidePanel className="hidden" />
|
||||
<UserDropdown hoverable={false} />
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import View from '@/views/Views'
|
|||
import Notification from '@/components/template/Notification'
|
||||
import Search from '@/components/template/Search'
|
||||
import LanguageSelector from '../template/LanguageSelector'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
import AiAssistant from '../template/AiAssistant'
|
||||
|
||||
const HeaderActionsStart = () => {
|
||||
|
|
@ -25,6 +26,7 @@ const HeaderActionsEnd = () => {
|
|||
<Search />
|
||||
<AiAssistant />
|
||||
<Notification />
|
||||
<MessengerWidget />
|
||||
<LanguageSelector />
|
||||
<SidePanel className="hidden" />
|
||||
<UserDropdown hoverable={false} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import View from '@/views/Views'
|
|||
import Search from '@/components/template/Search'
|
||||
import Notification from '@/components/template/Notification'
|
||||
import LanguageSelector from '../template/LanguageSelector'
|
||||
import MessengerWidget from '../template/MessengerWidget'
|
||||
import AiAssistant from '../template/AiAssistant'
|
||||
|
||||
const HeaderActionsStart = () => {
|
||||
|
|
@ -23,6 +24,7 @@ const HeaderActionsEnd = () => {
|
|||
<>
|
||||
<AiAssistant />
|
||||
<Notification />
|
||||
<MessengerWidget />
|
||||
<LanguageSelector />
|
||||
<SidePanel className="hidden" />
|
||||
<UserDropdown hoverable={false} />
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ import {
|
|||
} from '@/services/messenger.service'
|
||||
import { messengerSignalR } from '@/services/messenger.signalr'
|
||||
import { useStoreState } from '@/store'
|
||||
import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
||||
import dayjs from 'dayjs'
|
||||
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
FaArrowLeft,
|
||||
FaCompress,
|
||||
FaExpand,
|
||||
FaPaperclip,
|
||||
FaRegSmile,
|
||||
FaSearch,
|
||||
|
|
@ -33,6 +33,8 @@ import {
|
|||
} from 'react-icons/fa'
|
||||
import { IoCheckmarkDone, IoChatbubbleEllipsesOutline, IoSend } from 'react-icons/io5'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import useThemeClass from '@/utils/hooks/useThemeClass'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
||||
const CONTACT_REFRESH_INTERVAL = 30000
|
||||
|
|
@ -64,13 +66,15 @@ const mergeMessages = (
|
|||
)
|
||||
}
|
||||
|
||||
const MessengerWidget = () => {
|
||||
const MessengerWidgetContent = ({ className }: { className?: string }) => {
|
||||
const location = useLocation()
|
||||
const auth = useStoreState((state) => state.auth)
|
||||
const tenantId = auth.user.tenantId || auth.tenant?.tenantId
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const messageInputRef = useRef<HTMLTextAreaElement>(null)
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const { translate } = useLocalization()
|
||||
const { textTheme } = useThemeClass()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [contacts, setContacts] = useState<MessengerContactDto[]>([])
|
||||
|
|
@ -83,7 +87,6 @@ const MessengerWidget = () => {
|
|||
const [uploading, setUploading] = useState(false)
|
||||
const [showEmoji, setShowEmoji] = useState(false)
|
||||
const [multiSelect, setMultiSelect] = useState(false)
|
||||
const [maximized, setMaximized] = useState(false)
|
||||
const [unreadByContact, setUnreadByContact] = useState<Record<string, number>>({})
|
||||
const [reachableContactIds, setReachableContactIds] = useState<string[]>([])
|
||||
|
||||
|
|
@ -250,14 +253,24 @@ const MessengerWidget = () => {
|
|||
})
|
||||
}
|
||||
|
||||
const closeWidget = () => {
|
||||
const closeWidget = useCallback(() => {
|
||||
setOpen(false)
|
||||
setMaximized(false)
|
||||
setSelectedIds([])
|
||||
setText('')
|
||||
setAttachments([])
|
||||
setShowEmoji(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') closeWidget()
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [closeWidget, open])
|
||||
|
||||
const handleFiles = async (files: FileList | null) => {
|
||||
if (!files?.length) return
|
||||
|
|
@ -349,11 +362,10 @@ const MessengerWidget = () => {
|
|||
setText((current) => `${current}${emoji.emoji}`)
|
||||
}
|
||||
|
||||
if (!auth.session.signedIn) return null
|
||||
if (!auth.session.signedIn || !location.pathname.startsWith('/admin')) return null
|
||||
|
||||
const panelClassName = maximized
|
||||
? 'pointer-events-auto fixed inset-2 z-[51] overflow-hidden rounded-xl border border-gray-200 bg-white/80 shadow-[0_24px_80px_rgba(15,23,42,0.35)] backdrop-blur-xl dark:border-gray-700 dark:bg-gray-900/85 sm:inset-4'
|
||||
: 'pointer-events-auto fixed inset-x-2 bottom-20 top-3 z-[51] overflow-hidden rounded-xl border border-gray-200 bg-white/80 shadow-[0_24px_80px_rgba(15,23,42,0.35)] backdrop-blur-xl dark:border-gray-700 dark:bg-gray-900/85 sm:inset-auto sm:bottom-20 sm:right-5 sm:h-[min(75vh,680px)] sm:w-[min(78vw,800px)]'
|
||||
const panelClassName =
|
||||
'pointer-events-auto fixed inset-2 z-[51] overflow-hidden rounded-xl border border-gray-200 bg-white/80 shadow-[0_24px_80px_rgba(15,23,42,0.35)] backdrop-blur-xl dark:border-gray-700 dark:bg-gray-900/85 sm:inset-4'
|
||||
|
||||
const headerActionClassName =
|
||||
'text-gray-600 hover:bg-gray-900/10 hover:text-gray-900 active:bg-gray-900/15 dark:text-gray-200 dark:hover:bg-white/10 dark:hover:text-white dark:active:bg-white/15'
|
||||
|
|
@ -405,21 +417,6 @@ const MessengerWidget = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1 sm:hidden">
|
||||
<Tooltip
|
||||
title={
|
||||
maximized
|
||||
? translate('::MessengerWidget.Minimize')
|
||||
: translate('::MessengerWidget.Maximize')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="plain"
|
||||
icon={maximized ? <FaCompress /> : <FaExpand />}
|
||||
className={headerActionClassName}
|
||||
onClick={() => setMaximized((value) => !value)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="plain"
|
||||
|
|
@ -570,18 +567,6 @@ const MessengerWidget = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="plain"
|
||||
icon={maximized ? <FaCompress /> : <FaExpand />}
|
||||
className={headerActionClassName}
|
||||
onClick={() => setMaximized((value) => !value)}
|
||||
title={
|
||||
maximized
|
||||
? translate('::MessengerWidget.Minimize')
|
||||
: translate('::MessengerWidget.Maximize')
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="plain"
|
||||
|
|
@ -792,27 +777,42 @@ const MessengerWidget = () => {
|
|||
</>
|
||||
)}
|
||||
|
||||
<div className="fixed bottom-5 right-5 z-[52]">
|
||||
<Tooltip title={translate('::MessengerWidget.Title')}>
|
||||
<span className="pointer-events-auto relative inline-flex">
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
variant="solid"
|
||||
shape="circle"
|
||||
icon={<IoChatbubbleEllipsesOutline />}
|
||||
onClick={() => (open ? closeWidget() : setOpen(true))}
|
||||
/>
|
||||
{unread > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex min-h-[20px] min-w-[20px] items-center justify-center rounded-full bg-red-500 px-1.5 text-xs font-semibold leading-none text-white ring-2 ring-white dark:ring-gray-800">
|
||||
{unread > 99 ? '99+' : unread}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip title={translate('::MessengerWidget.Title')}>
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
textTheme,
|
||||
'pointer-events-auto relative !mx-0 text-2xl transition-colors duration-300 ease-in-out hover:bg-black hover:bg-opacity-5 dark:hover:bg-black dark:hover:bg-opacity-40',
|
||||
)}
|
||||
aria-label={translate('::MessengerWidget.Title')}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => (open ? closeWidget() : setOpen(true))}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
if (open) {
|
||||
closeWidget()
|
||||
} else {
|
||||
setOpen(true)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IoChatbubbleEllipsesOutline />
|
||||
{unread > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex min-h-[15px] min-w-[15px] items-center justify-center rounded-full bg-red-500 px-1.5 text-xs font-semibold leading-none text-white ring-2 ring-white dark:ring-gray-800">
|
||||
{unread > 99 ? '99+' : unread}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const MessengerWidgetHeaderItem = withHeaderItem(MessengerWidgetContent)
|
||||
|
||||
const MessengerWidget = () => <MessengerWidgetHeaderItem hoverable={false} />
|
||||
|
||||
export default MessengerWidget
|
||||
|
|
|
|||
|
|
@ -111,12 +111,12 @@ const StyleSwitcher = ({ onStyleChange }: { onStyleChange?: () => void }) => {
|
|||
|
||||
return (
|
||||
<Select
|
||||
menuPlacement="top"
|
||||
components={{
|
||||
Option: CustomSelectOption,
|
||||
Control: CustomControl,
|
||||
}}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={menuItemHeight * 7}
|
||||
options={styleMapOptions}
|
||||
value={styleMapOptions.find((o) => o.value === style)}
|
||||
onChange={(option: any) => {
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
|
|||
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
|
||||
<LayoutSwitcher />
|
||||
</div>
|
||||
<div>
|
||||
<h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6>
|
||||
<StyleSwitcher onStyleChange={handleStyleChange} />
|
||||
</div>
|
||||
<div>
|
||||
<h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6>
|
||||
<NavModeSwitcher />
|
||||
</div>
|
||||
<div>
|
||||
<h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6>
|
||||
<StyleSwitcher onStyleChange={handleStyleChange} />
|
||||
</div>
|
||||
<div>
|
||||
<h6 className="mb-3">{translate('::SidePanel.Themed')}</h6>
|
||||
<ThemeSwitcher />
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ const ThemeSwitcher = () => {
|
|||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Select<ColorList>
|
||||
menuPlacement="top"
|
||||
size="sm"
|
||||
options={colorList}
|
||||
components={{
|
||||
|
|
@ -114,6 +115,7 @@ const ThemeSwitcher = () => {
|
|||
onChange={(opt) => onThemeColorChange(opt as ColorList)}
|
||||
/>
|
||||
<Select<ColorLevelList>
|
||||
menuPlacement="top"
|
||||
size="sm"
|
||||
options={colorLevelList}
|
||||
value={colorLevelList.filter((color) => color.value === primaryColorLevel)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue