feat: add consumption tracking for services
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)
This commit is contained in:
@@ -105,12 +105,29 @@ export default function ServicesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-white font-medium capitalize">{service?.label || bill.type}</p>
|
<p className="text-white font-medium capitalize">{service?.label || bill.type}</p>
|
||||||
<p className="text-xs text-slate-500 capitalize">{new Date(bill.date).toLocaleDateString('es-AR', { dateStyle: 'long' })}</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>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-white font-mono font-medium">{formatCurrency(bill.amount)}</p>
|
<p className="text-white font-mono font-medium">{formatCurrency(bill.amount)}</p>
|
||||||
<p className="text-xs text-slate-500 uppercase">{bill.period}</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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,11 +22,24 @@ export function AddServiceModal({ isOpen, onClose }: AddServiceModalProps) {
|
|||||||
|
|
||||||
const [type, setType] = useState('electricity')
|
const [type, setType] = useState('electricity')
|
||||||
const [amount, setAmount] = useState('')
|
const [amount, setAmount] = useState('')
|
||||||
|
const [usage, setUsage] = useState('')
|
||||||
const [period, setPeriod] = useState(new Date().toISOString().slice(0, 7)) // YYYY-MM
|
const [period, setPeriod] = useState(new Date().toISOString().slice(0, 7)) // YYYY-MM
|
||||||
const [date, setDate] = useState(new Date().toISOString().split('T')[0])
|
const [date, setDate] = useState(new Date().toISOString().split('T')[0])
|
||||||
|
|
||||||
if (!isOpen) return null
|
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) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@@ -35,6 +48,8 @@ export function AddServiceModal({ isOpen, onClose }: AddServiceModalProps) {
|
|||||||
addServiceBill({
|
addServiceBill({
|
||||||
type: type as any,
|
type: type as any,
|
||||||
amount: parseFloat(amount),
|
amount: parseFloat(amount),
|
||||||
|
usage: usage ? parseFloat(usage) : undefined,
|
||||||
|
unit: unit || undefined,
|
||||||
date: new Date(date).toISOString(),
|
date: new Date(date).toISOString(),
|
||||||
period: period,
|
period: period,
|
||||||
notes: ''
|
notes: ''
|
||||||
@@ -42,6 +57,7 @@ export function AddServiceModal({ isOpen, onClose }: AddServiceModalProps) {
|
|||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
setAmount('')
|
setAmount('')
|
||||||
|
setUsage('')
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,22 +97,42 @@ export function AddServiceModal({ isOpen, onClose }: AddServiceModalProps) {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Amount */}
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
{/* Amount */}
|
||||||
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Monto Factura</label>
|
<div className={cn("space-y-2", !showUsage && "col-span-2")}>
|
||||||
<div className="relative">
|
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Monto</label>
|
||||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 font-semibold">$</span>
|
<div className="relative">
|
||||||
<input
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400 font-semibold">$</span>
|
||||||
type="number"
|
<input
|
||||||
step="0.01"
|
type="number"
|
||||||
placeholder="0.00"
|
step="0.01"
|
||||||
value={amount}
|
placeholder="0.00"
|
||||||
onChange={(e) => setAmount(e.target.value)}
|
value={amount}
|
||||||
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"
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
required
|
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"
|
||||||
autoFocus
|
required
|
||||||
/>
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ export interface ServiceBill {
|
|||||||
id: string
|
id: string
|
||||||
type: 'electricity' | 'water' | 'gas' | 'internet'
|
type: 'electricity' | 'water' | 'gas' | 'internet'
|
||||||
amount: number
|
amount: number
|
||||||
|
usage?: number
|
||||||
|
unit?: string
|
||||||
date: string
|
date: string
|
||||||
period: string // YYYY-MM
|
period: string // YYYY-MM
|
||||||
isPaid: boolean
|
isPaid: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user