fix(tracker): BillModal save/close race and pending badge logic
- Use controlled Dialog state (setDialogOpen) instead of immediate onClose() to let Radix cleanup properly before unmount - Amber 'Pending' badge now only shows for bank-linked bills — unlinked bills skip the pending-cleared check and show 'Paid' directly - TrackerPage onSave no longer nullifies edit state before BillModal can animate closed (batch 0.37.4)
This commit is contained in:
parent
fab4945d50
commit
ca514e5f26
|
|
@ -190,6 +190,10 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
bill?.autopay_verified_at ? new Date(bill.autopay_verified_at) : null
|
||||
);
|
||||
|
||||
// Controls the outer Dialog's open state so it closes via its own animation
|
||||
// rather than being abruptly unmounted, which can leave Radix cleanup in a broken state.
|
||||
const [dialogOpen, setDialogOpen] = useState(true);
|
||||
|
||||
// Deactivate dialog state
|
||||
const [deactivateOpen, setDeactivateOpen] = useState(false);
|
||||
const [deactivateReason, setDeactivateReason] = useState('');
|
||||
|
|
@ -594,7 +598,7 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
toast.success('Template saved');
|
||||
}
|
||||
onSave(savedBill);
|
||||
onClose();
|
||||
setDialogOpen(false);
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
|
@ -608,7 +612,7 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
await api.updateBill(bill.id, payload);
|
||||
toast.success(bill.active ? 'Bill deactivated' : 'Bill reactivated');
|
||||
onSave?.();
|
||||
onClose();
|
||||
setDialogOpen(false);
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
|
@ -617,7 +621,7 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
const inp = 'bg-background/50 border-border/60 h-9 text-sm w-full';
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={v => { if (!v) onClose(); }}>
|
||||
<Dialog open={dialogOpen} onOpenChange={v => { if (!v) onClose(); }}>
|
||||
<DialogContent className="max-h-[92svh] overflow-y-auto border-border/60 bg-card/95 backdrop-blur-xl sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base font-semibold tracking-tight">
|
||||
|
|
@ -1422,7 +1426,7 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button type="button" variant="ghost" disabled={isPending} onClick={onClose} className="text-xs">
|
||||
<Button type="button" variant="ghost" disabled={isPending} onClick={() => setDialogOpen(false)} className="text-xs">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" form="bill-modal-form" disabled={isPending} className="gap-1.5 text-xs">
|
||||
|
|
|
|||
|
|
@ -961,7 +961,7 @@ export default function TrackerPage() {
|
|||
initialBill={editBillData.initialBill}
|
||||
categories={editBillData.categories}
|
||||
onClose={() => setEditBillData(null)}
|
||||
onSave={() => { setEditBillData(null); refetch(); }}
|
||||
onSave={() => refetch()}
|
||||
onDuplicate={bill => setEditBillData({
|
||||
bill: null,
|
||||
initialBill: makeBillDraft(bill, { copy: true, categories: editBillData.categories }),
|
||||
|
|
|
|||
|
|
@ -575,7 +575,10 @@ function getTracker(userId, query = {}, now = new Date()) {
|
|||
// Only flag manually-entered payments as pending-cleared — bank-synced
|
||||
// or bank-matched payments are already settled so they don't need the badge.
|
||||
const isManualPayment = r.payment_source !== 'provider_sync' && r.payment_source !== 'transaction_match';
|
||||
if (r.status === 'paid' && r.last_paid_date && isManualPayment) {
|
||||
// Amber "Pending" only makes sense for bills linked to the bank — for unlinked bills
|
||||
// there's no bank clearing to wait for, so they should just show "Paid".
|
||||
const isBankLinked = !!(r.has_merchant_rule || r.has_linked_transactions);
|
||||
if (r.status === 'paid' && r.last_paid_date && isManualPayment && isBankLinked) {
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - bankTracking.pending_days);
|
||||
const paidAt = new Date(r.last_paid_date);
|
||||
|
|
|
|||
Loading…
Reference in New Issue