/* 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