feat(#36): Notification rework with single and all comments mark as read

This commit is contained in:
2026-01-18 18:42:10 +01:00
parent 105baf7c86
commit db788c4ee3
31 changed files with 711 additions and 94 deletions

View File

@@ -0,0 +1,193 @@
import { defineStore } from 'pinia'
import type { NotificationDto } from '~~/.api-client'
import { useNotificationApi } from '~/composables/notification/useNotificationApi'
import { useLogger } from '~/composables/useLogger'
import { useUserStore } from './useUserStore'
export const useNotificationStore = defineStore('Notification', () => {
const logger = useLogger().withTag('notificationStore')
const userStore = useUserStore()
const { user } = useUserSession()
const {
getNotifications,
getUnreadNotifications,
getUnreadNotificationCount,
markAllNotificationsAsRead,
markNotificationAsRead,
clearAllNotifications,
deleteNotification
} = useNotificationApi()
// State
const notifications = ref<NotificationDto[]>([])
const unreadNotifications = ref<NotificationDto[]>([])
const unreadCount = ref<number>(0)
const isLoading = ref(false)
// Getters
const hasUnread = computed(() => unreadCount.value > 0)
const organizationId = computed(() => userStore.selectedOrganization?.id)
const userId = computed(() => user.value?.keycloakId)
// Actions
async function fetchNotifications(page: number = 0, size: number = 20) {
if (!organizationId.value) {
logger.warn('No organization selected')
return
}
isLoading.value = true
try {
const response = await getNotifications(organizationId.value, page, size)
notifications.value = response.content || []
return response
} catch (error) {
logger.error('Failed to fetch notifications:', error)
throw error
} finally {
isLoading.value = false
}
}
async function fetchUnreadNotifications() {
if (!organizationId.value) {
logger.warn('No organization selected')
return
}
try {
const response = await getUnreadNotifications(organizationId.value)
unreadNotifications.value = response || []
return response
} catch (error) {
logger.error('Failed to fetch unread notifications:', error)
throw error
}
}
async function fetchUnreadCount() {
if (!userId.value || !organizationId.value) {
logger.warn('No user or organization selected')
return
}
try {
const count = await getUnreadNotificationCount(userId.value, organizationId.value)
unreadCount.value = count || 0
return count
} catch (error) {
logger.error('Failed to fetch unread count:', error)
throw error
}
}
async function markAllAsRead() {
if (!organizationId.value) {
logger.warn('No organization selected')
return
}
try {
await markAllNotificationsAsRead(organizationId.value)
unreadCount.value = 0
unreadNotifications.value = []
notifications.value = notifications.value.map((n) => ({ ...n, isRead: true }))
} catch (error) {
logger.error('Failed to mark all as read:', error)
throw error
}
}
async function markAsRead(notificationId: string) {
if (!organizationId.value) {
logger.warn('No organization selected')
return
}
try {
await markNotificationAsRead(notificationId, organizationId.value)
const index = notifications.value.findIndex((n) => n.id === notificationId)
if (index !== -1 && notifications.value[index]) {
notifications.value[index].isRead = true
}
// Remove from unread notifications
unreadNotifications.value = unreadNotifications.value.filter((n) => n.id !== notificationId)
if (unreadCount.value > 0) {
unreadCount.value--
}
} catch (error) {
logger.error('Failed to mark notification as read:', error)
throw error
}
}
async function handleNotificationClick(notification: NotificationDto) {
if (!notification.isRead) {
await markAsRead(notification.id)
}
if (notification.clickTarget) {
await navigateTo(notification.clickTarget)
}
}
function startPeriodicRefresh(intervalMs: number = 30000) {
const interval = setInterval(() => {
void fetchUnreadCount()
}, intervalMs)
onUnmounted(() => {
clearInterval(interval)
})
return interval
}
async function deleteAllNotifications() {
try {
await clearAllNotifications()
notifications.value = []
unreadNotifications.value = []
unreadCount.value = 0
} catch (error) {
logger.error('Failed to delete all notifications:', error)
throw error
}
}
async function deleteSingleNotification(notificationId: string) {
try {
// Check if notification was unread before deleting
const notification = notifications.value.find((n) => n.id === notificationId)
const wasUnread = notification && !notification.isRead
await deleteNotification(notificationId)
notifications.value = notifications.value.filter((n) => n.id !== notificationId)
unreadNotifications.value = unreadNotifications.value.filter((n) => n.id !== notificationId)
// Update unread count if the deleted notification was unread
if (wasUnread && unreadCount.value > 0) {
unreadCount.value--
}
} catch (error) {
logger.error('Failed to delete notification:', error)
throw error
}
}
return {
// State
notifications,
unreadNotifications,
unreadCount,
isLoading,
// Getters
hasUnread,
// Actions
fetchNotifications,
fetchUnreadNotifications,
fetchUnreadCount,
markAllAsRead,
markAsRead,
handleNotificationClick,
startPeriodicRefresh,
deleteAllNotifications,
deleteSingleNotification
}
})