/* ============ 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} />
) : (
| № |
Name |
Contact |
Source |
Status |
Priority |
Fee |
Paid |
|
{filtered.map((c, i) => (
| {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({ revenue: parseFloat(e.target.value)||0 })}/>
);
}
window.Clients = Clients;