/* global React */
/* ===========================================================
VOLTLAB · INFRAESTRUTURA
Quantitativo (BOM) de eletrocalhas, leitos, perfilados e
eletrodutos a partir das distâncias do projeto.
Adaptado para rodar dentro do shell VoltLab.
=========================================================== */
const { useState: useStateInfra, useMemo: useMemoInfra } = React;
const useInfraPersist = (key, initial) =>
window.useLocalStorage ? window.useLocalStorage(key, initial) : useStateInfra(initial);
/* ── Cálculo (fórmulas da planilha) ───────────────────── */
const INFRA_CALC = {
eletrocalha(m) {
const d = parseFloat(m) || 0; if (d <= 0) return null;
const u = Math.ceil(d / 3);
return [
{ item: "Eletrocalha (barra 3m)", qtd: u },
{ item: "Emendas tipo tala/simples", qtd: u * 2 },
{ item: 'Parafusos lentilha 1/4" × 3/4"', qtd: u * 8 },
{ item: 'Porcas 1/4"', qtd: u * 8 },
{ item: 'Arruelas lisas 1/4"', qtd: u * 8 },
{ item: "Mão francesa (paraf. fix. 3/8)", qtd: u * 2 },
{ item: "Fixação / bucha / parabolts", qtd: u * 4 },
];
},
leito(m) {
const d = parseFloat(m) || 0; if (d <= 0) return null;
const u = Math.ceil(d / 3);
return [
{ item: "Leito portacabos (barra 3m)", qtd: u },
{ item: "Emendas tipo tala/simples", qtd: u * 2 },
{ item: 'Parafusos lentilha 3/8" × 3/4"', qtd: u * 8 },
{ item: 'Porcas 3/8"', qtd: u * 8 },
{ item: 'Arruelas lisas 3/8"', qtd: u * 8 },
{ item: "Mão francesa (paraf. fix. 3/8)", qtd: u * 2 },
{ item: "Fixação / bucha / parabolts", qtd: u * 4 },
];
},
perfilado(m) {
const d = parseFloat(m) || 0; if (d <= 0) return null;
const u = Math.ceil(d / 6);
return [
{ item: "Perfilado 38×38 (barra 6m)", qtd: u },
{ item: "Suporte / gancho perfilado 38×38", qtd: u * 4 },
{ item: 'Parafuso lentilha 1/4" × 3/4"', qtd: u * 8 },
{ item: 'Porca 1/4"', qtd: u * 8 },
{ item: 'Arruela lisa 1/4"', qtd: u * 8 },
{ item: "Mão francesa", qtd: u * 4 },
{ item: "Fixação / bucha / parabolts", qtd: (u * 8) + 1 },
];
},
eletroduto(m, ce) {
const d = parseFloat(m) || 0; if (d <= 0) return null;
const u = Math.ceil(d / 3);
const c = parseInt(ce) || 0;
return [
{ item: "Eletroduto galvanizado (barra 3m)", qtd: u },
{ item: "Luvas", qtd: Math.max(0, u - 1) },
{ item: "Abraçadeiras", qtd: u * 2 },
{ item: "Parafusos auto-atarraxante 4,8mm × 50mm", qtd: u * 2 },
{ item: "Buchas nylon S8", qtd: u * 2 },
{ item: "Conduletes (trechos retos/curvas)", qtd: c },
];
},
};
const INFRA_TONE = {
eletrocalha: "var(--accent)",
leito: "var(--accent-2)",
perfilado: "var(--ok)",
eletroduto: "var(--warn)",
};
const InfraIc = ({ n, s = 18 }) => {
const p = { width: s, height: s, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" };
const d = {
tray: <>>,
rail: <>>,
bolt: ,
ring: <>>,
check: ,
info: <>>,
dl: <>>,
reset: <>>,
};
return ;
};
const InfraBanner = ({ kind, children }) => (
{children}
);
const InfraNum = ({ label, hint, value, onChange, suffix }) => (
);
const InfraMatTable = ({ title, icon, tipo, items, dist, barra }) => {
const cor = INFRA_TONE[tipo];
if (!items) return (
{title}
— informe a distância
);
return (
{title.toUpperCase()}
{title}
{items[0].qtd}
BARRAS · {dist}m ÷ {barra}m
| MATERIAL |
QTD. |
{items.map((row, i) => (
| {row.item} |
{row.qtd} |
))}
);
};
const InfraDiagram = ({ dc, dl, dp, de }) => {
const vals = [
{ label: "Eletrocalha", m: parseFloat(dc) || 0, cor: INFRA_TONE.eletrocalha },
{ label: "Leito", m: parseFloat(dl) || 0, cor: INFRA_TONE.leito },
{ label: "Perfilado", m: parseFloat(dp) || 0, cor: INFRA_TONE.perfilado },
{ label: "Eletroduto", m: parseFloat(de) || 0, cor: INFRA_TONE.eletroduto },
].filter(v => v.m > 0);
if (!vals.length) return null;
const max = Math.max(...vals.map(v => v.m));
return (
ESCALA VISUAL
{vals.map((v, i) => (
))}
);
};
const InfraBOM = ({ res }) => {
const tipos = ["eletrocalha", "leito", "perfilado", "eletroduto"];
const labels = { eletrocalha: "Eletrocalha", leito: "Leito", perfilado: "Perfilado", eletroduto: "Eletroduto" };
const ativos = tipos.filter(t => res[t]);
if (!ativos.length) return (
Informe ao menos uma distância na aba Dimensionamento.
);
const mapa = {};
tipos.forEach(tipo => { (res[tipo] || []).forEach(r => { if (!mapa[r.item]) mapa[r.item] = { item: r.item, tipos: {} }; mapa[r.item].tipos[tipo] = r.qtd; }); });
const totais = {};
ativos.forEach(t => { totais[t] = (res[t] || []).reduce((s, r) => s + r.qtd, 0); });
const exportCSV = () => {
const rows = [["MATERIAL", ...ativos.map(t => labels[t].toUpperCase())]];
Object.values(mapa).forEach(r => rows.push([r.item, ...ativos.map(t => r.tipos[t] ?? "")]));
const csv = rows.map(r => r.join(";")).join("\n");
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob(["\uFEFF" + csv], { type: "text/csv;charset=utf-8" }));
a.download = "infraestrutura-bom.csv"; a.click();
if (window.vlToast) window.vlToast("✓ CSV exportado");
};
return (
{tipos.map(tipo => {
const it = res[tipo]; const cor = INFRA_TONE[tipo];
return (
{labels[tipo].toUpperCase()}
{it ? it[0].qtd : "—"}
barras/peças
);
})}
CONSOLIDADO · BILL OF MATERIALS
Quantitativo geral do projeto
| MATERIAL |
{ativos.map(t => ({labels[t].toUpperCase()} | ))}
TOTAL |
{Object.values(mapa).map((row, i) => {
const total = ativos.reduce((s, t) => s + (row.tipos[t] || 0), 0);
return (
| {row.item} |
{ativos.map(t => ({row.tipos[t] || "—"} | ))}
{total || "—"} |
);
})}
| TOTAL DE ITENS |
{ativos.map(t => ({totais[t]} | ))}
{ativos.reduce((s, t) => s + totais[t], 0)} |
);
};
/* ── Componente principal ─────────────────────────────── */
const Infraestrutura = () => {
const [dc, setDc] = useInfraPersist("vl_infra_calha", "");
const [dl, setDl] = useInfraPersist("vl_infra_leito", "");
const [dp, setDp] = useInfraPersist("vl_infra_perf", "");
const [de, setDe] = useInfraPersist("vl_infra_eldt", "");
const [cond, setCond] = useInfraPersist("vl_infra_cond", "0");
const [tab, setTab] = useStateInfra("calc");
const res = useMemoInfra(() => ({
eletrocalha: INFRA_CALC.eletrocalha(dc),
leito: INFRA_CALC.leito(dl),
perfilado: INFRA_CALC.perfilado(dp),
eletroduto: INFRA_CALC.eletroduto(de, cond),
}), [dc, dl, dp, de, cond]);
const ativos = Object.values(res).filter(Boolean).length;
const totalItens = Object.values(res).filter(Boolean).flatMap(r => r).reduce((s, r) => s + r.qtd, 0);
const totalDist = [dc, dl, dp, de].reduce((s, v) => s + (parseFloat(v) || 0), 0);
const reset = () => { setDc(""); setDl(""); setDp(""); setDe(""); setCond("0"); if (window.vlToast) window.vlToast("✓ Campos limpos"); };
const Sync = window.VL_ProjetoSync;
return (
{/* Abas */}
{[{ id: "calc", label: "Dimensionamento" }, { id: "resumo", label: "Resumo / BOM" }].map(t => (
))}
{tab === "resumo" ? (
) : (
ENTRADA · DISTÂNCIAS
Dados do projeto
AUTO
{Sync && (
{ const c = s.get("comprimento"); if (c != null) setDe(String(c)); }}
push={() => ({ comprimento: parseFloat(de) || undefined })}
/>
)}
{[
["01", "Eletrocalha / Leito", "N = ⌈ distância ÷ 3 ⌉"],
["02", "Perfilado 38×38", "N = ⌈ distância ÷ 6 ⌉"],
["03", "Eletroduto galv.", "N = ⌈ distância ÷ 3 ⌉"],
].map(([n, title, formula]) => (
))}
{ativos === 0 && (Informe uma distância para gerar o quantitativo.
)}
)}
);
};
window.VL_Infraestrutura = Infraestrutura;