import React,
{ useState, useMemo } from 'react';
import {
useQuery } from '@tanstack/react-query';
import {
base44 } from '@/api/base44Client';
import { Button
} from '@/components/ui/button';
import { Input
} from '@/components/ui/input';
import { Label
} from '@/components/ui/label';
import { Select,
SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog,
DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Plus,
Trash2, TrendingUp, TrendingDown } from 'lucide-react';
import {
format, parseISO } from 'date-fns';
import {
formatCurrency } from '@/lib/format';
import { cn
} from '@/lib/utils';
import {
useEdificio } from '@/context/EdificioContext';
const SALDO_INICIAL
= 0;
function MovimientoForm({
open, onOpenChange, onSaved, edificioId }) {
const
[form, setForm] = useState({
tipo: 'gasto',
descripcion: '',
amount: '',
fecha: format(new Date(), 'yyyy-MM-dd'),
notes: '',
});
const
[saving, setSaving] = useState(false);
React.useEffect(()
=> {
if (open) {
setForm({
tipo: 'gasto',
descripcion:
'',
amount:
'',
fecha: format(new Date(), 'yyyy-MM-dd'),
notes: '',
});
}
},
[open]);
const
handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
await base44.entities.CajaChica.create({
edificio_id: edificioId,
tipo: form.tipo,
descripcion: form.descripcion,
amount: Number(form.amount),
fecha: form.fecha,
notes: form.notes || undefined,
});
setSaving(false);
onSaved?.();
onOpenChange(false);
};
return
(
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="font-serif text-2xl
tracking-tight">Nuevo movimiento</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4
pt-2">
<div className="space-y-1.5">
<Label>Tipo</Label>
<Select value={form.tipo} onValueChange={(v)
=> setForm({ ...form, tipo: v })}>
<SelectTrigger><SelectValue
/></SelectTrigger>
<SelectContent>
<SelectItem value="ingreso">Ingreso</SelectItem>
<SelectItem value="gasto">Gasto</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label htmlFor="desc">Descripción</Label>
<Input id="desc" value={form.descripcion} onChange={(e) => setForm({ ...form, descripcion:
e.target.value })} required />
</div>
<div className="grid grid-cols-2
gap-3">
<div className="space-y-1.5">
<Label htmlFor="amount">Monto
(S/.)</Label>
<Input id="amount" type="number"
step="0.01" min="0.01" value={form.amount} onChange={(e)
=> setForm({ ...form, amount: e.target.value })} required />
</div>
<div className="space-y-1.5">
<Label htmlFor="fecha">Fecha</Label>
<Input
id="fecha" type="date" value={form.fecha}
onChange={(e) => setForm({ ...form, fecha: e.target.value })} required />
</div>
</div>
<div className="space-y-1.5">
<Label htmlFor="notes">Notas
(opcional)</Label>
<Input id="notes"
value={form.notes} onChange={(e) => setForm({ ...form, notes: e.target.value
})} />
</div>
<div className="flex justify-end gap-2
pt-2">
<Button type="button" variant="ghost"
onClick={() => onOpenChange(false)}>Cancelar</Button>
<Button type="submit"
disabled={saving || !form.descripcion || !form.amount}>
{saving ? 'Guardando…' : 'Guardar'}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
}
export default
function CajaChica() {
const
[open, setOpen] = useState(false);
const { edificioActivo
} = useEdificio();
const {
data: movimientos = [], refetch } = useQuery({
queryKey: ['caja-chica',
edificioActivo?.id],
queryFn: () => base44.entities.CajaChica.filter({ edificio_id: edificioActivo?.id }, '-fecha'),
enabled: !!edificioActivo?.id,
initialData: [],
});
const
handleDelete = async (id) => {
await base44.entities.CajaChica.delete(id);
refetch();
};
const { totalIngresos, totalGastos,
saldoFinal
} = useMemo(() => {
let totalIngresos = 0, totalGastos = 0;
for (const m of movimientos) {
if (m.tipo === 'ingreso') totalIngresos += Number(m.amount) || 0;
else totalGastos += Number(m.amount)
|| 0;
}
return { totalIngresos, totalGastos, saldoFinal: SALDO_INICIAL
+ totalIngresos - totalGastos };
}, [movimientos]);
const sorted
= useMemo(
() => [...movimientos].sort((a, b) => (b.fecha || '').localeCompare(a.fecha || '')),
[movimientos]
);
return
(
<div className="space-y-8">
<header className="flex flex-col sm:flex-row sm:items-end
sm:justify-between gap-4">
<div>
<div className="text-[11px] uppercase
tracking-[0.18em] text-muted-foreground">Finanzas</div>
<h1 className="font-serif text-4xl
md:text-5xl tracking-tight mt-2">Caja Chica</h1>
<p className="text-sm text-muted-foreground
mt-2">Saldo inicial: {formatCurrency(SALDO_INICIAL)}</p>
</div>
<Button onClick={() => setOpen(true)}>
<Plus className="w-4 h-4 mr-1.5" />
Nuevo movimiento
</Button>
</header>
<div className="grid grid-cols-2 sm:grid-cols-4
gap-3">
<div className="bg-card border border-border
rounded-xl p-4">
<div className="text-[10px] uppercase
tracking-[0.15em] text-muted-foreground">Saldo inicial</div>
<div className="font-serif text-xl mt-2
tabular-nums">{formatCurrency(SALDO_INICIAL)}</div>
</div>
<div className="bg-card border border-border
rounded-xl p-4">
<div className="text-[10px] uppercase
tracking-[0.15em] text-muted-foreground">Ingresos</div>
<div className="font-serif text-xl mt-2
tabular-nums text-accent">+{formatCurrency(totalIngresos)}</div>
</div>
<div className="bg-card border border-border
rounded-xl p-4">
<div className="text-[10px] uppercase
tracking-[0.15em] text-muted-foreground">Gastos</div>
<div className="font-serif text-xl mt-2
tabular-nums text-destructive">-{formatCurrency(totalGastos)}</div>
</div>
<div className="bg-card border border-border
rounded-xl p-4 border-2 border-primary/20">
<div className="text-[10px] uppercase
tracking-[0.15em] text-muted-foreground">Saldo final</div>
<div className={cn('font-serif text-xl mt-2
tabular-nums font-semibold', saldoFinal >= 0 ? 'text-accent' : 'text-destructive')}>
{formatCurrency(saldoFinal)}
</div>
</div>
</div>
<div className="bg-card border border-border rounded-xl
overflow-hidden">
{sorted.length === 0 ? (
<div className="p-12 text-center">
<p className="font-serif text-xl
tracking-tight">Sin movimientos aún</p>
<p className="text-sm text-muted-foreground
mt-2">Registra el primer movimiento de caja chica.</p>
</div>
) : (
<table className="w-full">
<thead>
<tr className="text-[11px]
uppercase tracking-[0.15em] text-muted-foreground border-b border-border">
<th className="text-left
font-medium px-6 py-3">Fecha</th>
<th className="text-left
font-medium px-6 py-3">Tipo</th>
<th className="text-left
font-medium px-6 py-3">Descripción</th>
<th className="text-left
font-medium px-6 py-3 hidden sm:table-cell">Notas</th>
<th className="text-right
font-medium px-6 py-3">Monto</th>
<th className="w-12"></th>
</tr>
</thead>
<tbody>
{sorted.map((m) => (
<tr key={m.id} className="border-b
border-border last:border-0 hover:bg-muted/40 transition-colors">
<td className="px-6
py-4 text-sm text-muted-foreground tabular-nums whitespace-nowrap">
{m.fecha ? format(parseISO(m.fecha),
'dd MMM yyyy') : '—'}
</td>
<td className="px-6
py-4">
<span
className={cn('inline-flex items-center gap-1.5 text-xs font-medium px-2 py-1
rounded-full',
m.tipo
=== 'ingreso' ? 'bg-accent/10 text-accent' : 'bg-destructive/10
text-destructive')}>
{m.tipo
=== 'ingreso' ? <TrendingUp className="w-3 h-3" /> : <TrendingDown
className="w-3 h-3" />}
{m.tipo === 'ingreso' ? 'Ingreso' : 'Gasto'}
</span>
</td>
<td className="px-6
py-4 text-sm font-medium">{m.descripcion}</td>
<td className="px-6
py-4 text-sm text-muted-foreground hidden sm:table-cell">{m.notes || '—'}</td>
<td className={cn('px-6
py-4 text-right font-medium tabular-nums', m.tipo === 'ingreso' ? 'text-accent'
: 'text-destructive')}>
{m.tipo === 'ingreso'
? '+' : '-'}{formatCurrency(m.amount)}
</td>
<td className="pr-4">
<Button
variant="ghost" size="icon" className="h-8 w-8
text-muted-foreground hover:text-destructive" onClick={() =>
handleDelete(m.id)}>
<Trash2
className="w-4 h-4" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
)}
{sorted.length > 0 && (
<div className="border-t-2 border-border
bg-muted/30 px-6 py-4 flex items-center justify-between">
<span className="text-sm
font-semibold uppercase tracking-[0.12em] text-muted-foreground">Saldo Final</span>
<span className={cn('font-serif text-2xl
font-bold tabular-nums', saldoFinal >= 0 ? 'text-accent' : 'text-destructive')}>
{formatCurrency(saldoFinal)}
</span>
</div>
)}
</div>
<MovimientoForm open={open} onOpenChange={setOpen}
onSaved={refetch} edificioId={edificioActivo?.id} />
</div>
);
}