'use client'; import { format } from 'date-fns'; import { Archive, Calendar, Eye, MoreHorizontal, Pencil, Receipt, Tag } from 'lucide-react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card'; import { cn } from '@/lib/utils'; import type { ExpenseRow } from './expense-columns'; const PAYMENT_STATUS_COLORS: Record = { unpaid: 'bg-red-100 text-red-700 border-red-200', paid: 'bg-green-100 text-green-700 border-green-200', partial: 'bg-yellow-100 text-yellow-700 border-yellow-200', reconciled: 'bg-green-100 text-green-700 border-green-200', flagged: 'bg-rose-100 text-rose-700 border-rose-200', pending: 'bg-amber-100 text-amber-700 border-amber-200', }; /** * Accent bar by payment status: * paid / reconciled → emerald * pending → amber * flagged → rose * other / null → slate * If duplicateOf is set, override to amber-500. */ function deriveAccent(status: string | null, duplicateOf: string | null): string { if (duplicateOf) return 'bg-amber-500'; switch (status) { case 'paid': case 'reconciled': return 'bg-emerald-400'; case 'pending': return 'bg-amber-400'; case 'flagged': return 'bg-rose-400'; default: return 'bg-slate-300'; } } function formatAmount(amount: string, currency: string): string { try { return new Intl.NumberFormat('en', { style: 'currency', currency }).format(Number(amount)); } catch { return `${currency} ${amount}`; } } interface ExpenseCardProps { expense: ExpenseRow; portSlug: string; onEdit: (expense: ExpenseRow) => void; onArchive: (expense: ExpenseRow) => void; } export function ExpenseCard({ expense, portSlug, onEdit, onArchive }: ExpenseCardProps) { const accentClass = deriveAccent(expense.paymentStatus, expense.duplicateOf); const title = expense.establishmentName ?? (expense.description ? expense.description.slice(0, 40) : null) ?? 'Untitled expense'; // Subtitle: category (capitalized) or "{paymentMethod} by {payer}" let subtitle: string | null = null; if (expense.category) { subtitle = expense.category.replace(/_/g, ' '); // Capitalize first letter subtitle = subtitle.charAt(0).toUpperCase() + subtitle.slice(1); } else if (expense.paymentMethod || expense.payer) { const parts: string[] = []; if (expense.paymentMethod) parts.push(expense.paymentMethod); if (expense.payer) parts.push(`by ${expense.payer}`); subtitle = parts.join(' '); } const hasCategory = !!expense.category; let dateFormatted: string | null = null; try { dateFormatted = format(new Date(expense.expenseDate), 'MMM d, yyyy'); } catch { dateFormatted = expense.expenseDate; } const amountFormatted = formatAmount(expense.amount, expense.currency); const statusColor = expense.paymentStatus ? (PAYMENT_STATUS_COLORS[expense.paymentStatus] ?? 'bg-muted text-muted-foreground border-muted') : null; return ( View onEdit(expense)}> Edit onArchive(expense)}> Archive } >
} />
{/* Title row + spacer for actions button */}

{title}

{/* Category / payer subtitle */} {subtitle ? (

{hasCategory ? ( ) : null} {subtitle}

) : null} {/* Amount — prominent */}

{amountFormatted}

{/* Date meta */} {dateFormatted ? (
}>{dateFormatted}
) : null} {/* Status + duplicate pills */}
{statusColor && expense.paymentStatus ? ( {expense.paymentStatus} ) : null} {expense.duplicateOf ? ( Possible duplicate ) : null}
); }