Enhanced services module with comprehensive consumption tracking: - Add usage field to ServiceBill interface (optional number) - Add unit field to ServiceBill interface (kW, m³, or empty for internet) - Updated AddServiceModal with consumption input field - Auto-detect unit based on service type (electricity=kW, gas/water=m³, internet=none) - Responsive grid layout for amount and consumption inputs - Display consumption and unit in history list - Show price per unit in history (amount/usage) - Improved date display with responsive layout 🤖 Generated with [Claude Code](https://claude.com/claude-code)
178 lines
8.5 KiB
TypeScript
178 lines
8.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { useFinanzasStore } from '@/lib/store'
|
|
import { X, Calendar, Zap, Droplets, Flame, Wifi } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface AddServiceModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
}
|
|
|
|
const SERVICES = [
|
|
{ id: 'electricity', label: 'Luz', icon: Zap, color: 'text-yellow-400' },
|
|
{ id: 'water', label: 'Agua', icon: Droplets, color: 'text-blue-400' },
|
|
{ id: 'gas', label: 'Gas', icon: Flame, color: 'text-orange-400' },
|
|
{ id: 'internet', label: 'Internet', icon: Wifi, color: 'text-cyan-400' },
|
|
]
|
|
|
|
export function AddServiceModal({ isOpen, onClose }: AddServiceModalProps) {
|
|
const addServiceBill = useFinanzasStore((state) => state.addServiceBill)
|
|
|
|
const [type, setType] = useState('electricity')
|
|
const [amount, setAmount] = useState('')
|
|
const [usage, setUsage] = useState('')
|
|
const [period, setPeriod] = useState(new Date().toISOString().slice(0, 7)) // YYYY-MM
|
|
const [date, setDate] = useState(new Date().toISOString().split('T')[0])
|
|
|
|
if (!isOpen) return null
|
|
|
|
const getUnit = (serviceType: string) => {
|
|
switch (serviceType) {
|
|
case 'electricity': return 'kW'
|
|
case 'gas': return 'm³'
|
|
case 'water': return 'm³'
|
|
default: return ''
|
|
}
|
|
}
|
|
|
|
const unit = getUnit(type)
|
|
const showUsage = type !== 'internet'
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
if (!amount) return
|
|
|
|
addServiceBill({
|
|
type: type as any,
|
|
amount: parseFloat(amount),
|
|
usage: usage ? parseFloat(usage) : undefined,
|
|
unit: unit || undefined,
|
|
date: new Date(date).toISOString(),
|
|
period: period,
|
|
notes: ''
|
|
})
|
|
|
|
// Reset
|
|
setAmount('')
|
|
setUsage('')
|
|
onClose()
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200">
|
|
<div className="w-full max-w-lg rounded-xl bg-slate-900 border border-slate-800 shadow-2xl overflow-hidden scale-100 animate-in zoom-in-95 duration-200">
|
|
|
|
<div className="flex items-center justify-between p-6 border-b border-slate-800">
|
|
<h2 className="text-xl font-semibold text-white">Registrar Factura de Servicio</h2>
|
|
<button onClick={onClose} className="text-slate-400 hover:text-white transition-colors">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-6 space-y-5">
|
|
|
|
{/* Service Type Selection */}
|
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
{SERVICES.map((s) => {
|
|
const Icon = s.icon
|
|
const isSelected = type === s.id
|
|
return (
|
|
<div
|
|
key={s.id}
|
|
onClick={() => setType(s.id)}
|
|
className={cn(
|
|
"cursor-pointer p-3 rounded-xl border flex flex-col items-center gap-2 transition-all",
|
|
isSelected
|
|
? "border-cyan-500 bg-cyan-500/10 ring-1 ring-cyan-500"
|
|
: "border-slate-800 bg-slate-950 hover:border-slate-700 hover:bg-slate-900"
|
|
)}
|
|
>
|
|
<Icon className={cn("w-6 h-6", s.color)} />
|
|
<span className={cn("text-xs font-medium", isSelected ? "text-white" : "text-slate-400")}>{s.label}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Amount */}
|
|
<div className={cn("space-y-2", !showUsage && "col-span-2")}>
|
|
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Monto</label>
|
|
<div className="relative">
|
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 font-semibold">$</span>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="0.00"
|
|
value={amount}
|
|
onChange={(e) => setAmount(e.target.value)}
|
|
className="w-full pl-8 pr-4 py-3 bg-slate-950 border border-slate-800 rounded-lg focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 text-white text-lg font-mono outline-none"
|
|
required
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Usage */}
|
|
{showUsage && (
|
|
<div className="space-y-2">
|
|
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Consumo ({unit})</label>
|
|
<div className="relative">
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
placeholder="0"
|
|
value={usage}
|
|
onChange={(e) => setUsage(e.target.value)}
|
|
className="w-full px-4 py-3 bg-slate-950 border border-slate-800 rounded-lg focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 text-white text-lg font-mono outline-none"
|
|
/>
|
|
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-slate-500 text-sm font-medium">{unit}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Period */}
|
|
<div className="space-y-2">
|
|
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Periodo</label>
|
|
<input
|
|
type="month"
|
|
value={period}
|
|
onChange={(e) => setPeriod(e.target.value)}
|
|
className="w-full px-4 py-2.5 bg-slate-950 border border-slate-800 rounded-lg focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 text-white outline-none [color-scheme:dark]"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Date */}
|
|
<div className="space-y-2">
|
|
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Fecha Pago</label>
|
|
<input
|
|
type="date"
|
|
value={date}
|
|
onChange={(e) => setDate(e.target.value)}
|
|
className="w-full px-4 py-2.5 bg-slate-950 border border-slate-800 rounded-lg focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 text-white outline-none [color-scheme:dark]"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
<button
|
|
type="submit"
|
|
className="w-full py-3 bg-cyan-500 hover:bg-cyan-400 text-white font-semibold rounded-lg shadow-lg shadow-cyan-500/20 transition-all active:scale-[0.98]"
|
|
>
|
|
Guardar Factura
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|