feat: Phase 2 — Delegated State, Auth, Review-Gate, Notifications, Zombie-Reset
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Notification Store – Polls unread count and notifications from the API.
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { apiFetch } from '../services/api'
|
||||
|
||||
export interface NotificationItem {
|
||||
id: string
|
||||
type: string // "task_assigned", "task_review", "task_blocked"
|
||||
title: string
|
||||
message: string | null
|
||||
forUser: string
|
||||
taskId: string | null
|
||||
isRead: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface UnreadCount {
|
||||
count: number
|
||||
}
|
||||
|
||||
export const useNotificationStore = defineStore('notifications', {
|
||||
state: () => ({
|
||||
notifications: [] as NotificationItem[],
|
||||
unreadCount: 0,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
countRefreshInterval: null as ReturnType<typeof setInterval> | null,
|
||||
listRefreshInterval: null as ReturnType<typeof setInterval> | null,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async fetchNotifications(forUser = 'bao', limit = 50, unreadOnly = false) {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = new URLSearchParams({ forUser, limit: String(limit), unreadOnly: String(unreadOnly) })
|
||||
const res = await apiFetch(`/api/dashboard/notifications?${params}`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
this.notifications = await res.json()
|
||||
this.error = null
|
||||
} catch (err) {
|
||||
console.warn('[NotificationStore] fetchNotifications failed', err)
|
||||
this.error = 'Notifications could not be loaded'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async fetchUnreadCount(forUser = 'bao') {
|
||||
try {
|
||||
const params = new URLSearchParams({ forUser })
|
||||
const res = await apiFetch(`/api/dashboard/notifications/unread-count?${params}`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data: UnreadCount = await res.json()
|
||||
this.unreadCount = data.count
|
||||
} catch (err) {
|
||||
console.warn('[NotificationStore] fetchUnreadCount failed', err)
|
||||
}
|
||||
},
|
||||
|
||||
async markAsRead(id: string) {
|
||||
try {
|
||||
const res = await apiFetch(`/api/dashboard/notifications/${id}/read`, { method: 'PATCH' })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
// Update local state
|
||||
const n = this.notifications.find(n => n.id === id)
|
||||
if (n) n.isRead = true
|
||||
this.unreadCount = Math.max(0, this.unreadCount - 1)
|
||||
} catch (err) {
|
||||
console.warn('[NotificationStore] markAsRead failed', err)
|
||||
}
|
||||
},
|
||||
|
||||
async markAllAsRead(forUser = 'bao') {
|
||||
try {
|
||||
const params = new URLSearchParams({ forUser })
|
||||
const res = await apiFetch(`/api/dashboard/notifications/read-all?${params}`, { method: 'PATCH' })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
this.notifications.forEach(n => { n.isRead = true })
|
||||
this.unreadCount = 0
|
||||
} catch (err) {
|
||||
console.warn('[NotificationStore] markAllAsRead failed', err)
|
||||
}
|
||||
},
|
||||
|
||||
startPolling(forUser = 'bao') {
|
||||
// Unread count polling every 30s (for sidebar badge)
|
||||
if (!this.countRefreshInterval) {
|
||||
this.fetchUnreadCount(forUser)
|
||||
this.countRefreshInterval = setInterval(() => {
|
||||
this.fetchUnreadCount(forUser)
|
||||
}, 30000)
|
||||
}
|
||||
},
|
||||
|
||||
stopPolling() {
|
||||
if (this.countRefreshInterval) {
|
||||
clearInterval(this.countRefreshInterval)
|
||||
this.countRefreshInterval = null
|
||||
}
|
||||
if (this.listRefreshInterval) {
|
||||
clearInterval(this.listRefreshInterval)
|
||||
this.listRefreshInterval = null
|
||||
}
|
||||
},
|
||||
|
||||
startListPolling(forUser = 'bao') {
|
||||
if (!this.listRefreshInterval) {
|
||||
this.fetchNotifications(forUser)
|
||||
this.listRefreshInterval = setInterval(() => {
|
||||
this.fetchNotifications(forUser)
|
||||
}, 30000)
|
||||
}
|
||||
},
|
||||
|
||||
stopListPolling() {
|
||||
if (this.listRefreshInterval) {
|
||||
clearInterval(this.listRefreshInterval)
|
||||
this.listRefreshInterval = null
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user