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:
224
components/debts/DebtSection.tsx
Normal file
224
components/debts/DebtSection.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useFinanzasStore } from '@/lib/store'
|
||||
import { FixedDebt, VariableDebt } from '@/lib/types'
|
||||
import { DebtCard } from './DebtCard'
|
||||
import { FixedDebtForm } from './FixedDebtForm'
|
||||
import { VariableDebtForm } from './VariableDebtForm'
|
||||
import { Plus, Wallet } from 'lucide-react'
|
||||
import { cn, formatCurrency, calculateTotalFixedDebts, calculateTotalVariableDebts } from '@/lib/utils'
|
||||
|
||||
type DebtType = 'fixed' | 'variable'
|
||||
|
||||
export function DebtSection() {
|
||||
const [activeTab, setActiveTab] = useState<DebtType>('fixed')
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [editingDebt, setEditingDebt] = useState<FixedDebt | VariableDebt | null>(null)
|
||||
|
||||
const {
|
||||
fixedDebts,
|
||||
variableDebts,
|
||||
addFixedDebt,
|
||||
updateFixedDebt,
|
||||
deleteFixedDebt,
|
||||
toggleFixedDebtPaid,
|
||||
addVariableDebt,
|
||||
updateVariableDebt,
|
||||
deleteVariableDebt,
|
||||
toggleVariableDebtPaid,
|
||||
} = useFinanzasStore()
|
||||
|
||||
const currentDebts = activeTab === 'fixed' ? fixedDebts : variableDebts
|
||||
const totalUnpaid = activeTab === 'fixed'
|
||||
? calculateTotalFixedDebts(fixedDebts)
|
||||
: calculateTotalVariableDebts(variableDebts)
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditingDebt(null)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (debt: FixedDebt | VariableDebt) => {
|
||||
setEditingDebt(debt)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false)
|
||||
setEditingDebt(null)
|
||||
}
|
||||
|
||||
const handleSubmitFixed = (data: Omit<FixedDebt, 'id' | 'isPaid'>) => {
|
||||
if (editingDebt?.id) {
|
||||
updateFixedDebt(editingDebt.id, data)
|
||||
} else {
|
||||
addFixedDebt({ ...data, isPaid: false })
|
||||
}
|
||||
handleCloseModal()
|
||||
}
|
||||
|
||||
const handleSubmitVariable = (data: Omit<VariableDebt, 'id' | 'isPaid'>) => {
|
||||
if (editingDebt?.id) {
|
||||
updateVariableDebt(editingDebt.id, data)
|
||||
} else {
|
||||
addVariableDebt({ ...data, isPaid: false })
|
||||
}
|
||||
handleCloseModal()
|
||||
}
|
||||
|
||||
const handleDelete = (debt: FixedDebt | VariableDebt) => {
|
||||
if (confirm('¿Estás seguro de que deseas eliminar esta deuda?')) {
|
||||
if (activeTab === 'fixed') {
|
||||
deleteFixedDebt(debt.id)
|
||||
} else {
|
||||
deleteVariableDebt(debt.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleTogglePaid = (debt: FixedDebt | VariableDebt) => {
|
||||
if (activeTab === 'fixed') {
|
||||
toggleFixedDebtPaid(debt.id)
|
||||
} else {
|
||||
toggleVariableDebtPaid(debt.id)
|
||||
}
|
||||
}
|
||||
|
||||
const paidCount = currentDebts.filter(d => d.isPaid).length
|
||||
const unpaidCount = currentDebts.filter(d => !d.isPaid).length
|
||||
|
||||
return (
|
||||
<div className="bg-slate-900 min-h-screen p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Deudas</h1>
|
||||
<p className="text-slate-400 text-sm mt-1">
|
||||
Gestiona tus gastos fijos y variables
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className={cn(
|
||||
'flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium',
|
||||
'hover:bg-blue-500 transition-colors'
|
||||
)}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Agregar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-6">
|
||||
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
|
||||
<p className="text-slate-400 text-sm">Total pendiente</p>
|
||||
<p className="text-xl font-mono font-semibold text-emerald-400 mt-1">
|
||||
{formatCurrency(totalUnpaid)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
|
||||
<p className="text-slate-400 text-sm">Pagadas</p>
|
||||
<p className="text-xl font-semibold text-blue-400 mt-1">
|
||||
{paidCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
|
||||
<p className="text-slate-400 text-sm">Pendientes</p>
|
||||
<p className="text-xl font-semibold text-orange-400 mt-1">
|
||||
{unpaidCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2 mb-6">
|
||||
<button
|
||||
onClick={() => setActiveTab('fixed')}
|
||||
className={cn(
|
||||
'px-4 py-2 rounded-lg font-medium transition-colors',
|
||||
activeTab === 'fixed'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'
|
||||
)}
|
||||
>
|
||||
Fijas ({fixedDebts.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('variable')}
|
||||
className={cn(
|
||||
'px-4 py-2 rounded-lg font-medium transition-colors',
|
||||
activeTab === 'variable'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'
|
||||
)}
|
||||
>
|
||||
Variables ({variableDebts.length})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Debt List */}
|
||||
<div className="space-y-3">
|
||||
{currentDebts.length === 0 ? (
|
||||
<div className="text-center py-16 bg-slate-800/50 border border-slate-700/50 rounded-lg">
|
||||
<Wallet className="w-12 h-12 text-slate-600 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-slate-300">
|
||||
No hay deudas {activeTab === 'fixed' ? 'fijas' : 'variables'}
|
||||
</h3>
|
||||
<p className="text-slate-500 mt-2">
|
||||
Haz clic en "Agregar" para crear una nueva deuda
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
currentDebts.map((debt) => (
|
||||
<DebtCard
|
||||
key={debt.id}
|
||||
debt={debt}
|
||||
type={activeTab}
|
||||
onTogglePaid={() => handleTogglePaid(debt)}
|
||||
onEdit={() => handleEdit(debt)}
|
||||
onDelete={() => handleDelete(debt)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={handleCloseModal}
|
||||
/>
|
||||
<div className="relative bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-full max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-bold text-white mb-4">
|
||||
{editingDebt
|
||||
? 'Editar deuda'
|
||||
: activeTab === 'fixed'
|
||||
? 'Nueva deuda fija'
|
||||
: 'Nueva deuda variable'}
|
||||
</h2>
|
||||
{activeTab === 'fixed' ? (
|
||||
<FixedDebtForm
|
||||
initialData={editingDebt as Partial<FixedDebt> | undefined}
|
||||
onSubmit={handleSubmitFixed}
|
||||
onCancel={handleCloseModal}
|
||||
/>
|
||||
) : (
|
||||
<VariableDebtForm
|
||||
initialData={editingDebt as Partial<VariableDebt> | undefined}
|
||||
onSubmit={handleSubmitVariable}
|
||||
onCancel={handleCloseModal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user