/* global React */
/* ===========================================================
VOLTLAB · CONFIGURAÇÕES
- VL_PREFS: store global de preferências de engenharia,
persistido em localStorage + pub/sub (módulos podem ler
window.VL_PREFS.get("tensao") etc. como ponto de partida).
- VL_Settings: página de configurações (Aparência, Padrões de
cálculo, Identificação & Relatórios, Dados & privacidade).
=========================================================== */
const VL_PREFS_KEY = "vl_prefs_v1";
const VL_PREFS_DEFAULTS = {
// Padrões de cálculo
tensao: 220,
tensaoCustom: false,
sistema: "trifasico",
frequencia: 60,
material: "Cu",
tempAmbiente: 30,
isolacao: "PVC",
quedaMax: 4,
fpAlvo: 0.92,
// Identificação & relatórios
empresa: "",
responsavel: "",
crea: "",
exportFmt: "json",
decimais: 2,
};
window.VL_PREFS_DEFAULTS = VL_PREFS_DEFAULTS;
/* ---------- store singleton ---------- */
const VL_PREFS = (() => {
const subs = new Set();
let data = (() => {
try {
const r = localStorage.getItem(VL_PREFS_KEY);
return r ? { ...VL_PREFS_DEFAULTS, ...JSON.parse(r) } : { ...VL_PREFS_DEFAULTS };
} catch { return { ...VL_PREFS_DEFAULTS }; }
})();
const persist = () => { try { localStorage.setItem(VL_PREFS_KEY, JSON.stringify(data)); } catch {} };
const emit = () => { persist(); subs.forEach(f => f(data)); window.dispatchEvent(new CustomEvent("vl:prefs", { detail: data })); };
return {
all: () => data,
get: (k) => data[k],
set(k, v) { data = { ...data, [k]: v }; emit(); },
setMany(obj) { data = { ...data, ...obj }; emit(); },
reset() { data = { ...VL_PREFS_DEFAULTS }; emit(); },
subscribe(f) { subs.add(f); return () => subs.delete(f); },
};
})();
window.VL_PREFS = VL_PREFS;
function useVLPrefs() {
const [, force] = React.useState(0);
React.useEffect(() => VL_PREFS.subscribe(() => force(x => x + 1)), []);
return VL_PREFS;
}
window.useVLPrefs = useVLPrefs;
/* ---------- ícones locais ---------- */
const StIcon = ({ n, s = 16 }) => {
const p = { width: s, height: s, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" };
const d = {
palette: <>>,
calc: <>>,
file: <>>,
shield: <>>,
check: ,
trash: <>>,
refresh: <>>,
warn: <>>,
company: <>>,
};
return ;
};
/* ---------- primitivos de linha de configuração ---------- */
const SetRow = ({ label, hint, children, full }) => (
{label}
{hint && {hint}}
{children}
);
const SetSeg = ({ value, onChange, options }) => (
{options.map(o => (
))}
);
const SetNum = ({ value, onChange, unit, step = 1, min, max, placeholder }) => (
onChange(e.target.value === "" ? "" : parseFloat(e.target.value))}
/>
{unit && {unit}}
);
const SetText = ({ value, onChange, placeholder, icon }) => (
{icon && }
onChange(e.target.value)} />
);
const SetToggle = ({ value, onChange, on = "Ativado", off = "Desativado" }) => (
);
/* swatches de cor de acento (mesmos hex do ACCENT_MAP do app) */
const ACCENT_SWATCHES = [
{ hex: "#3D8BFF", name: "Azul", color: "oklch(72% 0.18 245)" },
{ hex: "#3DD6E6", name: "Ciano", color: "oklch(82% 0.14 200)" },
{ hex: "#5BE38C", name: "Verde", color: "oklch(84% 0.18 145)" },
{ hex: "#F2B043", name: "Âmbar", color: "oklch(80% 0.16 75)" },
];
/* ===========================================================
SETTINGS PAGE
=========================================================== */
const VL_Settings = ({ tweaks = {}, setTweak = () => {}, onNav = () => {} }) => {
const prefs = useVLPrefs();
const P = prefs.all();
const [active, setActive] = React.useState("aparencia");
const scrollerRef = React.useRef(null);
const SECTIONS = [
{ id: "aparencia", label: "Aparência", icon: "palette" },
{ id: "calculo", label: "Padrões de cálculo", icon: "calc" },
{ id: "relatorios", label: "Identificação & Relatórios", icon: "file" },
{ id: "dados", label: "Dados & privacidade", icon: "shield" },
];
const toast = (m) => window.vlToast ? window.vlToast(m) : null;
const set = (k, v) => prefs.set(k, v);
/* navegação por âncoras dentro do scroller do app */
const goSection = (id) => {
setActive(id);
const root = scrollerRef.current;
const target = root && root.querySelector(`#sec-${id}`);
const scroller = root && root.closest(".main-scroll");
if (target && scroller) {
const top = target.offsetTop - 14;
scroller.scrollTo({ top, behavior: "smooth" });
}
};
/* destaca seção ativa conforme rolagem */
React.useEffect(() => {
const root = scrollerRef.current;
const scroller = root && root.closest(".main-scroll");
if (!scroller) return;
const onScroll = () => {
const secs = SECTIONS.map(s => root.querySelector(`#sec-${s.id}`)).filter(Boolean);
const y = scroller.scrollTop + 80;
let cur = SECTIONS[0].id;
for (const el of secs) { if (el.offsetTop <= y) cur = el.id.replace("sec-", "") || el.id; }
const found = secs.reverse().find(el => el.offsetTop <= y);
if (found) setActive(found.id.replace("sec-", ""));
};
scroller.addEventListener("scroll", onScroll, { passive: true });
return () => scroller.removeEventListener("scroll", onScroll);
}, []);
const handleResetAppearance = () => {
setTweak("accent", "#3D8BFF");
setTweak("density", "comfortable");
setTweak("showCircuitry", true);
setTweak("monoReadouts", true);
toast("Aparência restaurada ao padrão");
};
const handleResetPrefs = () => {
prefs.reset();
toast("Padrões de cálculo restaurados");
};
const handleClearProjeto = () => {
if (window.VL_PROJETO) { window.VL_PROJETO.clear(); toast("Barramento do projeto limpo"); }
};
const handleWipe = () => {
const ok = window.confirm(
"Apagar TODOS os dados locais?\n\nIsto remove preferências, projeto compartilhado e o histórico salvo de todas as calculadoras neste navegador. Esta ação não pode ser desfeita."
);
if (!ok) return;
try {
Object.keys(localStorage)
.filter(k => k.startsWith("vl_") || k.startsWith("voltlab"))
.forEach(k => localStorage.removeItem(k));
} catch {}
prefs.reset();
if (window.VL_PROJETO) window.VL_PROJETO.clear();
toast("Dados locais apagados");
setTimeout(() => window.location.reload(), 600);
};
const tensoesComuns = [127, 220, 380, 440];
return (
{/* RAIL */}
{/* CONTENT */}
{/* ───────── APARÊNCIA ───────── */}
{ACCENT_SWATCHES.map(sw => (
))}
setTweak("density", v)}
options={[{ value: "comfortable", label: "Conforto" }, { value: "compact", label: "Compacta" }]}
/>
setTweak("showCircuitry", v)}/>
setTweak("monoReadouts", v)}/>
{/* ───────── PADRÕES DE CÁLCULO ───────── */}
ENGENHARIA · VALORES INICIAIS
Padrões de cálculo
APLICADO AOS MÓDULOS
Estes valores são usados como ponto de partida ao abrir as calculadoras. Você pode sobrescrevê-los em cada módulo.
{
if (v === "custom") { set("tensaoCustom", true); }
else { prefs.setMany({ tensaoCustom: false, tensao: parseFloat(v) }); }
}}
options={[...tensoesComuns.map(t => ({ value: String(t), label: t + " V" })), { value: "custom", label: "Outra" }]}
/>
{P.tensaoCustom && (
set("tensao", v)}/>
)}
set("sistema", v)}
options={[
{ value: "monofasico", label: "Mono" },
{ value: "bifasico", label: "Bi" },
{ value: "trifasico", label: "Tri" },
]}
/>
set("frequencia", parseFloat(v))}
options={[{ value: "60", label: "60 Hz" }, { value: "50", label: "50 Hz" }]}
/>
set("material", v)}
options={[{ value: "Cu", label: "Cobre" }, { value: "Al", label: "Alumínio" }]}
/>
set("isolacao", v)}
options={[{ value: "PVC", label: "PVC" }, { value: "EPR", label: "EPR" }, { value: "XLPE", label: "XLPE" }]}
/>
set("tempAmbiente", v)}/>
set("quedaMax", v)}/>
set("fpAlvo", v)}/>
{/* ───────── IDENTIFICAÇÃO & RELATÓRIOS ───────── */}
EXPORTAÇÃO
Identificação & Relatórios
Dados que aparecem no cabeçalho dos relatórios e exportações geradas pelas calculadoras.
set("empresa", v)}/>
set("responsavel", v)}/>
set("crea", v)}/>
set("exportFmt", v)}
options={[{ value: "json", label: "JSON" }, { value: "csv", label: "CSV" }]}
/>
set("decimais", parseInt(v, 10))}
options={[{ value: "0", label: "0" }, { value: "1", label: "1" }, { value: "2", label: "2" }, { value: "3", label: "3" }]}
/>
{/* ───────── DADOS & PRIVACIDADE ───────── */}
ARMAZENAMENTO LOCAL
Dados & privacidade
Suas preferências e o histórico das calculadoras ficam guardados apenas neste navegador.
Limpar barramento do projeto
Remove todos os valores compartilhados entre calculadoras (tensão, corrente, seção…).
Restaurar todas as configurações
Volta aparência e padrões de cálculo aos valores de fábrica.
Apagar todos os dados locais
Exclui preferências, projeto e histórico de todas as calculadoras. Ação irreversível.
);
};
window.VL_Settings = VL_Settings;