import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Users, Plus, MoreVertical, Eye, UserCheck, XCircle, RefreshCw, Search, Star, Phone, Mail, TrendingUp, } from 'lucide-react'; import { Button } from '@components/atoms/Button'; import { Card, CardHeader, CardTitle, CardContent } from '@components/molecules/Card'; import { DataTable, type Column } from '@components/organisms/DataTable'; import { Dropdown, type DropdownItem } from '@components/organisms/Dropdown'; import { Breadcrumbs } from '@components/organisms/Breadcrumbs'; import { ConfirmModal } from '@components/organisms/Modal'; import { NoDataEmptyState, ErrorEmptyState } from '@components/templates/EmptyState'; import { useLeads } from '@features/crm/hooks'; import type { Lead, LeadStatus, LeadSource } from '@features/crm/types'; import { formatNumber } from '@utils/formatters'; const statusLabels: Record = { new: 'Nuevo', contacted: 'Contactado', qualified: 'Calificado', converted: 'Convertido', lost: 'Perdido', }; const statusColors: Record = { new: 'bg-blue-100 text-blue-700', contacted: 'bg-yellow-100 text-yellow-700', qualified: 'bg-green-100 text-green-700', converted: 'bg-purple-100 text-purple-700', lost: 'bg-red-100 text-red-700', }; const sourceLabels: Record = { website: 'Sitio Web', phone: 'Telefono', email: 'Email', referral: 'Referido', social_media: 'Redes Sociales', advertising: 'Publicidad', event: 'Evento', other: 'Otro', }; const formatCurrency = (value: number): string => { return formatNumber(value, 'es-MX', { minimumFractionDigits: 0, maximumFractionDigits: 0 }); }; export function LeadsPage() { const navigate = useNavigate(); const [selectedStatus, setSelectedStatus] = useState(''); const [selectedSource, setSelectedSource] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [leadToConvert, setLeadToConvert] = useState(null); const [leadToLose, setLeadToLose] = useState(null); const { leads, total, page, totalPages, isLoading, error, setPage, refresh, convertLead, markLeadLost, } = useLeads({ status: selectedStatus || undefined, source: selectedSource || undefined, search: searchTerm || undefined, limit: 20, }); const getActionsMenu = (lead: Lead): DropdownItem[] => { const items: DropdownItem[] = [ { key: 'view', label: 'Ver detalle', icon: , onClick: () => navigate(`/crm/leads/${lead.id}`), }, ]; if (lead.status !== 'converted' && lead.status !== 'lost') { items.push({ key: 'convert', label: 'Convertir a oportunidad', icon: , onClick: () => setLeadToConvert(lead), }); items.push({ key: 'lose', label: 'Marcar como perdido', icon: , danger: true, onClick: () => setLeadToLose(lead), }); } return items; }; const columns: Column[] = [ { key: 'name', header: 'Lead', render: (lead) => (
{lead.name}
{lead.contactName && (
{lead.contactName}
)}
), }, { key: 'contact', header: 'Contacto', render: (lead) => (
{lead.email && (
{lead.email}
)} {lead.phone && (
{lead.phone}
)}
), }, { key: 'source', header: 'Origen', render: (lead) => ( {lead.source ? sourceLabels[lead.source] : '-'} ), }, { key: 'probability', header: 'Probabilidad', render: (lead) => (
{lead.probability}%
), }, { key: 'expectedRevenue', header: 'Ingreso Esperado', render: (lead) => (
{lead.expectedRevenue ? `$${formatCurrency(lead.expectedRevenue)}` : '-'}
), }, { key: 'priority', header: 'Prioridad', render: (lead) => (
{Array.from({ length: 3 }).map((_, i) => ( ))}
), }, { key: 'status', header: 'Estado', render: (lead) => ( {statusLabels[lead.status]} ), }, { key: 'actions', header: '', render: (lead) => ( } items={getActionsMenu(lead)} align="right" /> ), }, ]; const handleConvert = async () => { if (leadToConvert) { await convertLead(leadToConvert.id, { createOpportunity: true }); setLeadToConvert(null); } }; const handleLose = async () => { if (leadToLose) { await markLeadLost(leadToLose.id, 'Perdido por el usuario'); setLeadToLose(null); } }; // Calculate summary stats const newCount = leads.filter(l => l.status === 'new').length; const qualifiedCount = leads.filter(l => l.status === 'qualified').length; const convertedCount = leads.filter(l => l.status === 'converted').length; const totalExpectedRevenue = leads.reduce((sum, l) => sum + (l.expectedRevenue || 0), 0); if (error) { return (
); } return (

Leads

Gestiona prospectos y convierte leads en oportunidades

{/* Summary Stats */}
setSelectedStatus('new')}>
Nuevos
{newCount}
setSelectedStatus('qualified')}>
Calificados
{qualifiedCount}
setSelectedStatus('converted')}>
Convertidos
{convertedCount}
Potencial Total
${formatCurrency(totalExpectedRevenue)}
Lista de Leads
{/* Filters */}
setSearchTerm(e.target.value)} className="w-full rounded-md border border-gray-300 py-2 pl-10 pr-4 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
{(selectedStatus || selectedSource || searchTerm) && ( )}
{/* Table */} {leads.length === 0 && !isLoading ? ( ) : ( )}
{/* Convert Lead Modal */} setLeadToConvert(null)} onConfirm={handleConvert} title="Convertir lead" message={`¿Convertir "${leadToConvert?.name}" en una oportunidad de venta?`} variant="success" confirmText="Convertir" /> {/* Lose Lead Modal */} setLeadToLose(null)} onConfirm={handleLose} title="Marcar como perdido" message={`¿Marcar el lead "${leadToLose?.name}" como perdido? Esta accion no se puede deshacer.`} variant="danger" confirmText="Marcar como perdido" />
); } export default LeadsPage;