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 Statesconst [showAdvanced, setShowAdvanced] = useState(true);const [viewUnitMode, setViewUnitMode] = useState(false);// New Feedback Statesconst [notification, setNotification] = useState(null);const [confirmDialog, setConfirmDialog] = useState(null);// InicializaçãouseEffect(() => {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ênciauseEffect(() => { 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ínimoconst 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 é inseridotextArea.style.top = “0”;textArea.style.left = “0”;textArea.style.position = “fixed”;textArea.style.opacity = “0”; // Invisíveldocument.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árioFilamento\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 */}ZoomCalc3DPrecificação Inteligente{[{ 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çãoTotalUnitá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 InsuficienteO 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.)})});}