// Sales pipeline screen — deal kanban. (() => { const { Card, Badge, Avatar } = window.TechyFuelOSDesignSystem_be0222; const STAGE_CONFIG = [ { id: 'lead', label: 'New lead', dot: 'var(--slate-400)' }, { id: 'qualified', label: 'Qualified', dot: 'var(--blue-500)' }, { id: 'proposal', label: 'Proposal sent', dot: 'var(--violet-500)' }, { id: 'negotiation', label: 'Negotiation', dot: 'var(--amber-500)' }, { id: 'won', label: 'Won', dot: 'var(--green-500)' }, { id: 'lost', label: 'Lost', dot: 'var(--red-400)' }, ]; function fmtVal(n) { n = Number(n) || 0; if (!n) return '$0'; if (n >= 1000) return '$' + (n / 1000).toFixed(1) + 'K'; return '$' + n; } function Deal({ d }) { const [h, sh] = React.useState(false); const clientName = d.clients ? d.clients.name : '—'; const assignedName = d.team_members ? d.team_members.name : null; return (
sh(true)} onMouseLeave={() => sh(false)} style={{ background: 'var(--slate-0)', border: `1px solid ${h ? 'var(--slate-200)' : 'var(--border-subtle)'}`, borderRadius: 'var(--radius-lg)', padding: 11, boxShadow: h ? 'var(--shadow-md)' : 'var(--shadow-xs)', cursor: 'grab', transition: 'all var(--dur-fast) var(--ease-out)' }}>
{clientName} {d.probability >= 80 && }
{d.title}
{fmtVal(d.value)}
{d.probability != null && {d.probability}% prob.} {assignedName && }
); } function Pipeline() { useLucide(); const [stageMap, setStageMap] = React.useState(() => { const m = {}; STAGE_CONFIG.forEach(s => { m[s.id] = []; }); return m; }); const [totals, setTotals] = React.useState({ count: 0, value: 0 }); const [clients, setClients] = React.useState([]); const [modalOpen, setModalOpen] = React.useState(false); const [saving, setSaving] = React.useState(false); const [form, setForm] = React.useState({ title: '', client_id: '', value: '', stage: 'lead', probability: '' }); function set(k, v) { setForm(f => ({ ...f, [k]: v })); } React.useEffect(() => { if (!window.API) return; (async () => { try { const r = await window.API.getPipeline(); if (r.data) { const m = {}; STAGE_CONFIG.forEach(s => { m[s.id] = []; }); r.data.forEach(d => { const key = d.stage || 'lead'; if (!m[key]) m[key] = []; m[key].push(d); }); setStageMap(m); const openDeals = r.data.filter(d => d.stage !== 'won' && d.stage !== 'lost'); setTotals({ count: openDeals.length, value: openDeals.reduce((s, d) => s + (Number(d.value) || 0), 0) }); } } catch {} try { const r = await window.API.getClients(); if (r.data) setClients(r.data); } catch {} })(); }, []); async function handleAddDeal() { if (!form.title.trim()) return; setSaving(true); try { const payload = { title: form.title, stage: form.stage }; if (form.client_id) payload.client_id = form.client_id; if (form.value) payload.value = Number(form.value); if (form.probability) payload.probability = Number(form.probability); if (window.API) { const { data, error } = await window.API.createDeal(payload); if (!error && data) { const clientName = clients.find(c => c.id === form.client_id)?.name || null; const newDeal = { ...data, clients: clientName ? { name: clientName } : null }; const stage = data.stage || 'lead'; setStageMap(prev => ({ ...prev, [stage]: [...(prev[stage] || []), newDeal] })); if (stage !== 'won' && stage !== 'lost') { setTotals(prev => ({ count: prev.count + 1, value: prev.value + (data.value || 0) })); } } } setModalOpen(false); setForm({ title: '', client_id: '', value: '', stage: 'lead', probability: '' }); } finally { setSaving(false); } } return (

Sales pipeline

{totals.count} open deals · {fmtVal(totals.value)} weighted value

{STAGE_CONFIG.map(s => { const deals = stageMap[s.id] || []; const stageTotal = deals.reduce((a, d) => a + (Number(d.value) || 0), 0); return (
{s.label} {fmtVal(stageTotal)}
{deals.map((d, i) => )}
); })}
setModalOpen(false)} title="Add deal" onSubmit={handleAddDeal} loading={saving} submitLabel="Add deal"> set('title', e.target.value)} />
set('value', e.target.value)} /> set('probability', e.target.value)} />
); } Object.assign(window, { Pipeline }); })();