/* global React, ReactDOM, MATERIALS, PRINTERS, FINISHING_PRESETS, PLATFORM_PRESETS, CD3D, fmtBRL, fmtNum, fmtPct, cdStorage */ const STEP_useState = React.useState; const STEP_reverseCalc = window.CD3D.reverseCalc; const STEPS = [ { id: 0, label: "Material", short: "MAT" }, { id: 1, label: "Impressão", short: "PRT" }, { id: 2, label: "Mão de Obra", short: "LAB" }, { id: 3, label: "Preço & Venda", short: "PRC" } ]; // ====================== UI Atoms ====================== function Field({ label, hint, children, full, req }) { return (
{children}
); } function NumInput({ value, onChange, min = 0, step = "any", max, suffix, prefix }) { const wrap = (el) => suffix ? (
{el}{suffix}
) : prefix ? (
{el}{prefix}
) : el; return wrap( onChange(e.target.value === "" ? 0 : parseFloat(e.target.value))} /> ); } function TimeInput({ hours, minutes, onHours, onMinutes }) { return (
); } function Switch({ on, onChange }) { return ))} {resinMaterials.map(m => ( ))}
set({ pieceWeight: v })} suffix="g" /> set({ supportWeight: v })} suffix="g" /> set({ spoolCost: v })} prefix="R$" /> set({ spoolWeight: v })} suffix="g" />
{advanced && (
Multimaterial e Refugo
set({ filamentChanges: v })} suffix="x" /> set({ changeMinutes: v })} suffix="min" /> set({ failureRate: v })} suffix="%" max={50} />
)} ); } // ====================== Step 2: Print ====================== function StepPrint({ s, set, advanced }) { const handlePrinter = (e) => { const val = e.target.value; if (val === "custom") return; const [group, name] = val.split("|"); const grp = PRINTERS.find(g => g.group === group); const p = grp?.items.find(it => it.name === name); if (p) set({ printerName: `${group} ${name}`, printerWatts: p.watts, printerPrice: p.price }); }; const currentPrinterValue = (() => { for (const g of PRINTERS) { for (const it of g.items) { if (s.printerName === `${g.group} ${it.name}`) return `${g.group}|${it.name}`; } } return "custom"; })(); return (

02. Configuração de Impressão

Energia + Tempo
set({ printHours: v })} onMinutes={(v) => set({ printMinutes: v })} /> set({ printerWatts: v })} suffix="W" /> set({ energyRate: v })} prefix="R$" step="0.01" /> set({ batchPieces: v })} min={1} suffix="pç" />
{advanced && (
Depreciação da Impressora
set({ printerPrice: v })} prefix="R$" /> set({ printerLifeHours: v })} suffix="h" /> set({ bedSetupMinutes: v })} suffix="min" />
)}
); } // ====================== Step 3: Labor ====================== function StepLabor({ s, set, advanced }) { return (

03. Mão de Obra & Pós-Processo

Seu Tempo
set({ hourlyRate: v })} prefix="R$" suffix="/h" /> set({ finishingMinutes: v })} suffix="min" /> set({ packagingCost: v })} prefix="R$" step="0.01" /> {advanced && ( set({ designHours: v })} suffix="h" step="0.1" /> )}
); } // ====================== Step 4: Pricing ====================== function StepPricing({ s, set, advanced, calc }) { const [reverseTarget, setReverseTarget] = STEP_useState(""); const reverse = reverseTarget ? STEP_reverseCalc(s, parseFloat(reverseTarget)) : null; return (

04. Preço & Venda

Margem + Plataforma
set({ quantity: v })} min={1} suffix="pç" /> set({ profitMargin: v })} suffix="%" /> {advanced && ( <> set({ platformFee: v })} suffix="%" /> set({ taxRate: v })} suffix="%" /> set({ shippingCost: v })} prefix="R$" />
{s.shippingIncluded ? "Sim, embutido" : "Não, separado"} set({ shippingIncluded: v })} />
)}
{advanced && (

↩ Calculadora Reversa: Quanto cobrar para...

setReverseTarget(v)} prefix="R$" /> {reverse && (
{fmtPct(reverse.margin)}
margem · lucro: {fmtBRL.format(reverse.profit)}
)}
)}
Dados do Orçamento (opcional)
set({ clientName: e.target.value })} placeholder="Ex: João Silva" /> set({ projectName: e.target.value })} placeholder="Ex: Miniatura D&D" /> set({ validityDays: v })} suffix="dias" />
); } window.CD3D_Steps = { StepMaterial, StepPrint, StepLabor, StepPricing, STEPS }; window.CD3D_UI = { Field, NumInput, TimeInput, Switch };