148 lines
6.0 KiB
JavaScript
148 lines
6.0 KiB
JavaScript
import {
|
|
Table, TableHeader, TableBody, TableHead, TableRow, TableCell,
|
|
} from '@/components/ui/table';
|
|
import { Button } from '@/components/ui/button';
|
|
import { cn } from '@/lib/utils';
|
|
import { History } from 'lucide-react';
|
|
|
|
function hasHistoricalVisibility(bill) {
|
|
const visibility = bill.history_visibility;
|
|
return !!bill.has_history_ranges || (visibility && visibility !== 'default');
|
|
}
|
|
|
|
// Accepts row action handlers from BillsPage
|
|
export default function BillsTableInner({ bills, onEdit, onToggle, onDelete, onHistory }) {
|
|
return (
|
|
<Table>
|
|
|
|
<TableHeader className="bg-muted border-b border-border/70">
|
|
<TableRow className="hover:bg-transparent border-0">
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground">Bill</TableHead>
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground">Category</TableHead>
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground w-24">Due</TableHead>
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground w-28 text-right">Expected</TableHead>
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground w-28">Cycle</TableHead>
|
|
<TableHead className="px-6 py-3 text-xs uppercase text-muted-foreground w-24">Flags</TableHead>
|
|
<TableHead className="px-6 py-3 w-72" />
|
|
</TableRow>
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
{bills.map((bill) => (
|
|
<TableRow
|
|
key={bill.id}
|
|
className="group border-b border-border/50 last:border-0 hover:bg-accent/60 transition-colors"
|
|
>
|
|
|
|
{/* Bill name */}
|
|
<TableCell className="px-6 py-4">
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
className="text-left text-sm font-medium leading-tight text-foreground underline-offset-4 transition-colors hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded-sm"
|
|
onClick={() => onEdit?.(bill.id)}
|
|
title={`Edit ${bill.name}`}
|
|
>
|
|
{bill.name}
|
|
</button>
|
|
{hasHistoricalVisibility(bill) && (
|
|
<span
|
|
className="inline-flex h-5 w-5 items-center justify-center rounded-full border border-sky-500/25 bg-sky-500/10 text-sky-500"
|
|
title="Historical visibility configured"
|
|
aria-label="Historical visibility configured"
|
|
>
|
|
<History className="h-3 w-3" />
|
|
</span>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
|
|
{/* Category */}
|
|
<TableCell className="px-6 py-4">
|
|
{bill.category_name ? (
|
|
<span className="text-xs text-muted-foreground">{bill.category_name}</span>
|
|
) : (
|
|
<span className="text-muted-foreground/40 text-xs">—</span>
|
|
)}
|
|
</TableCell>
|
|
|
|
{/* Due day */}
|
|
<TableCell className="px-6 py-4 w-24">
|
|
<span className="text-sm text-muted-foreground">Day {bill.due_day}</span>
|
|
</TableCell>
|
|
|
|
{/* Expected amount */}
|
|
<TableCell className="px-6 py-4 w-28 text-right">
|
|
<span className="font-mono text-sm tabular-nums text-muted-foreground">
|
|
${Number(bill.expected_amount).toFixed(2)}
|
|
</span>
|
|
</TableCell>
|
|
|
|
{/* Billing cycle — field is billing_cycle, not cycle */}
|
|
<TableCell className="px-6 py-4 w-28">
|
|
<span className="text-xs text-muted-foreground capitalize">
|
|
{bill.billing_cycle || 'monthly'}
|
|
</span>
|
|
</TableCell>
|
|
|
|
{/* Flags */}
|
|
<TableCell className="px-6 py-4 w-24">
|
|
{(!!bill.autopay_enabled || !!bill.has_2fa) ? (
|
|
<div className="flex items-center gap-1.5">
|
|
{!!bill.autopay_enabled && (
|
|
<span className="text-[10px] font-semibold px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-400">AP</span>
|
|
)}
|
|
{!!bill.has_2fa && (
|
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-violet-500/15 text-violet-400">2FA</span>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<span className="text-muted-foreground/40 text-xs">—</span>
|
|
)}
|
|
</TableCell>
|
|
|
|
{/* Actions — visible on row hover */}
|
|
<TableCell className="px-6 py-4 w-72 text-right">
|
|
<div className="flex items-center justify-end gap-1.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className={cn(
|
|
'h-7 px-2.5 text-xs',
|
|
bill.active
|
|
? 'text-muted-foreground hover:text-destructive'
|
|
: 'text-emerald-500 hover:text-emerald-400',
|
|
)}
|
|
onClick={() => onToggle?.(bill)}
|
|
>
|
|
{bill.active ? 'Deactivate' : 'Activate'}
|
|
</Button>
|
|
{!bill.active && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 px-2.5 text-xs text-sky-500 hover:text-sky-400 hover:bg-sky-500/10"
|
|
onClick={() => onHistory?.(bill)}
|
|
>
|
|
History
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 px-2.5 text-xs text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
onClick={() => onDelete?.(bill)}
|
|
>
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
|
|
</Table>
|
|
);
|
|
}
|