/** * Supera Glia - Tab Comercial (Vendas) * @module tabs/ComercialTab */ (function() { 'use strict'; SuperaGlia.initComercialTab = function() { const { useState, useEffect, useMemo } = SuperaGlia.hooks; const { API_BASE } = SuperaGlia.config; const { Icon } = SuperaGlia; const { formatMoney, formatDate } = SuperaGlia.utils; const { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend } = SuperaGlia.charts; SuperaGlia.ComercialTab = () => { const [vendas, setVendas] = useState([]); const [carregando, setCarregando] = useState(true); const [erro, setErro] = useState(null); // Filtros const [dataInicio, setDataInicio] = useState(() => { const d = new Date(); d.setMonth(d.getMonth() - 1); return d.toISOString().split('T')[0]; }); const [dataFim, setDataFim] = useState(() => new Date().toISOString().split('T')[0]); const [filtroProduto, setFiltroProduto] = useState(''); const [filtroVendedor, setFiltroVendedor] = useState(''); const [filtroMidia, setFiltroMidia] = useState(''); const [filtroInteresse, setFiltroInteresse] = useState(''); const [buscaVenda, setBuscaVenda] = useState(''); const [agrupamento, setAgrupamento] = useState('dia'); // Estados para listas de filtros const [produtos, setProdutos] = useState([]); const [vendedores, setVendedores] = useState([]); const [midias, setMidias] = useState([]); const [interesses, setInteresses] = useState([]); const carregarVendas = async () => { setCarregando(true); setErro(null); try { const params = new URLSearchParams({ dataInicio, dataFim }); if (filtroProduto) params.append('produto', filtroProduto); if (filtroVendedor) params.append('vendedor', filtroVendedor); const res = await fetch(`${API_BASE}/financeiro/vendas?${params}`).then(r => r.json()).catch(() => ({ vendas: [] })); const vendasData = res?.vendas || []; setVendas(vendasData); // Extrair listas únicas para filtros setProdutos([...new Set(vendasData.map(v => v.produto).filter(Boolean))].sort()); setVendedores([...new Set(vendasData.map(v => v.vendedor).filter(Boolean))].sort()); setMidias([...new Set(vendasData.map(v => v.midia_contato).filter(Boolean))].sort()); setInteresses([...new Set(vendasData.map(v => v.interesse).filter(Boolean))].sort()); } catch (e) { console.error('Erro ao carregar vendas:', e); setErro('Erro ao carregar dados de vendas'); } finally { setCarregando(false); } }; useEffect(() => { carregarVendas(); }, []); // Aplicar filtros locais const vendasFiltradas = useMemo(() => { return vendas.filter(v => { if (buscaVenda && !v.nome?.toLowerCase().includes(buscaVenda.toLowerCase()) && !v.vendedor?.toLowerCase().includes(buscaVenda.toLowerCase()) && !v.produto?.toLowerCase().includes(buscaVenda.toLowerCase())) return false; if (filtroProduto && v.produto !== filtroProduto) return false; if (filtroVendedor && v.vendedor !== filtroVendedor) return false; if (filtroMidia && v.midia_contato !== filtroMidia) return false; if (filtroInteresse && v.interesse !== filtroInteresse) return false; return true; }); }, [vendas, buscaVenda, filtroProduto, filtroVendedor, filtroMidia, filtroInteresse]); // Calcular totais const totais = useMemo(() => { const result = { quantidade: vendasFiltradas.length, valorBruto: vendasFiltradas.reduce((s, v) => s + (parseFloat(v.valor_bruto) || 0), 0), valorDesconto: vendasFiltradas.reduce((s, v) => s + (parseFloat(v.valor_desconto) || 0), 0), valorEntrada: vendasFiltradas.reduce((s, v) => s + (parseFloat(v.valor_entrada) || 0), 0) }; result.valorLiquido = result.valorBruto - result.valorDesconto; return result; }, [vendasFiltradas]); // Função para obter chave de agrupamento const getChaveAgrupamento = (dataStr) => { if (!dataStr) return 'Sem data'; const data = dataStr.split('T')[0]; const [ano, mes, dia] = data.split('-'); if (agrupamento === 'mes') { const meses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']; return `${meses[parseInt(mes) - 1]}/${ano}`; } else if (agrupamento === 'semana') { const d = new Date(parseInt(ano), parseInt(mes) - 1, parseInt(dia)); const primeiroDia = new Date(d.getFullYear(), 0, 1); const numSemana = Math.ceil(((d - primeiroDia) / 86400000 + primeiroDia.getDay() + 1) / 7); return `Sem ${numSemana}/${ano}`; } return data; }; // Dados para gráfico const dadosGrafico = useMemo(() => { const agrupado = {}; vendasFiltradas.forEach(v => { const chave = getChaveAgrupamento(v.data_venda); if (!agrupado[chave]) { agrupado[chave] = { periodo: chave, quantidade: 0, valorBruto: 0, dataSort: v.data_venda || '' }; } agrupado[chave].quantidade++; agrupado[chave].valorBruto += parseFloat(v.valor_bruto) || 0; }); return Object.values(agrupado).sort((a, b) => a.dataSort.localeCompare(b.dataSort)); }, [vendasFiltradas, agrupamento]); // Função para exportar PDF const exportarPDF = () => { const printWindow = window.open('', '_blank'); const html = ` Relatório de Vendas - Supera São Bento

Relatório de Vendas

Período: ${formatDate(dataInicio)} a ${formatDate(dataFim)}

Total de Vendas

${totais.quantidade}

Valor Bruto

${formatMoney(totais.valorBruto)}

Descontos

${formatMoney(totais.valorDesconto)}

Valor Líquido

${formatMoney(totais.valorLiquido)}

${vendasFiltradas.map(v => ` `).join('')}
DataClienteVendedorProduto Valor BrutoDescontoEntradaParcelas
${formatDate(v.data_venda)} ${v.nome || '-'} ${v.vendedor || '-'} ${v.produto || '-'} ${formatMoney(v.valor_bruto)} ${formatMoney(v.valor_desconto)} ${formatMoney(v.valor_entrada)} ${v.num_parcelas || '-'}
`; printWindow.document.write(html); printWindow.document.close(); printWindow.onload = () => { printWindow.print(); }; }; if (carregando) { return React.createElement('div', { className: 'flex items-center justify-center h-64' }, React.createElement('div', { className: 'text-center' }, React.createElement('div', { className: 'w-10 h-10 border-4 border-orange-200 border-t-orange-500 rounded-full animate-spin mx-auto mb-4' }), React.createElement('p', { className: 'text-gray-500' }, 'Carregando dados comerciais...') ) ); } return React.createElement('div', { className: 'space-y-6' }, // Cards de Resumo React.createElement('div', { className: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [ { label: 'Total de Vendas', value: totais.quantidade, icon: 'shopping-cart', color: 'from-blue-500 to-blue-600' }, { label: 'Valor Bruto', value: formatMoney(totais.valorBruto), icon: 'dollar-sign', color: 'from-green-500 to-green-600' }, { label: 'Descontos', value: formatMoney(totais.valorDesconto), icon: 'percent', color: 'from-amber-500 to-amber-600' }, { label: 'Valor Líquido', value: formatMoney(totais.valorLiquido), icon: 'trending-up', color: 'from-orange-500 to-orange-600' } ].map((card, i) => React.createElement('div', { key: i, className: 'bg-white rounded-xl shadow-sm border border-gray-100 p-5 stat-card' }, React.createElement('div', { className: 'flex items-center justify-between mb-2' }, React.createElement('span', { className: 'text-sm text-gray-500' }, card.label), React.createElement('div', { className: `w-10 h-10 rounded-xl bg-gradient-to-br ${card.color} flex items-center justify-center` }, React.createElement(Icon, { name: card.icon, className: 'w-5 h-5 text-white' }) ) ), React.createElement('p', { className: 'text-2xl font-bold text-gray-900' }, card.value) )) ), // Filtros React.createElement('div', { className: 'bg-white rounded-xl shadow-sm border border-gray-100 p-5' }, React.createElement('div', { className: 'flex items-center justify-between mb-4' }, React.createElement('h3', { className: 'text-sm font-semibold text-gray-700 flex items-center gap-2' }, React.createElement(Icon, { name: 'filter', className: 'w-4 h-4' }), 'Filtros' ), React.createElement('div', { className: 'flex gap-2' }, React.createElement('button', { onClick: carregarVendas, className: 'flex items-center gap-2 px-4 py-2 bg-orange-500 text-white rounded-lg text-sm font-medium hover:bg-orange-600 transition-colors' }, React.createElement(Icon, { name: 'search', className: 'w-4 h-4' }), 'Buscar' ), React.createElement('button', { onClick: exportarPDF, className: 'flex items-center gap-2 px-4 py-2 bg-gray-700 text-white rounded-lg text-sm font-medium hover:bg-gray-800 transition-colors' }, React.createElement(Icon, { name: 'file-text', className: 'w-4 h-4' }), 'Exportar PDF' ) ) ), React.createElement('div', { className: 'grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4' }, React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Data Início'), React.createElement('input', { type: 'date', value: dataInicio, onChange: e => setDataInicio(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Data Fim'), React.createElement('input', { type: 'date', value: dataFim, onChange: e => setDataFim(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Produto'), React.createElement('select', { value: filtroProduto, onChange: e => setFiltroProduto(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: '' }, 'Todos'), produtos.map(p => React.createElement('option', { key: p, value: p }, p)) ) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Vendedor'), React.createElement('select', { value: filtroVendedor, onChange: e => setFiltroVendedor(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: '' }, 'Todos'), vendedores.map(v => React.createElement('option', { key: v, value: v }, v)) ) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Mídia'), React.createElement('select', { value: filtroMidia, onChange: e => setFiltroMidia(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: '' }, 'Todas'), midias.map(m => React.createElement('option', { key: m, value: m }, m)) ) ), React.createElement('div', null, React.createElement('label', { className: 'block text-xs font-medium text-gray-500 mb-1' }, 'Agrupamento'), React.createElement('select', { value: agrupamento, onChange: e => setAgrupamento(e.target.value), className: 'w-full px-3 py-2 border border-gray-200 rounded-lg text-sm' }, React.createElement('option', { value: 'dia' }, 'Por Dia'), React.createElement('option', { value: 'semana' }, 'Por Semana'), React.createElement('option', { value: 'mes' }, 'Por Mês') ) ) ) ), // Gráfico dadosGrafico.length > 0 && React.createElement('div', { className: 'bg-white rounded-xl shadow-sm border border-gray-100 p-5' }, React.createElement('h3', { className: 'text-sm font-semibold text-gray-700 mb-4 flex items-center gap-2' }, React.createElement(Icon, { name: 'bar-chart-2', className: 'w-4 h-4' }), 'Evolução de Vendas' ), React.createElement(ResponsiveContainer, { width: '100%', height: 300 }, React.createElement(AreaChart, { data: dadosGrafico }, React.createElement(CartesianGrid, { strokeDasharray: '3 3', stroke: '#f1f5f9' }), React.createElement(XAxis, { dataKey: 'periodo', tick: { fontSize: 11 } }), React.createElement(YAxis, { tick: { fontSize: 11 }, yAxisId: 'left' }), React.createElement(YAxis, { tick: { fontSize: 11 }, yAxisId: 'right', orientation: 'right', tickFormatter: v => formatMoney(v).replace('R$', '') }), React.createElement(Tooltip, { formatter: (value, name) => name === 'Valor Bruto' ? [formatMoney(value), 'Valor Bruto'] : [value, 'Quantidade'] }), React.createElement(Legend), React.createElement(Area, { yAxisId: 'left', type: 'monotone', dataKey: 'quantidade', name: 'Quantidade', stroke: '#F37021', fill: '#FEF3EC', strokeWidth: 2 }), React.createElement(Area, { yAxisId: 'right', type: 'monotone', dataKey: 'valorBruto', name: 'Valor Bruto', stroke: '#10B981', fill: '#D1FAE5', strokeWidth: 2 }) ) ) ), // Tabela de Vendas React.createElement('div', { className: 'bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden' }, React.createElement('div', { className: 'p-4 border-b border-gray-100 flex items-center justify-between' }, React.createElement('h3', { className: 'text-sm font-semibold text-gray-700 flex items-center gap-2' }, React.createElement(Icon, { name: 'list', className: 'w-4 h-4' }), `Vendas (${vendasFiltradas.length})` ), React.createElement('input', { type: 'text', placeholder: 'Buscar...', value: buscaVenda, onChange: e => setBuscaVenda(e.target.value), className: 'px-3 py-1.5 border border-gray-200 rounded-lg text-sm w-48' }) ), React.createElement('div', { className: 'overflow-x-auto' }, React.createElement('table', { className: 'w-full' }, React.createElement('thead', { className: 'bg-gray-50' }, React.createElement('tr', null, ['Data', 'Cliente', 'Vendedor', 'Produto', 'Valor Bruto', 'Desconto', 'Entrada', 'Parcelas'].map(h => React.createElement('th', { key: h, className: `text-${h.includes('Valor') || h === 'Desconto' || h === 'Entrada' ? 'right' : 'left'} text-xs font-semibold text-gray-500 uppercase px-4 py-3` }, h) ) ) ), React.createElement('tbody', { className: 'divide-y divide-gray-100' }, vendasFiltradas.length === 0 ? React.createElement('tr', null, React.createElement('td', { colSpan: 8, className: 'text-center text-gray-500 py-8' }, 'Nenhuma venda encontrada')) : vendasFiltradas.slice(0, 100).map((v, i) => React.createElement('tr', { key: i, className: 'hover:bg-gray-50 transition-colors' }, React.createElement('td', { className: 'px-4 py-3 text-sm text-gray-900' }, formatDate(v.data_venda)), React.createElement('td', { className: 'px-4 py-3 text-sm font-medium text-gray-900' }, v.nome || '-'), React.createElement('td', { className: 'px-4 py-3 text-sm text-gray-700' }, v.vendedor || '-'), React.createElement('td', { className: 'px-4 py-3' }, React.createElement('span', { className: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800' }, v.produto || '-') ), React.createElement('td', { className: 'px-4 py-3 text-sm text-right font-medium text-gray-900' }, formatMoney(v.valor_bruto)), React.createElement('td', { className: 'px-4 py-3 text-sm text-right text-amber-600' }, formatMoney(v.valor_desconto)), React.createElement('td', { className: 'px-4 py-3 text-sm text-right text-green-600' }, formatMoney(v.valor_entrada)), React.createElement('td', { className: 'px-4 py-3 text-sm text-center text-gray-700' }, v.num_parcelas || '-') ) ) ) ) ), vendasFiltradas.length > 100 && React.createElement('div', { className: 'p-3 bg-gray-50 border-t text-center text-sm text-gray-500' }, `Mostrando 100 de ${vendasFiltradas.length} registros` ) ) ); }; }; })();