Código Calculadora Impressão 3D feita no Gemini

Philipe Cardoso Autor 28 min de leitura Atualizado em 03/02/2026

Código da calculadora mostrada no vídeo:

https://www.youtube.com/watch?v=7qrordnM114

 

import React, { useState, useEffect, useMemo } from ‘react’;
import {
Calculator,
Settings,
Save,
Trash2,
AlertCircle,
TrendingUp,
Package,
Zap,
Clock,
Info,
Download,
History,
MessageCircle,
Sliders,
CheckCircle2,
Share2,
FileSpreadsheet,
HelpCircle,
X,
ChevronRight,
Copy,
Coins,
Box
} from ‘lucide-react’;
// — Utilitários —
const parseLocalFloat = (str) => {
if (typeof str === ‘number’) return str;
if (!str || typeof str !== ‘string’ || str.trim() === ”) return0;
return parseFloat(str.replace(‘,’, ‘.’));
};
const parseSmartTime = (input) => {
if (!input) return0;
const str = input.toString().toLowerCase().replace(‘,’, ‘.’);
if (str.includes(‘:’)) {
const [h, m] = str.split(‘:’);
return parseFloat(h) + (parseFloat(m) / 60);
}
let totalHours = 0;
const matchH = str.match(/([\d.]+)h/);
const matchM = str.match(/([\d.]+)m/);
if (matchH) totalHours += parseFloat(matchH[1]);
if (matchM) totalHours += parseFloat(matchM[1]) / 60;
if (!matchH && !matchM) return parseFloat(str) || 0;
return totalHours;
};
const parseSmartWeight = (input) => {
if (!input) return0;
const str = input.toString().toLowerCase().replace(‘,’, ‘.’);
if (str.includes(‘kg’)) return parseFloat(str.replace(‘kg’, ”)) * 1000;
if (str.includes(‘g’)) return parseFloat(str.replace(‘g’, ”));
return parseFloat(str) || 0;
};
const formatCurrency = (value) => {
if (isNaN(value) || value === null) return’R$ 0,00′;
returnnewIntl.NumberFormat(‘pt-BR’, { style: ‘currency’, currency: ‘BRL’ }).format(value);
};
const formatPercentage = (value) => {
if (isNaN(value) || value === null) return’0,00%’;
return`${value.toFixed(1).replace(‘.’, ‘,’)}%`;
};
// — Presets —
const MATERIAL_PRESETS = [
{ label: ‘PLA’, cost: ‘100.00’ },
{ label: ‘PETG’, cost: ‘110.00’ },
{ label: ‘ABS’, cost: ‘90.00’ },
{ label: ‘Resina’, cost: ‘250.00’ }
];
const PRINTER_PRESETS = [
{ label: ‘Ender 3/V2’, watts: ‘150’, price: ‘1800.00’ },
{ label: ‘Bambu A1’, watts: ‘130’, price: ‘3600.00’ },
{ label: ‘Bambu X1C’, watts: ‘350’, price: ‘12000.00’ },
{ label: ‘Resina’, watts: ’80’, price: ‘2500.00’ }
];
// — Componentes UI —
const Toast = ({ message, type, onClose }) => (
type === ‘error’ ? ‘bg-red-50 border-red-200 text-red-800’ : ‘bg-emerald-50 border-emerald-200 text-emerald-800’
}`}>
{type === ‘error’ ? : }
{message}
);
const ConfirmModal = ({ isOpen, title, message, onConfirm, onCancel }) => {
if (!isOpen) returnnull;
return (

{title}

{message}

);
};
const Card = ({ children, className = “”, noPadding = false }) => (
{children}
);
const Tooltip = ({ text, children }) => (
{children}
{text}
);
const InputField = ({ id, label, value, onChange, error, placeholder, icon: Icon, helpText, suffix, onBlur }) => (
{Icon && }
{label}
{helpText && (
)}
id={id}
type=”text”
inputMode=”decimal”
value={value}
onChange={(e) => onChange(id, e.target.value)}
onBlur={onBlur}
placeholder={placeholder}
className={`w-full pl-4 pr-10 py-3 bg-gray-50/50 border rounded-xl transition-all duration-300 font-semibold text-gray-700 placeholder-gray-400
${error
? ‘border-red-300 bg-red-50/50 focus:ring-4 focus:ring-red-100 focus:border-red-400’
: ‘border-gray-200 focus:bg-white focus:border-indigo-500 focus:ring-4 focus:ring-indigo-500/10 hover:border-gray-300’
}
focus:outline-none`}
/>
{suffix && (
{suffix}
)}
{error &&

{error}

}
);
const SliderInput = ({ id, label, value, onChange, min = 0, max = 100, step = 1, suffix = “%”, presets = [] }) => {
const numericValue = parseLocalFloat(value);
return (
type=”number” value={numericValue}
onChange={(e) => onChange(id, e.target.value)}
className=”w-12 text-sm font-bold text-indigo-600 text-right focus:outline-none”
/>
{suffix}
type=”range” min={min} max={max} step={step} value={numericValue}
onChange={(e) => onChange(id, e.target.value)}
className=”w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-indigo-600 hover:accent-indigo-500 transition-all”
/>
{presets.length > 0 && (
{presets.map(p => (
{p}{suffix}
))}
)}
);
};
const ProgressBar = ({ label, value, total, colorClass }) => {
const percent = total > 0 ? (value / total) * 100 : 0;
return (
{label}
{formatCurrency(value)} ({percent.toFixed(1)}%)
);
};
// — Configurações Iniciais —
const DEFAULT_SETTINGS = {
consumo_watts: ‘150’,
custo_kwh: ‘1.25’,
valor_impressora: ‘6000.00’,
vida_util_horas: ‘4000’,
custo_filamento_padrao: ‘110.00’,
lucro_minimo_projeto: ‘15.00’
};
const DEFAULT_PROJECT = {
id: null,
nome: ”,
quantidade_pecas: ‘1’,
gramas_filamento: ”,
horas_impressao: ”,
custo_filamento_kg: ”,
custo_outros: ‘0’,
custo_embalagem: ‘0’,
margem_lucro_percentual: ‘100’,
falha_risco_percentual: ‘0’
};
// — Componente Principal —
export default function App() {
const [activeTab, setActiveTab] = useState(‘calc’);
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
const [project, setProject] = useState(DEFAULT_PROJECT);
const [history, setHistory] = useState([]);
// UI States
const [showAdvanced, setShowAdvanced] = useState(true);
const [viewUnitMode, setViewUnitMode] = useState(false);
// New Feedback States
const [notification, setNotification] = useState(null);
const [confirmDialog, setConfirmDialog] = useState(null);
// Inicialização
useEffect(() => {
const savedSettings = localStorage.getItem(‘calc3d-settings-v6’);
const savedProject = localStorage.getItem(‘calc3d-project-v6’);
const savedHistory = localStorage.getItem(‘calc3d-history-v1’);
if (savedSettings) setSettings(JSON.parse(savedSettings));
if (savedProject) setProject(JSON.parse(savedProject));
if (savedHistory) setHistory(JSON.parse(savedHistory));
else {
setProject(prev => ({…prev, custo_filamento_kg: DEFAULT_SETTINGS.custo_filamento_padrao}));
}
}, []);
// Persistência
useEffect(() => { localStorage.setItem(‘calc3d-settings-v6’, JSON.stringify(settings)); }, [settings]);
useEffect(() => { localStorage.setItem(‘calc3d-project-v6’, JSON.stringify(project)); }, [project]);
useEffect(() => { localStorage.setItem(‘calc3d-history-v1’, JSON.stringify(history)); }, [history]);
const showToast = (message, type = ‘success’) => {
setNotification({ message, type });
setTimeout(() => setNotification(null), 4000);
};
const handleSettingChange = (id, val) => setSettings(prev => ({ …prev, [id]: val }));
const handleProjectChange = (id, val) => setProject(prev => ({ …prev, [id]: val }));
const handleTimeBlur = () => {
const hours = parseSmartTime(project.horas_impressao);
if (hours > 0) handleProjectChange(‘horas_impressao’, hours.toFixed(2));
};
const handleWeightBlur = () => {
const grams = parseSmartWeight(project.gramas_filamento);
if (grams > 0) handleProjectChange(‘gramas_filamento’, grams.toFixed(1));
};
// — CÁLCULOS —
const results = useMemo(() => {
constS = {
watts: parseLocalFloat(settings.consumo_watts),
kwh: parseLocalFloat(settings.custo_kwh),
printerPrice: parseLocalFloat(settings.valor_impressora),
lifeHours: parseLocalFloat(settings.vida_util_horas),
minProfit: parseLocalFloat(settings.lucro_minimo_projeto)
};
constP = {
qty: Math.max(1, parseInt(project.quantidade_pecas) || 1),
grams: parseSmartWeight(project.gramas_filamento),
hours: parseSmartTime(project.horas_impressao),
filPrice: parseLocalFloat(project.custo_filamento_kg) || parseLocalFloat(settings.custo_filamento_padrao),
others: parseLocalFloat(project.custo_outros),
pack: parseLocalFloat(project.custo_embalagem),
margin: parseLocalFloat(project.margem_lucro_percentual),
risk: parseLocalFloat(project.falha_risco_percentual)
};
const kwhPrice = (S.watts / 1000) * S.kwh;
const depreciationPrice = S.lifeHours > 0 ? S.printerPrice / S.lifeHours : 0;
const costFilament = (P.grams / 1000) * P.filPrice;
const costEnergy = kwhPrice * P.hours;
const costDepreciation = depreciationPrice * P.hours;
const costExtras = P.others + P.pack;
const productionSum = costFilament + costEnergy + costDepreciation;
const costFailMargin = productionSum * (P.risk / 100);
const baseProductionCost = productionSum + costFailMargin;
// CUSTO TOTAL (Custo de manufatura + Extras)
const totalCost = baseProductionCost + costExtras;
const unitTotalCost = totalCost / P.qty;
const profitValue = baseProductionCost * (P.margin / 100);
const finalPrice = baseProductionCost + profitValue + costExtras;
const actualProfit = finalPrice – totalCost;
const unitPrice = finalPrice / P.qty;
const unitProfit = actualProfit / P.qty;
// Sugestão de Preço para Lucro Mínimo
const suggestedPriceMinProfit = totalCost + S.minProfit;
return {
S, P,
costs: { filament: costFilament, energy: costEnergy, depreciation: costDepreciation, risk: costFailMargin, packaging: P.pack, others: P.others, totalExtras: costExtras },
baseProductionCost,
totalCost,
unitTotalCost,
actualProfit, finalPrice, unitPrice, unitProfit,
isLowProfit: actualProfit < S.minProfit,
isHighRisk: P.risk > 30,
suggestedPriceMinProfit,
isValid: P.grams > 0 && P.hours > 0,
missingFields: { grams: P.grams <= 0, hours: P.hours <= 0 }
};
}, [settings, project]);
// — Ações —
const saveToHistory = () => {
if (!project.nome) {
showToast(“Dê um nome ao projeto para salvar.”, “error”);
return;
}
const newEntry = { …project, id: Date.now(), date: newDate().toISOString(), finalPrice: results.finalPrice };
setHistory(prev => [newEntry, …prev]);
showToast(“Projeto salvo no histórico!”);
};
const confirmReset = () => {
setConfirmDialog({
title: “Limpar Projeto”,
message: “Tem certeza que deseja apagar todos os dados atuais do formulário?”,
action: () => {
setProject({ …DEFAULT_PROJECT, custo_filamento_kg: settings.custo_filamento_padrao });
setConfirmDialog(null);
showToast(“Dados limpos com sucesso.”);
}
});
};
const confirmDeleteHistory = (id) => {
setConfirmDialog({
title: “Excluir Registro”,
message: “Essa ação não pode ser desfeita. Deseja continuar?”,
action: () => {
setHistory(prev => prev.filter(i => i.id !== id));
setConfirmDialog(null);
showToast(“Item excluído.”);
}
});
};
const loadFromHistory = (item) => {
setProject(item);
setActiveTab(‘calc’);
showToast(“Projeto carregado!”);
};
const applyPsychologicalPrice = () => {
const current = results.finalPrice;
const integerPart = Math.floor(current);
let suggestion = integerPart + 0.90;
if (suggestion < current) suggestion += 1;
const extras = results.costs.totalExtras;
const cost = results.baseProductionCost;
if (cost > 0) {
const newMargin = (((suggestion – extras) / cost) – 1) * 100;
handleProjectChange(‘margem_lucro_percentual’, newMargin.toFixed(2));
showToast(“Preço arredondado!”);
}
};
const openWhatsApp = () => {
const text = `*Orçamento 3D*\n📦 Projeto: ${project.nome || ‘Peça’}\n💰 Valor: ${formatCurrency(results.finalPrice)}\n(Gerado por ZoomCalc3D)`;
const url = `https://wa.me/?text=${encodeURIComponent(text)}`;
window.open(url, ‘_blank’);
};
// — COPY TO CLIPBOARD (Universal) —
const copyToClipboard = (text) => {
const textArea = document.createElement(“textarea”);
textArea.value = text;
// Evita scroll quando o elemento é inserido
textArea.style.top = “0”;
textArea.style.left = “0”;
textArea.style.position = “fixed”;
textArea.style.opacity = “0”; // Invisível
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand(‘copy’);
if(successful) showToast(“Copiado! Cole no Google Sheets (Ctrl+V).”, “success”);
else showToast(“Erro ao copiar.”, “error”);
} catch (err) {
console.error(‘Fallback: Oops, unable to copy’, err);
showToast(“Erro no navegador.”, “error”);
}
document.body.removeChild(textArea);
};
const copyToSheets = () => {
if (!results.isValid) return;
const fmt = (num) => typeof num === ‘number’ ? num.toFixed(2).replace(‘.’, ‘,’) : num;
const tsvContent = `
PROJETO\t${project.nome || “Sem nome”}
DATA\t${new Date().toLocaleDateString()}
Item\tTotal\tUnitário
Filamento\t${fmt(results.costs.filament)}\t${fmt(results.costs.filament/results.P.qty)}
Energia\t${fmt(results.costs.energy)}\t${fmt(results.costs.energy/results.P.qty)}
Desgaste\t${fmt(results.costs.depreciation)}\t${fmt(results.costs.depreciation/results.P.qty)}
Risco\t${fmt(results.costs.risk)}\t${fmt(results.costs.risk/results.P.qty)}
Extras\t${fmt(results.costs.totalExtras)}\t${fmt(results.costs.totalExtras/results.P.qty)}
CUSTO TOTAL\t${fmt(results.totalCost)}\t${fmt(results.totalCost/results.P.qty)}
LUCRO\t${fmt(results.actualProfit)}\t${fmt(results.actualProfit/results.P.qty)}
PREÇO FINAL\t${fmt(results.finalPrice)}\t${fmt(results.unitPrice)}
`.trim();
copyToClipboard(tsvContent);
};
// — EXPORT ALL HISTORY CSV —
const exportHistoryCSV = () => {
if (history.length === 0) {
showToast(“Histórico vazio.”, “error”);
return;
}
constBOM = “\uFEFF”;
const header = [“Data”, “Projeto”, “Qtd”, “Preco Final”];
const rows = history.map(item => [
newDate(item.date).toLocaleDateString(),
item.nome || “Sem nome”,
item.quantidade_pecas,
formatCurrency(item.finalPrice).replace(‘R$’, ”).trim()
]);
const csvContent = BOM + [header, …rows].map(e => e.join(“;”)).join(“\n”);
const blob = newBlob([csvContent], { type: ‘text/csv;charset=utf-8;’ });
const link = document.createElement(“a”);
link.href = URL.createObjectURL(blob);
link.download = `historico_completo_${new Date().toISOString().slice(0,10)}.csv`;
link.click();
};
const exportToCSV = () => {
if (!results.isValid) return;
const safeName = project.nome ? project.nome.replace(/[^a-z0-9]/gi, ‘_’) : ‘orcamento’;
constBOM = “\uFEFF”;
const fmt = (num) => typeof num === ‘number’ ? num.toFixed(2).replace(‘.’, ‘,’) : num;
const rows = [
[“ITEM”, “VALOR TOTAL”, “VALOR UNITARIO”],
[“Nome do Projeto”, project.nome || “-“],
[“CUSTO: Filamento”, fmt(results.costs.filament), fmt(results.costs.filament/results.P.qty)],
[“CUSTO: Energia”, fmt(results.costs.energy), fmt(results.costs.energy/results.P.qty)],
[“CUSTO: Desgaste”, fmt(results.costs.depreciation), fmt(results.costs.depreciation/results.P.qty)],
[“CUSTO: Risco”, fmt(results.costs.risk), fmt(results.costs.risk/results.P.qty)],
[“CUSTO: Extras”, fmt(results.costs.totalExtras), fmt(results.costs.totalExtras/results.P.qty)],
[“—“, “—“, “—“],
[“CUSTO TOTAL (PRODUÇÃO)”, fmt(results.totalCost), fmt(results.totalCost/results.P.qty)],
[“Lucro”, fmt(results.actualProfit), fmt(results.actualProfit/results.P.qty)],
[“PREÇO FINAL”, fmt(results.finalPrice), fmt(results.unitPrice)]
];
const csvContent = BOM + rows.map(e => e.join(“;”)).join(“\n”);
const blob = newBlob([csvContent], { type: ‘text/csv;charset=utf-8;’ });
const link = document.createElement(“a”);
link.href = URL.createObjectURL(blob);
link.download = `${safeName}.csv`;
link.click();
};
return (
{notification && setNotification(null)} />}
isOpen={!!confirmDialog}
title={confirmDialog?.title}
message={confirmDialog?.message}
onConfirm={confirmDialog?.action}
onCancel={() => setConfirmDialog(null)}
/>
{/* Modern Gradient Header */}
ZoomCalc3D
{[{ id: ‘calc’, icon: Calculator, label: ‘Calc’ }, { id: ‘history’, icon: History, label: ‘Histórico’ }, { id: ‘settings’, icon: Settings, label: ‘Ajustes’ }].map(tab => (
{tab.label}
))}
{/* — SETTINGS TAB — */}
{activeTab === ‘settings’ && (

Configurações

Personalize os parâmetros da sua oficina.

Perfil da Impressora

{PRINTER_PRESETS.map((p, i) => (
{p.label}
))}

Parâmetros de Negócio

)}
{/* — HISTORY TAB — */}
{activeTab === ‘history’ && (

Histórico

Seus orçamentos salvos.

Exportar Tudo (CSV)
{history.length} Projetos
{history.length === 0 ? (

Nenhum projeto salvo ainda.

) : (
{history.map(item => (

{item.nome}

{new Date(item.date).toLocaleDateString()}
{item.quantidade_pecas} {item.quantidade_pecas > 1 ? ‘Peças’ : ‘Peça’}
{formatCurrency(item.finalPrice)}
))}
)}
)}
{/* — CALCULATOR TAB — */}
{activeTab === ‘calc’ && (
{/* Form Card */}

Parâmetros
{showAdvanced ? ‘Completo’ : ‘Básico’}
{MATERIAL_PRESETS.map(m => (
{m.label}
))}

Custos Extras

Lucro Desejado
{results.isValid ? (
<>
{/* === DUAL HERO SECTION (Total vs Unit) === */}
{/* 1. LADO ESQUERDO: TOTAL DO PROJETO */}

Projeto Completo

R$
{formatCurrency(results.finalPrice).replace(‘R$’, ”)}
Lucro
{formatCurrency(results.actualProfit)}
Custo
{formatCurrency(results.totalCost)}
{/* 2. LADO DIREITO: UNITÁRIO (Se houver > 1 peça) */}
{results.P.qty > 1 ? (

Por Unidade

R$
{formatCurrency(results.unitPrice).replace(‘R$’, ”)}
Lucro
{formatCurrency(results.unitProfit)}
Custo
{formatCurrency(results.unitTotalCost)}
) : (
/* Se for só 1 peça, ocupa o espaço com info extra ou vazio */

Peça única no lote.

Arredondar
)}
{/* Round Price Button (Mobile Only or when needed) */}
Arredondar Preço
{/* Tabela de Detalhes Estilizada */}
Composição
Total
Unitário
{[
{ label: ‘Filamento’, val: results.costs.filament, color: ‘text-blue-600′, bg:’bg-blue-500’ },
{ label: ‘Energia’, val: results.costs.energy, color: ‘text-amber-600′, bg:’bg-amber-500’ },
{ label: ‘Desgaste’, val: results.costs.depreciation, color: ‘text-purple-600′, bg:’bg-purple-500’ },
{ label: ‘Risco’, val: results.costs.risk, color: ‘text-rose-500′, bg:’bg-rose-500’ },
{ label: ‘Extras’, val: results.costs.totalExtras, color: ‘text-gray-600′, bg:’bg-gray-400’ },
].map((row, idx) => (
{row.label}
{formatCurrency(row.val)}
{formatCurrency(row.val / results.P.qty)}
))}
Custo Total
{formatCurrency(results.totalCost)}
{formatCurrency(results.unitTotalCost)}
Lucro Líquido
{formatCurrency(results.actualProfit)}
{formatCurrency(results.actualProfit / results.P.qty)}
{/* Actions Footer */}
Salvar
CSV
{/* Botão Copy to Sheets */}
Sheets
Enviar
{results.isLowProfit && (
Lucro Insuficiente

O lucro atual ({formatCurrency(results.actualProfit)}) está abaixo do mínimo configurado de {formatCurrency(results.S.minProfit)}.

Preço Sugerido: {formatCurrency(results.suggestedPriceMinProfit)}

)}

Raio-X Visual
{(results.costs.risk > 0) && }
{(results.costs.totalExtras > 0) && }
) : (

Vamos começar?

Preencha o peso e o tempo para gerar seu orçamento detalhado.

)}
)}
);
}
Visualizações do post: 223

Sobre o autor

Philipe Cardoso

Com 33 anos de idade, sou um carioca apaixonado por tecnologia e fotografia. Além de ser o criador do Portal Zoom Digital, que preserva sua essência desde os tempos em que era um blog, também sou um verdadeiro entusiasta e amante de todas as formas de tecnologia. Através do Portal, compartilho minha paixão pela tecnologia e trago as últimas novidades e tendências para os leitores. Também sou fascinado pelo mundo da fotografia, explorando o poder das imagens para capturar momentos únicos e transmitir histórias cativantes.

Ver todos os conteúdos