feat: event log, pending operations, and MCP staging (#135)
* feat: event log, pending operations, and MCP staging - Event log system: persist bus events to event_log table for external automation platforms. Batch insert for transaction.synced. Daily cleanup cron at 02:00 UTC. - Pending operations: MCP write tools (categorize, create customer, create invoice) now stage to pending_operations instead of executing directly. Users review and commit/reject from /pending in the web UI. - Granskning page: card-based review UI with expandable previews, commit/reject dialogs. Only shown in nav when pending ops exist. - Commit route re-executes using core lib functions (no extension imports). Guards against stale state (double-commit, deleted entities). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: stage new MCP write tools after main merge Add staging for 4 new write tools from #133: - mark_invoice_paid, send_invoice, mark_invoice_sent, match_transaction_invoice - Expand pending_operations CHECK constraint - Add commit executors with full execution logic - Add UI labels and generic preview component - Remove confirm parameter from categorize (single-call staging) - Fix UUID in pending op title (fetch transaction description) - Hide Granskning nav when no pending ops Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback - Fix TS build error: use `select('*, customer:customers(*)')` for match_transaction_invoice to avoid array type inference - Add status guard to commitSendInvoice (prevents duplicate sends) - Replace auth.admin.getUserById with user email from session auth - Restore optimistic lock check in commitMatchTransactionInvoice - Fix tool description typo: expense_software → expense_office Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4d2152946a
commit
f3ae3cd361
@@ -25,6 +25,7 @@ import {
|
||||
FileInput,
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
ClipboardCheck,
|
||||
} from 'lucide-react'
|
||||
import { resolveIcon } from '@/lib/extensions/icon-resolver'
|
||||
import type { EntityType } from '@/types'
|
||||
@@ -39,6 +40,7 @@ interface DashboardNavProps {
|
||||
companyName: string
|
||||
entityType: EntityType
|
||||
uncategorizedTransactionCount?: number
|
||||
pendingOperationsCount?: number
|
||||
isSandbox?: boolean
|
||||
extensionNavItems?: ExtensionNavItem[]
|
||||
}
|
||||
@@ -66,6 +68,7 @@ const navItems: NavItem[] = [
|
||||
{ href: '/suppliers', label: 'Leverantörer', icon: Building2, group: 'inköp', hidden: true },
|
||||
{ href: '/supplier-invoices', label: 'Leverantörsfakturor', icon: FileInput, group: 'inköp', hidden: true },
|
||||
// General accounting
|
||||
{ href: '/pending', label: 'Granskning', icon: ClipboardCheck, group: 'redovisning' },
|
||||
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'redovisning' },
|
||||
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'redovisning' },
|
||||
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'redovisning' },
|
||||
@@ -82,7 +85,7 @@ const groupLabels: Record<string, string> = {
|
||||
övrigt: 'Övrigt',
|
||||
}
|
||||
|
||||
export default function DashboardNav({ companyName, entityType, uncategorizedTransactionCount = 0, isSandbox = false, extensionNavItems = [] }: DashboardNavProps) {
|
||||
export default function DashboardNav({ companyName, entityType, uncategorizedTransactionCount = 0, pendingOperationsCount = 0, isSandbox = false, extensionNavItems = [] }: DashboardNavProps) {
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const supabase = createClient()
|
||||
@@ -123,10 +126,14 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
|
||||
}, 200)
|
||||
}
|
||||
|
||||
// Filter nav items by entity type and hidden flag
|
||||
const filteredItems = navItems.filter(item =>
|
||||
!item.hidden && (!item.modes || item.modes.includes(entityType))
|
||||
)
|
||||
// Filter nav items by entity type, hidden flag, and conditional visibility
|
||||
const filteredItems = navItems.filter(item => {
|
||||
if (item.hidden) return false
|
||||
if (item.modes && !item.modes.includes(entityType)) return false
|
||||
// Only show Granskning when there are pending operations
|
||||
if (item.href === '/pending' && pendingOperationsCount === 0) return false
|
||||
return true
|
||||
})
|
||||
|
||||
const mainItems = filteredItems.filter(i => i.group === 'main')
|
||||
const övrigtItems = filteredItems.filter(i => i.group === 'övrigt')
|
||||
@@ -202,7 +209,9 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
|
||||
const active = isActive(item.href)
|
||||
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
|
||||
? uncategorizedTransactionCount
|
||||
: null
|
||||
: item.href === '/pending' && pendingOperationsCount > 0
|
||||
? pendingOperationsCount
|
||||
: null
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
@@ -442,7 +451,9 @@ export default function DashboardNav({ companyName, entityType, uncategorizedTra
|
||||
const active = isActive(item.href)
|
||||
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
|
||||
? uncategorizedTransactionCount
|
||||
: null
|
||||
: item.href === '/pending' && pendingOperationsCount > 0
|
||||
? pendingOperationsCount
|
||||
: null
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
|
||||
Reference in New Issue
Block a user