import React from 'react';
import { ArrowDown, ArrowUp, Copy, GripVertical, PenLine, EyeOff, Eye, Clock, Trash2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { scheduleLabel } from '@/lib/billingSchedule';
import { formatUSDWhole } from '@/lib/money';
import { MobileBillRow } from '@/components/MobileBillRow';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import type { Bill } from '@/types';
import type { DragProps, MoveControls } from '@/components/tracker/MobileTrackerRow';
interface BillPrefs {
showCategory?: boolean;
showDueDay?: boolean;
showAmount?: boolean;
showCycle?: boolean;
showApr?: boolean;
showBalance?: boolean;
showMinPayment?: boolean;
showAutopay?: boolean;
show2fa?: boolean;
showSubscription?: boolean;
}
function ordinal(n: number | null | undefined): string {
const d = Number(n);
if (!d) return '—';
if (d > 3 && d < 21) return `${d}th`;
switch (d % 10) {
case 1: return `${d}st`;
case 2: return `${d}nd`;
case 3: return `${d}rd`;
default: return `${d}th`;
}
}
function hasHistoricalVisibility(bill: Bill): boolean {
return !!bill.has_history_ranges || (!!bill.history_visibility && bill.history_visibility !== 'default');
}
function AprColor({ rate }: { rate: number }) {
const cls =
rate >= 25 ? 'text-rose-400' :
rate >= 15 ? 'text-amber-400' :
'text-muted-foreground';
return {rate}% APR;
}
const ALL_ON: BillPrefs = {
showCategory: true, showDueDay: true, showAmount: true, showCycle: true,
showApr: true, showBalance: true, showMinPayment: true, showAutopay: true, show2fa: true,
};
interface BillCardProps {
bill: Bill;
prefs?: BillPrefs;
onEdit?: (id: number) => void;
onToggle?: (bill: Bill) => void;
onDelete?: (bill: Bill) => void;
onHistory?: (bill: Bill) => void;
onDuplicate?: (bill: Bill) => void;
moveControls?: MoveControls;
dragProps?: DragProps;
}
function BillCard({ bill, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory, onDuplicate, moveControls, dragProps }: BillCardProps) {
const isDebt = bill.current_balance != null || bill.minimum_payment != null;
const hasHistory = hasHistoricalVisibility(bill);
return (
['onDragStart']}
onDragEnter={dragProps?.onDragEnter as React.ComponentProps<'div'>['onDragEnter']}
onDragOver={dragProps?.onDragOver as React.ComponentProps<'div'>['onDragOver']}
onDragEnd={dragProps?.onDragEnd as React.ComponentProps<'div'>['onDragEnd']}
onDrop={dragProps?.onDrop as React.ComponentProps<'div'>['onDrop']}
className={cn(
'group flex items-center gap-3 px-5 py-3.5 transition-colors',
'hover:bg-accent/20',
!bill.active && 'opacity-60',
dragProps?.isDragging && 'opacity-45',
dragProps?.isDropTarget && 'ring-2 ring-inset ring-primary/35',
)}>
{/* Main info */}
{/* Name + badges */}
{prefs.showCategory && bill.category_name && (
{bill.category_name}
)}
{prefs.showAutopay && !!bill.autopay_enabled && (
AP
Autopay enabled
)}
{prefs.show2fa && !!bill.has_2fa && (
2FA
Two-factor authentication configured
)}
{prefs.showSubscription && !!bill.is_subscription && (
S
Subscription
)}
{(!!bill.has_merchant_rule || !!bill.has_linked_transactions) && (
L
Linked to bank transactions
)}
{hasHistory && (
Historical visibility configured
)}
{/* Meta row */}
{prefs.showCycle &&
{scheduleLabel(bill)}}
{prefs.showCycle && prefs.showDueDay &&
·}
{prefs.showDueDay &&
Due {ordinal(bill.due_day)}}
{prefs.showApr && isDebt && bill.interest_rate != null && (
<>
{(prefs.showCycle || prefs.showDueDay) &&
·}
>
)}
{prefs.showBalance && isDebt && bill.current_balance != null && (
<>
{(prefs.showCycle || prefs.showDueDay || (prefs.showApr && bill.interest_rate != null)) &&
·}
{formatUSDWhole(bill.current_balance)} balance
>
)}
{/* Amount */}
{prefs.showAmount && (
${Number(bill.expected_amount).toFixed(2)}
{prefs.showMinPayment && bill.minimum_payment != null && (
${Number(bill.minimum_payment).toFixed(0)} min
)}
)}
{/* Action icons */}
{!bill.active && onHistory && (
)}
);
}
interface BillsTableInnerProps {
bills: Bill[];
prefs?: BillPrefs;
onEdit?: (id: number) => void;
onToggle?: (bill: Bill) => void;
onDelete?: (bill: Bill) => void;
onHistory?: (bill: Bill) => void;
onDuplicate?: (bill: Bill) => void;
moveControlsFor?: (bill: Bill, index: number) => MoveControls | undefined;
dragPropsFor?: (bill: Bill, index: number) => DragProps | undefined;
}
export default function BillsTableInner({ bills, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory, onDuplicate, moveControlsFor, dragPropsFor }: BillsTableInnerProps) {
return (
<>
{bills.map((bill, index) => (
))}
{bills.map((bill, index) => (
))}
>
);
}