'use client'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { ArrowDown, ArrowUp, ArrowUpDown, BarChart3 } from 'lucide-react'; import { useEffect, useState } from 'react'; interface DREItem { codfilial: string; data_competencia: string; data_cai: string; grupo: string; subgrupo: string; centro_custo: string; codigo_conta: number; conta: string; valor: string; } interface HierarchicalRow { type: 'grupo' | 'subgrupo' | 'centro_custo' | 'conta'; level: number; grupo?: string; subgrupo?: string; centro_custo?: string; conta?: string; codigo_conta?: number; total?: number; isExpanded?: boolean; valoresPorMes?: Record; } type SortField = 'descricao' | 'valor'; type SortDirection = 'asc' | 'desc'; interface SortConfig { field: SortField; direction: SortDirection; } export default function Teste() { 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 [mesesDisponiveis, setMesesDisponiveis] = useState([]); useEffect(() => { fetchData(); }, []); const fetchData = async () => { try { setLoading(true); const response = await fetch('/api/dre'); if (!response.ok) { throw new Error('Erro ao carregar dados'); } 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); } }; const formatCurrency = (value: string | number) => { const numValue = typeof value === 'string' ? parseFloat(value) : value; return numValue.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', }); }; const toggleGroup = (grupo: string) => { const newExpanded = new Set(expandedGroups); if (newExpanded.has(grupo)) { newExpanded.delete(grupo); } else { newExpanded.add(grupo); } setExpandedGroups(newExpanded); }; const toggleSubgrupo = (subgrupo: string) => { const newExpanded = new Set(expandedSubgrupos); if (newExpanded.has(subgrupo)) { newExpanded.delete(subgrupo); } else { newExpanded.add(subgrupo); } setExpandedSubgrupos(newExpanded); }; const toggleCentro = (centro: string) => { const newExpanded = new Set(expandedCentros); if (newExpanded.has(centro)) { newExpanded.delete(centro); } else { newExpanded.add(centro); } setExpandedCentros(newExpanded); }; 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 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 buildHierarchicalData = (): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Agrupar por grupo const grupos = data.reduce((acc, item) => { if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); return acc; }, {} as Record); // Ordenar grupos const sortedGrupos = Object.entries(grupos).sort(([a], [b]) => { if (sortConfig.field === 'descricao') { return sortConfig.direction === 'asc' ? a.localeCompare(b) : b.localeCompare(a); } else { const totalA = grupos[a].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const totalB = grupos[b].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); return sortConfig.direction === 'asc' ? totalA - totalB : totalB - totalA; } }); sortedGrupos.forEach(([grupo, items]) => { const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do grupo rows.push({ type: 'grupo', level: 0, grupo, total: totalGrupo, isExpanded: expandedGroups.has(grupo), valoresPorMes: calcularValoresPorMes(items), }); if (expandedGroups.has(grupo)) { // Agrupar por subgrupo dentro do grupo const subgrupos = items.reduce((acc, item) => { if (!acc[item.subgrupo]) { acc[item.subgrupo] = []; } acc[item.subgrupo].push(item); return acc; }, {} as Record); // Ordenar subgrupos const sortedSubgrupos = Object.entries(subgrupos).sort(([a], [b]) => { if (sortConfig.field === 'descricao') { return sortConfig.direction === 'asc' ? a.localeCompare(b) : b.localeCompare(a); } else { const totalA = subgrupos[a].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const totalB = subgrupos[b].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); return sortConfig.direction === 'asc' ? totalA - totalB : totalB - totalA; } }); sortedSubgrupos.forEach(([subgrupo, subgrupoItems]) => { const totalSubgrupo = subgrupoItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do subgrupo rows.push({ type: 'subgrupo', level: 1, grupo, subgrupo, total: totalSubgrupo, isExpanded: expandedSubgrupos.has(`${grupo}-${subgrupo}`), valoresPorMes: calcularValoresPorMes(subgrupoItems), }); if (expandedSubgrupos.has(`${grupo}-${subgrupo}`)) { // Agrupar por centro de custo dentro do subgrupo const centros = subgrupoItems.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo const sortedCentros = Object.entries(centros).sort(([a], [b]) => { if (sortConfig.field === 'descricao') { return sortConfig.direction === 'asc' ? a.localeCompare(b) : b.localeCompare(a); } else { const totalA = centros[a].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const totalB = centros[b].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); return sortConfig.direction === 'asc' ? totalA - totalB : totalB - totalA; } }); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo rows.push({ type: 'centro_custo', level: 2, grupo, subgrupo, centro_custo: centro, total: totalCentro, isExpanded: expandedCentros.has( `${grupo}-${subgrupo}-${centro}` ), valoresPorMes: calcularValoresPorMes(centroItems), }); if (expandedCentros.has(`${grupo}-${subgrupo}-${centro}`)) { // Agrupar por conta dentro do centro de custo const contas = centroItems.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas const sortedContas = Object.entries(contas).sort(([a], [b]) => { if (sortConfig.field === 'descricao') { return sortConfig.direction === 'asc' ? a.localeCompare(b) : b.localeCompare(a); } else { const totalA = contas[a].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const totalB = contas[b].reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); return sortConfig.direction === 'asc' ? totalA - totalB : totalB - totalA; } }); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (sem ano/mês no nome) rows.push({ type: 'conta', level: 3, grupo, subgrupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, total: totalConta, valoresPorMes: calcularValoresPorMes(contaItems), }); }); } }); } }); } }); return rows; }; const getRowStyle = (row: HierarchicalRow) => { const baseStyle = 'transition-colors hover:bg-muted/50'; switch (row.type) { case 'grupo': return `${baseStyle} bg-primary/5 font-semibold`; case 'subgrupo': return `${baseStyle} bg-primary/10 font-medium`; case 'centro_custo': return `${baseStyle} bg-secondary/30 font-medium`; case 'conta': return `${baseStyle} bg-muted/20`; default: return baseStyle; } }; const getIndentStyle = (level: number) => { return { paddingLeft: `${level * 20}px` }; }; const renderCellContent = (row: HierarchicalRow) => { switch (row.type) { case 'grupo': return (
{row.grupo}
); case 'subgrupo': return (
{row.subgrupo}
); case 'centro_custo': return (
{row.centro_custo}
); case 'conta': return (
{row.conta}
); default: return null; } }; if (loading) { return (

Carregando dados...

); } if (error) { return (

Erro ao carregar DRE Gerencial

{error}

); } const hierarchicalData = buildHierarchicalData(); const totalGeral = data.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); return (

DRE Gerencial - Hierárquica

Total Geral: {formatCurrency(totalGeral)}

{mesesDisponiveis.map((mes) => ( {mes} ))} {hierarchicalData.map((row, index) => ( {renderCellContent(row)} {mesesDisponiveis.map((mes) => ( {row.valoresPorMes && row.valoresPorMes[mes] ? formatCurrency(row.valoresPorMes[mes]) : '-'} ))} {formatCurrency(row.total!)} ))}
); }