NEXUS / app /billing /page.tsx
dimensionalpulsar's picture
Upload 34 files
d3ab8d1 verified
"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>
);
}