146 lines
9.0 KiB
TypeScript
146 lines
9.0 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { useFinanzasStore } from '@/lib/store'
|
|
import { predictNextBill, calculateTrend } from '@/lib/predictions'
|
|
import { formatCurrency } from '@/lib/utils'
|
|
import { Zap, Droplets, Flame, Wifi, TrendingUp, TrendingDown, Plus, History } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import { AddServiceModal } from '@/components/modals/AddServiceModal'
|
|
import { DashboardLayout } from '@/components/layout/DashboardLayout'
|
|
|
|
const SERVICES = [
|
|
{ id: 'electricity', label: 'Luz (Electricidad)', icon: Zap, color: 'text-yellow-400', bg: 'bg-yellow-400/10' },
|
|
{ id: 'water', label: 'Agua', icon: Droplets, color: 'text-blue-400', bg: 'bg-blue-400/10' },
|
|
{ id: 'gas', label: 'Gas', icon: Flame, color: 'text-orange-400', bg: 'bg-orange-400/10' },
|
|
{ id: 'internet', label: 'Internet', icon: Wifi, color: 'text-cyan-400', bg: 'bg-cyan-400/10' },
|
|
]
|
|
|
|
export default function ServicesPage() {
|
|
const serviceBills = useFinanzasStore((state) => state.serviceBills)
|
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false)
|
|
|
|
return (
|
|
<DashboardLayout title="Servicios">
|
|
<div className="space-y-6">
|
|
|
|
{/* Header */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-white">Servicios y Predicciones</h2>
|
|
<p className="text-slate-400 text-sm">Gestiona tus consumos de Luz, Agua y Gas.</p>
|
|
</div>
|
|
<button
|
|
onClick={() => setIsAddModalOpen(true)}
|
|
className="flex items-center gap-2 px-4 py-2 bg-cyan-500 hover:bg-cyan-400 text-white rounded-lg transition shadow-lg shadow-cyan-500/20 font-medium self-start sm:self-auto"
|
|
>
|
|
<Plus size={18} /> Nuevo Pago
|
|
</button>
|
|
</div>
|
|
|
|
{/* Service Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
{SERVICES.map((service) => {
|
|
const Icon = service.icon
|
|
const prediction = predictNextBill(serviceBills, service.id as any)
|
|
const trend = calculateTrend(serviceBills, service.id as any)
|
|
const lastBill = serviceBills
|
|
.filter(b => b.type === service.id)
|
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0]
|
|
|
|
return (
|
|
<div key={service.id} className="bg-slate-900 border border-slate-800 rounded-xl p-5 space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className={cn("p-2 rounded-lg", service.bg)}>
|
|
<Icon className={cn("w-6 h-6", service.color)} />
|
|
</div>
|
|
{trend !== 0 && (
|
|
<div className={cn("flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full", trend > 0 ? "bg-red-500/10 text-red-400" : "bg-emerald-500/10 text-emerald-400")}>
|
|
{trend > 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
|
|
{Math.abs(trend).toFixed(0)}%
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-slate-400 text-sm font-medium">{service.label}</p>
|
|
<div className="flex items-baseline gap-2">
|
|
<h3 className="text-2xl font-bold text-white mt-1">
|
|
{formatCurrency(prediction || (lastBill?.amount ?? 0))}
|
|
</h3>
|
|
{prediction > 0 && <span className="text-xs text-slate-500 font-mono">(est.)</span>}
|
|
</div>
|
|
<p className="text-xs text-slate-500 mt-1">
|
|
{lastBill
|
|
? `Último: ${formatCurrency(lastBill.amount)}`
|
|
: 'Sin historial'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* History List */}
|
|
<div className="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
|
<div className="p-5 border-b border-slate-800 flex items-center gap-2">
|
|
<History size={18} className="text-slate-400" />
|
|
<h3 className="text-lg font-semibold text-white">Historial de Pagos</h3>
|
|
</div>
|
|
<div className="divide-y divide-slate-800">
|
|
{serviceBills.length === 0 ? (
|
|
<div className="p-8 text-center text-slate-500 text-sm">
|
|
No hay facturas registradas. Comienza agregando una para ver predicciones.
|
|
</div>
|
|
) : (
|
|
serviceBills
|
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
|
.map((bill) => {
|
|
const service = SERVICES.find(s => s.id === bill.type)
|
|
const Icon = service?.icon || Zap
|
|
|
|
return (
|
|
<div key={bill.id} className="p-4 flex items-center justify-between hover:bg-slate-800/50 transition-colors">
|
|
<div className="flex items-center gap-4">
|
|
<div className={cn("p-2 rounded-lg", service?.bg || 'bg-slate-800')}>
|
|
<Icon className={cn("w-5 h-5", service?.color || 'text-slate-400')} />
|
|
</div>
|
|
<div>
|
|
<p className="text-white font-medium capitalize">{service?.label || bill.type}</p>
|
|
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
|
|
<p className="text-xs text-slate-500 capitalize">{new Date(bill.date).toLocaleDateString('es-AR', { dateStyle: 'long' })}</p>
|
|
{bill.usage && (
|
|
<>
|
|
<span className="hidden sm:inline text-slate-700">•</span>
|
|
<p className="text-xs text-slate-400">
|
|
Consumo: <span className="text-slate-300 font-medium">{bill.usage} {bill.unit}</span>
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-white font-mono font-medium">{formatCurrency(bill.amount)}</p>
|
|
<div className="flex flex-col items-end">
|
|
<p className="text-xs text-slate-500 uppercase">{bill.period}</p>
|
|
{bill.usage && bill.amount && (
|
|
<p className="text-[10px] text-cyan-500/80 font-mono">
|
|
{formatCurrency(bill.amount / bill.usage)} / {bill.unit}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<AddServiceModal isOpen={isAddModalOpen} onClose={() => setIsAddModalOpen(false)} />
|
|
</div>
|
|
</DashboardLayout>
|
|
)
|
|
}
|