Private
Public Access
1
0

Implement Phase 5: reconciliation, SignalR notifications, payees, splits & plugins

Add full frontend UI for five backend features: bank reconciliation (3-step
stepper workflow), payee management (CRUD with alias chips), transaction splits
(expandable rows + split editor), plugins (card grid), and real-time SignalR
notifications (bell dropdown with badge). Fix backend PayeeResponse to expose
alias IDs for deletion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 14:14:23 -05:00
parent d0c3e3913c
commit eb49147080
14 changed files with 1084 additions and 18 deletions
@@ -0,0 +1,81 @@
import { ref, computed } from 'vue'
import { HubConnectionBuilder, HubConnection, LogLevel } from '@microsoft/signalr'
import { useAuthStore } from '@/stores/auth'
import type { AppNotification, FileImportNotification } from '@/types'
const notifications = ref<AppNotification[]>([])
const isConnected = ref(false)
let connection: HubConnection | null = null
const unreadCount = computed(() => notifications.value.filter(n => !n.read).length)
function connect() {
if (connection) return
const authStore = useAuthStore()
connection = new HubConnectionBuilder()
.withUrl('/hubs/notifications', {
accessTokenFactory: () => authStore.token || ''
})
.withAutomaticReconnect()
.configureLogging(LogLevel.Warning)
.build()
connection.on('FileImportReady', (data: FileImportNotification) => {
notifications.value.unshift({
id: crypto.randomUUID(),
type: 'FileImportReady',
data,
timestamp: new Date().toISOString(),
read: false
})
})
connection.onclose(() => {
isConnected.value = false
})
connection.onreconnected(() => {
isConnected.value = true
})
connection.start()
.then(() => { isConnected.value = true })
.catch(() => { isConnected.value = false })
}
function disconnect() {
if (connection) {
connection.stop()
connection = null
}
isConnected.value = false
notifications.value = []
}
function markAsRead(id: string) {
const notification = notifications.value.find(n => n.id === id)
if (notification) notification.read = true
}
function markAllAsRead() {
notifications.value.forEach(n => { n.read = true })
}
function clearAll() {
notifications.value = []
}
export function useNotifications() {
return {
notifications,
unreadCount,
isConnected,
connect,
disconnect,
markAsRead,
markAllAsRead,
clearAll
}
}