diff --git a/client/components/BillModal.jsx b/client/components/BillModal.jsx index 9837b1c..a5d66d9 100644 --- a/client/components/BillModal.jsx +++ b/client/components/BillModal.jsx @@ -1,5 +1,5 @@ import { useActionState, useEffect, useState } from 'react'; -import { Copy, Link2, Link2Off, Loader2, RefreshCw } from 'lucide-react'; +import { Copy, Loader2 } from 'lucide-react'; import { validateNonNegativeMoney } from '@/lib/money'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; @@ -17,13 +17,13 @@ import { } from '@/components/ui/select'; import { api } from '@/api'; import { cn, fmt, fmtDate, todayStr } from '@/lib/utils'; -import BillMerchantRules from '@/components/BillMerchantRules'; import DebtDetailsSection from '@/components/bill-modal/DebtDetailsSection'; import AutopayTrustIndicator from '@/components/bill-modal/AutopayTrustIndicator'; import PaymentHistoryList from '@/components/bill-modal/PaymentHistoryList'; import PaymentFormFields from '@/components/bill-modal/PaymentFormFields'; import UnmatchDialogs from '@/components/bill-modal/UnmatchDialogs'; -import { transactionTitle, transactionDate, fmtTransactionAmount, isSimilarPayee } from '@/components/bill-modal/transactionDisplay'; +import LinkedTransactionsSection from '@/components/bill-modal/LinkedTransactionsSection'; +import { transactionTitle, isSimilarPayee } from '@/components/bill-modal/transactionDisplay'; import { BILLING_SCHEDULE_OPTIONS, billingCycleForSchedule, @@ -177,6 +177,44 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa loadLinkedTransactions(); }, [bill?.id]); + // Imported payments (via sync or a merchant-rule historical import) must + // refresh the payment list AND the Tracker behind the modal, not just the + // linked transactions — matching the unmatch handlers. + async function refreshAfterImport() { + await Promise.all([loadPayments(), loadLinkedTransactions()]); + onSave?.(); + } + + async function handleSyncBillPayments() { + setSyncingPayments(true); + const promise = api.syncBillSimplefinPayments(sourceBill.id); + toast.promise(promise, { + loading: 'Scanning bank history…', + success: (result) => result.added > 0 + ? `${result.added} payment${result.added !== 1 ? 's' : ''} imported from bank history.` + : 'No new matching transactions found.', + error: (err) => err.message || 'Sync failed.', + }); + try { + const result = await promise; + if (result.added > 0) await refreshAfterImport(); + if (result.late_attributions?.length) { + window.dispatchEvent(new CustomEvent('tracker:late-attributions', { + detail: { attributions: result.late_attributions }, + })); + } + } catch { + // toast.promise already surfaced the error + } finally { + setSyncingPayments(false); + } + } + + async function handleRulesChanged() { + setLocalHasRules(true); + await refreshAfterImport(); + } + const validateName = (val) => { if (!val || val.trim() === '') return 'Name is required'; if (val.trim().length < 2) return 'Name must be at least 2 characters'; @@ -917,147 +955,20 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa onDelete={setDeletePaymentTarget} /> - {/* Bank Matching Rules */} - {!isNew && ( -
Bank matching rules
-- Transactions whose description contains these patterns are automatically imported as payments. -
-Linked transactions
-{linkedTransactions.length} confirmed matches
-{transactionTitle(transaction)}
- - {transaction.source_label || transaction.source_type_label || 'Transaction'} - -- {transactionDate(transaction) ? fmtDate(transactionDate(transaction)) : 'No date'} · {transaction.description || transaction.memo || 'No description'} -
- {transaction.account_name && ( -{transaction.account_name}
- )} -- {fmtTransactionAmount(transaction.amount, transaction.currency)} -
- -Bank matching rules
++ Transactions whose description contains these patterns are automatically imported as payments. +
+Linked transactions
+{linkedTransactions.length} confirmed matches
+{transactionTitle(transaction)}
+ + {transaction.source_label || transaction.source_type_label || 'Transaction'} + ++ {transactionDate(transaction) ? fmtDate(transactionDate(transaction)) : 'No date'} · {transaction.description || transaction.memo || 'No description'} +
+ {transaction.account_name && ( +{transaction.account_name}
+ )} ++ {fmtTransactionAmount(transaction.amount, transaction.currency)} +
+ +