/* Clientes — Distribuidores e Picolezeiros */ function Clientes({ ctx }) { const [sub, setSub] = useState("distribuidores"); const [editing, setEditing] = useState(null); const [history, setHistory] = useState(null); // { kind, client } return ( <>

Clientes

Cadastro completo de distribuidores e picolezeiros

{sub === "distribuidores" && setHistory({ kind:"distributor", client:d })} />} {sub === "picolezeiros" && setHistory({ kind:"picolezeiro", client:p })} />} {editing && (sub === "distribuidores" ? setEditing(null)} onSave={(v) => { if (editing === "new") ctx.actions.addDistributor(v); else ctx.actions.updateDistributor(editing.id, v); setEditing(null); }} /> : setEditing(null)} onSave={(v) => { if (editing === "new") ctx.actions.addPicolezeiro(v); else ctx.actions.updatePicolezeiro(editing.id, v); setEditing(null); }} /> )} {history && ( setHistory(null)} /> )} ); } function DistribuidoresList({ ctx, onEdit, onHistory }) { const [search, setSearch] = useState(""); const list = ctx.state.distributors.filter(d => d.name.toLowerCase().includes(search.toLowerCase()) || d.city.toLowerCase().includes(search.toLowerCase()) || d.doc.includes(search)); const onDelete = (d) => ctx.askConfirm({ title: "Excluir distribuidor?", message: `Excluir "${d.name}"?`, detail: "O histórico de vendas deste cliente permanecerá nos relatórios.", danger: true, onConfirm: () => ctx.actions.deleteDistributor(d.id), }); return ( <>
} label="Distribuidores ativos" value={ctx.state.distributors.length + ""} sub="Pessoa jurídica" /> } label="Compras (mês)" value={ctx.state.distributors.reduce((s,d)=>s+d.totals.compras,0)+""} sub="Vendas registradas" /> } label="Volume (mês)" value={fmtBRL(ctx.state.distributors.reduce((s,d)=>s+d.totals.valor,0))} trend="+12,8%" trendDir="up" /> } label="Ticket médio" value={fmtBRL(ctx.state.distributors.length ? ctx.state.distributors.reduce((s,d)=>s+d.totals.valor,0) / Math.max(1, ctx.state.distributors.reduce((s,d)=>s+d.totals.compras,0)) : 0)} sub="Por compra" />
setSearch(e.target.value)} />
} > {list.length === 0 ? (

{search ? "Nenhum resultado" : "Nenhum distribuidor"}

{search ? "Tente outro termo de busca." : "Clique em \"Novo distribuidor\" para cadastrar o primeiro."}

) : ( {list.map(d => ( onHistory(d)}> ))}
Razão social CNPJ Cidade Pagamento Compras Volume
{d.name.split(" ").map(w => w[0]).slice(0,2).join("")}
{d.name}
{d.phone}
{d.doc} {d.city} {d.payment} {d.totals.compras} {fmtBRL(d.totals.valor)} e.stopPropagation()}>
)}
); } function PicolezeirosList({ ctx, onEdit, onHistory }) { const onDelete = (p) => ctx.askConfirm({ title: "Excluir picolezeiro?", message: `Excluir "${p.name}"?`, detail: "O histórico de saídas permanecerá nos relatórios.", danger: true, onConfirm: () => ctx.actions.deletePicolezeiro(p.id), }); if (ctx.state.picolezeiros.length === 0) { return (

Nenhum picolezeiro cadastrado

Cadastre picolezeiros para começar a registrar saídas e consignações.

); } return (
{ctx.state.picolezeiros.map(p => { const beaches = p.beaches.map(bid => ctx.state.beaches.find(b => b.id === bid)?.name).filter(Boolean); return (
{p.photo}
{p.name}
{p.phone}
{p.since ? "Desde " + fmtDate(p.since) : ""}
{p.commission}%
Praias atendidas
{beaches.length > 0 ? beaches.map((b,i) => {b}) : Nenhuma praia atribuída}
{p.totals.saidas}Saídas
{fmtBRL(p.totals.vendido)}Faturado
{fmtBRL(p.totals.comissao)}Comissão
); })}
); } function DistribuidorModal({ initial, onClose, onSave }) { const [type, setType] = useState(initial?.type || "PJ"); const [name, setName] = useState(initial?.name || ""); const [doc, setDoc] = useState(initial?.doc || ""); const [ie, setIe] = useState(initial?.ie || ""); const [phone, setPhone] = useState(initial?.phone || ""); const [email, setEmail] = useState(initial?.email || ""); const [cep, setCep] = useState(initial?.cep || ""); const [city, setCity] = useState(initial?.city || ""); const [address, setAddress] = useState(initial?.address || ""); const [payment, setPayment] = useState(initial?.payment || "PIX"); const [err, setErr] = useState({}); const submit = () => { const e = {}; if (!name.trim()) e.name = "Informe a razão social"; if (!doc.trim()) e.doc = "Informe o documento"; if (!phone.trim()) e.phone = "Informe o WhatsApp"; setErr(e); if (Object.keys(e).length) return; onSave({ type, name: name.trim(), doc, ie, phone, email, cep, city, address, payment }); }; return ( } >
setName(e.target.value)} placeholder={type === "PJ" ? "Empresa LTDA" : "Nome completo"} autoFocus /> setDoc(type === "PJ" ? maskCNPJ(e.target.value) : maskCPF(e.target.value))} placeholder={type === "PJ" ? "00.000.000/0000-00" : "000.000.000-00"} /> {type === "PJ" && setIe(e.target.value)} placeholder="000.000.000.000" />} {type === "PF" && } setPhone(maskPhone(e.target.value))} placeholder="(00) 00000-0000" /> setEmail(e.target.value)} placeholder="contato@..." /> setCep(maskCEP(e.target.value))} placeholder="00000-000" /> setCity(e.target.value)} placeholder="Salvador / BA" /> setAddress(e.target.value)} placeholder="Rua, número, bairro" />
); } function PicolezeiroModal({ initial, beaches, defaultCommission, onClose, onSave }) { const [name, setName] = useState(initial?.name || ""); const [cpf, setCpf] = useState(initial?.cpf || ""); const [rg, setRg] = useState(initial?.rg || ""); const [phone, setPhone] = useState(initial?.phone || ""); const [address, setAddress] = useState(initial?.address || ""); const [commission, setCommission] = useState(initial?.commission ?? defaultCommission); const [selBeaches, setSelBeaches] = useState(initial?.beaches || []); const [since, setSince] = useState(initial?.since || ""); const [err, setErr] = useState({}); const submit = () => { const e = {}; if (!name.trim()) e.name = "Informe o nome"; if (!cpf.trim()) e.cpf = "Informe o CPF"; if (!phone.trim()) e.phone = "Informe o WhatsApp"; if (selBeaches.length === 0) e.beaches = "Selecione ao menos uma praia"; setErr(e); if (Object.keys(e).length) return; const photo = name.trim().split(" ").map(w => w[0]).slice(0,2).join("").toUpperCase(); onSave({ name: name.trim(), cpf, rg, phone, address, commission: parseInt(commission || 0, 10), beaches: selBeaches, photo, since, }); }; return ( } >
setName(e.target.value)} placeholder="Nome do picolezeiro" autoFocus /> setCpf(maskCPF(e.target.value))} placeholder="000.000.000-00" /> setRg(e.target.value)} placeholder="00.000.000-0" /> setPhone(maskPhone(e.target.value))} placeholder="(00) 00000-0000" /> setSince(e.target.value)} placeholder="AAAA-MM-DD" /> setAddress(e.target.value)} placeholder="Rua, número, bairro, cidade" /> setCommission(e.target.value.replace(/\D/g,""))} />
{beaches.length === 0 ? ( Cadastre praias em Configurações antes. ) : beaches.map(b => { const on = selBeaches.includes(b.id); return ( ); })}
); } window.Clientes = Clientes;