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)
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import { AlertCircle, CreditCard, PiggyBank, Wallet } from 'lucide-react'
|
|
import { MetricCard } from './MetricCard'
|
|
import { ExpenseChart } from './ExpenseChart'
|
|
import { useFinanzasStore } from '@/lib/store'
|
|
import {
|
|
calculateTotalFixedDebts,
|
|
calculateTotalVariableDebts,
|
|
} from '@/lib/utils'
|
|
import {
|
|
getCurrentMonthBudget,
|
|
calculateCurrentSpending,
|
|
} from '@/lib/alerts'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export function SummarySection() {
|
|
const {
|
|
fixedDebts,
|
|
variableDebts,
|
|
creditCards,
|
|
monthlyBudgets,
|
|
alerts,
|
|
currentMonth,
|
|
currentYear,
|
|
} = useFinanzasStore()
|
|
|
|
// Calcular métricas
|
|
const totalFixedDebts = calculateTotalFixedDebts(fixedDebts)
|
|
const totalVariableDebts = calculateTotalVariableDebts(variableDebts)
|
|
const totalPendingDebts = totalFixedDebts + totalVariableDebts
|
|
|
|
const totalCardBalance = creditCards.reduce(
|
|
(sum, card) => sum + card.currentBalance,
|
|
0
|
|
)
|
|
|
|
const currentBudget = getCurrentMonthBudget(
|
|
monthlyBudgets,
|
|
currentMonth,
|
|
currentYear
|
|
)
|
|
|
|
const currentSpending = calculateCurrentSpending(fixedDebts, variableDebts)
|
|
|
|
// Presupuesto disponible (ingresos - gastos actuales)
|
|
const availableBudget = currentBudget
|
|
? currentBudget.totalIncome - currentSpending
|
|
: 0
|
|
|
|
// Meta de ahorro proyectada
|
|
const projectedSavings = currentBudget
|
|
? currentBudget.totalIncome - currentSpending
|
|
: 0
|
|
|
|
const savingsGoal = currentBudget?.savingsGoal || 0
|
|
|
|
// Alertas no leídas (primeras 3)
|
|
const unreadAlerts = alerts
|
|
.filter((alert) => !alert.isRead)
|
|
.slice(0, 3)
|
|
|
|
// Colores por severidad de alerta
|
|
const severityColors = {
|
|
danger: 'border-rose-500 bg-rose-500/10 text-rose-400',
|
|
warning: 'border-amber-500 bg-amber-500/10 text-amber-400',
|
|
info: 'border-blue-500 bg-blue-500/10 text-blue-400',
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Grid de métricas */}
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<MetricCard
|
|
title="Deudas Pendientes"
|
|
amount={totalPendingDebts}
|
|
subtitle={`${fixedDebts.filter((d) => !d.isPaid).length + variableDebts.filter((d) => !d.isPaid).length} pagos pendientes`}
|
|
icon={Wallet}
|
|
color="text-rose-400"
|
|
/>
|
|
|
|
<MetricCard
|
|
title="Balance en Tarjetas"
|
|
amount={totalCardBalance}
|
|
subtitle={`${creditCards.length} tarjetas activas`}
|
|
icon={CreditCard}
|
|
color="text-blue-400"
|
|
/>
|
|
|
|
<MetricCard
|
|
title="Presupuesto Disponible"
|
|
amount={availableBudget}
|
|
subtitle={
|
|
currentBudget
|
|
? `de ${currentBudget.totalIncome.toLocaleString('es-AR', {
|
|
style: 'currency',
|
|
currency: 'ARS',
|
|
})} ingresos`
|
|
: 'Sin presupuesto definido'
|
|
}
|
|
icon={PiggyBank}
|
|
color="text-emerald-400"
|
|
/>
|
|
|
|
<MetricCard
|
|
title="Meta de Ahorro"
|
|
amount={projectedSavings}
|
|
subtitle={
|
|
savingsGoal > 0
|
|
? `${((projectedSavings / savingsGoal) * 100).toFixed(0)}% de la meta`
|
|
: 'Sin meta definida'
|
|
}
|
|
icon={PiggyBank}
|
|
color="text-violet-400"
|
|
/>
|
|
</div>
|
|
|
|
{/* Gráfico y alertas */}
|
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
{/* Gráfico de distribución */}
|
|
<ExpenseChart fixedDebts={fixedDebts} variableDebts={variableDebts} />
|
|
|
|
{/* Alertas destacadas */}
|
|
<div className="rounded-xl border border-slate-700 bg-slate-800 p-6">
|
|
<h3 className="mb-4 text-lg font-semibold text-white">
|
|
Alertas Destacadas
|
|
</h3>
|
|
|
|
{unreadAlerts.length === 0 ? (
|
|
<div className="flex h-48 items-center justify-center">
|
|
<p className="text-slate-500">No hay alertas pendientes</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{unreadAlerts.map((alert) => (
|
|
<div
|
|
key={alert.id}
|
|
className={cn(
|
|
'flex items-start gap-3 rounded-lg border p-4',
|
|
severityColors[alert.severity]
|
|
)}
|
|
>
|
|
<AlertCircle className="mt-0.5 h-5 w-5 shrink-0" />
|
|
<div>
|
|
<h4 className="font-medium">{alert.title}</h4>
|
|
<p className="mt-1 text-sm opacity-90">{alert.message}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|