# Componentes - DRE Gerencial ## Visão Geral O sistema DRE Gerencial é construído com componentes React funcionais em TypeScript, seguindo padrões modernos de desenvolvimento frontend. ## Estrutura de Componentes ### 1. **Componente Principal** (`src/app/DRE/teste.tsx`) #### Responsabilidades - Orquestração da interface DRE hierárquica - Gerenciamento de estado de expansão/colapso - Controle de ordenação e filtros - Integração com componente analítico #### Estados Principais ```typescript const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expandedGroups, setExpandedGroups] = useState>(new Set()); const [expandedSubgrupos, setExpandedSubgrupos] = useState>(new Set()); const [expandedCentros, setExpandedCentros] = useState>(new Set()); const [sortConfig, setSortConfig] = useState({ field: 'descricao', direction: 'asc', }); const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: '', dataFim: '', centroCusto: '', codigoGrupo: '', codigoSubgrupo: '', codigoConta: '', }); ``` #### Funções Principais ##### `fetchData()` ```typescript const fetchData = async () => { try { setLoading(true); setError(null); const response = await fetch('/api/dre'); if (!response.ok) { throw new Error(`Erro ao carregar dados: ${response.status}`); } const result = await response.json(); setData(result); // Extrair meses únicos dos dados const meses = [...new Set( result.map((item: DREItem) => { const dataCompetencia = new Date(item.data_competencia); return `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, '0')}`; }) )].sort() as string[]; setMesesDisponiveis(meses); } catch (err) { setError(err instanceof Error ? err.message : 'Erro desconhecido'); } finally { setLoading(false); } }; ``` ##### `buildHierarchicalData()` ```typescript const buildHierarchicalData = (): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Agrupar por grupo, tratando grupo 05 como subgrupo do grupo 04 const grupos = data.reduce((acc, item) => { if (item.grupo.includes('05')) { // Lógica especial para grupo 05 const grupo04Key = Object.keys(acc).find((key) => key.includes('04')); if (grupo04Key) { acc[grupo04Key].push(item); } else { const grupo04Nome = '04 - GRUPO 04'; if (!acc[grupo04Nome]) { acc[grupo04Nome] = []; } acc[grupo04Nome].push(item); } } else { if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); } return acc; }, {} as Record); // Construir hierarquia completa // ... lógica de construção hierárquica return rows; }; ``` ##### `handleRowClick()` ```typescript const handleRowClick = (row: HierarchicalRow, mesSelecionado?: string) => { if (!data.length) return; // Calcular período baseado nos dados const datas = data.map((item) => item.data_competencia); const dataInicio = Math.min(...datas.map((d) => new Date(d).getTime())); const dataFim = Math.max(...datas.map((d) => new Date(d).getTime())); const dataInicioStr = new Date(dataInicio).toISOString().substring(0, 7); const dataFimStr = new Date(dataFim).toISOString().substring(0, 7); const { codigoGrupo, codigoSubgrupo } = extractCodes( row.grupo || '', row.subgrupo ); // Criar identificador único para a linha const linhaId = `${row.type}-${row.grupo || ''}-${row.subgrupo || ''}-${ row.centro_custo || '' }-${row.codigo_conta || ''}`; setLinhaSelecionada(linhaId); // Configurar filtros para análise analítica const dataInicioFiltro = mesSelecionado || dataInicioStr; const dataFimFiltro = mesSelecionado || dataFimStr; setAnaliticoFiltros({ dataInicio: dataInicioFiltro, dataFim: dataFimFiltro, centroCusto: row.centro_custo || '', codigoGrupo, codigoSubgrupo, codigoConta: row.codigo_conta?.toString() || '', }); }; ``` #### Renderização ```typescript return (

DRE Gerencial

{/* Tabela hierárquica */}
{/* Header fixo */}
{/* ... header content */}
{/* Dados hierárquicos */}
{hierarchicalData.map((row, index) => (
{/* ... row content */}
))}
{/* Componente Analítico */} {!loading && data.length > 0 && ( )}
); ``` --- ### 2. **Componente Analítico** (`src/app/DRE/analitico.tsx`) #### Responsabilidades - Visualização detalhada de transações - Ordenação de dados analíticos - Exportação para Excel - Aplicação de filtros dinâmicos #### Props ```typescript interface AnaliticoProps { filtros: { dataInicio: string; dataFim: string; centroCusto?: string; codigoGrupo?: string; codigoSubgrupo?: string; codigoConta?: string; }; } ``` #### Estados ```typescript const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [sortConfig, setSortConfig] = useState({ field: 'data_competencia', direction: 'desc', }); ``` #### Funções Principais ##### `fetchData()` ```typescript const fetchData = useCallback(async () => { if (!filtros.dataInicio || !filtros.dataFim) { setData([]); return; } setLoading(true); try { const params = new URLSearchParams({ dataInicio: filtros.dataInicio, dataFim: filtros.dataFim, ...(filtros.centroCusto && { centroCusto: filtros.centroCusto }), ...(filtros.codigoGrupo && { codigoGrupo: filtros.codigoGrupo }), ...(filtros.codigoSubgrupo && { codigoSubgrupo: filtros.codigoSubgrupo }), ...(filtros.codigoConta && { codigoConta: filtros.codigoConta }), }); const response = await fetch(`/api/analitico?${params}`); if (response.ok) { const result = await response.json(); setData(result as AnaliticoItem[]); } else { console.error('Erro ao buscar dados:', await response.text()); } } catch (error) { console.error('Erro ao buscar dados:', error); } finally { setLoading(false); } }, [filtros]); ``` ##### `exportToExcel()` ```typescript const exportToExcel = () => { if (data.length === 0) return; // Preparar dados para exportação const exportData = data.map((item) => ({ 'Data Competência': new Date(item.data_competencia).toLocaleDateString('pt-BR'), 'Data Vencimento': new Date(item.data_vencimento).toLocaleDateString('pt-BR'), 'Data Caixa': new Date(item.data_caixa).toLocaleDateString('pt-BR'), 'Código Fornecedor': item.codigo_fornecedor, Fornecedor: item.nome_fornecedor, 'Código Centro Custo': item.codigo_centrocusto, 'Centro Custo': item.codigo_centrocusto, 'Código Conta': item.codigo_conta, Conta: item.conta, Valor: typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor, Histórico: item.historico, 'Histórico 2': item.historico2, Recnum: item.recnum, })); // Criar workbook const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(exportData); // Adicionar resumo na segunda aba const resumoData = [ { Métrica: 'Total de Registros', Valor: data.length }, { Métrica: 'Valor Total', Valor: totalValor }, ]; const wsResumo = XLSX.utils.json_to_sheet(resumoData); // Adicionar abas ao workbook XLSX.utils.book_append_sheet(wb, ws, 'Dados Analíticos'); XLSX.utils.book_append_sheet(wb, wsResumo, 'Resumo'); // Gerar nome do arquivo com timestamp const now = new Date(); const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-'); const fileName = `analitico_${timestamp}.xlsx`; // Fazer download XLSX.writeFile(wb, fileName); }; ``` --- ### 3. **Componentes UI** (`src/components/ui/`) #### Button Component ```typescript // src/components/ui/button.tsx import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'h-10 w-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : 'button'; return ( ); } ); Button.displayName = 'Button'; export { Button, buttonVariants }; ``` ## Padrões de Design ### 1. **Composition Pattern** - Componentes pequenos e focados - Props tipadas com TypeScript - Reutilização através de composition ### 2. **State Management** - Estados locais com `useState` - Callbacks com `useCallback` para performance - Effects com `useEffect` para side effects ### 3. **Styling** - Tailwind CSS para styling - Class variance authority para variantes - Responsive design mobile-first ### 4. **Type Safety** - Interfaces TypeScript para props - Tipos específicos para dados - Validação de tipos em runtime ## Utilitários ### 1. **Formatação** ```typescript const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL', }).format(value); }; const formatCurrencyWithColor = (value: number) => { const formatted = formatCurrency(value); const isNegative = value < 0; return { formatted, isNegative }; }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('pt-BR'); }; ``` ### 2. **Extração de Códigos** ```typescript const extractCodes = (grupo: string, subgrupo?: string) => { const grupoMatch = grupo.match(/^(\d+)/); const codigoGrupo = grupoMatch ? grupoMatch[1] : ''; let codigoSubgrupo = ''; if (subgrupo) { const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/); if (subgrupoMatch) { codigoSubgrupo = subgrupoMatch[1]; } else { codigoSubgrupo = subgrupo; } } return { codigoGrupo, codigoSubgrupo }; }; ``` ### 3. **Cálculos** ```typescript const calcularValoresPorMes = (items: DREItem[]): Record => { const valoresPorMes: Record = {}; items.forEach((item) => { const dataCompetencia = new Date(item.data_competencia); const anoMes = `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, '0')}`; if (!valoresPorMes[anoMes]) { valoresPorMes[anoMes] = 0; } valoresPorMes[anoMes] += parseFloat(item.valor); }); return valoresPorMes; }; const calcularPercentuaisPorMes = ( valoresPorMes: Record, grupo: string ): Record => { const percentuais: Record = {}; // Se for o grupo 03, retorna 100% para todos os meses if (grupo.includes('03')) { Object.keys(valoresPorMes).forEach((mes) => { percentuais[mes] = 100; }); return percentuais; } // Para outros grupos, calcular percentual baseado no grupo 03 Object.keys(valoresPorMes).forEach((mes) => { const valorAtual = valoresPorMes[mes]; // Encontrar o valor do grupo 03 para o mesmo mês const grupo03Items = data.filter((item) => { const dataCompetencia = new Date(item.data_competencia); const anoMes = `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, '0')}`; return anoMes === mes && item.grupo.includes('03'); }); const valorGrupo03 = grupo03Items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); if (valorGrupo03 !== 0) { percentuais[mes] = (valorAtual / valorGrupo03) * 100; } else { percentuais[mes] = 0; } }); return percentuais; }; ``` ## Performance ### 1. **Otimizações Implementadas** - `useCallback` para funções de fetch - `useMemo` para cálculos pesados (potencial) - Renderização condicional ### 2. **Estratégias de Renderização** - Lazy loading de componentes - Virtualização para listas grandes (potencial) - Debounce para filtros (potencial) ## Testes ### 1. **Testes Unitários** ```typescript // Exemplo de teste para componente import { render, screen } from '@testing-library/react'; import Teste from './teste'; describe('Teste Component', () => { it('renders DRE title', () => { render(); expect(screen.getByText('DRE Gerencial')).toBeInTheDocument(); }); }); ``` ### 2. **Testes de Integração** ```typescript // Teste de interação com API describe('DRE Integration', () => { it('loads data from API', async () => { render(); await waitFor(() => { expect(screen.getByText('Carregando dados...')).toBeInTheDocument(); }); // Verificar se dados foram carregados }); }); ``` ## Próximos Passos 1. **Implementar Context API** para estado global 2. **Adicionar React Query** para cache de dados 3. **Implementar Error Boundaries** robustos 4. **Adicionar testes unitários** e de integração 5. **Implementar lazy loading** de componentes 6. **Adicionar acessibilidade** (ARIA labels) 7. **Implementar temas** dark/light 8. **Adicionar animações** e transições