Claude güncellemesi Notification güncellemesi
This commit is contained in:
parent
42d502f812
commit
3ecb4e2102
9 changed files with 178 additions and 216 deletions
|
|
@ -41,10 +41,15 @@ public override partial NotificationRuleDto Map(NotificationRule source);
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class NotificationToNotificationDtoMapper : MapperBase<Notification, NotificationDto>
|
public partial class NotificationToNotificationDtoMapper : MapperBase<Notification, NotificationDto>
|
||||||
{
|
{
|
||||||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
// NotificationType hedefte string. Kaynaktaki navigation entity'si doğrudan
|
||||||
|
// eşlenirse ToString() devreye girip "[ENTITY: NotificationType] Id = ..."
|
||||||
|
// yazısı UI'a düşüyor; bu yüzden açıkça Name alanından beslenir.
|
||||||
|
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
|
||||||
|
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||||
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
||||||
public override partial NotificationDto Map(Notification source);
|
public override partial NotificationDto Map(Notification source);
|
||||||
|
|
||||||
|
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
|
||||||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||||
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
||||||
public override partial void Map(Notification source, NotificationDto destination);
|
public override partial void Map(Notification source, NotificationDto destination);
|
||||||
|
|
|
||||||
|
|
@ -279,4 +279,20 @@ public static class LookupQueryValues
|
||||||
$"WHERE \"SkillTypeId\" IN (SELECT \"Id\" FROM \"{FullNameTable(TableNameEnum.SkillType)}\" WHERE \"Name\"='Technical Skills' ) " +
|
$"WHERE \"SkillTypeId\" IN (SELECT \"Id\" FROM \"{FullNameTable(TableNameEnum.SkillType)}\" WHERE \"Name\"='Technical Skills' ) " +
|
||||||
$"ORDER BY \"Name\"";
|
$"ORDER BY \"Name\"";
|
||||||
|
|
||||||
|
public static string NotificationIdentityValues =
|
||||||
|
$"SELECT " +
|
||||||
|
$"\"Id\" AS \"Key\", " +
|
||||||
|
$"\"Name\" AS \"Name\" " +
|
||||||
|
$"FROM \"AbpRoles\"" +
|
||||||
|
$"UNION ALL " +
|
||||||
|
$"SELECT " +
|
||||||
|
$"\"Id\" AS \"Key\", " +
|
||||||
|
$"\"UserName\" AS \"Name\" " +
|
||||||
|
$"FROM \"AbpUsers\"" +
|
||||||
|
$"UNION ALL " +
|
||||||
|
$"SELECT " +
|
||||||
|
$"\"Id\" AS \"Key\", " +
|
||||||
|
$"\"DisplayName\" AS \"Name\" " +
|
||||||
|
$"FROM \"AbpOrganizationUnits\"" +
|
||||||
|
$"ORDER BY \"Name\"";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5585,7 +5585,12 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
ListOrderNo = 5,
|
ListOrderNo = 5,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
LookupJson = JsonSerializer.Serialize(new LookupDto {
|
||||||
|
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
||||||
|
DisplayExpr = "Name",
|
||||||
|
ValueExpr = "Key",
|
||||||
|
LookupQuery = LookupQueryValues.NotificationIdentityValues
|
||||||
|
}),
|
||||||
AllowSearch = true,
|
AllowSearch = true,
|
||||||
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import Dropdown from '@/components/ui/Dropdown'
|
||||||
import ScrollBar from '@/components/ui/ScrollBar'
|
import ScrollBar from '@/components/ui/ScrollBar'
|
||||||
import Spinner from '@/components/ui/Spinner'
|
import Spinner from '@/components/ui/Spinner'
|
||||||
import Tooltip from '@/components/ui/Tooltip'
|
import Tooltip from '@/components/ui/Tooltip'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { APP_NAME, AVATAR_URL } from '@/constants/app.constant'
|
||||||
import NotificationChannels from '@/constants/notification-channel.enum'
|
import NotificationChannels from '@/constants/notification-channel.enum'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import {
|
import {
|
||||||
|
|
@ -26,9 +26,9 @@ import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { FaEnvelopeOpen } from 'react-icons/fa'
|
import { FaEnvelopeOpen } from 'react-icons/fa'
|
||||||
|
import { FcAdvertising } from 'react-icons/fc'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Notification as Notify, toast } from '../ui'
|
import { Notification as Notify, toast } from '../ui'
|
||||||
import { FcAdvertising } from 'react-icons/fc'
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
|
@ -45,10 +45,6 @@ type NotificationList = {
|
||||||
const notificationHeight = 'h-72'
|
const notificationHeight = 'h-72'
|
||||||
const notificationInterval = 120000 // 2 minutes
|
const notificationInterval = 120000 // 2 minutes
|
||||||
|
|
||||||
const notificationTypeAvatar = (creatorId: string, tenantId?: string) => {
|
|
||||||
return <Avatar shape="circle" src={AVATAR_URL(creatorId, tenantId)} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const NotificationToggle = ({
|
const NotificationToggle = ({
|
||||||
className,
|
className,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
|
|
@ -78,12 +74,12 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
|
|
||||||
const [notificationList, setNotificationList] = useState<NotificationList[]>([])
|
const [notificationList, setNotificationList] = useState<NotificationList[]>([])
|
||||||
const [unreadNotificationCount, setUnreadNotificationCount] = useState(0)
|
const [unreadNotificationCount, setUnreadNotificationCount] = useState(0)
|
||||||
const [noResult, setNoResult] = useState(false)
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
const toastNotificationList = useRef<string[]>([])
|
// Aynı bildirimin sekme ömrü boyunca ikinci kez toast/desktop olarak
|
||||||
const desktopNotificationList = useRef<string[]>([])
|
// gösterilmesini engeller.
|
||||||
const pushNotificationInterval = useRef<ReturnType<typeof setInterval>>()
|
const pushedIds = useRef(new Set<string>())
|
||||||
|
const listLoaded = useRef(false)
|
||||||
|
|
||||||
const { bgTheme } = useThemeClass()
|
const { bgTheme } = useThemeClass()
|
||||||
const { larger } = useResponsive()
|
const { larger } = useResponsive()
|
||||||
|
|
@ -95,7 +91,7 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
tabHasFocusRef.current = tabHasFocus
|
tabHasFocusRef.current = tabHasFocus
|
||||||
}, [tabHasFocus])
|
}, [tabHasFocus])
|
||||||
|
|
||||||
const getReactNotificationCount = useCallback(async () => {
|
const getUnreadCount = useCallback(async () => {
|
||||||
const resp = await getList({
|
const resp = await getList({
|
||||||
channels: [NotificationChannels.UiActivity],
|
channels: [NotificationChannels.UiActivity],
|
||||||
isListRequest: false,
|
isListRequest: false,
|
||||||
|
|
@ -103,85 +99,9 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
maxResultCount: 1,
|
maxResultCount: 1,
|
||||||
})
|
})
|
||||||
setUnreadNotificationCount(resp.data?.totalCount ?? 0)
|
setUnreadNotificationCount(resp.data?.totalCount ?? 0)
|
||||||
}, [setUnreadNotificationCount])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
const getPushNotifications = useCallback(async () => {
|
||||||
getReactNotificationCount()
|
|
||||||
var intervalId = setInterval(() => {
|
|
||||||
if (tabHasFocusRef.current) {
|
|
||||||
getReactNotificationCount()
|
|
||||||
}
|
|
||||||
}, notificationInterval)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
}
|
|
||||||
}, [getReactNotificationCount])
|
|
||||||
|
|
||||||
const onNotificationOpen = useCallback(async () => {
|
|
||||||
const currentUnread = notificationList.filter((a) => !a.readed).length
|
|
||||||
if (currentUnread !== unreadNotificationCount) {
|
|
||||||
setLoading(true)
|
|
||||||
const resp = await getList({
|
|
||||||
channels: [NotificationChannels.UiActivity],
|
|
||||||
isListRequest: false,
|
|
||||||
maxResultCount: 1000,
|
|
||||||
})
|
|
||||||
const items = resp.data.items ?? []
|
|
||||||
if (items.length > 0) {
|
|
||||||
await updateSentMany(items.map((notification) => notification.id), true)
|
|
||||||
}
|
|
||||||
const newNotificationList = items.map(
|
|
||||||
(a) =>
|
|
||||||
({
|
|
||||||
id: a.id,
|
|
||||||
notificationType: a.notificationType,
|
|
||||||
date: a.creationTime.toLocaleString(),
|
|
||||||
message: a.message,
|
|
||||||
creatorId: a.creatorId,
|
|
||||||
tenantId: a.tenantId,
|
|
||||||
readed: a.isRead,
|
|
||||||
}) as NotificationList,
|
|
||||||
)
|
|
||||||
|
|
||||||
setLoading(false)
|
|
||||||
setNotificationList(newNotificationList)
|
|
||||||
setNoResult(newNotificationList.length == 0)
|
|
||||||
}
|
|
||||||
}, [notificationList, setLoading, unreadNotificationCount])
|
|
||||||
|
|
||||||
const onMarkAllAsRead = useCallback(async () => {
|
|
||||||
await updateReadAll(NotificationChannels.UiToast, true)
|
|
||||||
|
|
||||||
const list = notificationList.map((item: NotificationList) => {
|
|
||||||
if (!item.readed) {
|
|
||||||
item.readed = true
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
setNotificationList(list)
|
|
||||||
setUnreadNotificationCount(0)
|
|
||||||
}, [notificationList])
|
|
||||||
|
|
||||||
const onMarkAsRead = useCallback(
|
|
||||||
async (id: string) => {
|
|
||||||
await updateRead(id, true)
|
|
||||||
|
|
||||||
const list = notificationList.map((item) => {
|
|
||||||
if (item.id === id) {
|
|
||||||
item.readed = true
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
setNotificationList(list)
|
|
||||||
|
|
||||||
const unreadCount = notificationList.filter((item) => !item.readed).length
|
|
||||||
setUnreadNotificationCount(unreadCount)
|
|
||||||
},
|
|
||||||
[notificationList],
|
|
||||||
)
|
|
||||||
|
|
||||||
const getPushNotifications = async () => {
|
|
||||||
const desktopGranted =
|
const desktopGranted =
|
||||||
'Notification' in window && window.Notification.permission === 'granted'
|
'Notification' in window && window.Notification.permission === 'granted'
|
||||||
|
|
||||||
|
|
@ -194,84 +114,130 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
isSent: false,
|
isSent: false,
|
||||||
maxResultCount: 1000,
|
maxResultCount: 1000,
|
||||||
})
|
})
|
||||||
const items = resp.data.items ?? []
|
|
||||||
|
|
||||||
// Toast
|
const pending = (resp.data.items ?? []).filter(
|
||||||
const newToastList = items.filter(
|
(a) => !a.isSent && !pushedIds.current.has(a.id),
|
||||||
(a) =>
|
|
||||||
a.notificationChannel === NotificationChannels.UiToast &&
|
|
||||||
!toastNotificationList.current.includes(a.id) &&
|
|
||||||
!a.isSent,
|
|
||||||
)
|
)
|
||||||
toastNotificationList.current = [
|
if (pending.length === 0) {
|
||||||
...toastNotificationList.current,
|
return
|
||||||
...newToastList.map((a) => a.id),
|
|
||||||
]
|
|
||||||
for (const notification of newToastList) {
|
|
||||||
toast.push(
|
|
||||||
<Notify type="success" duration={0} closable={true}>
|
|
||||||
{notification.message}
|
|
||||||
</Notify>,
|
|
||||||
{ placement: 'bottom-end' },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
pending.forEach((a) => pushedIds.current.add(a.id))
|
||||||
|
|
||||||
// Desktop
|
for (const notification of pending) {
|
||||||
const newDesktopList = desktopGranted
|
if (notification.notificationChannel === NotificationChannels.UiToast) {
|
||||||
? items.filter(
|
toast.push(
|
||||||
(a) =>
|
<Notify type="success" duration={0} closable={true}>
|
||||||
a.notificationChannel === NotificationChannels.Desktop &&
|
{notification.message}
|
||||||
!desktopNotificationList.current.includes(a.id) &&
|
</Notify>,
|
||||||
!a.isSent,
|
{ placement: 'bottom-end' },
|
||||||
)
|
)
|
||||||
: []
|
continue
|
||||||
if (desktopGranted) {
|
}
|
||||||
desktopNotificationList.current = [
|
|
||||||
...desktopNotificationList.current,
|
|
||||||
...newDesktopList.map((a) => a.id),
|
|
||||||
]
|
|
||||||
for (const notification of newDesktopList) {
|
|
||||||
const title = notification.notificationType || 'Bildirim'
|
|
||||||
const options = {
|
|
||||||
body: notification.message,
|
|
||||||
dir: 'ltr',
|
|
||||||
requireInteraction: true,
|
|
||||||
} as NotificationOptions
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
const title = notification.notificationType || APP_NAME
|
||||||
const reg = await navigator.serviceWorker.ready
|
const options: NotificationOptions = {
|
||||||
await reg.showNotification(title, options)
|
body: notification.message,
|
||||||
} else {
|
dir: 'ltr',
|
||||||
new window.Notification(title, options)
|
requireInteraction: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
||||||
|
const reg = await navigator.serviceWorker.ready
|
||||||
|
await reg.showNotification(title, options)
|
||||||
|
} else {
|
||||||
|
new window.Notification(title, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const processedIds = [...newToastList, ...newDesktopList].map((notification) => notification.id)
|
const processedIds = pending.map((a) => a.id)
|
||||||
if (processedIds.length > 0) {
|
await updateSentMany(processedIds, true)
|
||||||
await updateSentMany(processedIds, true)
|
await updateReadMany(processedIds, true)
|
||||||
await updateReadMany(processedIds, true)
|
}, [])
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ('Notification' in window && window.Notification.permission === 'default') {
|
if ('Notification' in window && window.Notification.permission === 'default') {
|
||||||
window.Notification.requestPermission()
|
window.Notification.requestPermission()
|
||||||
}
|
}
|
||||||
|
|
||||||
getPushNotifications()
|
const poll = () => {
|
||||||
|
getUnreadCount()
|
||||||
|
getPushNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
pushNotificationInterval.current = setInterval(() => {
|
poll()
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
if (tabHasFocusRef.current) {
|
if (tabHasFocusRef.current) {
|
||||||
getPushNotifications()
|
poll()
|
||||||
}
|
}
|
||||||
}, notificationInterval)
|
}, notificationInterval)
|
||||||
|
|
||||||
return () => {
|
return () => clearInterval(intervalId)
|
||||||
clearInterval(pushNotificationInterval.current)
|
}, [getUnreadCount, getPushNotifications])
|
||||||
|
|
||||||
|
const onNotificationOpen = useCallback(async () => {
|
||||||
|
const currentUnread = notificationList.filter((a) => !a.readed).length
|
||||||
|
if (listLoaded.current && currentUnread === unreadNotificationCount) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const resp = await getList({
|
||||||
|
channels: [NotificationChannels.UiActivity],
|
||||||
|
isListRequest: false,
|
||||||
|
maxResultCount: 1000,
|
||||||
|
})
|
||||||
|
const items = resp.data.items ?? []
|
||||||
|
if (items.length > 0) {
|
||||||
|
await updateSentMany(
|
||||||
|
items.map((notification) => notification.id),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotificationList(
|
||||||
|
items.map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
notificationType: a.notificationType,
|
||||||
|
date: a.creationTime.toLocaleString(),
|
||||||
|
message: a.message,
|
||||||
|
creatorId: a.creatorId,
|
||||||
|
tenantId: a.tenantId,
|
||||||
|
readed: a.isRead,
|
||||||
|
})) as NotificationList[],
|
||||||
|
)
|
||||||
|
listLoaded.current = true
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [notificationList, unreadNotificationCount])
|
||||||
|
|
||||||
|
const onMarkAllAsRead = useCallback(async () => {
|
||||||
|
await updateReadAll(NotificationChannels.UiActivity, true)
|
||||||
|
|
||||||
|
setNotificationList((list) => list.map((item) => ({ ...item, readed: true })))
|
||||||
|
setUnreadNotificationCount(0)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const onMarkAsRead = useCallback(
|
||||||
|
async (id: string) => {
|
||||||
|
const target = notificationList.find((item) => item.id === id)
|
||||||
|
if (!target || target.readed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateRead(id, true)
|
||||||
|
|
||||||
|
setNotificationList((list) =>
|
||||||
|
list.map((item) => (item.id === id ? { ...item, readed: true } : item)),
|
||||||
|
)
|
||||||
|
setUnreadNotificationCount((count) => Math.max(0, count - 1))
|
||||||
|
},
|
||||||
|
[notificationList],
|
||||||
|
)
|
||||||
|
|
||||||
|
const noResult = !loading && listLoaded.current && notificationList.length === 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={translate('::App.Notifications')}>
|
<Tooltip title={translate('::App.Notifications')}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
|
@ -298,33 +264,34 @@ const _Notification = ({ className }: { className?: string }) => {
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<div className={classNames('overflow-y-auto', notificationHeight)}>
|
<div className={classNames('overflow-y-auto', notificationHeight)}>
|
||||||
<ScrollBar direction={direction}>
|
<ScrollBar direction={direction}>
|
||||||
{notificationList.length > 0 &&
|
{notificationList.map((item, index) => (
|
||||||
notificationList.map((item, index) => (
|
<div
|
||||||
<div
|
key={item.id}
|
||||||
key={item.id}
|
className={`relative flex px-4 py-4 cursor-pointer hover:bg-gray-50 active:bg-gray-100 dark:hover:bg-black dark:hover:bg-opacity-20 ${
|
||||||
className={`relative flex px-4 py-4 cursor-pointer hover:bg-gray-50 active:bg-gray-100 dark:hover:bg-black dark:hover:bg-opacity-20 ${
|
!isLastChild(notificationList, index)
|
||||||
!isLastChild(notificationList, index)
|
? 'border-b border-gray-200 dark:border-gray-600'
|
||||||
? 'border-b border-gray-200 dark:border-gray-600'
|
: ''
|
||||||
: ''
|
}`}
|
||||||
}`}
|
onClick={() => onMarkAsRead(item.id)}
|
||||||
onClick={() => onMarkAsRead(item.id)}
|
>
|
||||||
>
|
<div>
|
||||||
<div>{notificationTypeAvatar(item.creatorId, item.tenantId)}</div>
|
<Avatar shape="circle" src={AVATAR_URL(item.creatorId, item.tenantId)} />
|
||||||
<div className="ltr:ml-3 rtl:mr-3">
|
|
||||||
<div>
|
|
||||||
{item.notificationType && (
|
|
||||||
<span className="font-semibold heading-text">{item.notificationType} </span>
|
|
||||||
)}
|
|
||||||
<div>{item.message}</div>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs">{dayjs(item.date).fromNow()}</span>
|
|
||||||
</div>
|
|
||||||
<Badge
|
|
||||||
className="absolute top-4 ltr:right-4 rtl:left-4 mt-1.5"
|
|
||||||
innerClass={`${item.readed ? 'bg-gray-300' : bgTheme} `}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="ltr:ml-3 rtl:mr-3">
|
||||||
|
<div>
|
||||||
|
{item.notificationType && (
|
||||||
|
<span className="font-semibold heading-text">{item.notificationType} </span>
|
||||||
|
)}
|
||||||
|
<div>{item.message}</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs">{dayjs(item.date).fromNow()}</span>
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
className="absolute top-4 ltr:right-4 rtl:left-4 mt-1.5"
|
||||||
|
innerClass={`${item.readed ? 'bg-gray-300' : bgTheme} `}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className={classNames('flex items-center justify-center', notificationHeight)}>
|
<div className={classNames('flex items-center justify-center', notificationHeight)}>
|
||||||
<Spinner size={40} />
|
<Spinner size={40} />
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import ConfigProvider from '@/components/ui/ConfigProvider'
|
||||||
import { themeConfig } from '@/proxy/theme/theme.config'
|
import { themeConfig } from '@/proxy/theme/theme.config'
|
||||||
import { useStoreActions, useStoreState } from '@/store'
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
import useDarkMode from '@/utils/hooks/useDarkmode'
|
import useDarkMode from '@/utils/hooks/useDarkmode'
|
||||||
import useNotification from '@/utils/hooks/useNotification'
|
|
||||||
import { useSetting } from '@/utils/hooks/useSetting'
|
import { useSetting } from '@/utils/hooks/useSetting'
|
||||||
import useTabFocus from '@/utils/hooks/useTabFocus'
|
import useTabFocus from '@/utils/hooks/useTabFocus'
|
||||||
import { ComponentProps, useEffect } from 'react'
|
import { ComponentProps, useEffect } from 'react'
|
||||||
|
|
@ -74,7 +73,6 @@ const Theme = (props: CommonProps) => {
|
||||||
'en'
|
'en'
|
||||||
useDarkMode()
|
useDarkMode()
|
||||||
useTabFocus()
|
useTabFocus()
|
||||||
useNotification()
|
|
||||||
useAppVersionNotice()
|
useAppVersionNotice()
|
||||||
|
|
||||||
type ConfigProviderValue = NonNullable<ComponentProps<typeof ConfigProvider>['value']>
|
type ConfigProviderValue = NonNullable<ComponentProps<typeof ConfigProvider>['value']>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const [display, setDisplay] = useState('show')
|
// Toast tarafından yönetilmeyen bildirimler kapatma animasyonu
|
||||||
|
// tamamlandıktan sonra kendilerini DOM'dan kaldırır.
|
||||||
|
const [hidden, setHidden] = useState(false)
|
||||||
|
|
||||||
const { clear } = useTimeout(
|
const { clear } = useTimeout(
|
||||||
onClose as () => void,
|
onClose as () => void,
|
||||||
|
|
@ -44,12 +46,11 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
||||||
|
|
||||||
const handleClose = useCallback(
|
const handleClose = useCallback(
|
||||||
(e: MouseEvent<HTMLSpanElement>) => {
|
(e: MouseEvent<HTMLSpanElement>) => {
|
||||||
setDisplay('hiding')
|
|
||||||
onClose?.(e)
|
onClose?.(e)
|
||||||
clear()
|
clear()
|
||||||
if (!triggerByToast) {
|
if (!triggerByToast) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDisplay('hide')
|
setHidden(true)
|
||||||
}, 400)
|
}, 400)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -58,7 +59,7 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
||||||
|
|
||||||
const notificationClass = classNames('notification', className)
|
const notificationClass = classNames('notification', className)
|
||||||
|
|
||||||
if (display === 'hide') {
|
if (hidden) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,6 @@ export const updateReadAll = (notificationChannel: string, isRead: boolean) =>
|
||||||
params: { notificationChannel, isRead },
|
params: { notificationChannel, isRead },
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateSent = (notificationId: string, isSent: boolean) =>
|
|
||||||
apiService.fetchData<NotificationDto>({
|
|
||||||
method: 'PUT',
|
|
||||||
url: `/api/app/notification/sent/${notificationId}`,
|
|
||||||
params: { isSent },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateReadMany = (notificationIds: string[], isRead: boolean) =>
|
export const updateReadMany = (notificationIds: string[], isRead: boolean) =>
|
||||||
apiService.fetchData<number, string[]>({
|
apiService.fetchData<number, string[]>({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
function useNotification() {
|
|
||||||
useEffect(() => {
|
|
||||||
if (window === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('Notification' in window)) {
|
|
||||||
console.log('This browser does not support desktop notification')
|
|
||||||
} else {
|
|
||||||
Notification.requestPermission()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useNotification
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Field, Form, Formik, FormikHelpers } from 'formik'
|
import { Field, Form, Formik, FormikHelpers } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
export interface NotificationDto {
|
interface CreateNotificationValues {
|
||||||
id: string
|
id: string
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
@ -34,34 +34,28 @@ function CreateNotification({
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const handleSubmit = async (
|
const handleSubmit = async (
|
||||||
values: NotificationDto,
|
values: CreateNotificationValues,
|
||||||
{ setSubmitting }: FormikHelpers<NotificationDto>,
|
{ setSubmitting }: FormikHelpers<CreateNotificationValues>,
|
||||||
) => {
|
) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitting(true)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await postMyNotificationByNotificationRuleId({ id, message: values.message })
|
await postMyNotificationByNotificationRuleId({ id, message: values.message })
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="success" duration={2000}>
|
<Notification type="success" duration={2000}>
|
||||||
{translate('::Kaydet')}
|
{translate('::App.Platform.Success')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{
|
{ placement: 'bottom-end' },
|
||||||
placement: 'bottom-end',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
onDialogClose()
|
onDialogClose()
|
||||||
} catch {
|
} catch {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="danger" duration={2000}>
|
<Notification type="danger" duration={2000}>
|
||||||
{'Hata'}
|
{translate('::App.Platform.Error')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{
|
{ placement: 'bottom-end' },
|
||||||
placement: 'bottom-end',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue