167 lines
11 KiB
TypeScript
167 lines
11 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { Users, Plus, Edit2, Trash2, Mail, Phone, MapPin } from 'lucide-react';
|
|
import { useCRM } from '../context/CRMContext';
|
|
import { Customer } from '../types';
|
|
|
|
const Customers: React.FC = () => {
|
|
const { customers, addCustomer, updateCustomer, deleteCustomer } = useCRM();
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
const [formData, setFormData] = useState<Partial<Customer>>({
|
|
name: '', email: '', phone: '', city: '', status: 'Active'
|
|
});
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (editingId) {
|
|
updateCustomer(editingId, formData);
|
|
} else {
|
|
addCustomer(formData as any);
|
|
}
|
|
closeModal();
|
|
};
|
|
|
|
const openModal = (customer?: Customer) => {
|
|
if (customer) {
|
|
setEditingId(customer.id);
|
|
setFormData(customer);
|
|
} else {
|
|
setEditingId(null);
|
|
setFormData({ name: '', email: '', phone: '', city: '', status: 'Active' });
|
|
}
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const closeModal = () => {
|
|
setIsModalOpen(false);
|
|
setEditingId(null);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-8 animate-in fade-in duration-500">
|
|
{/* HEADER */}
|
|
<div className="flex justify-between items-center glass-card p-6 rounded-2xl border border-white/5 shadow-sm">
|
|
<div>
|
|
<h2 className="text-xl font-bold text-white tracking-tight">Carteira de Clientes</h2>
|
|
<p className="text-xs text-slate-500 tracking-wide mt-1">Gestão de Relacionamento (CRM)</p>
|
|
</div>
|
|
<button
|
|
onClick={() => openModal()}
|
|
className="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-500 text-white px-5 py-2.5 rounded-lg font-bold text-sm transition-all shadow-lg shadow-indigo-600/20 active:scale-95"
|
|
>
|
|
<Plus size={16} /> Novo Cliente
|
|
</button>
|
|
</div>
|
|
|
|
{/* LIST */}
|
|
<div className="glass-card rounded-2xl border border-white/5 overflow-hidden">
|
|
<table className="w-full text-left border-collapse">
|
|
<thead>
|
|
<tr className="bg-black/20 text-[10px] font-bold text-slate-500 uppercase tracking-widest border-b border-white/5">
|
|
<th className="px-6 py-4">Cliente</th>
|
|
<th className="px-6 py-4">Status</th>
|
|
<th className="px-6 py-4">Contato</th>
|
|
<th className="px-6 py-4 text-right">LTV (Total Pago)</th>
|
|
<th className="px-6 py-4 text-right">Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-white/5">
|
|
{customers.map(c => (
|
|
<tr key={c.id} className="hover:bg-white/[0.02] transition-colors group">
|
|
<td className="px-6 py-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-slate-800 rounded-lg flex items-center justify-center text-white font-bold text-xs">
|
|
{c.name.charAt(0)}
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-slate-200">{c.name}</p>
|
|
<p className="text-[10px] text-slate-500">{c.city || 'Localização n/a'}</p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide border ${c.status === 'Active' ? 'text-emerald-500 border-emerald-500/20 bg-emerald-500/5' :
|
|
c.status === 'Prospect' ? 'text-amber-500 border-amber-500/20 bg-amber-500/5' :
|
|
'text-slate-500 border-slate-600/30 bg-slate-500/5'
|
|
}`}>
|
|
{c.status === 'Active' ? 'ATIVO' : c.status === 'Prospect' ? 'PROSPECT' : 'INATIVO'}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="space-y-1">
|
|
{c.email && (
|
|
<div className="flex items-center gap-2 text-slate-400 text-xs">
|
|
<Mail size={12} className="text-slate-600" /> {c.email}
|
|
</div>
|
|
)}
|
|
{c.phone && (
|
|
<div className="flex items-center gap-2 text-slate-400 text-xs">
|
|
<Phone size={12} className="text-slate-600" /> {c.phone}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<p className="text-xs font-mono font-bold text-emerald-400">R$ {c.totalPurchased?.toLocaleString('pt-BR') || '0,00'}</p>
|
|
</td>
|
|
<td className="px-6 py-4 text-right opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<div className="flex justify-end gap-2">
|
|
<button onClick={() => openModal(c)} className="p-1.5 text-slate-500 hover:text-white hover:bg-white/10 rounded transition-all"><Edit2 size={14} /></button>
|
|
<button onClick={() => deleteCustomer(c.id)} className="p-1.5 text-slate-500 hover:text-rose-400 hover:bg-rose-500/10 rounded transition-all"><Trash2 size={14} /></button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
{customers.length === 0 && (
|
|
<div className="py-20 text-center text-slate-700 font-mono text-xs">NO CUSTOMERS FOUND.</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* MODAL */}
|
|
{isModalOpen && (
|
|
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
|
<div className="bg-[#0F1115] border border-white/10 w-full max-w-md rounded-2xl p-6 shadow-2xl animate-in zoom-in-95 duration-200">
|
|
<h3 className="text-lg font-bold text-white mb-6 border-b border-white/5 pb-4">{editingId ? 'Editar Cliente' : 'Novo Cliente'}</h3>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Nome Completo</label>
|
|
<input required type="text" value={formData.name} onChange={e => setFormData({ ...formData, name: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Telefone</label>
|
|
<input type="text" value={formData.phone} onChange={e => setFormData({ ...formData, phone: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Status</label>
|
|
<select value={formData.status} onChange={e => setFormData({ ...formData, status: e.target.value as any })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all">
|
|
<option value="Active">Ativo</option>
|
|
<option value="Prospect">Prospect</option>
|
|
<option value="Inactive">Inativo</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Email</label>
|
|
<input type="email" value={formData.email} onChange={e => setFormData({ ...formData, email: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Cidade / Estado</label>
|
|
<input type="text" value={formData.city} onChange={e => setFormData({ ...formData, city: e.target.value })} className="w-full bg-black/40 border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:border-indigo-500 outline-none transition-all" />
|
|
</div>
|
|
<div className="flex gap-3 mt-8 pt-4 border-t border-white/5">
|
|
<button type="button" onClick={closeModal} className="flex-1 py-2 text-xs font-bold text-slate-400 hover:text-white transition-colors uppercase tracking-wide">Cancelar</button>
|
|
<button type="submit" className="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white py-2 rounded-lg font-bold shadow-lg transition-all text-sm">Salvar</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Customers;
|