+ {/* ---- head -------------------------------------------- */}
+
+
+
+
+
Pipeline
+
{leads.length} leads · {stageCount} stages
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder="Search leads…"
+ aria-label="Search leads"
+ />
+ {query && }
+
+
+
+ {/* ---- board ------------------------------------------- */}
+
+ {STAGES.map((stage) => {
+ const items = byStage(stage.key);
+ const step = PROGRESSION.indexOf(stage.key);
+ return (
+
{ e.preventDefault(); setOverStage(stage.key); }}
+ onDragLeave={() => setOverStage((s) => (s === stage.key ? null : s))}
+ onDrop={() => moveTo(stage.key)}
+ >
+
+
+ {stage.label}
+ {items.length}
+ {step >= 0 && {step + 1}/{stageCount}}
+
+ = 0 ? `${((step + 1) / stageCount) * 100}%` : "100%" }} />
+
+
+ {items.map((l) => (
+
setSelected(l)}
+ onDragStart={() => setDragId(l.id)}
+ onDragEnd={() => { setDragId(null); setOverStage(null); }}
+ dragging={dragId === l.id}
+ />
+ ))}
+ {items.length === 0 && Drop leads here
}
+
+
+ );
+ })}
+
+
+
setSelected(null)} />
+
+ );
+}
+
+/* ---------------------------------------------------------- */
+/* Card */
+/* ---------------------------------------------------------- */
+
+function PipelineCard({ lead, onOpen, onDragStart, onDragEnd, dragging }: {
+ lead: PLead; onOpen: () => void; onDragStart: () => void; onDragEnd: () => void; dragging: boolean;
+}) {
+ return (
+