119 lines
5.0 KiB
TypeScript
119 lines
5.0 KiB
TypeScript
|
|
import { useState } from 'react';
|
||
|
|
import { motion } from 'framer-motion';
|
||
|
|
import { Play, ShieldCheck, Lock, X } from 'lucide-react';
|
||
|
|
import api from '../services/api';
|
||
|
|
import { useNavigate } from 'react-router-dom'
|
||
|
|
import { useToast } from '../context/use-toast';
|
||
|
|
|
||
|
|
interface Props {
|
||
|
|
onSuccess: () => void;
|
||
|
|
onCancel?: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function CashOpeningModal({ onSuccess, onCancel }: Props) {
|
||
|
|
const { showToast } = useToast();
|
||
|
|
const [openingBalance, setOpeningBalance] = useState<number>(0);
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const navigate = useNavigate();
|
||
|
|
|
||
|
|
const handleCancel = () => {
|
||
|
|
if (onCancel) {
|
||
|
|
onCancel();
|
||
|
|
} else {
|
||
|
|
navigate('/dashboard');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleOpen = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
if (openingBalance < 0) return;
|
||
|
|
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
// Enviamos el fondo inicial al endpoint que creamos en C#
|
||
|
|
await api.post('/cashsessions/open', openingBalance);
|
||
|
|
showToast("Caja abierta correctamente. ¡Buen turno!", "success");
|
||
|
|
onSuccess();
|
||
|
|
} catch (error) {
|
||
|
|
showToast("Error al abrir la sesión de caja", "error");
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 bg-slate-950/80 backdrop-blur-md z-[500] flex items-center justify-center p-4">
|
||
|
|
<motion.div
|
||
|
|
initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }}
|
||
|
|
className="bg-white w-full max-w-md rounded-[2.5rem] shadow-2xl overflow-hidden border border-white/20"
|
||
|
|
>
|
||
|
|
{/* Header (Igual al anterior) */}
|
||
|
|
<div className="p-8 bg-slate-900 text-white relative">
|
||
|
|
<div className="absolute top-0 right-0 p-8 opacity-10"><Lock size={80} /></div>
|
||
|
|
{/* Botón X de cerrar en el header */}
|
||
|
|
|
||
|
|
<div className="relative z-10">
|
||
|
|
<span className="text-[10px] font-black uppercase tracking-[0.3em] text-blue-400 block mb-2">Inicio de Jornada</span>
|
||
|
|
<h2 className="text-3xl font-black tracking-tight uppercase">Apertura de Caja</h2>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={handleCancel}
|
||
|
|
className="absolute z-20 top-6 right-6 p-2 hover:bg-white/10 rounded-xl transition-colors text-slate-400 hover:text-white"
|
||
|
|
>
|
||
|
|
<X size={20} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<form onSubmit={handleOpen} className="p-8 space-y-8">
|
||
|
|
<div className="bg-blue-50 border border-blue-100 p-4 rounded-2xl flex gap-4">
|
||
|
|
<ShieldCheck className="text-blue-600 shrink-0" size={24} />
|
||
|
|
<p className="text-[11px] text-blue-800 font-bold leading-tight uppercase opacity-80">
|
||
|
|
Debe declarar el fondo de inicio (dinero para cambio) recibido de tesorería para habilitar los cobros.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-3">
|
||
|
|
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Fondo Inicial en Efectivo</label>
|
||
|
|
<div className="relative">
|
||
|
|
<div className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-300 font-mono text-2xl font-bold">$</div>
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
required autoFocus
|
||
|
|
min="0"
|
||
|
|
step="0.01"
|
||
|
|
className="w-full pl-12 pr-6 py-6 bg-slate-50 border-2 border-slate-100 rounded-2xl outline-none focus:border-blue-600 focus:bg-white text-3xl font-mono font-black text-slate-900 transition-all shadow-inner"
|
||
|
|
placeholder="0.00"
|
||
|
|
value={openingBalance || ''}
|
||
|
|
onChange={e => setOpeningBalance(parseFloat(e.target.value) || 0)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button
|
||
|
|
type="submit" disabled={loading}
|
||
|
|
className="w-full py-5 bg-blue-600 text-white font-black uppercase text-xs tracking-[0.2em] rounded-2xl shadow-xl shadow-blue-200 hover:bg-blue-700 transition-all flex items-center justify-center gap-3 active:scale-95"
|
||
|
|
>
|
||
|
|
<Play size={18} fill="currentColor" /> {loading ? 'Procesando...' : 'Iniciar Sesión de Trabajo'}
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<div className="flex gap-4">
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={handleCancel}
|
||
|
|
className="flex-1 py-5 bg-slate-100 text-slate-500 font-black uppercase text-xs tracking-widest rounded-2xl hover:bg-slate-200 transition-all active:scale-95"
|
||
|
|
>
|
||
|
|
Cancelar
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="submit"
|
||
|
|
disabled={loading}
|
||
|
|
className="flex-[2] py-5 bg-blue-600 text-white font-black uppercase text-xs tracking-[0.2em] rounded-2xl shadow-xl shadow-blue-200 hover:bg-blue-700 transition-all flex items-center justify-center gap-3 active:scale-95"
|
||
|
|
>
|
||
|
|
<Play size={18} fill="currentColor" /> {loading ? 'Cargando...' : 'Iniciar Turno'}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</motion.div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|