Spaces:
Running
Running
| "use client"; | |
| import React, { useState } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { | |
| FileText, | |
| Download, | |
| Eye, | |
| Search, | |
| Filter, | |
| CreditCard, | |
| CheckCircle2, | |
| Clock, | |
| AlertCircle, | |
| TrendingUp, | |
| Receipt, | |
| User, | |
| Calendar, | |
| DollarSign | |
| } from "lucide-react"; | |
| const mockInvoices = [ | |
| { | |
| id: "INV-2023-001", | |
| customer: "Nexus Corp", | |
| date: "2023-11-15", | |
| amount: 15420.50, | |
| status: "paid", | |
| items: [ | |
| { description: "Soporte Técnico Enterprise", qty: 1, price: 5000 }, | |
| { description: "Licencia Anual ERP Nexus", qty: 1, price: 8000 }, | |
| { description: "Implementación Cloud", qty: 1, price: 2420.50 } | |
| ] | |
| }, | |
| { | |
| id: "INV-2023-002", | |
| customer: "Global Tech Solutions", | |
| date: "2023-11-18", | |
| amount: 4200.00, | |
| status: "pending", | |
| items: [ | |
| { description: "Consultoría IT", qty: 10, price: 420 } | |
| ] | |
| }, | |
| { | |
| id: "INV-2023-003", | |
| customer: "Alina Systems", | |
| date: "2023-11-20", | |
| amount: 1250.75, | |
| status: "overdue", | |
| items: [ | |
| { description: "Mantenimiento Preventivo", qty: 5, price: 250.15 } | |
| ] | |
| }, | |
| ]; | |
| export default function BillingPage() { | |
| const [selectedInvoice, setSelectedInvoice] = useState<any>(null); | |
| return ( | |
| <div className="p-10 bg-[#0f172a] min-h-screen text-white"> | |
| <header className="mb-12 flex justify-between items-end"> | |
| <div> | |
| <h1 className="text-5xl font-black tracking-tighter italic bg-gradient-to-r from-white to-gray-500 bg-clip-text text-transparent"> | |
| FACTURACIÓN <span className="text-blue-500">/ BILLING</span> | |
| </h1> | |
| <p className="text-gray-500 font-bold mt-2 uppercase text-xs tracking-[0.3em]">Gestión de Ingresos y Comprobantes Fiscales</p> | |
| </div> | |
| <div className="flex gap-4"> | |
| <button className="bg-white/5 border border-white/10 px-6 py-3 rounded-2xl text-xs font-black uppercase tracking-widest hover:bg-white/10 transition-all flex items-center gap-2"> | |
| <Download size={16} /> Exportar CSV | |
| </button> | |
| <button className="bg-blue-600 px-6 py-3 rounded-2xl text-xs font-black uppercase tracking-widest hover:bg-blue-500 transition-all shadow-xl shadow-blue-900/40"> | |
| Nueva Factura | |
| </button> | |
| </div> | |
| </header> | |
| {/* Stats Quick View */} | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12"> | |
| <StatWidget label="Recaudado Mes" value="$20,871.25" icon={<TrendingUp className="text-emerald-400" />} /> | |
| <StatWidget label="Pendiente Cobro" value="$4,200.00" icon={<Clock className="text-orange-400" />} /> | |
| <StatWidget label="Facturas Activas" value="28" icon={<Receipt className="text-blue-400" />} /> | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| {/* Invoice List */} | |
| <div className="lg:col-span-2 space-y-4"> | |
| <div className="bg-white/5 border border-white/10 rounded-[2.5rem] p-8"> | |
| <div className="flex justify-between items-center mb-8"> | |
| <h2 className="text-lg font-black italic uppercase tracking-widest text-gray-400">Historial de <span className="text-white">Facturas</span></h2> | |
| <div className="flex gap-2"> | |
| <div className="relative"> | |
| <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" size={14} /> | |
| <input className="bg-white/5 border border-white/10 rounded-xl py-2 pl-9 pr-4 text-xs focus:ring-1 focus:ring-blue-500 outline-none w-48" placeholder="Buscar..." /> | |
| </div> | |
| <button className="bg-white/5 border border-white/10 p-2 rounded-xl text-gray-400 hover:text-white"><Filter size={16} /></button> | |
| </div> | |
| </div> | |
| <div className="space-y-3"> | |
| {mockInvoices.map((inv) => ( | |
| <motion.div | |
| key={inv.id} | |
| whileHover={{ x: 10, backgroundColor: "rgba(255,255,255,0.05)" }} | |
| onClick={() => setSelectedInvoice(inv)} | |
| className={`flex items-center justify-between p-4 rounded-2xl border border-white/5 cursor-pointer transition-all ${selectedInvoice?.id === inv.id ? 'bg-white/10 border-blue-500/50 shadow-lg' : 'hover:border-white/10'}`} | |
| > | |
| <div className="flex items-center gap-4"> | |
| <div className={`w-10 h-10 rounded-xl flex items-center justify-center ${ | |
| inv.status === 'paid' ? 'bg-emerald-500/10 text-emerald-400' : | |
| inv.status === 'pending' ? 'bg-orange-500/10 text-orange-400' : 'bg-red-500/10 text-red-400' | |
| }`}> | |
| <FileText size={18} /> | |
| </div> | |
| <div> | |
| <p className="text-sm font-black italic">{inv.id}</p> | |
| <p className="text-[10px] text-gray-500 font-bold uppercase">{inv.customer}</p> | |
| </div> | |
| </div> | |
| <div className="text-right"> | |
| <p className="text-sm font-black tracking-tighter">${inv.amount.toLocaleString()}</p> | |
| <p className="text-[9px] text-gray-500 font-medium">{inv.date}</p> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <StatusBadge status={inv.status} /> | |
| <button className="p-2 text-gray-500 hover:text-white transition-colors"> | |
| <Eye size={16} /> | |
| </button> | |
| </div> | |
| </motion.div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Invoice Detail Breakdown */} | |
| <div className="lg:col-span-1"> | |
| <AnimatePresence mode="wait"> | |
| {selectedInvoice ? ( | |
| <motion.div | |
| key={selectedInvoice.id} | |
| initial={{ opacity: 0, x: 20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| exit={{ opacity: 0, x: 20 }} | |
| className="bg-white text-slate-900 rounded-[3rem] p-8 shadow-2xl relative overflow-hidden h-full flex flex-col" | |
| > | |
| {/* Decorative Elements */} | |
| <div className="absolute top-0 right-0 w-32 h-32 bg-blue-50 rounded-bl-[100%] z-0"></div> | |
| <div className="relative z-10 flex-1"> | |
| <div className="flex justify-between items-start mb-10"> | |
| <div> | |
| <h3 className="text-2xl font-black tracking-tighter italic uppercase text-blue-600">INVOICE</h3> | |
| <p className="text-[10px] font-black uppercase text-slate-400">{selectedInvoice.id}</p> | |
| </div> | |
| <div className="w-12 h-12 bg-blue-600 rounded-2xl flex items-center justify-center text-white font-black italic text-xl">N</div> | |
| </div> | |
| <div className="grid grid-cols-2 gap-6 mb-10"> | |
| <div className="space-y-1"> | |
| <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest flex items-center gap-1"><User size={10} /> Cliente</p> | |
| <p className="text-xs font-bold text-slate-700">{selectedInvoice.customer}</p> | |
| </div> | |
| <div className="space-y-1"> | |
| <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest flex items-center gap-1"><Calendar size={10} /> Fecha</p> | |
| <p className="text-xs font-bold text-slate-700">{selectedInvoice.date}</p> | |
| </div> | |
| </div> | |
| {/* Items Table */} | |
| <div className="border-t border-slate-100 pt-6 mb-10"> | |
| <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest mb-4">Desglose de Servicios</p> | |
| <div className="space-y-4"> | |
| {selectedInvoice.items.map((item: any, idx: number) => ( | |
| <div key={idx} className="flex justify-between items-center text-sm border-b border-slate-50 pb-2 last:border-0"> | |
| <div className="flex flex-col"> | |
| <span className="font-bold text-slate-700">{item.description}</span> | |
| <span className="text-[10px] text-slate-400 font-medium">Cant: {item.qty} x ${item.price.toLocaleString()}</span> | |
| </div> | |
| <span className="font-black text-slate-800">${(item.qty * item.price).toLocaleString()}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Calculations */} | |
| <div className="space-y-2 mt-auto"> | |
| <div className="flex justify-between text-xs font-medium text-slate-400"> | |
| <span>Subtotal</span> | |
| <span>${(selectedInvoice.amount * 0.84).toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between text-xs font-medium text-slate-400 border-b border-slate-100 pb-2"> | |
| <span>IVA (16%)</span> | |
| <span>${(selectedInvoice.amount * 0.16).toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between items-center pt-2"> | |
| <span className="text-sm font-black uppercase tracking-widest text-slate-800">Total Neto</span> | |
| <span className="text-2xl font-black italic tracking-tighter text-blue-600">${selectedInvoice.amount.toLocaleString()}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button className="mt-10 w-full bg-slate-900 text-white rounded-2xl py-4 font-black uppercase tracking-widest text-xs flex items-center justify-center gap-2 hover:bg-slate-800 active:scale-95 transition-all"> | |
| <Download size={16} /> Descargar PDF | |
| </button> | |
| </motion.div> | |
| ) : ( | |
| <div className="bg-white/5 border border-white/10 border-dashed rounded-[3rem] p-8 flex flex-col items-center justify-center text-center h-[500px]"> | |
| <div className="w-16 h-16 bg-white/5 rounded-full flex items-center justify-center mb-6"> | |
| <AlertCircle className="text-gray-500" size={32} /> | |
| </div> | |
| <h3 className="font-black italic uppercase tracking-widest text-gray-400 mb-2">Seleccione una factura</h3> | |
| <p className="text-xs text-gray-600 font-medium max-w-[200px]">Haga clic en una factura de la lista para ver el desglose detallado para el cliente.</p> | |
| </div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| </div> | |
| {/* Payment Methods Footer */} | |
| <div className="mt-12 bg-white/5 border border-white/10 rounded-[2.5rem] p-8 flex items-center justify-between"> | |
| <div className="flex items-center gap-6"> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 rounded-full bg-emerald-500 drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]"></div> | |
| <span className="text-[10px] font-black uppercase tracking-widest text-gray-400">Gateways Activos</span> | |
| </div> | |
| <div className="flex gap-4"> | |
| <div className="h-4 w-12 bg-white/10 rounded"></div> | |
| <div className="h-4 w-12 bg-white/10 rounded"></div> | |
| <div className="h-4 w-12 bg-white/10 rounded"></div> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-4 text-[10px] font-black uppercase tracking-widest text-gray-500"> | |
| <span className="flex items-center gap-1"><CreditCard size={12} /> Pagos Automáticos</span> | |
| <span className="w-1 h-1 bg-white/10 rounded-full"></span> | |
| <span className="flex items-center gap-1"><DollarSign size={12} /> Liquidación Instantánea</span> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function StatWidget({ label, value, icon }: any) { | |
| return ( | |
| <div className="bg-white/5 border border-white/10 rounded-[2rem] p-6 relative group overflow-hidden"> | |
| <div className="absolute top-0 left-0 w-1 h-full bg-blue-500/20 group-hover:bg-blue-500 transition-colors"></div> | |
| <div className="flex justify-between items-start"> | |
| <div className="space-y-1"> | |
| <p className="text-[10px] font-black uppercase tracking-widest text-gray-500">{label}</p> | |
| <p className="text-3xl font-black italic tracking-tighter">{value}</p> | |
| </div> | |
| <div className="w-10 h-10 bg-white/5 rounded-xl flex items-center justify-center"> | |
| {icon} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function StatusBadge({ status }: { status: "paid" | "pending" | "overdue" | string }) { | |
| const styles: Record<string, { bg: string; text: string; label: string; icon: React.ReactNode }> = { | |
| paid: { bg: "bg-emerald-500/10", text: "text-emerald-400", label: "Pagado", icon: <CheckCircle2 size={10} /> }, | |
| pending: { bg: "bg-orange-500/10", text: "text-orange-400", label: "Pendiente", icon: <Clock size={10} /> }, | |
| overdue: { bg: "bg-red-500/10", text: "text-red-400", label: "Vencido", icon: <AlertCircle size={10} /> }, | |
| }; | |
| const style = styles[status] || { bg: "bg-gray-500/10", text: "text-gray-400", label: "Desconocido", icon: null }; | |
| return ( | |
| <div className={`${style.bg} ${style.text} px-3 py-1.5 rounded-full text-[9px] font-black uppercase flex items-center gap-1 tracking-wider border border-white/5`}> | |
| {style.icon} | |
| {style.label} | |
| </div> | |
| ); | |
| } | |