"use client"; import { useState, useEffect } from 'react'; import { useToast } from '@/components/layout/ToastContext'; import Modal from '@/components/layout/Modal'; import { EllipsisVerticalIcon, PlusIcon, UserIcon, EnvelopeIcon, PhoneIcon, Bars2Icon, TagIcon, ChatBubbleLeftRightIcon, CalendarIcon, ClockIcon } from '@heroicons/react/24/outline'; interface Stage { id: string; name: string; color: string; order_index: number; } interface Lead { id: string; name: string; email: string; phone: string; stage_id: string; funnel_id: string; notes?: string; tags?: string[]; status?: string; created_at?: string; } interface KanbanBoardProps { funnelId: string; campaignId?: string; } export default function KanbanBoard({ funnelId, campaignId }: KanbanBoardProps) { const [stages, setStages] = useState([]); const [leads, setLeads] = useState([]); const [loading, setLoading] = useState(true); const [draggedLeadId, setDraggedLeadId] = useState(null); const [dropTargetStageId, setDropTargetStageId] = useState(null); const [movingLeadId, setMovingLeadId] = useState(null); // Modal states const [isLeadModalOpen, setIsLeadModalOpen] = useState(false); const [selectedLead, setSelectedLead] = useState(null); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [targetStageId, setTargetStageId] = useState(null); const [isSaving, setIsSaving] = useState(false); const [formData, setFormData] = useState({ name: '', email: '', phone: '', notes: '', tags: '' }); const toast = useToast(); useEffect(() => { if (funnelId) { fetchData(); } }, [funnelId, campaignId]); // Refetch quando houver alterações externas (ex: criação de etapa no modal de configurações) useEffect(() => { const handleRefresh = () => { console.log('KanbanBoard: External refresh triggered'); fetchData(); }; window.addEventListener('kanban-refresh', handleRefresh); return () => window.removeEventListener('kanban-refresh', handleRefresh); }, []); const fetchData = async () => { console.log('KanbanBoard: Fetching data for funnel:', funnelId, 'campaign:', campaignId); setLoading(true); try { const token = localStorage.getItem('token'); const headers = { 'Authorization': `Bearer ${token}` }; const [stagesRes, leadsRes] = await Promise.all([ fetch(`/api/crm/funnels/${funnelId}/stages`, { headers }), campaignId ? fetch(`/api/crm/lists/${campaignId}/leads`, { headers }) : fetch(`/api/crm/leads`, { headers }) ]); if (stagesRes.ok && leadsRes.ok) { const stagesData = await stagesRes.json(); const leadsData = await leadsRes.json(); console.log('KanbanBoard: Received stages:', stagesData.stages?.length); console.log('KanbanBoard: Received leads:', leadsData.leads?.length); setStages(stagesData.stages || []); setLeads(leadsData.leads || []); } else { console.error('KanbanBoard: API Error', stagesRes.status, leadsRes.status); toast.error('Erro ao carregar dados do servidor'); } } catch (error) { console.error('Error fetching kanban data:', error); toast.error('Erro de conexão ao carregar monitoramento'); } finally { setLoading(false); } }; const moveLead = async (leadId: string, newStageId: string) => { setMovingLeadId(leadId); // Optimistic update const originalLeads = [...leads]; setLeads(prev => prev.map(l => l.id === leadId ? { ...l, stage_id: newStageId } : l)); try { const response = await fetch(`/api/crm/leads/${leadId}/stage`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ funnel_id: funnelId, stage_id: newStageId }) }); console.log('KanbanBoard: Move lead response:', response.status); if (!response.ok) { setLeads(originalLeads); toast.error('Erro ao mover lead'); } } catch (error) { console.error('Error moving lead:', error); setLeads(originalLeads); toast.error('Erro ao mover lead'); } finally { setMovingLeadId(null); } }; const handleDragStart = (e: React.DragEvent, leadId: string) => { console.log('KanbanBoard: Drag Start', leadId); setDraggedLeadId(leadId); e.dataTransfer.setData('text/plain', leadId); e.dataTransfer.effectAllowed = 'move'; // Add a slight delay to make the original item semi-transparent const currentTarget = e.currentTarget as HTMLElement; setTimeout(() => { if (currentTarget) currentTarget.style.opacity = '0.4'; }, 0); }; const handleDragEnd = (e: React.DragEvent) => { console.log('KanbanBoard: Drag End'); const currentTarget = e.currentTarget as HTMLElement; if (currentTarget) currentTarget.style.opacity = '1'; setDraggedLeadId(null); setDropTargetStageId(null); }; const handleDragOver = (e: React.DragEvent, stageId: string) => { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'move'; if (dropTargetStageId !== stageId) { setDropTargetStageId(stageId); } return false; }; const handleDrop = (e: React.DragEvent, stageId: string) => { e.preventDefault(); e.stopPropagation(); // Use state if dataTransfer is empty (fallback) const leadId = e.dataTransfer.getData('text/plain') || draggedLeadId; console.log('KanbanBoard: Drop', { leadId, stageId }); setDropTargetStageId(null); if (!leadId) { console.error('KanbanBoard: No leadId found'); return; } const lead = leads.find(l => l.id === leadId); if (lead && lead.stage_id !== stageId) { console.log('KanbanBoard: Moving lead', leadId, 'to stage', stageId); moveLead(leadId, stageId); } else { console.log('KanbanBoard: Lead already in stage or not found', { lead, stageId }); } }; const handleAddLead = (stageId: string) => { setTargetStageId(stageId); setFormData({ name: '', email: '', phone: '', notes: '', tags: '' }); setIsAddModalOpen(true); }; const handleEditLead = (lead: Lead) => { setSelectedLead(lead); setFormData({ name: lead.name || '', email: lead.email || '', phone: lead.phone || '', notes: lead.notes || '', tags: lead.tags?.join(', ') || '' }); setIsLeadModalOpen(true); }; const saveLead = async (e: React.FormEvent) => { e.preventDefault(); setIsSaving(true); try { const token = localStorage.getItem('token'); const isEditing = !!selectedLead; const url = isEditing ? `/api/crm/leads/${selectedLead.id}` : '/api/crm/leads'; const method = isEditing ? 'PUT' : 'POST'; const payload = { ...formData, tags: formData.tags.split(',').map(t => t.trim()).filter(t => t), funnel_id: funnelId, stage_id: isEditing ? selectedLead.stage_id : targetStageId, status: isEditing ? selectedLead.status : 'novo' }; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(payload) }); if (response.ok) { toast.success(isEditing ? 'Lead atualizado' : 'Lead criado'); setIsAddModalOpen(false); setIsLeadModalOpen(false); fetchData(); } else { toast.error('Erro ao salvar lead'); } } catch (error) { console.error('Error saving lead:', error); toast.error('Erro de conexão'); } finally { setIsSaving(false); } }; if (loading) { return (
); } return (
e.preventDefault()} > {stages.map(stage => (
handleDragOver(e, stage.id)} onDragEnter={(e) => { e.preventDefault(); setDropTargetStageId(stage.id); }} onDrop={(e) => handleDrop(e, stage.id)} > {/* Header da Coluna */}

{stage.name}

{leads.filter(l => l.stage_id === stage.id).length}

{/* Lista de Cards */}
{leads.filter(l => l.stage_id === stage.id).map(lead => (
handleDragStart(e, lead.id)} onDragEnd={handleDragEnd} onClick={() => handleEditLead(lead)} className={`bg-white p-4 rounded-xl shadow-sm border border-zinc-200 hover:shadow-md hover:border-brand-300 transition-all duration-200 cursor-grab active:cursor-grabbing group relative select-none ${draggedLeadId === lead.id ? 'ring-2 ring-brand-500 ring-offset-2' : '' } ${movingLeadId === lead.id ? 'opacity-50 grayscale' : ''}`} > {movingLeadId === lead.id && (
)}

{lead.name || 'Sem nome'}

{lead.email && (
{lead.email}
)} {lead.phone && (
{lead.phone}
)}
{lead.tags && lead.tags.length > 0 && (
{lead.tags.slice(0, 2).map((tag, i) => ( {tag} ))} {lead.tags.length > 2 && ( +{lead.tags.length - 2} )}
)} {/* Badge de Status (Opcional) */}
#{lead.id.slice(0, 6)}
{lead.notes && ( )}
AG
))} {leads.filter(l => l.stage_id === stage.id).length === 0 && (
Vazio
)}
{/* Footer da Coluna */} {campaignId && (
)}
))} {/* Modal de Adicionar/Editar Lead */} { setIsAddModalOpen(false); setIsLeadModalOpen(false); setSelectedLead(null); }} title={isAddModalOpen ? 'Novo Lead' : 'Detalhes do Lead'} maxWidth="lg" >
setFormData({ ...formData, name: e.target.value })} />
setFormData({ ...formData, email: e.target.value })} />
setFormData({ ...formData, phone: e.target.value })} />
setFormData({ ...formData, tags: e.target.value })} />