// ============================================================ // MOORE AI v13 — PIPELINE TAB // Requires: shared/utils.js, shared/components.jsx loaded first // ============================================================ const PipelineView = ({ crmData, filteredOpps, pipelines, pipelineView, setPipelineView, pipelineFilter, setPipelineFilter, setSelectedLead, updateOppStatus, searchQuery, setSearchQuery, syncOpportunities, dragItemRef, selectedPipelineId, setSelectedPipelineId }) => { const { useMemo } = React; const pipelineOptions = useMemo(() => { const opts = []; if (pipelines && pipelines.length > 0) { pipelines.forEach(p => opts.push({ id: p.id, name: p.name })); } else { const seen = {}; filteredOpps.forEach(o => { if (o.pipelineId && !seen[o.pipelineId]) { seen[o.pipelineId] = true; opts.push({ id: o.pipelineId, name: o.pipelineName || `Pipeline ${Object.keys(seen).length}` }); } }); } return opts; }, [pipelines, filteredOpps]); const handlePipelineChange = (id) => { setSelectedPipelineId(id); syncOpportunities(id); }; const statusFilteredOpps = useMemo(() => { if (pipelineFilter === 'all') return filteredOpps; return filteredOpps.filter(o => o.status === pipelineFilter); }, [filteredOpps, pipelineFilter]); const stages = useMemo(() => { const stageMap = {}; const stageOrder = []; const oppsForStages = statusFilteredOpps.filter(o => o.status !== 'won' && o.status !== 'lost'); oppsForStages.forEach(o => { const key = o.stageName || 'Prospect'; if (!stageMap[key]) { stageMap[key] = { name: key, id: o.stageId || key, opps: [], total: 0 }; stageOrder.push(key); } stageMap[key].opps.push(o); stageMap[key].total += Number(o.value) || 0; }); return stageOrder.map(k => stageMap[k]); }, [statusFilteredOpps]); const stageColors = ['#3b82f6', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b', '#ec4899', '#f97316']; const wonOpps = filteredOpps.filter(o => o.status === 'won'); const lostOpps = filteredOpps.filter(o => o.status === 'lost'); return (
{/* Toolbar */}

Pipeline

{pipelineOptions.length > 0 && (
)}
{['all', 'open', 'won', 'lost'].map(f => ( ))}
setSearchQuery(e.target.value)} placeholder="Search leads..." className="bg-slate-900/60 border border-slate-800 rounded-lg pl-8 pr-3 py-1.5 text-xs text-slate-300 outline-none focus:border-blue-500/50 w-44 placeholder:text-slate-700" />
{[['kanban', 'layout-grid'], ['list', 'list']].map(([v, icon]) => ( ))}
{/* Stats row */}
{filteredOpps.length} total {filteredOpps.filter(o=>o.status==='open').length} open {wonOpps.length} won {lostOpps.length} lost {selectedPipelineId && pipelineOptions.length > 0 && ( {pipelineOptions.find(p=>p.id===selectedPipelineId)?.name || 'Selected Pipeline'} )} Value: {fmt$(filteredOpps.reduce((a,o)=>a+Number(o.value||0),0))}
{/* Content */} {pipelineView === 'kanban' ? (
{stages.map((stage, si) => (
{stage.name}
{stage.opps.length} deals · {fmt$(stage.total)}
{stage.opps.map(opp => (
setSelectedLead(opp)} className="bg-[#0d1424] border border-slate-800/70 rounded-xl p-3 cursor-pointer hover:border-slate-600/70 hover:bg-slate-900/60 transition-all group">
{opp.name}
{opp.contact}
{fmt$(opp.value)}
))} {stage.opps.length === 0 &&
Empty
}
))} {(pipelineFilter === 'all' || pipelineFilter === 'won') && (
Closed Won
{wonOpps.length} deals · {fmt$(wonOpps.reduce((a,o)=>a+Number(o.value||0),0))}
{wonOpps.length === 0 ?
No won deals
: wonOpps.map(opp => (
setSelectedLead(opp)} className="bg-emerald-950/30 border border-emerald-900/30 rounded-xl p-3 cursor-pointer hover:border-emerald-700/40 transition-all">
{opp.name}
{opp.contact}
{fmt$(opp.value)}
))}
)} {(pipelineFilter === 'all' || pipelineFilter === 'lost') && (
Closed Lost
{lostOpps.length} deals
{lostOpps.length === 0 ?
No lost deals
: lostOpps.map(opp => (
setSelectedLead(opp)} className="bg-red-950/20 border border-red-900/20 rounded-xl p-3 cursor-pointer hover:border-red-700/30 transition-all">
{opp.name}
{opp.contact}
{fmt$(opp.value)}
))}
)}
) : (
{['Deal Name', 'Contact', 'Stage', 'Value', 'Status', 'Source', ''].map((h, i) => ( ))} {statusFilteredOpps.map(opp => ( setSelectedLead(opp)} className="border-b border-slate-800/30 data-row cursor-pointer transition-colors"> ))}
{h}
{opp.name}
{opp.contact} {opp.stageName || '—'} {fmt$(opp.value)} {opp.source}
{statusFilteredOpps.length === 0 && (
{filteredOpps.length === 0 ? 'No deals in this pipeline — try Refresh' : 'No records match filter'}
)}
)}
); };