// Kanban.jsx — Drag & Drop Sales Funnel (function () { const { useState, useRef } = React; function fmtVal(v) { if (v >= 1000000) return (v/1000000).toFixed(1).replace('.',',') + ' M zł'; if (v >= 1000) return Math.round(v/1000) + ' k zł'; return v + ' zł'; } const sourceColors = { 'Google': '#7C5CFF', 'Polecenia': '#4DA3FF', 'Social': '#00D9FF', 'Formularz': '#22C55E', }; const sourceLabels = { 'Google':'Google Ads', 'Polecenia':'Polecenia', 'Social':'Meta Ads', 'Formularz':'Formularz' }; // ── Lead Detail Drawer ─────────────────────────────────────────── function LeadDrawer({ lead, columns, onClose, onMove, onAddNote }) { const [noteText, setNoteText] = React.useState(''); if (!lead) return null; const col = columns.find(c => c.id === lead.column); return ( <>
{lead.initials}
{lead.company}
{lead.person}
{col?.label} {sourceLabels[lead.source] || lead.source}
Szczegóły leada
{[ { icon:'◈', label:`Wartość: ${fmtVal(lead.value)}` }, { icon:'✉', label:`Osoba kontaktowa: ${lead.person}` }, { icon:'●', label:`Źródło: ${sourceLabels[lead.source] || lead.source}` }, ].map((r,i) => (
{r.icon} {r.label}
))}
Przenieś do etapu
{columns.map(c => ( ))}
Notatki
{lead.notes &&

{lead.notes}

}
setNoteText(e.target.value)} placeholder="Dopisz notatkę…" style={{ flex:1, padding:'8px 10px', borderRadius:8, border:'1px solid var(--bdr)', background:'var(--bg-input)', color:'var(--t1)', fontSize:12.5, outline:'none' }} onKeyDown={e => { if (e.key==='Enter' && noteText.trim()) { onAddNote(lead.id, noteText.trim()); setNoteText(''); } }} />
); } // ── Kanban Card ─────────────────────────────────────────────────── function KanbanCard({ lead, onDragStart, onOpen }) { const [hov, setHov] = useState(false); const dragStarted = useRef(false); return (
{ dragStarted.current = true; onDragStart(lead.id); }} onClick={() => { if (!dragStarted.current) onOpen(lead); dragStarted.current = false; }} onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)} style={{ background: hov ? 'var(--bg2)' : 'var(--bg1)', border: '1px solid var(--bdr)', borderRadius: 12, padding: '12px 14px', cursor: 'pointer', transition: 'all .18s ease', transform: hov ? 'translateY(-2px)' : 'none', boxShadow: hov ? '0 8px 24px rgba(0,0,0,0.3)' : '0 2px 8px rgba(0,0,0,0.15)', userSelect: 'none', }} > {/* Header: avatar + company */}
{lead.initials}
{lead.company}
{lead.person}
{/* Value + source */}
{fmtVal(lead.value)} {sourceLabels[lead.source] || lead.source}
); } // ── Kanban Column ───────────────────────────────────────────────── function KanbanColumn({ col, leads, onDragStart, onDrop, onOpen }) { const [dragOver, setDragOver] = useState(false); const total = leads.reduce((s, l) => s + l.value, 0); return (
{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={() => { setDragOver(false); onDrop(col.id); }} style={{ minWidth: 220, maxWidth: 240, flex: '1 1 220px', display: 'flex', flexDirection: 'column', gap: 0, background: dragOver ? 'rgba(124,92,255,0.06)' : 'transparent', border: dragOver ? '1.5px dashed rgba(124,92,255,0.4)' : '1.5px dashed transparent', borderRadius: 16, transition: 'all .18s ease', }} > {/* Column header */}
{col.label} {leads.length}
{leads.length > 0 && (
{fmtVal(total)}
)}
{/* Cards */}
{leads.map(lead => ( ))} {leads.length === 0 && (
Upuść tutaj
)}
); } // ── Kanban (main export) ────────────────────────────────────────── window.Kanban = function Kanban({ leads, setLeads }) { const dragId = useRef(null); const S = window.SEED; const [openLead, setOpenLead] = useState(null); const handleDragStart = (id) => { dragId.current = id; }; const handleDrop = (colId) => { if (!dragId.current) return; setLeads(prev => prev.map(l => l.id === dragId.current ? { ...l, column: colId } : l)); dragId.current = null; }; const moveLead = (id, colId) => { setLeads(prev => prev.map(l => l.id === id ? { ...l, column: colId } : l)); setOpenLead(prev => prev && prev.id === id ? { ...prev, column: colId } : prev); }; const addLeadNote = (id, text) => { setLeads(prev => prev.map(l => l.id === id ? { ...l, notes: l.notes ? l.notes + '\n' + text : text } : l)); setOpenLead(prev => prev && prev.id === id ? { ...prev, notes: prev.notes ? prev.notes + '\n' + text : text } : prev); }; const totalWon = leads.filter(l => l.column === 'won').reduce((s, l) => s + l.value, 0); const totalPipeline = leads.filter(l => !['won','lost'].includes(l.column)).reduce((s, l) => s + l.value, 0); return (
{/* Stats bar */}
{[ { label: 'W toku', val: fmtVal(totalPipeline), color: '#7C5CFF' }, { label: 'Wygranych', val: fmtVal(totalWon), color: '#22C55E' }, { label: 'Łącznie leadów', val: leads.length, color: '#4DA3FF' }, ].map(s => (
{s.label}
{s.val}
))}
{/* Board */}
{S.kanbanColumns.map(col => ( l.column === col.id)} onDragStart={handleDragStart} onDrop={handleDrop} onOpen={setOpenLead} /> ))}
{openLead && setOpenLead(null)} onMove={moveLead} onAddNote={addLeadNote} />}
); }; })();