feat: initial commit - finanzas app
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)
This commit is contained in:
168
components/dashboard/RecentActivity.tsx
Normal file
168
components/dashboard/RecentActivity.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user