2026-05-30 20:04:50 -05:00
|
|
|
import { ArrowDown, ArrowUp, Copy, GripVertical, PenLine, EyeOff, Eye, Clock, Trash2 } from 'lucide-react';
|
2026-05-03 19:51:57 -05:00
|
|
|
import { cn } from '@/lib/utils';
|
2026-05-30 21:20:51 -05:00
|
|
|
import { scheduleLabel } from '@/lib/billingSchedule';
|
2026-05-16 15:38:28 -05:00
|
|
|
import { MobileBillRow } from '@/components/MobileBillRow';
|
2026-06-06 23:29:34 -05:00
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
2026-05-15 01:36:56 -05:00
|
|
|
|
|
|
|
|
function ordinal(n) {
|
|
|
|
|
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`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 19:51:57 -05:00
|
|
|
|
|
|
|
|
function hasHistoricalVisibility(bill) {
|
2026-05-15 01:36:56 -05:00
|
|
|
return !!bill.has_history_ranges || (bill.history_visibility && bill.history_visibility !== 'default');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function AprColor({ rate }) {
|
|
|
|
|
const cls =
|
|
|
|
|
rate >= 25 ? 'text-rose-400' :
|
|
|
|
|
rate >= 15 ? 'text-amber-400' :
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
'text-muted-foreground';
|
|
|
|
|
return <span className={cn('tracker-number text-[11px] font-semibold', cls)}>{rate}% APR</span>;
|
2026-05-03 19:51:57 -05:00
|
|
|
}
|
|
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
const ALL_ON = {
|
|
|
|
|
showCategory: true, showDueDay: true, showAmount: true, showCycle: true,
|
|
|
|
|
showApr: true, showBalance: true, showMinPayment: true, showAutopay: true, show2fa: true,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-30 20:04:50 -05:00
|
|
|
function BillCard({ bill, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory, onDuplicate, moveControls, dragProps }) {
|
2026-05-15 01:36:56 -05:00
|
|
|
const isDebt = bill.current_balance != null || bill.minimum_payment != null;
|
2026-05-04 13:14:32 -05:00
|
|
|
const hasHistory = hasHistoricalVisibility(bill);
|
|
|
|
|
|
|
|
|
|
return (
|
2026-05-30 20:04:50 -05:00
|
|
|
<div
|
|
|
|
|
draggable={dragProps?.draggable}
|
|
|
|
|
onDragStart={dragProps?.onDragStart}
|
|
|
|
|
onDragEnter={dragProps?.onDragEnter}
|
|
|
|
|
onDragOver={dragProps?.onDragOver}
|
|
|
|
|
onDragEnd={dragProps?.onDragEnd}
|
|
|
|
|
onDrop={dragProps?.onDrop}
|
|
|
|
|
className={cn(
|
|
|
|
|
'group flex items-center gap-3 px-5 py-3.5 transition-colors',
|
2026-05-15 01:36:56 -05:00
|
|
|
'hover:bg-accent/20',
|
|
|
|
|
!bill.active && 'opacity-60',
|
2026-05-30 20:04:50 -05:00
|
|
|
dragProps?.isDragging && 'opacity-45',
|
|
|
|
|
dragProps?.isDropTarget && 'ring-2 ring-inset ring-primary/35',
|
2026-05-15 01:36:56 -05:00
|
|
|
)}>
|
|
|
|
|
|
2026-05-30 20:04:50 -05:00
|
|
|
<div className="flex shrink-0 items-center gap-0.5">
|
|
|
|
|
<GripVertical
|
|
|
|
|
className={cn(
|
|
|
|
|
'h-4 w-4 text-muted-foreground/55',
|
|
|
|
|
moveControls?.enabled && 'cursor-grab group-active:cursor-grabbing',
|
|
|
|
|
!moveControls?.enabled && 'opacity-30',
|
|
|
|
|
)}
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
|
|
|
|
<div className="hidden flex-col sm:flex">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={moveControls?.onMoveUp}
|
|
|
|
|
disabled={!moveControls?.enabled || !moveControls?.canMoveUp || moveControls?.moving}
|
|
|
|
|
className="rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-25"
|
|
|
|
|
title="Move bill up"
|
|
|
|
|
aria-label={`Move ${bill.name} up`}
|
|
|
|
|
>
|
|
|
|
|
<ArrowUp className="h-3 w-3" />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={moveControls?.onMoveDown}
|
|
|
|
|
disabled={!moveControls?.enabled || !moveControls?.canMoveDown || moveControls?.moving}
|
|
|
|
|
className="rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-25"
|
|
|
|
|
title="Move bill down"
|
|
|
|
|
aria-label={`Move ${bill.name} down`}
|
|
|
|
|
>
|
|
|
|
|
<ArrowDown className="h-3 w-3" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
{/* Main info */}
|
|
|
|
|
<div className="flex-1 min-w-0 space-y-1.5">
|
|
|
|
|
|
|
|
|
|
{/* Name + badges */}
|
|
|
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onEdit?.(bill.id)}
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
className="max-w-[240px] truncate text-left text-sm font-semibold text-foreground transition-colors hover:text-primary"
|
2026-05-15 01:36:56 -05:00
|
|
|
>
|
|
|
|
|
{bill.name}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{prefs.showCategory && bill.category_name && (
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<span className="shrink-0 rounded border border-border/70 bg-secondary/60 px-1.5 py-0.5 text-[11px] font-medium text-muted-foreground">
|
2026-05-15 01:36:56 -05:00
|
|
|
{bill.category_name}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-05-04 13:14:32 -05:00
|
|
|
|
2026-06-06 23:29:34 -05:00
|
|
|
<TooltipProvider delayDuration={300}>
|
|
|
|
|
{prefs.showAutopay && !!bill.autopay_enabled && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<span className="shrink-0 rounded border border-sky-500/25 bg-sky-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-sky-600 dark:text-sky-300 cursor-default">
|
|
|
|
|
AP
|
|
|
|
|
</span>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Autopay enabled</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
{prefs.show2fa && !!bill.has_2fa && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<span className="shrink-0 rounded bg-violet-500/20 px-1.5 py-0.5 text-[11px] font-semibold text-violet-300 cursor-default">
|
|
|
|
|
2FA
|
|
|
|
|
</span>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Two-factor authentication configured</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
{prefs.showSubscription && !!bill.is_subscription && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<span className="shrink-0 rounded border border-indigo-500/25 bg-indigo-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-indigo-600 dark:text-indigo-300 cursor-default">
|
|
|
|
|
S
|
|
|
|
|
</span>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Subscription</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
{(!!bill.has_merchant_rule || !!bill.has_linked_transactions) && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<span className="shrink-0 rounded border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[11px] font-semibold text-emerald-600 dark:text-emerald-400 cursor-default">
|
|
|
|
|
L
|
|
|
|
|
</span>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Linked to bank transactions</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
{hasHistory && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500 cursor-default">
|
|
|
|
|
<Clock className="h-2.5 w-2.5" />
|
|
|
|
|
</span>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Historical visibility configured</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
)}
|
|
|
|
|
</TooltipProvider>
|
2026-05-04 13:14:32 -05:00
|
|
|
</div>
|
|
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
{/* Meta row */}
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs font-medium text-muted-foreground">
|
2026-05-30 21:20:51 -05:00
|
|
|
{prefs.showCycle && <span>{scheduleLabel(bill)}</span>}
|
2026-05-04 13:14:32 -05:00
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
{prefs.showCycle && prefs.showDueDay && <span className="text-border">·</span>}
|
|
|
|
|
|
|
|
|
|
{prefs.showDueDay && <span>Due {ordinal(bill.due_day)}</span>}
|
|
|
|
|
|
|
|
|
|
{prefs.showApr && isDebt && bill.interest_rate != null && (
|
|
|
|
|
<>
|
|
|
|
|
{(prefs.showCycle || prefs.showDueDay) && <span className="text-border">·</span>}
|
|
|
|
|
<AprColor rate={bill.interest_rate} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{prefs.showBalance && isDebt && bill.current_balance != null && (
|
|
|
|
|
<>
|
|
|
|
|
{(prefs.showCycle || prefs.showDueDay || (prefs.showApr && bill.interest_rate != null)) && <span className="text-border">·</span>}
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<span className="tracker-number text-[11px] font-medium text-muted-foreground">
|
2026-05-15 01:36:56 -05:00
|
|
|
${Number(bill.current_balance).toLocaleString(undefined, { maximumFractionDigits: 0 })} balance
|
|
|
|
|
</span>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2026-05-04 13:14:32 -05:00
|
|
|
</div>
|
2026-05-15 01:36:56 -05:00
|
|
|
|
2026-05-04 13:14:32 -05:00
|
|
|
</div>
|
|
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
{/* Amount */}
|
|
|
|
|
{prefs.showAmount && (
|
|
|
|
|
<div className="text-right shrink-0 hidden sm:block">
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<p className="tracker-number text-sm font-bold text-foreground">
|
2026-05-15 01:36:56 -05:00
|
|
|
${Number(bill.expected_amount).toFixed(2)}
|
|
|
|
|
</p>
|
|
|
|
|
{prefs.showMinPayment && bill.minimum_payment != null && (
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<p className="tracker-number text-[11px] font-medium text-muted-foreground">
|
2026-05-15 01:36:56 -05:00
|
|
|
${Number(bill.minimum_payment).toFixed(0)} min
|
|
|
|
|
</p>
|
2026-05-04 13:14:32 -05:00
|
|
|
)}
|
2026-05-15 01:36:56 -05:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Action icons */}
|
|
|
|
|
<div className="flex items-center gap-0.5 shrink-0">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onEdit?.(bill.id)}
|
|
|
|
|
title="Edit"
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-muted/70 hover:text-foreground transition-colors"
|
2026-05-04 13:14:32 -05:00
|
|
|
>
|
2026-05-15 01:36:56 -05:00
|
|
|
<PenLine className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
|
2026-05-16 15:38:28 -05:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onDuplicate?.(bill)}
|
|
|
|
|
title="Duplicate"
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-primary/10 hover:text-primary transition-colors"
|
2026-05-16 15:38:28 -05:00
|
|
|
>
|
|
|
|
|
<Copy className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
|
2026-05-15 01:36:56 -05:00
|
|
|
{!bill.active && onHistory && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
2026-05-04 13:14:32 -05:00
|
|
|
onClick={() => onHistory?.(bill)}
|
2026-05-15 01:36:56 -05:00
|
|
|
title="History visibility"
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-sky-500/10 hover:text-sky-400 transition-colors"
|
2026-05-04 13:14:32 -05:00
|
|
|
>
|
2026-05-15 01:36:56 -05:00
|
|
|
<Clock className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
2026-05-04 13:14:32 -05:00
|
|
|
)}
|
2026-05-15 01:36:56 -05:00
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onToggle?.(bill)}
|
|
|
|
|
title={bill.active ? 'Deactivate' : 'Activate'}
|
|
|
|
|
className={cn(
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
'rounded-md p-1.5 transition-colors',
|
2026-05-15 01:36:56 -05:00
|
|
|
bill.active
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
? 'text-muted-foreground hover:bg-amber-500/10 hover:text-amber-400'
|
|
|
|
|
: 'text-muted-foreground hover:bg-emerald-500/10 hover:text-emerald-400',
|
2026-05-15 01:36:56 -05:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{bill.active ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
2026-05-04 13:14:32 -05:00
|
|
|
onClick={() => onDelete?.(bill)}
|
2026-05-15 01:36:56 -05:00
|
|
|
title="Delete"
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors"
|
2026-05-04 13:14:32 -05:00
|
|
|
>
|
2026-05-15 01:36:56 -05:00
|
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
2026-05-04 13:14:32 -05:00
|
|
|
</div>
|
2026-05-15 01:36:56 -05:00
|
|
|
|
2026-05-04 13:14:32 -05:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 20:04:50 -05:00
|
|
|
export default function BillsTableInner({ bills, prefs = ALL_ON, onEdit, onToggle, onDelete, onHistory, onDuplicate, moveControlsFor, dragPropsFor }) {
|
2026-05-03 19:51:57 -05:00
|
|
|
return (
|
2026-05-16 15:38:28 -05:00
|
|
|
<>
|
|
|
|
|
<div className="hidden divide-y divide-border/30 sm:block">
|
2026-05-30 20:04:50 -05:00
|
|
|
{bills.map((bill, index) => (
|
2026-05-16 15:38:28 -05:00
|
|
|
<BillCard
|
|
|
|
|
key={bill.id}
|
|
|
|
|
bill={bill}
|
|
|
|
|
prefs={prefs}
|
|
|
|
|
onEdit={onEdit}
|
|
|
|
|
onToggle={onToggle}
|
|
|
|
|
onDelete={onDelete}
|
|
|
|
|
onHistory={onHistory}
|
|
|
|
|
onDuplicate={onDuplicate}
|
2026-05-30 20:04:50 -05:00
|
|
|
moveControls={moveControlsFor?.(bill, index)}
|
|
|
|
|
dragProps={dragPropsFor?.(bill, index)}
|
2026-05-16 15:38:28 -05:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-3 p-3 sm:hidden">
|
2026-05-30 20:04:50 -05:00
|
|
|
{bills.map((bill, index) => (
|
2026-05-16 15:38:28 -05:00
|
|
|
<MobileBillRow
|
|
|
|
|
key={bill.id}
|
|
|
|
|
bill={bill}
|
|
|
|
|
onEdit={onEdit}
|
|
|
|
|
onToggle={onToggle}
|
|
|
|
|
onDelete={onDelete}
|
|
|
|
|
onHistory={onHistory}
|
|
|
|
|
onDuplicate={onDuplicate}
|
2026-05-30 20:04:50 -05:00
|
|
|
moveControls={moveControlsFor?.(bill, index)}
|
|
|
|
|
dragProps={dragPropsFor?.(bill, index)}
|
2026-05-16 15:38:28 -05:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
2026-05-03 19:51:57 -05:00
|
|
|
);
|
|
|
|
|
}
|