Complete personal finance management application with: - Dashboard with financial metrics and alerts - Credit card management and payments - Fixed and variable debt tracking - Monthly budget planning - Intelligent alert system - Responsive design with Tailwind CSS Tech stack: Next.js 14, TypeScript, Zustand, Recharts 🤖 Generated with [Claude Code](https://claude.com/claude-code)
141 lines
4.6 KiB
TypeScript
141 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import { FixedDebt, VariableDebt } from '@/lib/types'
|
|
import { formatCurrency, formatShortDate } from '@/lib/utils'
|
|
import { cn } from '@/lib/utils'
|
|
import { Pencil, Trash2, Check } from 'lucide-react'
|
|
|
|
interface DebtCardProps {
|
|
debt: FixedDebt | VariableDebt
|
|
type: 'fixed' | 'variable'
|
|
onTogglePaid: () => void
|
|
onEdit: () => void
|
|
onDelete: () => void
|
|
}
|
|
|
|
const fixedCategoryColors: Record<string, string> = {
|
|
housing: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
|
|
services: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
|
subscription: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
|
|
other: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
|
|
}
|
|
|
|
const variableCategoryColors: Record<string, string> = {
|
|
shopping: 'bg-pink-500/20 text-pink-400 border-pink-500/30',
|
|
food: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
|
|
entertainment: 'bg-indigo-500/20 text-indigo-400 border-indigo-500/30',
|
|
health: 'bg-red-500/20 text-red-400 border-red-500/30',
|
|
transport: 'bg-cyan-500/20 text-cyan-400 border-cyan-500/30',
|
|
other: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
|
|
}
|
|
|
|
const categoryLabels: Record<string, string> = {
|
|
housing: 'Vivienda',
|
|
services: 'Servicios',
|
|
subscription: 'Suscripción',
|
|
shopping: 'Compras',
|
|
food: 'Comida',
|
|
entertainment: 'Entretenimiento',
|
|
health: 'Salud',
|
|
transport: 'Transporte',
|
|
other: 'Otro',
|
|
}
|
|
|
|
export function DebtCard({ debt, type, onTogglePaid, onEdit, onDelete }: DebtCardProps) {
|
|
const isFixed = type === 'fixed'
|
|
const categoryColors = isFixed ? fixedCategoryColors : variableCategoryColors
|
|
const categoryColor = categoryColors[debt.category] || categoryColors.other
|
|
|
|
const getDueInfo = () => {
|
|
if (isFixed) {
|
|
const fixedDebt = debt as FixedDebt
|
|
return `Vence día ${fixedDebt.dueDay}`
|
|
} else {
|
|
const variableDebt = debt as VariableDebt
|
|
return formatShortDate(variableDebt.date)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'group relative bg-slate-800 border border-slate-700/50 rounded-lg p-4',
|
|
'transition-all duration-200 hover:border-slate-600',
|
|
debt.isPaid && 'opacity-60'
|
|
)}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
{/* Checkbox */}
|
|
<button
|
|
onClick={onTogglePaid}
|
|
className={cn(
|
|
'mt-1 w-5 h-5 rounded border-2 flex items-center justify-center',
|
|
'transition-colors duration-200',
|
|
debt.isPaid
|
|
? 'bg-emerald-500 border-emerald-500'
|
|
: 'border-slate-500 hover:border-emerald-400'
|
|
)}
|
|
aria-label={debt.isPaid ? 'Marcar como no pagada' : 'Marcar como pagada'}
|
|
>
|
|
{debt.isPaid && <Check className="w-3 h-3 text-white" />}
|
|
</button>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div>
|
|
<h3
|
|
className={cn(
|
|
'text-white font-medium truncate',
|
|
debt.isPaid && 'line-through text-slate-400'
|
|
)}
|
|
>
|
|
{debt.name}
|
|
</h3>
|
|
<p className="text-slate-400 text-sm mt-0.5">{getDueInfo()}</p>
|
|
</div>
|
|
<span className="font-mono text-emerald-400 font-semibold whitespace-nowrap">
|
|
{formatCurrency(debt.amount)}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between mt-3">
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border',
|
|
categoryColor
|
|
)}
|
|
>
|
|
{categoryLabels[debt.category] || debt.category}
|
|
</span>
|
|
|
|
{isFixed && (debt as FixedDebt).isAutoDebit && (
|
|
<span className="text-xs text-slate-500">
|
|
Débito automático
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<button
|
|
onClick={onEdit}
|
|
className="p-1.5 text-slate-400 hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors"
|
|
aria-label="Editar"
|
|
>
|
|
<Pencil className="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
onClick={onDelete}
|
|
className="p-1.5 text-slate-400 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors"
|
|
aria-label="Eliminar"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|