Código da calculadora mostrada no vídeo:
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 }) => (<div className={`fixed top-4 right-4 z-50 px-6 py-4 rounded-2xl shadow-xl flex items-center gap-3 animate-fade-in-down border border-opacity-20 backdrop-blur-md ${type === ‘error’ ? ‘bg-red-50 border-red-200 text-red-800’ : ‘bg-emerald-50 border-emerald-200 text-emerald-800’}`}>{type === ‘error’ ? <AlertCircle size={20}/> : <CheckCircle2 size={20}/>}<span className=”font-medium text-sm”>{message}</span><button onClick={onClose} className=”ml-2 hover:opacity-70 transition-opacity”><X size={16}/></button></div>);const ConfirmModal = ({ isOpen, title, message, onConfirm, onCancel }) => {if (!isOpen) returnnull;return (<div className=”fixed inset-0 z-50 flex items-center justify-center bg-gray-900/40 backdrop-blur-sm p-4 transition-all”><div className=”bg-white rounded-2xl shadow-2xl max-w-sm w-full p-6 animate-scale-in border border-gray-100″><h3 className=”text-xl font-bold text-gray-800 mb-2″>{title}</h3><p className=”text-gray-500 text-sm mb-8 leading-relaxed”>{message}</p><div className=”flex justify-end gap-3″><button onClick={onCancel} className=”px-5 py-2.5 text-gray-600 hover:bg-gray-100 rounded-xl text-sm font-semibold transition-colors”>Cancelar</button><button onClick={onConfirm} className=”px-5 py-2.5 bg-red-500 hover:bg-red-600 text-white rounded-xl text-sm font-semibold shadow-lg shadow-red-500/30 transition-all active:scale-95″>Confirmar</button></div></div></div>);};const Card = ({ children, className = “”, noPadding = false }) => (<div className={`bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden transition-all hover:shadow-md ${className}`}>{children}</div>);const Tooltip = ({ text, children }) => (<div className=”relative flex items-center group”>{children}<div className=”absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 w-48 p-3 text-xs font-medium text-white bg-gray-800 rounded-xl shadow-xl opacity-0 group-hover:opacity-100 transition-all duration-200 z-50 pointer-events-none text-center translate-y-2 group-hover:translate-y-0″>{text}<div className=”absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-800″></div></div></div>);const InputField = ({ id, label, value, onChange, error, placeholder, icon: Icon, helpText, suffix, onBlur }) => (<div className=”flex flex-col mb-5 relative group”><label htmlFor={id} className=”mb-2 text-xs font-bold text-gray-500 uppercase tracking-wide flex items-center justify-between”><span className=”flex items-center gap-1.5″>{Icon && <Icon className=”w-3.5 h-3.5 text-indigo-500″ />}{label}{helpText && (<Tooltip text={helpText}><Info className=”w-3.5 h-3.5 text-gray-400 cursor-help hover:text-indigo-500 transition-colors” /></Tooltip>)}</span></label><div className=”relative”><inputid={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 && (<span className=”absolute right-4 top-3.5 text-gray-400 text-xs font-bold pointer-events-none”>{suffix}</span>)}</div>{error && <p className=”mt-1.5 text-xs text-red-500 flex items-center font-medium animate-pulse”><AlertCircle className=”w-3 h-3 mr-1″/>{error}</p>}</div>);const SliderInput = ({ id, label, value, onChange, min = 0, max = 100, step = 1, suffix = “%”, presets = [] }) => {const numericValue = parseLocalFloat(value);return (<div className=”mb-6 p-4 bg-gray-50/80 rounded-2xl border border-gray-100″><div className=”flex justify-between items-center mb-3″><label htmlFor={id} className=”text-sm font-bold text-gray-700″>{label}</label><div className=”flex items-center bg-white px-3 py-1 rounded-lg border border-gray-200 shadow-sm”><inputtype=”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”/><span className=”text-xs text-gray-400 font-bold ml-1″>{suffix}</span></div></div><div className=”flex flex-col gap-3″><inputtype=”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”/></div>{presets.length > 0 && (<div className=”flex gap-2 mt-3 overflow-x-auto pb-1 scrollbar-hide”>{presets.map(p => (<button key={p} onClick={() => onChange(id, p.toString())} className=”px-3 py-1.5 text-xs font-semibold bg-white border border-gray-200 text-gray-600 rounded-lg hover:border-indigo-300 hover:text-indigo-600 transition-all shadow-sm active:scale-95 whitespace-nowrap”>{p}{suffix}</button>))}</div>)}</div>);};const ProgressBar = ({ label, value, total, colorClass }) => {const percent = total > 0 ? (value / total) * 100 : 0;return (<div className=”mb-3 last:mb-0″><div className=”flex justify-between text-xs mb-1.5″><span className=”text-gray-500 font-medium flex items-center gap-1″>{label}</span><span className=”font-bold text-gray-700″>{formatCurrency(value)} <span className=”text-gray-400 font-normal ml-1″>({percent.toFixed(1)}%)</span></span></div><div className=”w-full bg-gray-100 rounded-full h-2 overflow-hidden”><div className={`h-full rounded-full ${colorClass} transition-all duration-700 ease-out`} style={{ width: `${Math.min(percent, 100)}%` }}></div></div></div>);};// — 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 (<div className=”min-h-screen bg-slate-50 text-gray-800 font-sans pb-24 selection:bg-indigo-100 selection:text-indigo-800″>{notification && <Toast message={notification.message} type={notification.type} onClose={() => setNotification(null)} />}<ConfirmModalisOpen={!!confirmDialog}title={confirmDialog?.title}message={confirmDialog?.message}onConfirm={confirmDialog?.action}onCancel={() => setConfirmDialog(null)}/>{/* Modern Gradient Header */}<div className=”sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-gray-200/60 shadow-sm transition-all”><div className=”max-w-5xl mx-auto px-4 sm:px-6 h-18 flex items-center justify-between py-3″><div className=”flex items-center gap-3″><div className=”bg-gradient-to-br from-indigo-500 to-violet-600 text-white p-2 rounded-xl shadow-lg shadow-indigo-200″><Calculator size={22} className=”stroke-[2.5]” /></div><div><span className=”font-bold text-xl tracking-tight text-gray-900 leading-tight block”>ZoomCalc3D</span><span className=”text-[10px] font-semibold text-gray-400 uppercase tracking-widest hidden sm:block”>Precificação Inteligente</span></div></div><nav className=”flex bg-gray-100/80 p-1.5 rounded-xl overflow-x-auto gap-1″>{[{ id: ‘calc’, icon: Calculator, label: ‘Calc’ }, { id: ‘history’, icon: History, label: ‘Histórico’ }, { id: ‘settings’, icon: Settings, label: ‘Ajustes’ }].map(tab => (<button key={tab.id} onClick={() => setActiveTab(tab.id)} className={`px-4 py-2 rounded-lg text-sm font-bold transition-all flex items-center gap-2 whitespace-nowrap outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${activeTab === tab.id ? ‘bg-white text-indigo-600 shadow-sm’ : ‘text-gray-500 hover:text-gray-700 hover:bg-gray-200/50’}`}><tab.icon size={16} className={activeTab === tab.id ? “stroke-[2.5]” : “stroke-2”} /> {tab.label}</button>))}</nav></div></div><main className=”max-w-5xl mx-auto px-4 sm:px-6 py-8″>{/* — SETTINGS TAB — */}{activeTab === ‘settings’ && (<div className=”max-w-2xl mx-auto animate-fade-in”><div className=”mb-6″><h2 className=”text-2xl font-bold text-gray-900″>Configurações</h2><p className=”text-gray-500 text-sm”>Personalize os parâmetros da sua oficina.</p></div><Card className=”p-8 space-y-8″><div><div className=”flex justify-between items-center mb-6″><h3 className=”text-sm font-bold text-gray-400 uppercase tracking-wider flex items-center gap-2″><Zap size={14}/> Perfil da Impressora</h3><div className=”flex gap-2″>{PRINTER_PRESETS.map((p, i) => (<button key={i} onClick={() => setSettings(prev => ({…prev, consumo_watts: p.watts, valor_impressora: p.price}))} className=”text-[10px] font-bold bg-indigo-50 text-indigo-600 px-3 py-1.5 rounded-lg border border-indigo-100 hover:bg-indigo-100 hover:border-indigo-200 transition-colors uppercase tracking-wide”>{p.label}</button>))}</div></div><div className=”grid grid-cols-1 sm:grid-cols-2 gap-6″><InputField id=”consumo_watts” label=”Consumo (Watts)” value={settings.consumo_watts} onChange={handleSettingChange} suffix=”W” icon={Zap} /><InputField id=”custo_kwh” label=”Custo Energia (kWh)” value={settings.custo_kwh} onChange={handleSettingChange} icon={Zap} /><InputField id=”valor_impressora” label=”Preço da Máquina” value={settings.valor_impressora} onChange={handleSettingChange} icon={Calculator} /><InputField id=”vida_util_horas” label=”Vida Útil” value={settings.vida_util_horas} onChange={handleSettingChange} suffix=”h” icon={Clock} /></div></div><div className=”border-t border-gray-100 pt-8″><h3 className=”text-sm font-bold text-gray-400 uppercase tracking-wider mb-6 flex items-center gap-2″><TrendingUp size={14}/> Parâmetros de Negócio</h3><div className=”grid grid-cols-1 sm:grid-cols-2 gap-6″><InputField id=”custo_filamento_padrao” label=”Custo Kg Padrão” value={settings.custo_filamento_padrao} onChange={handleSettingChange} icon={Package} /><InputField id=”lucro_minimo_projeto” label=”Lucro Mínimo (R$)” value={settings.lucro_minimo_projeto} onChange={handleSettingChange} icon={TrendingUp} /></div></div></Card></div>)}{/* — HISTORY TAB — */}{activeTab === ‘history’ && (<div className=”max-w-3xl mx-auto animate-fade-in”><div className=”flex justify-between items-end mb-6″><div><h2 className=”text-2xl font-bold text-gray-900 flex items-center gap-2″>Histórico</h2><p className=”text-gray-500 text-sm”>Seus orçamentos salvos.</p></div><div className=”flex gap-2″><button onClick={exportHistoryCSV} className=”bg-white border border-gray-200 text-gray-600 px-3 py-1.5 rounded-lg text-xs font-bold hover:bg-gray-50 flex items-center gap-2 transition-colors”><FileSpreadsheet size={14}/> Exportar Tudo (CSV)</button><div className=”bg-indigo-50 text-indigo-600 px-3 py-1.5 rounded-lg text-xs font-bold border border-indigo-100″>{history.length} Projetos</div></div></div>{history.length === 0 ? (<div className=”text-center py-20 bg-white rounded-3xl border-2 border-dashed border-gray-200″><div className=”bg-gray-50 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4″><History className=”text-gray-300″ size={32}/></div><p className=”text-gray-500 font-medium”>Nenhum projeto salvo ainda.</p><button onClick={() => setActiveTab(‘calc’)} className=”mt-4 text-indigo-600 font-bold text-sm hover:underline”>Criar novo orçamento</button></div>) : (<div className=”space-y-4″>{history.map(item => (<div key={item.id} className=”bg-white p-5 rounded-2xl border border-gray-100 shadow-sm hover:shadow-lg hover:border-indigo-100 transition-all group”><div className=”flex justify-between items-center”><div><h3 className=”font-bold text-gray-800 text-lg group-hover:text-indigo-600 transition-colors”>{item.nome}</h3><div className=”flex gap-3 text-xs text-gray-400 mt-1 font-medium uppercase tracking-wide”><span>{new Date(item.date).toLocaleDateString()}</span><span>•</span><span>{item.quantidade_pecas} {item.quantidade_pecas > 1 ? ‘Peças’ : ‘Peça’}</span></div></div><div className=”flex items-center gap-6″><span className=”font-bold text-gray-900 text-xl”>{formatCurrency(item.finalPrice)}</span><div className=”flex gap-2″><button onClick={() => loadFromHistory(item)} className=”p-2.5 text-gray-500 hover:text-indigo-600 hover:bg-indigo-50 rounded-xl transition-colors” title=”Carregar”><Download size={20}/></button><button onClick={() => confirmDeleteHistory(item.id)} className=”p-2.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-xl transition-colors” title=”Excluir”><Trash2 size={20}/></button></div></div></div></div>))}</div>)}</div>)}{/* — CALCULATOR TAB — */}{activeTab === ‘calc’ && (<div className=”grid grid-cols-1 lg:grid-cols-12 gap-8″><div className=”lg:col-span-5 space-y-6″>{/* Form Card */}<Card><div className=”bg-gray-50/80 backdrop-blur px-6 py-4 border-b border-gray-100 flex justify-between items-center”><h3 className=”font-bold text-gray-600 flex items-center gap-2 text-xs uppercase tracking-widest”><Sliders size={14}/> Parâmetros</h3><div className=”flex gap-2″><button onClick={() => setShowAdvanced(!showAdvanced)} className={`text-[10px] font-bold px-3 py-1.5 rounded-lg border transition-all uppercase tracking-wide ${showAdvanced ? ‘bg-indigo-50 text-indigo-600 border-indigo-100’ : ‘bg-white text-gray-400 border-gray-200 hover:border-gray-300’}`}>{showAdvanced ? ‘Completo’ : ‘Básico’}</button><button onClick={confirmReset} className=”text-gray-400 hover:text-red-500 hover:bg-red-50 p-1.5 rounded-lg transition-colors”><Trash2 size={16} /></button></div></div><div className=”p-6″><div className=”grid grid-cols-3 gap-4 mb-2″><div className=”col-span-2″><InputField id=”nome” label=”Nome do Projeto” value={project.nome} onChange={handleProjectChange} placeholder=”Ex: Vaso Geométrico” /></div><div className=”col-span-1″><InputField id=”quantidade_pecas” label=”Qtd. Peças (Finalizadas)” value={project.quantidade_pecas} onChange={handleProjectChange} placeholder=”1″ helpText=”Quantidade total de peças finalizadas no lote.”/></div></div><div className=”bg-gradient-to-br from-indigo-50 to-blue-50 p-5 rounded-2xl border border-indigo-100 mb-6 relative overflow-hidden”><div className=”absolute top-0 right-0 w-20 h-20 bg-white opacity-20 rounded-full blur-2xl -mr-10 -mt-10 pointer-events-none”></div><div className=”grid grid-cols-2 gap-4 relative z-10″><InputField id=”gramas_filamento” label=”Peso Total” value={project.gramas_filamento} onChange={handleProjectChange} onBlur={handleWeightBlur} suffix=”g” placeholder=”0″ error={results.missingFields.grams && “Obrigatório”} /><InputField id=”horas_impressao” label=”Tempo Total” value={project.horas_impressao} onChange={handleProjectChange} onBlur={handleTimeBlur} suffix=”h” placeholder=”0:00″ error={results.missingFields.hours && “Obrigatório”} /></div></div><div className=”mb-6″><div className=”flex justify-between items-center mb-3″><label className=”text-xs font-bold text-gray-500 uppercase tracking-wide”>Material (Kg)</label><div className=”flex gap-2″>{MATERIAL_PRESETS.map(m => (<button key={m.label} onClick={() => handleProjectChange(‘custo_filamento_kg’, m.cost)} className=”text-[10px] font-bold bg-white text-gray-500 px-2 py-1 rounded border border-gray-200 hover:border-indigo-300 hover:text-indigo-600 transition-all uppercase”>{m.label}</button>))}</div></div><InputField id=”custo_filamento_kg” label=”” value={project.custo_filamento_kg} onChange={handleProjectChange} placeholder={settings.custo_filamento_padrao} icon={Package} /></div><div className={`transition-all duration-500 overflow-hidden ${showAdvanced ? ‘max-h-[500px] opacity-100’ : ‘max-h-0 opacity-0’}`}><div className=”border-t border-gray-100 pt-6 mt-2″><h4 className=”text-xs font-bold text-gray-400 uppercase mb-4 flex items-center gap-1.5″><TrendingUp size={12}/> Custos Extras</h4><div className=”grid grid-cols-2 gap-4 mb-4″><InputField id=”custo_outros” label=”Outros (Imãs, etc)” value={project.custo_outros} onChange={handleProjectChange} placeholder=”0.00″ /><InputField id=”custo_embalagem” label=”Embalagem” value={project.custo_embalagem} onChange={handleProjectChange} placeholder=”0.00″ /></div><SliderInput id=”falha_risco_percentual” label=”Margem de Risco” value={project.falha_risco_percentual} onChange={handleProjectChange} max={50} step={5} presets={[0, 5, 10, 20]} /></div></div><div className=”border-t border-gray-100 pt-6 mt-2″><div className=”flex items-center gap-2 mb-2″><TrendingUp size={16} className=”text-indigo-600″/><span className=”text-sm font-bold text-gray-800″>Lucro Desejado</span></div><SliderInput id=”margem_lucro_percentual” label=”Markup” value={project.margem_lucro_percentual} onChange={handleProjectChange} max={300} step={10} presets={[50, 100, 150, 200]} /></div></div></Card></div><div className=”lg:col-span-7 space-y-6″>{results.isValid ? (<><div className=”relative group perspective-1000″><div className=”absolute -inset-0.5 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-1000 group-hover:duration-200″></div><Card className=”relative bg-white border-none ring-1 ring-gray-200 overflow-hidden”>{/* === DUAL HERO SECTION (Total vs Unit) === */}<div className=”grid grid-cols-1 md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-gray-100″>{/* 1. LADO ESQUERDO: TOTAL DO PROJETO */}<div className=”p-6 text-center bg-gradient-to-br from-white to-blue-50/30 flex flex-col justify-between h-full relative overflow-hidden”><div className=”absolute top-0 right-0 p-2 opacity-10″><Box size={40} className=”text-blue-500″/></div><div><p className=”text-xs font-bold text-blue-400 uppercase tracking-widest mb-1″>Projeto Completo</p><div className=”text-4xl font-black text-gray-800 tracking-tighter drop-shadow-sm mb-1″><span className=”text-xl text-gray-400 font-bold mr-1″>R$</span>{formatCurrency(results.finalPrice).replace(‘R$’, ”)}</div></div><div className=”mt-4 space-y-2″><div className=”flex justify-between items-center px-3 py-1.5 bg-emerald-50 rounded-lg border border-emerald-100/50″><span className=”text-[10px] font-bold text-emerald-600 uppercase”>Lucro</span><span className=”text-sm font-bold text-emerald-700″>{formatCurrency(results.actualProfit)}</span></div><div className=”flex justify-between items-center px-3 py-1.5 bg-gray-50 rounded-lg border border-gray-100″><span className=”text-[10px] font-bold text-gray-400 uppercase”>Custo</span><span className=”text-sm font-bold text-gray-600″>{formatCurrency(results.totalCost)}</span></div></div></div>{/* 2. LADO DIREITO: UNITÁRIO (Se houver > 1 peça) */}{results.P.qty > 1 ? (<div className=”p-6 text-center bg-gradient-to-br from-white to-indigo-50/30 flex flex-col justify-between h-full relative overflow-hidden”><div className=”absolute top-0 right-0 p-2 opacity-10″><Coins size={40} className=”text-indigo-500″/></div><div><p className=”text-xs font-bold text-indigo-400 uppercase tracking-widest mb-1″>Por Unidade</p><div className=”text-4xl font-black text-indigo-700 tracking-tighter drop-shadow-sm mb-1″><span className=”text-xl text-indigo-300 font-bold mr-1″>R$</span>{formatCurrency(results.unitPrice).replace(‘R$’, ”)}</div></div><div className=”mt-4 space-y-2″><div className=”flex justify-between items-center px-3 py-1.5 bg-emerald-50 rounded-lg border border-emerald-100/50″><span className=”text-[10px] font-bold text-emerald-600 uppercase”>Lucro</span><span className=”text-sm font-bold text-emerald-700″>{formatCurrency(results.unitProfit)}</span></div><div className=”flex justify-between items-center px-3 py-1.5 bg-gray-50 rounded-lg border border-gray-100″><span className=”text-[10px] font-bold text-gray-400 uppercase”>Custo</span><span className=”text-sm font-bold text-gray-600″>{formatCurrency(results.unitTotalCost)}</span></div></div></div>) : (/* Se for só 1 peça, ocupa o espaço com info extra ou vazio */<div className=”hidden md:flex p-6 items-center justify-center bg-gray-50/30″><div className=”text-center text-gray-400″><p className=”text-xs font-medium mb-2″>Peça única no lote.</p><button onClick={applyPsychologicalPrice} className=”text-[10px] font-bold text-indigo-600 bg-white border border-indigo-100 px-3 py-1.5 rounded-full hover:bg-indigo-50 hover:border-indigo-200 transition-all shadow-sm inline-flex items-center gap-1″><Zap size={10} className=”fill-indigo-600″/> Arredondar</button></div></div>)}</div>{/* Round Price Button (Mobile Only or when needed) */}<div className=”bg-white border-t border-gray-100 p-2 flex justify-center md:hidden”><button onClick={applyPsychologicalPrice} className=”text-xs font-bold text-indigo-600 bg-indigo-50 px-4 py-1.5 rounded-full hover:bg-indigo-100 transition-colors flex items-center gap-1″><Zap size={12} className=”fill-indigo-600″/> Arredondar Preço</button></div>{/* Tabela de Detalhes Estilizada */}<div className=”border-t border-gray-100 bg-white”><div className=”bg-gray-50/50 px-8 py-3 text-[10px] font-bold text-gray-400 uppercase flex justify-between tracking-wider”><span className=”w-1/3″>Composição</span><span className=”w-1/3 text-right”>Total</span><span className=”w-1/3 text-right”>Unitário</span></div><div className=”divide-y divide-gray-50 text-sm”>{[{ 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) => (<div key={idx} className=”px-8 py-3 flex justify-between hover:bg-gray-50 transition-colors group”><span className=”w-1/3 font-semibold text-gray-700 flex items-center gap-2″><span className={`w-2 h-2 rounded-full ${row.bg}`}></span> {row.label}</span><span className=”w-1/3 text-right font-medium text-gray-900″>{formatCurrency(row.val)}</span><span className=”w-1/3 text-right text-gray-400 font-mono text-xs pt-0.5″>{formatCurrency(row.val / results.P.qty)}</span></div>))}<div className=”px-8 py-3 flex justify-between bg-gray-50/80 font-bold border-t border-gray-100″><span className=”w-1/3 text-gray-800 text-xs uppercase pt-0.5″>Custo Total</span><span className=”w-1/3 text-right text-gray-800″>{formatCurrency(results.totalCost)}</span><span className=”w-1/3 text-right text-gray-500 font-mono text-xs pt-0.5″>{formatCurrency(results.unitTotalCost)}</span></div><div className=”px-8 py-4 bg-emerald-50/30 flex justify-between items-center border-t border-emerald-100/50″><div className=”w-1/3″><span className=”block font-bold text-emerald-700 text-xs uppercase”>Lucro Líquido</span></div><span className=”w-1/3 text-right font-bold text-emerald-700 text-lg”>{formatCurrency(results.actualProfit)}</span><span className=”w-1/3 text-right font-bold text-emerald-600/70 font-mono text-sm”>{formatCurrency(results.actualProfit / results.P.qty)}</span></div></div></div>{/* Actions Footer */}<div className=”grid grid-cols-1 sm:grid-cols-4 border-t border-gray-100 divide-y sm:divide-y-0 sm:divide-x divide-gray-100 bg-gray-50/30″><button onClick={() => saveToHistory()} className=”py-4 text-xs font-bold text-gray-500 hover:bg-white hover:text-indigo-600 flex justify-center items-center gap-2 transition-all uppercase tracking-wide group”><Save size={18} className=”text-gray-400 group-hover:text-indigo-500 transition-colors” /> Salvar</button><button onClick={exportToCSV} className=”py-4 text-xs font-bold text-gray-500 hover:bg-white hover:text-emerald-600 flex justify-center items-center gap-2 transition-all uppercase tracking-wide group”><FileSpreadsheet size={18} className=”text-gray-400 group-hover:text-emerald-500 transition-colors” /> CSV</button>{/* Botão Copy to Sheets */}<button onClick={copyToSheets} className=”py-4 text-xs font-bold text-gray-500 hover:bg-white hover:text-blue-600 flex justify-center items-center gap-2 transition-all uppercase tracking-wide group”><Copy size={18} className=”text-gray-400 group-hover:text-blue-500 transition-colors” /> Sheets</button><button onClick={openWhatsApp} className=”py-4 text-xs font-bold text-gray-500 hover:bg-white hover:text-green-600 flex justify-center items-center gap-2 transition-all uppercase tracking-wide group”><MessageCircle size={18} className=”text-gray-400 group-hover:text-green-500 transition-colors” /> Enviar</button></div></Card></div>{results.isLowProfit && (<div className=”bg-red-50 border border-red-200 p-5 rounded-xl flex items-start gap-4 animate-fade-in shadow-sm”><div className=”bg-red-100 p-2 rounded-full”><TrendingUp className=”text-red-600 w-5 h-5″ /></div><div className=”flex-1″><span className=”font-bold text-red-900 block text-sm mb-1 uppercase tracking-wide”>Lucro Insuficiente</span><p className=”text-xs text-red-700 mb-2″>O lucro atual ({formatCurrency(results.actualProfit)}) está abaixo do mínimo configurado de {formatCurrency(results.S.minProfit)}.</p><div className=”bg-white/60 p-2 rounded-lg inline-block border border-red-100″><p className=”text-xs font-bold text-red-800″>Preço Sugerido: <span className=”text-sm”>{formatCurrency(results.suggestedPriceMinProfit)}</span></p></div></div></div>)}<Card className=”p-8″><div className=”flex justify-between items-center mb-6″><h3 className=”font-bold text-gray-700 text-xs uppercase tracking-widest flex items-center gap-2″><Info size={14}/> Raio-X Visual</h3></div><div className=”space-y-4″><ProgressBar label=”Material” value={results.costs.filament} total={results.finalPrice} colorClass=”bg-blue-500″ /><div className=”grid grid-cols-2 gap-8″><ProgressBar label=”Energia” value={results.costs.energy} total={results.finalPrice} colorClass=”bg-amber-400″ /><ProgressBar label=”Desgaste” value={results.costs.depreciation} total={results.finalPrice} colorClass=”bg-purple-500″ /></div>{(results.costs.risk > 0) && <ProgressBar label=”Risco” value={results.costs.risk} total={results.finalPrice} colorClass=”bg-rose-400″ />}{(results.costs.totalExtras > 0) && <ProgressBar label=”Extras” value={results.costs.totalExtras} total={results.finalPrice} colorClass=”bg-gray-400″ />}<div className=”pt-2 border-t border-gray-100 mt-2″><ProgressBar label=”Lucro Líquido” value={results.actualProfit} total={results.finalPrice} colorClass=”bg-emerald-500″ /></div></div></Card></>) : (<div className=”h-full flex flex-col items-center justify-center text-center p-12 border-2 border-dashed border-gray-200 rounded-3xl bg-gray-50/50 hover:bg-gray-50 transition-colors”><div className=”bg-white p-6 rounded-3xl shadow-sm mb-6 animate-float”><Calculator size={48} className=”text-indigo-200 stroke-1″ /></div><h3 className=”text-xl font-bold text-gray-800 mb-2″>Vamos começar?</h3><p className=”text-gray-400 font-medium max-w-xs mx-auto mb-8″>Preencha o <strong>peso</strong> e o <strong>tempo</strong> para gerar seu orçamento detalhado.</p><div className=”flex gap-2 justify-center”><div className=”w-2 h-2 rounded-full bg-gray-300″></div><div className=”w-2 h-2 rounded-full bg-gray-300″></div><div className=”w-2 h-2 rounded-full bg-gray-300″></div></div></div>)}</div></div>)}</main></div>);}
