import { useState, useRef } from 'react'; import { ArrowDown, ArrowUp, GripVertical, Pencil } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api.js'; import { cn, fmt, fmtDate } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from '@/components/ui/alert-dialog'; import MonthlyStateDialog from '@/components/tracker/MonthlyStateDialog'; import PaymentModal from '@/components/tracker/PaymentModal'; import { paymentDateForTrackerMonth, paymentSummary, ROW_STATUS_CLS } from '@/lib/trackerUtils'; import { StatusBadge } from '@/components/tracker/StatusBadge'; import { PaymentProgress } from '@/components/tracker/PaymentProgress'; import { LowerThisMonthButton } from '@/components/tracker/LowerThisMonthButton'; import { PaymentLedgerDialog } from '@/components/tracker/PaymentLedgerDialog'; import { NotesCell } from '@/components/tracker/NotesCell'; import { AutopaySuggestionActions } from '@/components/tracker/AutopaySuggestionActions'; export function MobileTrackerRow({ row, year, month, refresh, index, onEditBill, moveControls, dragProps }) { const amountRef = useRef(null); const [editPayment, setEditPayment] = useState(null); const [paymentLedgerOpen, setPaymentLedgerOpen] = useState(false); const [showMbs, setShowMbs] = useState(false); const [confirmUnpay, setConfirmUnpay] = useState(false); const [suggestionLoading, setSuggestionLoading] = useState(false); const threshold = row.actual_amount != null ? row.actual_amount : row.expected_amount; const defaultPaymentDate = paymentDateForTrackerMonth(year, month, row.due_day); const isSkipped = !!row.is_skipped; const hasAutopaySuggestion = !!row.autopay_suggestion && !isSkipped; const isPaidByThreshold = row.total_paid > 0 && row.total_paid >= threshold; const isPaid = row.status === 'paid' || (row.status === 'autodraft' && !hasAutopaySuggestion) || isPaidByThreshold; const effectiveStatus = isSkipped ? 'skipped' : (isPaidByThreshold && row.status !== 'paid' && row.status !== 'autodraft') ? 'paid' : row.status; const rowBg = isSkipped ? '' : (ROW_STATUS_CLS[effectiveStatus] || ''); const remaining = Math.max((threshold || 0) - (row.total_paid || 0), 0); const summary = paymentSummary(row, threshold); async function handleQuickPay() { const val = parseFloat(amountRef.current?.value); if (!val || val <= 0) { toast.error('Enter a payment amount'); return; } try { await api.quickPay({ bill_id: row.id, amount: val, paid_date: defaultPaymentDate }); toast.success('Payment added'); refresh(); } catch (err) { toast.error(err.message); } } async function performTogglePaid() { try { const result = await api.togglePaid(row.id, { amount: isPaid ? undefined : threshold, year: year, month: month, }); if (isPaid && result.paymentId) { toast.success('Payment moved to recovery', { action: { label: 'Undo', onClick: async () => { try { await api.restorePayment(result.paymentId); toast.success('Payment restored'); refresh(); } catch (err) { toast.error(err.message || 'Failed to restore payment'); } }, }, }); } else { toast.success('Payment recorded'); } refresh(); } catch (err) { toast.error(err.message || 'Failed to toggle payment status'); } } function handleTogglePaid() { if (isPaid) { setConfirmUnpay(true); return; } performTogglePaid(); } async function handleConfirmSuggestion() { setSuggestionLoading(true); try { const result = await api.confirmAutopaySuggestion(row.id, { year, month }); toast.success(result.created ? 'Autopay payment confirmed' : 'Autopay already recorded'); refresh(); } catch (err) { toast.error(err.message || 'Failed to confirm autopay suggestion'); } finally { setSuggestionLoading(false); } } async function handleDismissSuggestion() { setSuggestionLoading(true); try { await api.dismissAutopaySuggestion(row.id, { year, month }); toast.success('Autopay suggestion dismissed'); refresh(); } catch (err) { toast.error(err.message || 'Failed to dismiss autopay suggestion'); } finally { setSuggestionLoading(false); } } return ( <>
{row.monthly_notes}
)}Due
{fmtDate(row.due_date)}
Category
{row.category_name || 'Uncategorized'}
Expected
{fmt(threshold)}
Last Month
{fmt(row.previous_month_paid)}
Remaining
0 ? 'text-foreground' : 'text-emerald-300')}> {fmt(remaining)}