Refactor: Implement DashboardLayout, fix mobile nav, and resolve scroll issues
This commit is contained in:
165
app/incomes/page.tsx
Normal file
165
app/incomes/page.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useFinanzasStore } from '@/lib/store'
|
||||
import { Plus, Trash2, TrendingUp, DollarSign } from 'lucide-react'
|
||||
import { format } from 'date-fns'
|
||||
import { es } from 'date-fns/locale'
|
||||
import { DashboardLayout } from '@/components/layout/DashboardLayout'
|
||||
|
||||
export default function IncomesPage() {
|
||||
const incomes = useFinanzasStore((state) => state.incomes) || []
|
||||
const addIncome = useFinanzasStore((state) => state.addIncome)
|
||||
const removeIncome = useFinanzasStore((state) => state.removeIncome)
|
||||
|
||||
const [newIncome, setNewIncome] = useState({
|
||||
amount: '',
|
||||
description: '',
|
||||
category: 'salary' as const,
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!newIncome.amount || !newIncome.description) return
|
||||
|
||||
addIncome({
|
||||
amount: parseFloat(newIncome.amount),
|
||||
description: newIncome.description,
|
||||
category: newIncome.category,
|
||||
date: new Date().toISOString(),
|
||||
})
|
||||
setNewIncome({ amount: '', description: '', category: 'salary' })
|
||||
}
|
||||
|
||||
const totalIncomes = incomes.reduce((acc, curr) => acc + curr.amount, 0)
|
||||
|
||||
return (
|
||||
<DashboardLayout title="Ingresos">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-3xl font-bold flex items-center gap-2 text-white">
|
||||
<TrendingUp className="text-green-500" /> Ingresos
|
||||
</h2>
|
||||
<div className="text-right">
|
||||
<p className="text-sm text-gray-400">Total Acumulado</p>
|
||||
<p className="text-2xl font-bold text-green-400">
|
||||
${totalIncomes.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{/* Formulario */}
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-xl md:col-span-1 h-fit overflow-hidden">
|
||||
<div className="p-6 border-b border-slate-700">
|
||||
<h3 className="font-semibold text-lg text-white">Nuevo Ingreso</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-gray-400 block mb-2">Descripción</label>
|
||||
<input
|
||||
placeholder="Ej. Pago Freelance"
|
||||
value={newIncome.description}
|
||||
onChange={(e) =>
|
||||
setNewIncome({ ...newIncome, description: e.target.value })
|
||||
}
|
||||
className="w-full bg-slate-700 border-slate-600 border rounded-lg px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-400 block mb-2">Monto</label>
|
||||
<div className="relative">
|
||||
<DollarSign className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<input
|
||||
type="number"
|
||||
placeholder="0.00"
|
||||
value={newIncome.amount}
|
||||
onChange={(e) =>
|
||||
setNewIncome({ ...newIncome, amount: e.target.value })
|
||||
}
|
||||
className="w-full pl-9 bg-slate-700 border-slate-600 border rounded-lg px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-400 block mb-2">Categoría</label>
|
||||
<select
|
||||
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-emerald-500"
|
||||
value={newIncome.category}
|
||||
onChange={(e) =>
|
||||
setNewIncome({
|
||||
...newIncome,
|
||||
category: e.target.value as any,
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="salary">Salario</option>
|
||||
<option value="freelance">Freelance</option>
|
||||
<option value="business">Negocio</option>
|
||||
<option value="gift">Regalo</option>
|
||||
<option value="other">Otro</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-semibold py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" /> Agregar Ingreso
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lista */}
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-xl md:col-span-2 overflow-hidden">
|
||||
<div className="p-6 border-b border-slate-700">
|
||||
<h3 className="font-semibold text-lg text-white">Historial de Ingresos</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{incomes.length === 0 ? (
|
||||
<p className="text-center text-gray-500 py-8">
|
||||
No hay ingresos registrados aún.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{incomes
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
)
|
||||
.map((income) => (
|
||||
<div
|
||||
key={income.id}
|
||||
className="flex items-center justify-between p-4 bg-slate-700/50 rounded-lg border border-slate-700 hover:border-slate-600 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<p className="font-semibold text-white">
|
||||
{income.description}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 capitalize">
|
||||
{income.category} •{' '}
|
||||
{format(new Date(income.date), "d 'de' MMMM", {
|
||||
locale: es,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-emerald-400 font-mono font-bold">
|
||||
+${income.amount.toLocaleString()}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => removeIncome(income.id)}
|
||||
className="text-slate-500 hover:text-red-400 transition-colors"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user