df94ed3cd4
- GatewayBridgeController: MCP-artiger Kommando-Adapter für Agent-zu-Backend - TaskBridgeService + LiveUpdateService: SSE Live-Sync + Bridge-Kommandos - FlowBoard.vue: Board-first orchestration dashboard panel - live-sync.ts store + live.ts service: SSE-basierte Live-Updates - Nullability-Warnung in HealthController.cs gefixt - nginx.conf: SSE-Proxy + CORS für Bridge-Endpunkte - .gitignore: pnpm/corepack local caches ausgeschlossen - docs: architecture-board-first-orchestration.md hinzugefügt - README: Backend Bridge API dokumentiert
123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
/**
|
||
* 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') {
|
||
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
|
||
}
|
||
},
|
||
},
|
||
})
|