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)
169 lines
5.0 KiB
TypeScript
169 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
import { ArrowDownLeft, ArrowUpRight, CreditCard, Wallet } from 'lucide-react'
|
|
import { useFinanzasStore } from '@/lib/store'
|
|
import { formatCurrency, formatShortDate } from '@/lib/utils'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface RecentActivityProps {
|
|
limit?: number
|
|
}
|
|
|
|
interface ActivityItem {
|
|
id: string
|
|
type: 'fixed_debt' | 'variable_debt' | 'card_payment'
|
|
title: string
|
|
amount: number
|
|
date: string
|
|
description?: string
|
|
}
|
|
|
|
export function RecentActivity({ limit = 5 }: RecentActivityProps) {
|
|
const { fixedDebts, variableDebts, cardPayments, creditCards } =
|
|
useFinanzasStore()
|
|
|
|
// Combinar todas las actividades
|
|
const activities: ActivityItem[] = [
|
|
// Deudas fijas recientes
|
|
...fixedDebts.slice(0, limit).map((debt) => ({
|
|
id: debt.id,
|
|
type: 'fixed_debt' as const,
|
|
title: debt.name,
|
|
amount: debt.amount,
|
|
date: new Date().toISOString(), // Usar fecha actual ya que fixedDebt no tiene fecha de creación
|
|
description: `Vence el día ${debt.dueDay}`,
|
|
})),
|
|
|
|
// Deudas variables recientes
|
|
...variableDebts.slice(0, limit).map((debt) => ({
|
|
id: debt.id,
|
|
type: 'variable_debt' as const,
|
|
title: debt.name,
|
|
amount: debt.amount,
|
|
date: debt.date,
|
|
description: debt.notes,
|
|
})),
|
|
|
|
// Pagos de tarjetas recientes
|
|
...cardPayments.slice(0, limit).map((payment) => {
|
|
const card = creditCards.find((c) => c.id === payment.cardId)
|
|
return {
|
|
id: payment.id,
|
|
type: 'card_payment' as const,
|
|
title: `Pago - ${card?.name || 'Tarjeta'}`,
|
|
amount: payment.amount,
|
|
date: payment.date,
|
|
description: payment.description,
|
|
}
|
|
}),
|
|
]
|
|
|
|
// Ordenar por fecha (más recientes primero)
|
|
const sortedActivities = activities
|
|
.sort(
|
|
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
|
)
|
|
.slice(0, limit)
|
|
|
|
// Configuración por tipo de actividad
|
|
const activityConfig = {
|
|
fixed_debt: {
|
|
icon: Wallet,
|
|
label: 'Deuda Fija',
|
|
color: 'text-amber-400',
|
|
bgColor: 'bg-amber-400/10',
|
|
},
|
|
variable_debt: {
|
|
icon: ArrowUpRight,
|
|
label: 'Gasto',
|
|
color: 'text-rose-400',
|
|
bgColor: 'bg-rose-400/10',
|
|
},
|
|
card_payment: {
|
|
icon: CreditCard,
|
|
label: 'Pago Tarjeta',
|
|
color: 'text-blue-400',
|
|
bgColor: 'bg-blue-400/10',
|
|
},
|
|
}
|
|
|
|
if (sortedActivities.length === 0) {
|
|
return (
|
|
<div className="rounded-xl border border-slate-700 bg-slate-800 p-6">
|
|
<h3 className="mb-4 text-lg font-semibold text-white">
|
|
Actividad Reciente
|
|
</h3>
|
|
<div className="flex h-32 items-center justify-center">
|
|
<p className="text-slate-500">No hay actividad reciente</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-xl border border-slate-700 bg-slate-800 p-6">
|
|
<h3 className="mb-4 text-lg font-semibold text-white">
|
|
Actividad Reciente
|
|
</h3>
|
|
|
|
<div className="space-y-3">
|
|
{sortedActivities.map((activity) => {
|
|
const config = activityConfig[activity.type]
|
|
const Icon = config.icon
|
|
|
|
return (
|
|
<div
|
|
key={activity.id}
|
|
className="flex items-center gap-4 rounded-lg border border-slate-700/50 bg-slate-700/30 p-4 transition-colors hover:bg-slate-700/50"
|
|
>
|
|
{/* Icono */}
|
|
<div
|
|
className={cn(
|
|
'flex h-10 w-10 items-center justify-center rounded-full',
|
|
config.bgColor
|
|
)}
|
|
>
|
|
<Icon className={cn('h-5 w-5', config.color)} />
|
|
</div>
|
|
|
|
{/* Contenido */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<h4 className="truncate font-medium text-white">
|
|
{activity.title}
|
|
</h4>
|
|
<span
|
|
className={cn(
|
|
'shrink-0 font-mono font-medium',
|
|
activity.type === 'card_payment'
|
|
? 'text-emerald-400'
|
|
: 'text-rose-400'
|
|
)}
|
|
>
|
|
{activity.type === 'card_payment' ? '+' : '-'}
|
|
{formatCurrency(activity.amount)}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mt-1 flex items-center gap-2 text-sm text-slate-400">
|
|
<span className={cn('text-xs', config.color)}>
|
|
{config.label}
|
|
</span>
|
|
<span>•</span>
|
|
<span>{formatShortDate(activity.date)}</span>
|
|
{activity.description && (
|
|
<>
|
|
<span>•</span>
|
|
<span className="truncate">{activity.description}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|