/* ============================================================ pipeline.jsx — Pipeline comercial (Kanban arrastável) ============================================================ */ function DealCard({ deal, onDragStart, onOpen, dragging }) { const svcNames = deal.items.map(i => i.nome); const stage = PIPELINE_STAGES.find(s => s.id === deal.stage); return (
onDragStart(e, deal.id)} onClick={() => onOpen(deal)}>

{deal.cliente.nome || 'Sem nome'}

{deal.cliente.empresa &&
{deal.cliente.empresa}
}
{svcNames.slice(0, 2).map((n, i) => {n})} {svcNames.length > 2 && +{svcNames.length - 2}}
{fmtBRL(deal.total)} {new Date(deal.createdAt).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' })}
); } function DealDrawer({ deal, onClose, onMove, onDelete, onResume }) { if (!deal) return null; const stage = PIPELINE_STAGES.find(s => s.id === deal.stage); const idx = PIPELINE_STAGES.findIndex(s => s.id === deal.stage); return (
); } function Pipeline({ deals, onMove, onDelete, onNew, onResume }) { const [dragId, setDragId] = React.useState(null); const [overCol, setOverCol] = React.useState(null); const [open, setOpen] = React.useState(null); const [query, setQuery] = React.useState(''); const filtered = deals.filter(d => !query || (d.cliente.nome + d.cliente.empresa).toLowerCase().includes(query.toLowerCase())); const onDragStart = (e, id) => { setDragId(id); e.dataTransfer.effectAllowed = 'move'; }; const onDrop = (stageId) => { if (dragId) onMove(dragId, stageId); setDragId(null); setOverCol(null); }; const totalGeral = deals.reduce((s, d) => s + d.total, 0); const ganho = deals.filter(d => ['pagamento', 'contrato', 'projeto'].includes(d.stage)).reduce((s, d) => s + d.total, 0); const openDeal = open ? deals.find(d => d.id === open) : null; return (

Pipeline comercial

{deals.length} negócios

Em aberto {fmtBRL(totalGeral)} · Confirmado {fmtBRL(ganho)}

setQuery(e.target.value)} />
{deals.length === 0 ? (
{window.FAVICON ? :
}

Nenhum negócio ainda

Crie a primeira proposta. Ao registrar o aceite, o negócio aparece aqui automaticamente.

) : (
{PIPELINE_STAGES.map(stage => { const cards = filtered.filter(d => d.stage === stage.id); const colTotal = cards.reduce((s, d) => s + d.total, 0); return (
{ e.preventDefault(); setOverCol(stage.id); }} onDragLeave={(e) => { if (e.currentTarget === e.target) setOverCol(null); }} onDrop={() => onDrop(stage.id)}>
{stage.nome} {cards.length}
{fmtBRL(colTotal)}
{cards.map(d => ( setOpen(dd.id)} /> ))} {cards.length === 0 &&
Solte aqui
}
); })}
)} setOpen(null)} onMove={onMove} onDelete={onDelete} onResume={onResume} />
); } Object.assign(window, { Pipeline });