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:
156
components/dashboard/SummarySection.tsx
Normal file
156
components/dashboard/SummarySection.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user