/* global React, ReactDOM */ const { useState, useMemo, useEffect, useCallback } = React; const STORAGE_KEY = 'calc_lucros_redesign_v1'; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#e3b878", "density": "default", "showRail": true }/*EDITMODE-END*/; const ACCENT_PALETTE = { '#e3b878': { strong: '#f0c890', on: '#15110a', soft: 'rgba(227, 184, 120, 0.10)', focus: 'rgba(227, 184, 120, 0.18)' }, '#6ee7b7': { strong: '#86efac', on: '#0a1612', soft: 'rgba(110, 231, 183, 0.10)', focus: 'rgba(110, 231, 183, 0.18)' }, '#93c5fd': { strong: '#bfdbfe', on: '#08111f', soft: 'rgba(147, 197, 253, 0.10)', focus: 'rgba(147, 197, 253, 0.18)' }, '#c4b5fd': { strong: '#ddd6fe', on: '#0f0a1f', soft: 'rgba(196, 181, 253, 0.10)', focus: 'rgba(196, 181, 253, 0.18)' }, }; function applyAccent(hex) { const p = ACCENT_PALETTE[hex] || ACCENT_PALETTE['#e3b878']; const root = document.documentElement; root.style.setProperty('--accent', hex); root.style.setProperty('--accent-strong', p.strong); root.style.setProperty('--accent-soft', p.soft); root.style.setProperty('--on-accent', p.on); root.style.setProperty('--sh-focus', `0 0 0 3px ${p.focus}`); } function loadState() { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch (e) {} return { venda: window.VENDA_PADRAO(), produtos: [window.PRODUTO_PADRAO()], params: window.PARAMS_PADRAO(), }; } function saveStateLS(s) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch (e) {} } function App() { const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS); useEffect(() => { applyAccent(tweaks.accent); }, [tweaks.accent]); useEffect(() => { document.documentElement.setAttribute('data-density', tweaks.density); }, [tweaks.density]); const [state, setState] = useState(loadState); const [view, setView] = useState('venda'); const [activeProduto, setActiveProduto] = useState(state.produtos[0].id); const [toast, setToast] = useState(null); // Autenticação const [authReady, setAuthReady] = useState(false); const [user, setUser] = useState(null); // Loading global const [loading, setLoading] = useState(null); // Modal de confirmação const [confirmState, setConfirmState] = useState(null); // Listas do histórico const [histSims, setHistSims] = useState([]); const [histProds, setHistProds] = useState([]); const [histSimsLoaded, setHistSimsLoaded] = useState(false); const [histProdsLoaded, setHistProdsLoaded] = useState(false); // Modo visualização (volátil) const [viewingMode, setViewingMode] = useState(null); const [vendaBackup, setVendaBackup] = useState(null); const [paramsBackup, setParamsBackup] = useState(null); const [produtoBackup, setProdutoBackup] = useState(null); const [salvandoSim, setSalvandoSim] = useState(false); const [salvandoProd, setSalvandoProd] = useState(false); // Persiste rascunho — EXCETO em modo visualização (snapshot é volátil) useEffect(() => { if (!viewingMode) saveStateLS(state); }, [state, viewingMode]); const showToast = useCallback((msg, kind = '') => { setToast({ msg, kind }); setTimeout(() => setToast(null), 2800); }, []); // Boot de autenticação useEffect(() => { (async () => { if (!window.SUPABASE_CONFIGURED) { console.warn('[Agenflex] Supabase não configurado.'); setAuthReady(true); return; } const session = await window.sb.ensureValidSession(); if (session) setUser(session.user); setAuthReady(true); })(); }, []); const onLoginSuccess = useCallback(() => { const session = window.sb.getSession(); if (session) setUser(session.user); showToast('Login realizado com sucesso', 'ok'); }, [showToast]); const askConfirm = useCallback((opts) => new Promise(resolve => { setConfirmState({ title: opts.title || 'Confirmar', message: opts.message, okLabel: opts.okLabel || 'Confirmar', danger: !!opts.danger, onOk: () => { setConfirmState(null); resolve(true); }, onCancel: () => { setConfirmState(null); resolve(false); }, }); }), []); const doLogout = useCallback(async () => { const ok = await askConfirm({ title: 'Sair', message: 'Sair da sessão atual?', okLabel: 'Sair', danger: true, }); if (!ok) return; setLoading({ text: 'Saindo…' }); await window.sb.signOut(); setViewingMode(null); setVendaBackup(null); setParamsBackup(null); setProdutoBackup(null); setUser(null); setLoading(null); }, [askConfirm]); // Produtos const setProduto = (updated) => { setState(s => ({ ...s, produtos: s.produtos.map(p => p.id === updated.id ? updated : p), })); }; const addProduto = () => { const id = 'prod_' + Date.now(); const novo = { id, nome: 'Novo produto', nf: { valor: 0, kg: 0, icms: 18, ipi: 0, simples: 'NAO', frete: 0, seguro: 0, outras: 0, desconto: 0, xmlInfo: null }, insumos: JSON.parse(JSON.stringify(window.INSUMOS_DEFAULT)).map(i => ({ ...i, valor: 0 })), prod: { mpKg: 0, qtd: 0 }, }; setState(s => ({ ...s, produtos: [...s.produtos, novo] })); setActiveProduto(id); setView('produto'); showToast('Produto criado', 'ok'); }; const removeProduto = async (id) => { if (state.produtos.length <= 1) { showToast('Não é possível remover o último produto', 'err'); return; } const p = state.produtos.find(x => x.id === id); const ok = await askConfirm({ title: 'Remover produto', message: `Remover "${p?.nome}"? Esta ação não pode ser desfeita.`, okLabel: 'Remover', danger: true, }); if (!ok) return; setState(s => { const novos = s.produtos.filter(p => p.id !== id); let newVenda = s.venda; if (s.venda.produtoId === id) newVenda = { ...s.venda, produtoId: novos[0].id }; return { ...s, produtos: novos, venda: newVenda }; }); if (activeProduto === id) { const next = state.produtos.find(p => p.id !== id); if (next) setActiveProduto(next.id); } showToast('Produto removido'); }; const produto = state.produtos.find(p => p.id === state.venda.produtoId); const produtoEdit = state.produtos.find(p => p.id === activeProduto); const calc = useMemo( () => window.calcVenda(state.venda, produto, state.params), [state.venda, produto, state.params] ); // Salvar simulação const salvarSimulacao = async () => { if (salvandoSim) return; if (!window.SUPABASE_CONFIGURED) { showToast('Supabase não configurado', 'err'); return; } if (!user) { showToast('Faça login para salvar', 'err'); return; } setSalvandoSim(true); setLoading({ text: 'Salvando simulação…' }); try { const saved = await window.cloud.saveSim(state, calc); showToast(`Simulação #${String(saved.seq).padStart(4, '0')} salva`, 'ok'); setHistSimsLoaded(false); } catch (e) { showToast('Falha ao salvar: ' + e.message, 'err'); } finally { setLoading(null); setSalvandoSim(false); } }; // Salvar produto const salvarProduto = async () => { if (salvandoProd) return; if (!window.SUPABASE_CONFIGURED) { showToast('Supabase não configurado', 'err'); return; } if (!user) { showToast('Faça login para salvar', 'err'); return; } if (!produtoEdit) return; setSalvandoProd(true); setLoading({ text: 'Salvando produto…' }); try { const saved = await window.cloud.saveProduto(produtoEdit, state.params); showToast(`Produto v#${String(saved.seq).padStart(4, '0')} salvo`, 'ok'); setHistProdsLoaded(false); } catch (e) { showToast('Falha ao salvar: ' + e.message, 'err'); } finally { setLoading(null); setSalvandoProd(false); } }; // Recarregar históricos const recarregarSims = async () => { if (!user) { showToast('Faça login primeiro', 'err'); return; } setLoading({ text: 'Carregando…' }); try { const data = await window.cloud.listSims(); setHistSims(data || []); setHistSimsLoaded(true); showToast(`${(data || []).length} simulação(ões) carregada(s)`, 'ok'); } catch (e) { showToast('Falha ao carregar: ' + e.message, 'err'); } finally { setLoading(null); } }; const recarregarProds = async () => { if (!user) { showToast('Faça login primeiro', 'err'); return; } setLoading({ text: 'Carregando…' }); try { const data = await window.cloud.listProds(); setHistProds(data || []); setHistProdsLoaded(true); showToast(`${(data || []).length} produto(s) carregado(s)`, 'ok'); } catch (e) { showToast('Falha ao carregar: ' + e.message, 'err'); } finally { setLoading(null); } }; // Visualizar simulação const visualizarSim = async (simId) => { if (!user) { showToast('Faça login primeiro', 'err'); return; } if (viewingMode) { const ok = await askConfirm({ title: 'Substituir visualização', message: 'Você já está visualizando um snapshot. Sair do modo atual e carregar este?', okLabel: 'Substituir', }); if (!ok) return; sairModoVisualizacao(); } setLoading({ text: 'Carregando simulação…' }); try { const sim = await window.cloud.getSim(simId); setVendaBackup(JSON.parse(JSON.stringify(state.venda))); setParamsBackup(JSON.parse(JSON.stringify(state.params))); const novaVenda = { ...state.venda, qtd: sim.qtd, valor: sim.valor_total, uf: sim.uf_destino, contrib: sim.contribuinte ? 'SIM' : 'NAO', mkt: sim.marketplace ? 'SIM' : 'NAO', comissao: sim.comissao_pct || 0, ipi: sim.com_ipi ? 'SIM' : 'NAO', }; const novosParams = sim.params_snapshot ? { ...state.params, ...sim.params_snapshot } : state.params; setState(s => ({ ...s, venda: novaVenda, params: novosParams })); setViewingMode({ tipo: 'simulacao', seq: sim.seq, createdAt: sim.created_at, produtoNome: sim.produto_nome, }); setView('venda'); showToast(`Visualizando #${String(sim.seq).padStart(4, '0')}`, 'ok'); } catch (e) { showToast('Falha: ' + e.message, 'err'); } finally { setLoading(null); } }; // Visualizar produto const visualizarProd = async (prodVersionId) => { if (!user) { showToast('Faça login primeiro', 'err'); return; } if (viewingMode) { const ok = await askConfirm({ title: 'Substituir visualização', message: 'Você já está visualizando um snapshot. Sair do modo atual e carregar este?', okLabel: 'Substituir', }); if (!ok) return; sairModoVisualizacao(); } setLoading({ text: 'Carregando produto…' }); try { const pv = await window.cloud.getProd(prodVersionId); const alvoId = activeProduto; const alvo = state.produtos.find(p => p.id === alvoId); if (!alvo) throw new Error('Produto ativo não encontrado'); setProdutoBackup(JSON.parse(JSON.stringify(alvo))); const novo = { ...alvo, nome: pv.nome, nf: { valor: pv.nf_valor, kg: pv.nf_kg, icms: pv.nf_icms, ipi: pv.nf_ipi, simples: pv.nf_simples ? 'SIM' : 'NAO', frete: pv.nf_frete, seguro: pv.nf_seguro, outras: pv.nf_outras, desconto: pv.nf_desconto, xmlInfo: pv.nf_xml_info, }, insumos: (pv.insumos || []).map(i => ({ ...i })), prod: { mpKg: pv.mp_kg_dia, qtd: pv.qtd_dia }, }; setState(s => ({ ...s, produtos: s.produtos.map(p => p.id === alvoId ? novo : p), })); setViewingMode({ tipo: 'produto', seq: pv.seq, createdAt: pv.created_at, produtoNome: pv.nome, prodId: alvoId, }); setView('produto'); showToast(`Visualizando v#${String(pv.seq).padStart(4, '0')}`, 'ok'); } catch (e) { showToast('Falha: ' + e.message, 'err'); } finally { setLoading(null); } }; const sairModoVisualizacao = () => { if (!viewingMode) return; if (viewingMode.tipo === 'simulacao' && vendaBackup) { setState(s => ({ ...s, venda: vendaBackup, params: paramsBackup || s.params, })); } else if (viewingMode.tipo === 'produto' && produtoBackup) { setState(s => ({ ...s, produtos: s.produtos.map(p => p.id === produtoBackup.id ? produtoBackup : p), })); } setVendaBackup(null); setParamsBackup(null); setProdutoBackup(null); setViewingMode(null); showToast('Voltou para edição', 'ok'); }; // Deletar histórico const deletarSim = async (simId, seq) => { const ok = await askConfirm({ title: 'Excluir simulação', message: `Excluir #${String(seq).padStart(4, '0')}? Esta ação não pode ser desfeita.`, okLabel: 'Excluir', danger: true, }); if (!ok) return; setLoading({ text: 'Excluindo…' }); try { await window.cloud.deleteSim(simId); setHistSims(prev => prev.filter(s => s.id !== simId)); showToast('Simulação excluída', 'ok'); } catch (e) { showToast('Falha: ' + e.message, 'err'); } finally { setLoading(null); } }; const deletarProd = async (prodId, seq) => { const ok = await askConfirm({ title: 'Excluir produto', message: `Excluir v#${String(seq).padStart(4, '0')}? Esta ação não pode ser desfeita.`, okLabel: 'Excluir', danger: true, }); if (!ok) return; setLoading({ text: 'Excluindo…' }); try { await window.cloud.deleteProd(prodId); setHistProds(prev => prev.filter(p => p.id !== prodId)); showToast('Produto excluído', 'ok'); } catch (e) { showToast('Falha: ' + e.message, 'err'); } finally { setLoading(null); } }; const topbar = useMemo(() => { if (view === 'venda') return { eyebrow: 'Operação', name: 'Simulação de venda' }; if (view === 'produto') return { eyebrow: 'Produto', name: produtoEdit?.nome || 'Produto' }; if (view === 'hist-sim') return { eyebrow: 'Histórico', name: 'Simulações salvas' }; if (view === 'hist-prod') return { eyebrow: 'Histórico', name: 'Versões de produtos' }; if (view === 'params') return { eyebrow: 'Configuração', name: 'Parâmetros tributários' }; if (view === 'ufs') return { eyebrow: 'Referência', name: 'Tabela de alíquotas (UFs)' }; return { eyebrow: '', name: '' }; }, [view, produtoEdit]); const showRailNow = tweaks.showRail && view === 'venda'; const needsLogin = window.SUPABASE_CONFIGURED && authReady && !user; if (!authReady) { return ; } return ( <> {needsLogin && }
{topbar.eyebrow} {topbar.name}
{view === 'venda' && ( showToast('PDF exportado (demo)', 'ok')} /> )} {view === 'produto' && ( )} {view === 'hist-sim' && ( )} {view === 'hist-prod' && ( )} {view === 'params' && setState({ ...state, params: p })} />} {view === 'ufs' && }
{showRailNow && } setTweak('accent', v)} options={['#e3b878', '#6ee7b7', '#93c5fd', '#c4b5fd']} /> setTweak('density', v)} options={[ { value: 'compact', label: 'Compacto' }, { value: 'default', label: 'Padrão' }, { value: 'cozy', label: 'Espaçoso' }, ]} /> setTweak('showRail', v)} />
{loading && } {confirmState && } ); } ReactDOM.createRoot(document.getElementById('root')).render();