import React, { useState, useEffect, useCallback } from 'react'; import { toast } from 'sonner'; import { TrendingUp, EyeOff, Eye, ArrowRight, Loader2 } from 'lucide-react'; import { api } from '@/api'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from '@/components/ui/dialog'; function fmt(n) { return Number(n || 0).toLocaleString('en-US', { style: 'currency', currency: 'USD' }); } export default function IncomeBreakdownModal({ open, onClose, year, month, bankTracking }) { const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(''); const [showIgnored, setShowIgnored] = useState(false); const [actionId, setActionId] = useState(null); // tx being acted on const load = useCallback(async () => { if (!open) return; setLoading(true); setLoadError(''); try { const d = await api.spendingIncome({ year, month, include_ignored: showIgnored ? 'true' : undefined, limit: 100 }); setTransactions(d.transactions || []); } catch (err) { const msg = err.message || 'Failed to load income transactions'; setLoadError(msg); toast.error(msg); } finally { setLoading(false); } }, [open, year, month, showIgnored]); useEffect(() => { load(); }, [load]); const handleIgnore = async (tx) => { setActionId(tx.id); try { await api.ignoreTransaction(tx.id); toast.success(`"${tx.payee}" marked as excluded`); } catch (err) { toast.error(err.message || 'Failed to exclude transaction'); setActionId(null); return; // don't reload if the action itself failed } setActionId(null); await load(); // reload outside the action try so load errors are surfaced separately }; const handleUnignore = async (tx) => { setActionId(tx.id); try { await api.unignoreTransaction(tx.id); toast.success(`"${tx.payee}" restored as income`); } catch (err) { toast.error(err.message || 'Failed to restore transaction'); setActionId(null); return; } setActionId(null); await load(); }; const active = transactions.filter(t => !t.ignored); const ignored = transactions.filter(t => t.ignored); const total = active.reduce((s, t) => s + t.amount, 0); const bt = bankTracking || {}; return ( { if (!v) onClose(); }}> Starting Balance Breakdown How your starting balance was calculated from your bank account {/* Balance calculation */} {bt.enabled && (
{bt.org_name || bt.account_name || 'Bank'} balance {fmt(bt.balance)}
{bt.pending_payments > 0 && (
Pending payments ({bt.pending_days}d window) −{fmt(bt.pending_payments)}
)}
Effective starting balance {fmt(bt.effective_balance)}
)} {/* Income transactions */}

Income this month

{ignored.length > 0 || showIgnored ? ( ) : null} {active.length > 0 && ( {fmt(total)} )}
{loading ? (
Loading…
) : loadError ? (

{loadError}

) : transactions.length === 0 ? (

No income transactions found for this month.

) : (
{active.map(tx => (

{tx.payee}

{tx.date}

+{fmt(tx.amount)}
))} {showIgnored && ignored.map(tx => (

{tx.payee}

{tx.date}

+{fmt(tx.amount)}
))}
)}

Showing positive unmatched transactions from your bank. Exclude transfers or non-income deposits.

); }