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:
@@ -52,7 +52,37 @@
|
||||
:icon="isDark ? 'mdi-weather-sunny' : 'mdi-weather-night'"
|
||||
@click="toggleTheme"
|
||||
/>
|
||||
<v-btn icon="mdi-bell-outline" />
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props: bellProps }">
|
||||
<v-btn icon v-bind="bellProps">
|
||||
<v-badge :content="unreadCount" :model-value="unreadCount > 0" color="error" floating>
|
||||
<v-icon>mdi-bell-outline</v-icon>
|
||||
</v-badge>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact" min-width="320" max-height="400">
|
||||
<v-list-item v-if="notifications.length === 0">
|
||||
<v-list-item-title class="text-medium-emphasis text-center">No notifications</v-list-item-title>
|
||||
</v-list-item>
|
||||
<template v-else>
|
||||
<v-list-item class="d-flex justify-end">
|
||||
<v-btn variant="text" size="small" @click="markAllAsRead">Mark all as read</v-btn>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item
|
||||
v-for="n in notifications"
|
||||
:key="n.id"
|
||||
:class="{ 'bg-blue-lighten-5': !n.read }"
|
||||
@click="handleNotificationClick(n)"
|
||||
>
|
||||
<v-list-item-title>{{ n.data.fileName }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ n.data.newTransactions }} new, {{ n.data.possibleDuplicates }} duplicates
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn icon v-bind="props">
|
||||
@@ -82,6 +112,8 @@ import { ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useNotifications } from '@/composables/useNotifications'
|
||||
import type { AppNotification } from '@/types'
|
||||
|
||||
const drawer = ref(true)
|
||||
const rail = ref(false)
|
||||
@@ -90,6 +122,8 @@ const router = useRouter()
|
||||
const theme = useTheme()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const { notifications, unreadCount, markAllAsRead, markAsRead } = useNotifications()
|
||||
|
||||
const isDark = computed(() => theme.global.current.value.dark)
|
||||
|
||||
const navItems = [
|
||||
@@ -97,8 +131,10 @@ const navItems = [
|
||||
{ title: 'Accounts', icon: 'mdi-bank', route: '/accounts' },
|
||||
{ title: 'Transactions', icon: 'mdi-swap-horizontal', route: '/transactions' },
|
||||
{ title: 'Categories', icon: 'mdi-tag-multiple', route: '/categories' },
|
||||
{ title: 'Payees', icon: 'mdi-account-group', route: '/payees' },
|
||||
{ title: 'Budgets', icon: 'mdi-calculator', route: '/budgets' },
|
||||
{ title: 'Scheduled', icon: 'mdi-clock-outline', route: '/scheduled' },
|
||||
{ title: 'Reconciliation', icon: 'mdi-scale-balance', route: '/reconciliation' },
|
||||
{ title: 'Imports', icon: 'mdi-file-upload', route: '/imports' },
|
||||
{ title: 'Loans', icon: 'mdi-home-city', route: '/loans' },
|
||||
{ title: 'Reports', icon: 'mdi-chart-bar', route: '/reports' },
|
||||
@@ -119,6 +155,11 @@ function toggleTheme() {
|
||||
theme.global.name.value = isDark.value ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
function handleNotificationClick(n: AppNotification) {
|
||||
markAsRead(n.id)
|
||||
router.push('/imports')
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
authStore.logout()
|
||||
router.push('/login')
|
||||
|
||||
Reference in New Issue
Block a user