diff --git a/src/app/DRE/analitico.tsx b/src/app/DRE/analitico.tsx new file mode 100644 index 0000000..ff87a97 --- /dev/null +++ b/src/app/DRE/analitico.tsx @@ -0,0 +1,293 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; + +interface AnaliticoItem { + codigo_grupo: string; + codigo_subgrupo: string; + codigo_fornecedor: string; + nome_fornecedor: string; + id: number; + codfilial: string; + recnum: number; + data_competencia: string; + data_vencimento: string; + data_pagamento: string; + data_caixa: string; + codigo_conta: string; + conta: string; + codigo_centrocusto: string; + valor: number; + historico: string; + historico2: string; + created_at: string; + updated_at: string; +} + +type SortField = 'nome_fornecedor' | 'data_competencia' | 'valor' | 'conta'; +type SortDirection = 'asc' | 'desc'; + +interface SortConfig { + field: SortField; + direction: SortDirection; +} + +interface AnaliticoProps { + filtros: { + dataInicio: string; + dataFim: string; + centroCusto?: string; + codigoGrupo?: string; + codigoSubgrupo?: string; + codigoConta?: string; + }; +} + +export default function AnaliticoComponent({ filtros }: AnaliticoProps) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [sortConfig, setSortConfig] = useState({ + field: 'data_competencia', + direction: 'desc', + }); + + const fetchData = useCallback(async () => { + // Só faz a requisição se tiver dataInicio e dataFim + 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]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleSort = (field: SortField) => { + setSortConfig((prev) => ({ + field, + direction: + prev.field === field && prev.direction === 'asc' ? 'desc' : 'asc', + })); + }; + + const getSortIcon = (field: SortField) => { + if (sortConfig.field !== field) { + return ; + } + return sortConfig.direction === 'asc' ? ( + + ) : ( + + ); + }; + + const sortedData = [...data].sort((a, b) => { + const aValue = a[sortConfig.field]; + const bValue = b[sortConfig.field]; + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sortConfig.direction === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + } + + if (typeof aValue === 'number' && typeof bValue === 'number') { + return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue; + } + + return 0; + }); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('pt-BR'); + }; + + const totalValor = data.reduce((sum, item) => { + const valor = + typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor; + return sum + (isNaN(valor) ? 0 : valor); + }, 0); + + return ( +
+
+

Análise Analítica

+
+ + {/* Filtros aplicados */} + {/*
+
+ Filtros aplicados: +
+ {filtros.centroCusto && ( + + Centro: {filtros.centroCusto} + + )} + {filtros.codigoGrupo && ( + + Grupo: {filtros.codigoGrupo} + + )} + {filtros.codigoSubgrupo && ( + + Subgrupo: {filtros.codigoSubgrupo} + + )} + {filtros.codigoConta && ( + + Conta: {filtros.codigoConta} + + )} +
+
+
*/} + + {/* Resumo */} + + {/* Tabela */} +
+ {/* Header fixo */} +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
Histórico
+
+
+ +
+ {loading ? ( +
+ Carregando dados analíticos... +
+ ) : sortedData.length === 0 ? ( +
+ Nenhum dado analítico encontrado para os filtros aplicados. +
+ ) : ( + sortedData.map((row, index) => ( +
+
+ {row.nome_fornecedor || '-'} +
+
+ {formatDate(row.data_competencia)} +
+
+ {row.conta} +
+
+ {formatCurrency( + typeof row.valor === 'string' + ? parseFloat(row.valor) + : row.valor + )} +
+
+ {row.historico || '-'} +
+
+ )) + )} +
+
+ {data.length > 0 && ( +
+
+
+

+ Total de Registros: {data.length} +

+
+
+

+ Valor Total: {formatCurrency(totalValor)} +

+
+
+
+ )} +
+ ); +} diff --git a/src/app/DRE/teste.tsx b/src/app/DRE/teste.tsx index f53ac0e..0e86e05 100644 --- a/src/app/DRE/teste.tsx +++ b/src/app/DRE/teste.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'; // Removed unused table imports import { ArrowDown, ArrowUp, ArrowUpDown, BarChart3 } from 'lucide-react'; import { useEffect, useState } from 'react'; +import AnaliticoComponent from './analitico'; interface DREItem { codfilial: string; @@ -55,6 +56,16 @@ export default function Teste() { }); const [mesesDisponiveis, setMesesDisponiveis] = useState([]); + // Estados para analítico + const [analiticoFiltros, setAnaliticoFiltros] = useState({ + dataInicio: '', + dataFim: '', + centroCusto: '', + codigoGrupo: '', + codigoSubgrupo: '', + codigoConta: '', + }); + useEffect(() => { fetchData(); }, []); @@ -62,10 +73,13 @@ export default function Teste() { const fetchData = async () => { try { setLoading(true); + setError(null); const response = await fetch('/api/dre'); + if (!response.ok) { - throw new Error('Erro ao carregar dados'); + throw new Error(`Erro ao carregar dados: ${response.status}`); } + const result = await response.json(); setData(result); @@ -97,6 +111,47 @@ export default function Teste() { }); }; + // Função para extrair códigos dos grupos e subgrupos + 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+)+)/); + codigoSubgrupo = subgrupoMatch ? subgrupoMatch[1] : ''; + } + + return { codigoGrupo, codigoSubgrupo }; + }; + + // Função para lidar com clique nas linhas + const handleRowClick = (row: HierarchicalRow) => { + if (!data.length) return; + + // Pegar todas as datas disponíveis para definir o período + 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); // YYYY-MM + const dataFimStr = new Date(dataFim).toISOString().substring(0, 7); // YYYY-MM + + const { codigoGrupo, codigoSubgrupo } = extractCodes( + row.grupo || '', + row.subgrupo + ); + + setAnaliticoFiltros({ + dataInicio: dataInicioStr, + dataFim: dataFimStr, + centroCusto: row.centro_custo || '', + codigoGrupo, + codigoSubgrupo, + codigoConta: row.codigo_conta?.toString() || '', + }); + }; + const toggleGroup = (grupo: string) => { const newExpanded = new Set(expandedGroups); if (newExpanded.has(grupo)) { @@ -404,7 +459,12 @@ export default function Teste() { > {row.isExpanded ? '▼' : '▶'} - {row.grupo} + ); case 'subgrupo': @@ -416,7 +476,12 @@ export default function Teste() { > {row.isExpanded ? '▼' : '▶'} - {row.subgrupo} + ); case 'centro_custo': @@ -430,14 +495,24 @@ export default function Teste() { > {row.isExpanded ? '▼' : '▶'} - {row.centro_custo} + ); case 'conta': return (
- {row.conta} +
); default: @@ -546,6 +621,11 @@ export default function Teste() { ))} + + {/* Componente Analítico */} + {!loading && data.length > 0 && ( + + )} ); } diff --git a/src/app/api/analitico/route.ts b/src/app/api/analitico/route.ts new file mode 100644 index 0000000..73806cb --- /dev/null +++ b/src/app/api/analitico/route.ts @@ -0,0 +1,122 @@ +import db from '@/db'; +import { sql } from 'drizzle-orm'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const dataInicio = searchParams.get('dataInicio'); + const dataFim = searchParams.get('dataFim'); + const centroCusto = searchParams.get('centroCusto'); + const codigoGrupo = searchParams.get('codigoGrupo'); + const codigoSubgrupo = searchParams.get('codigoSubgrupo'); + const codigoConta = searchParams.get('codigoConta'); + + if (!dataInicio || !dataFim) { + return NextResponse.json( + { message: 'Parâmetros obrigatórios: dataInicio, dataFim' }, + { status: 400 } + ); + } + + let whereConditions = []; + let params = [dataInicio, dataFim]; + + if (centroCusto) { + whereConditions.push(`ffa.codigo_centrocusto = $${params.length + 1}`); + params.push(centroCusto); + } + + if (codigoGrupo) { + whereConditions.push(`drec.codigo_grupo = $${params.length + 1}`); + params.push(codigoGrupo); + } + + if (codigoSubgrupo) { + whereConditions.push(`dg.codigo_subgrupo = $${params.length + 1}`); + params.push(codigoSubgrupo); + } + + if (codigoConta) { + whereConditions.push(`ffa.codigo_conta = $${params.length + 1}`); + params.push(codigoConta); + } + + // Query com filtros aplicados + let query; + + if (centroCusto || codigoGrupo || codigoSubgrupo || codigoConta) { + // Query com filtros específicos + const conditions = []; + if (centroCusto) + conditions.push(`ffa.codigo_centrocusto = '${centroCusto}'`); + if (codigoConta) conditions.push(`ffa.codigo_conta = '${codigoConta}'`); + + const whereClause = + conditions.length > 0 ? `AND ${conditions.join(' AND ')}` : ''; + + query = sql` + SELECT + ffa.codigo_fornecedor, + ffa.nome_fornecedor, + ffa.id, + ffa.codfilial, + ffa.recnum, + ffa.data_competencia, + ffa.data_vencimento, + ffa.data_pagamento, + ffa.data_caixa, + ffa.codigo_conta, + ffa.conta, + ffa.codigo_centrocusto, + ffa.valor, + ffa.historico, + ffa.historico2, + ffa.created_at, + ffa.updated_at + FROM fato_financeiro_analitico AS ffa + WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN ${dataInicio} AND ${dataFim} + ${sql.raw(whereClause)} + LIMIT 50 + `; + } else { + // Query sem filtros específicos + query = sql` + SELECT + ffa.codigo_fornecedor, + ffa.nome_fornecedor, + ffa.id, + ffa.codfilial, + ffa.recnum, + ffa.data_competencia, + ffa.data_vencimento, + ffa.data_pagamento, + ffa.data_caixa, + ffa.codigo_conta, + ffa.conta, + ffa.codigo_centrocusto, + ffa.valor, + ffa.historico, + ffa.historico2, + ffa.created_at, + ffa.updated_at + FROM fato_financeiro_analitico AS ffa + WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN ${dataInicio} AND ${dataFim} + LIMIT 50 + `; + } + + const data = await db.execute(query); + + return NextResponse.json(data.rows); + } catch (error) { + console.error('Erro ao buscar dados analíticos:', error); + return NextResponse.json( + { + message: 'Erro ao buscar dados analíticos', + error: error instanceof Error ? error.message : String(error), + }, + { status: 500 } + ); + } +}