import { useEffect, useState, useCallback } from 'react'
import { leadsApi, exportApi } from '../api'
import { useProject } from '../contexts/ProjectContext'
const STATUS_OPTIONS = ['new', 'contacted', 'qualified', 'converted', 'archived']
const SOURCE_OPTIONS = ['csv', 'excel', 'linkedin', 'web_scrape', 'manual']
function Badge({ value }) {
const cls = `badge badge-${value?.replace(/\s/g, '_').toLowerCase() || 'new'}`
return {value || '—'}
}
function LeadModal({ lead, projectId, onClose, onSave }) {
const [form, setForm] = useState({ ...lead })
const [saving, setSaving] = useState(false)
const set = (k, v) => setForm(f => ({ ...f, [k]: v }))
const handleSave = async () => {
setSaving(true)
try {
const data = { ...form }
if (projectId && !data.project_id) data.project_id = projectId
await (lead.id ? leadsApi.update(lead.id, data) : leadsApi.create(data))
onSave()
} catch (e) {
alert(e.message)
} finally {
setSaving(false)
}
}
const fields = [
['company_name', 'Company Name'], ['contact_name', 'Contact Name'],
['email', 'Email'], ['phone', 'Phone'],
['website', 'Website'], ['linkedin_url', 'LinkedIn URL'],
['industry', 'Industry'], ['company_size', 'Company Size'],
['location', 'Location'],
]
return (
{lead.id ? 'Edit Lead' : 'Add Lead'}
{fields.map(([k, label]) => (
set(k, e.target.value)} />
))}
)
}
export default function LeadsTable() {
const { activeProject } = useProject()
const [leads, setLeads] = useState([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const PAGE_SIZE = 20
const [search, setSearch] = useState('')
const [status, setStatus] = useState('')
const [source, setSource] = useState('')
const [loading, setLoading] = useState(false)
const [selected, setSelected] = useState(new Set())
const [editLead, setEditLead] = useState(null)
const [error, setError] = useState(null)
const fetchLeads = useCallback(async () => {
setLoading(true)
setError(null)
try {
const data = await leadsApi.list({
page, page_size: PAGE_SIZE,
search: search || undefined,
status: status || undefined,
source: source || undefined,
project_id: activeProject?.id ?? undefined,
})
setLeads(data.leads)
setTotal(data.total)
} catch (e) {
setError(e.message)
} finally {
setLoading(false)
}
}, [page, search, status, source, activeProject])
useEffect(() => { setPage(1) }, [activeProject])
useEffect(() => { fetchLeads() }, [fetchLeads])
const toggleSelect = (id) => setSelected(s => { const ns = new Set(s); ns.has(id) ? ns.delete(id) : ns.add(id); return ns })
const selectAll = () => setSelected(selected.size === leads.length && leads.length > 0 ? new Set() : new Set(leads.map(l => l.id)))
const deleteSelected = async () => {
if (!window.confirm(`Delete ${selected.size} lead(s)?`)) return
try { await leadsApi.bulkDelete([...selected]); setSelected(new Set()); fetchLeads() }
catch (e) { alert(e.message) }
}
const deleteLead = async (id) => {
if (!window.confirm('Delete this lead?')) return
try { await leadsApi.delete(id); fetchLeads() }
catch (e) { alert(e.message) }
}
const totalPages = Math.ceil(total / PAGE_SIZE)
const exportParams = { status: status || undefined, source: source || undefined, project_id: activeProject?.id ?? undefined }
return (
{editLead && (
setEditLead(null)}
onSave={() => { setEditLead(null); fetchLeads() }}
/>
)}
Leads
({total})
{activeProject &&
Project: {activeProject.name}
}
{selected.size > 0 && (
)}
{ setSearch(e.target.value); setPage(1) }} />
{error && {error}
}
{loading ? (
Loading...
) : leads.length === 0 ? (
👤
No leads found
Import data or use the scraper to generate leads.
) : (
)}
{totalPages > 1 && (
Page {page} of {totalPages}
)}
)
}