/* Histórico do cliente — modal compartilhado
Mostra todas as transações de um distribuidor ou picolezeiro num único cadastro.
- Distribuidor: lista vendas com itens, hora, forma de pagamento, status
- Picolezeiro: lista saídas/retornos com itens, praias, faturado, comissão
Cada linha pode ser expandida e impressa individualmente. */
function ClientHistoryModal({ ctx, kind, client, onClose }) {
// kind: "distributor" | "picolezeiro"
const [expanded, setExpanded] = useState(null);
const [printPick, setPrintPick] = useState(null); // {kind, payload}
const [period, setPeriod] = useState({ kind: "all" });
const isDist = kind === "distributor";
// pega registros
const records = useMemo(() => {
if (isDist) {
return ctx.state.sales
.filter(s => s.clientId === client.id)
.filter(s => inPeriod(s.date, period, DEFAULT_REF_DATE))
.sort((a,b) => (b.date + " " + (b.time||"")).localeCompare(a.date + " " + (a.time||"")));
} else {
return ctx.state.trips
.filter(t => t.picolezeiroId === client.id)
.filter(t => inPeriod(t.date, period, DEFAULT_REF_DATE))
.sort((a,b) => b.date.localeCompare(a.date));
}
}, [ctx.state.sales, ctx.state.trips, client.id, isDist, period]);
const totalFat = isDist
? records.reduce((s, r) => s + r.total, 0)
: records.filter(t => t.status === "fechado").reduce((s, t) => s + (t.faturado || 0), 0);
const totalUnidades = isDist
? records.reduce((s, r) => s + r.items.reduce((a, i) => a + i.units, 0), 0)
: records.reduce((s, t) => s + t.items.reduce((a, i) => a + i.qty, 0), 0);
const aReceber = isDist
? records.filter(r => r.status === "a_receber").reduce((s, r) => s + r.total, 0)
: 0;
const comissaoTotal = !isDist
? records.filter(t => t.status === "fechado").reduce((s, t) => s + (t.comissao || 0), 0)
: 0;
/* Impressão de uma transação específica */
const reprintSale = (sale, fmt) => {
const distClient = ctx.state.distributors.find(d => d.id === sale.clientId) || client;
printSaleReceipt({
company: ctx.state.company,
sale: { id: sale.id, date: sale.date, client: distClient, payment: sale.payment, subtotal: sale.subtotal, discount: sale.discount, total: sale.total, perBox: sale.perBox },
items: sale.items.map(({productId, boxes}) => ({ productId, boxes })),
products: ctx.state.products,
fmt,
});
};
const reprintTrip = (trip, fmt) => {
const pz = client;
if (trip.status === "fechado") {
printRetornoReceipt({
company: ctx.state.company,
trip, picolezeiro: pz, beaches: ctx.state.beaches, products: ctx.state.products, fmt,
});
} else {
printSaidaReceipt({
company: ctx.state.company,
trip, picolezeiro: pz, beaches: ctx.state.beaches, products: ctx.state.products, fmt,
});
}
};
/* Impressão do histórico inteiro do cliente */
const printFullHistory = () => {
const logo = ctx.state.company.logo;
const logoIsImage = logo && !/^data:application\/pdf/i.test(logo);
const headerHtml = `
${logoIsImage ? `

` : `
P
`}
Histórico de ${isDist ? "compras" : "consignação"}
${ctx.state.company.name} · Gerado em ${new Date().toLocaleString("pt-BR")}
${client.name}
${isDist ? (client.doc||"") + " · " + (client.city||"") : (client.cpf||"") + " · " + (client.phone||"")}
${records.length}${isDist ? "compras" : "saídas/retornos"}
${totalUnidades}unidades
${fmtBRL(totalFat)}${isDist ? "faturado" : "faturado (fechados)"}
${isDist ? `
${fmtBRL(aReceber)}a receber
` : `
${fmtBRL(comissaoTotal)}comissão paga
`}
`;
const rows = records.map(r => {
if (isDist) {
const itemsTxt = r.items.map(it => {
const p = ctx.state.products.find(x => x.id === it.productId);
return `${p?.flavor||"?"} ${it.boxes}cx`;
}).join(" · ");
return `
${fmtDate(r.date)} ${r.time||""} |
${itemsTxt} |
${r.payment||"—"} |
${fmtBRL(r.total)} |
${r.status === "confirmado" ? "✓ Pago" : "⏳ A receber"} |
`;
} else {
const itemsTxt = r.items.map(it => {
const p = ctx.state.products.find(x => x.id === it.productId);
return `${p?.flavor||"?"} ${it.qty}un`;
}).join(" · ");
const sold = (r.sold||[]).reduce((s,x)=>s+x.qty,0);
return `
| ${fmtDate(r.date)} |
${itemsTxt} |
${r.status === "fechado" ? `${sold} un vendidas` : "—"} |
${r.status === "fechado" ? fmtBRL(r.faturado||0) : "—"} |
${r.status === "fechado" ? "✓ Fechado" : "⏳ Em rota"} |
`;
}
}).join("");
const headers = isDist
? `| Data / Hora | Itens | Pagamento | Total | Status |
`
: `| Data | Itens retirados | Resultado | Faturado | Status |
`;
const body = `
${headerHtml}
${ctx.state.company.name} · Documento sem valor fiscal
`;
const css = `@page { size: A4; margin: 12mm 10mm; } * { box-sizing: border-box; } html, body { margin:0; padding:0; } body { font-family: Helvetica, Arial, sans-serif; color:#222; font-size:11px; line-height:1.5; }
table { width:100%; border-collapse:collapse; table-layout:auto; word-wrap:break-word; }
table th { background:transparent; font-weight:700; font-size:9.5px; text-transform:uppercase; letter-spacing:.08em; padding:7px 6px; border-bottom:1.5px solid #333; text-align:left; color:#555; }
table td { padding:6px; border-bottom:1px solid #eee; font-size:10.5px; vertical-align:top; }
tbody tr:last-child td { border-bottom:none; }`;
const win = window.open("", "_blank", "width=1000,height=900");
win.document.open();
win.document.write(`Histórico — ${client.name}
Histórico — ${client.name}
${body}
`);
win.document.close();
};
const initial = client.name.split(" ").map(w => w[0]).slice(0,2).join("");
const subtitle = isDist
? `${client.doc || ""} · ${client.city || ""}`
: `${client.cpf || ""} · ${client.phone || ""} · ${client.commission || 0}% comissão`;
return (
>
}
>
{/* Sumário */}
{records.length === 0 ? (
Nenhuma {isDist ? "compra" : "saída"} registrada
Quando este cliente realizar uma {isDist ? "compra" : "saída"}, ela aparece aqui.
) : (
{records.map((r, idx) => (
setExpanded(expanded === (r.id || idx) ? null : (r.id || idx))}
onPrint={() => setPrintPick({ record: r })}
isLast={idx === records.length - 1}
/>
))}
)}
{printPick && (
setPrintPick(null)}
onPrint={(fmt) => {
if (isDist) reprintSale(printPick.record, fmt);
else reprintTrip(printPick.record, fmt);
setPrintPick(null);
}}
/>
)}
);
}
function SumPill({ label, value, color }) {
return (
{label}
{value}
);
}
function ClientHistoryRow({ kind, record, products, beaches, expanded, onToggle, onPrint, isLast }) {
const isDist = kind === "distributor";
const isFechado = !isDist && record.status === "fechado";
const totalUnits = isDist
? record.items.reduce((s, i) => s + i.units, 0)
: record.items.reduce((s, i) => s + i.qty, 0);
const headLeft = isDist ? (
<>
Venda · {record.id?.slice(-6).toUpperCase()}
{fmtDate(record.date)}{record.time ? " · " + record.time : ""}
·
{record.payment}
·
{record.items.length} sabor{record.items.length===1?"":"es"} · {totalUnits} un
>
) : (
<>
{isFechado ? "Retorno fechado" : "Saída em rota"} · {record.id?.slice(-6).toUpperCase()}
{fmtDate(record.date)}
·
{(record.beachesActual || record.beachesPlanned || []).map(bid => beaches.find(b=>b.id===bid)?.name).filter(Boolean).join(", ") || "—"}
·
{totalUnits} un retiradas
>
);
const headRight = isDist ? (
{fmtBRL(record.total)}
{record.status === "confirmado" ? pago : a receber}
) : (
{isFechado ? fmtBRL(record.faturado||0) : "—"}
{isFechado ? fechado : em rota}
);
return (
{headLeft}
{headRight}
{expanded && (
{isDist ? (
| Produto |
Caixas |
Unidades |
Preço un. |
Subtotal |
{record.items.map(it => {
const p = products.find(x => x.id === it.productId);
return (
| {p?.flavor||"?"} |
{it.boxes} |
{it.units} |
{fmtBRL(it.unitPrice)} |
{fmtBRL(it.subtotal)} |
);
})}
| Total da venda |
{fmtBRL(record.total)} |
{record.discount > 0 && (
| (desconto aplicado: {fmtBRL(record.discount)}) | |
)}
) : (
| Produto |
Retirado |
Vendido |
Sobra |
Faturado |
{record.items.map(it => {
const p = products.find(x => x.id === it.productId);
const sold = (record.sold||[]).find(x => x.productId === it.productId)?.qty || 0;
const ret = (record.returned||[]).find(x => x.productId === it.productId)?.qty;
const sobra = ret !== undefined ? ret : (isFechado ? it.qty - sold : null);
return (
| {p?.flavor||"?"} |
{it.qty} |
{isFechado ? sold : "—"} |
{isFechado ? sobra : "—"} |
{isFechado ? fmtBRL(sold * (p?.price||0)) : "—"} |
);
})}
{isFechado && (
<>
| Faturado |
{fmtBRL(record.faturado||0)} |
| Comissão |
− {fmtBRL(record.comissao||0)} |
| Líquido para empresa |
{fmtBRL((record.faturado||0) - (record.comissao||0))} |
>
)}
)}
)}
);
}
Object.assign(window, { ClientHistoryModal });