/* ============ Clients ============ */ const CLIENT_SOURCES = ['Phone','Facebook','Website','Networking','Referral','Flyer','Other']; const CLIENT_STATUSES = ['Not Contacted','Contacted','Awaiting Response','Pending','Won','Lost']; const CLIENT_PRIORITY = ['Low','Medium','High']; function Clients({ search }) { const { clients } = useStore(); const [statusF, setStatusF] = React.useState(''); const [sourceF, setSourceF] = React.useState(''); const [priorityF, setPriorityF] = React.useState(''); const [editing, setEditing] = React.useState(null); const filtered = React.useMemo(() => { const q = (search || '').toLowerCase().trim(); return clients.filter(c => { if (statusF && c.status !== statusF) return false; if (sourceF && c.source !== sourceF) return false; if (priorityF && c.priority !== priorityF) return false; if (q) { const hay = `${c.name} ${c.phone} ${c.email}`.toLowerCase(); if (!hay.includes(q)) return false; } return true; }); }, [clients, statusF, sourceF, priorityF, search]); const sumRevenue = filtered.reduce((s,c) => s + (parseFloat(c.revenue)||0), 0); const sumUnpaid = filtered.filter(c => !c.paid).reduce((s,c) => s + (parseFloat(c.revenue)||0), 0); return (
{filtered.length} clients · {fmt.money(sumRevenue)} total {sumUnpaid > 0 && <> · {fmt.money(sumUnpaid)} unpaid}
{filtered.length === 0 ? ( setEditing({ _new: true })}>New client} /> ) : (
{filtered.map((c, i) => ( ))}
Name Contact Source Status Priority Fee Paid
{String(i+1).padStart(2,'0')} Store.Clients.update(c.id, { name: e.target.value })} placeholder="Client name"/> Store.Clients.update(c.id, { phone: e.target.value })} placeholder="Phone" /> Store.Clients.update(c.id, { email: e.target.value })} placeholder="Email" style={{ marginTop: 2 }}/> {c.status} {c.priority} Store.Clients.update(c.id, { revenue: parseFloat(e.target.value)||0 })} style={{ textAlign: 'right', minWidth: 80 }}/> Store.Clients.update(c.id, { paid: v })} /> setEditing(c)} />
)}
{editing && setEditing(null)} />}
); } function ClientEditor({ draft, onClose }) { const isNew = !!draft._new; const [c, setC] = React.useState(isNew ? { name: '', phone: '', email: '', source: 'Phone', status: 'Not Contacted', priority: 'Low', revenue: 0, paid: false, notes: '', } : draft); const toast = useToast(); const set = (patch) => setC(prev => ({ ...prev, ...patch })); const save = () => { if (isNew) { Store.Clients.add(c); toast('Client added'); } else { Store.Clients.update(draft.id, c); toast('Client updated'); } onClose(); }; const del = () => { if (confirm(`Delete ${c.name || 'this client'}?`)) { Store.Clients.remove(draft.id); toast('Client deleted'); onClose(); } }; return ( {!isNew && }
}> set({ name: e.target.value })} placeholder="John Appleseed" autoFocus />
set({ phone: e.target.value })} placeholder="(555) 555-0123"/> set({ email: e.target.value })} placeholder="name@example.com"/>
set({ status: e.target.value })} options={CLIENT_STATUSES}/>
set({ revenue: parseFloat(e.target.value)||0 })}/>