diff --git a/client/components/CommandPalette.jsx b/client/components/CommandPalette.jsx index 490a4a0..22d5201 100644 --- a/client/components/CommandPalette.jsx +++ b/client/components/CommandPalette.jsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { BarChart2, Calendar, CreditCard, Loader2, Navigation, Plus, Receipt, Search, Settings, Snowflake, Tag, Upload, User, X, + Landmark, ArrowRightLeft, Download, } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api'; @@ -28,6 +29,10 @@ const NAV_COMMANDS = [ { id: 'nav-snowball', label: 'Go to Snowball', icon: Snowflake, path: '/snowball', group: 'Navigate' }, { id: 'nav-categories', label: 'Go to Categories', icon: Tag, path: '/categories', group: 'Navigate' }, { id: 'nav-data', label: 'Go to Data', icon: Upload, path: '/data', group: 'Navigate' }, + { id: 'nav-data-bank', label: 'Data: Bank sync', icon: Landmark, path: '/data?section=bank-sync', group: 'Navigate' }, + { id: 'nav-data-tx', label: 'Data: Transactions', icon: ArrowRightLeft, path: '/data?section=transactions', group: 'Navigate' }, + { id: 'nav-data-import', label: 'Data: Import', icon: Upload, path: '/data?section=import', group: 'Navigate' }, + { id: 'nav-data-export', label: 'Data: Export & backups', icon: Download, path: '/data?section=export', group: 'Navigate' }, { id: 'nav-settings', label: 'Go to Settings', icon: Settings, path: '/settings', group: 'Navigate' }, { id: 'nav-profile', label: 'Go to Profile', icon: User, path: '/profile', group: 'Navigate' }, { id: 'action-new-bill', label: 'Add a new bill', icon: Plus, path: '/bills?new=1', group: 'Actions' }, diff --git a/client/components/data/ConnectionHero.jsx b/client/components/data/ConnectionHero.jsx index 4c0745d..be03f67 100644 --- a/client/components/data/ConnectionHero.jsx +++ b/client/components/data/ConnectionHero.jsx @@ -55,6 +55,7 @@ export default function ConnectionHero({ enabled, // status.enabled (server feature flag) hasConnections, // status.has_connections conn, // the simplefin data_source (name, last_sync_at, last_error) or null + txnTotal, // total synced transactions (at-a-glance), or null onRetry, onGoTo, // (sectionId) => void onSynced, // () => void — refresh after a successful sync @@ -160,7 +161,14 @@ export default function ConnectionHero({

{needsAttention ? conn.last_error - : <>{conn.name || 'SimpleFIN'}{synced ? <> · synced {synced} : null} · syncs automatically} + : ( + <> + {conn.name || 'SimpleFIN'} + {Number(txnTotal) > 0 ? <> · {Number(txnTotal).toLocaleString('en-US')} transactions : null} + {synced ? <> · synced {synced} : null} + {' · syncs automatically'} + + )}

diff --git a/client/pages/DataPage.jsx b/client/pages/DataPage.jsx index 1669a4b..7004d6d 100644 --- a/client/pages/DataPage.jsx +++ b/client/pages/DataPage.jsx @@ -58,6 +58,8 @@ export default function DataPage() { const [hasConnections, setHasConnections] = useState(false); const [syncLoading, setSyncLoading] = useState(true); const [syncError, setSyncError] = useState(false); + const [unmatchedCount, setUnmatchedCount] = useState(0); + const [txnTotal, setTxnTotal] = useState(null); const reduceMotion = useReducedMotion(); const paneRef = useRef(null); const firstRender = useRef(true); @@ -130,7 +132,19 @@ export default function DataPage() { } }, []); + // Cheap "N to review" + transaction count (summary is aggregate, so limit:1 is enough). + const loadTxnStats = useCallback(async () => { + try { + const data = await api.bankTransactionsLedger({ limit: 1 }); + setUnmatchedCount(data?.summary?.unmatched || 0); + setTxnTotal(data?.summary?.total ?? null); + } catch { + setUnmatchedCount(0); + } + }, []); + useEffect(() => { loadHistory(); loadSimplefinSummary(); }, [loadHistory, loadSimplefinSummary]); + useEffect(() => { loadTxnStats(); }, [loadTxnStats, transactionRefreshKey]); const handleTransactionImportComplete = useCallback(() => { loadHistory(); @@ -218,6 +232,14 @@ export default function DataPage() { ? {} : { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.15 } }; + // Health dot for Bank sync + "N to review" badge for Transactions. + const bankDot = (syncError || !syncEnabled || !hasConnections) ? 'gray' : simplefinConn?.last_error ? 'amber' : 'green'; + const navSections = SECTIONS.map(s => + s.id === 'bank-sync' ? { ...s, dot: bankDot } + : s.id === 'transactions' ? { ...s, badge: unmatchedCount || undefined } + : s, + ); + return (
@@ -245,13 +267,14 @@ export default function DataPage() { enabled={syncEnabled} hasConnections={hasConnections} conn={simplefinConn} + txnTotal={txnTotal} onRetry={loadSimplefinSummary} onGoTo={goTo} onSynced={handleSynced} />
- +