fix: otimização do processo de expansão do drill da tabela sintética
This commit is contained in:
parent
362d422fce
commit
324730c830
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LoaderPinwheel, ChevronDown, ChevronRight, Filter, Maximize2, Minimize2 } from "lucide-react";
|
import { LoaderPinwheel, ChevronDown, ChevronRight, Filter, Maximize2, Minimize2 } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback, startTransition, memo } from "react";
|
||||||
import AnaliticoComponent from "./analitico";
|
import AnaliticoComponent from "./analitico";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -53,6 +53,123 @@ interface HierarchicalRow {
|
||||||
isCalculado?: boolean;
|
isCalculado?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Componente memoizado para linhas da tabela
|
||||||
|
const TableRow = memo(({
|
||||||
|
row,
|
||||||
|
index,
|
||||||
|
toggleGroup,
|
||||||
|
toggleSubgrupo,
|
||||||
|
toggleCentro,
|
||||||
|
handleRowClick,
|
||||||
|
getRowStyle,
|
||||||
|
getIndentStyle,
|
||||||
|
renderCellContent,
|
||||||
|
mesesDisponiveis,
|
||||||
|
formatCurrency,
|
||||||
|
formatCurrencyWithColor
|
||||||
|
}: {
|
||||||
|
row: HierarchicalRow;
|
||||||
|
index: number;
|
||||||
|
toggleGroup: (grupo: string) => void;
|
||||||
|
toggleSubgrupo: (subgrupo: string) => void;
|
||||||
|
toggleCentro: (centro: string) => void;
|
||||||
|
handleRowClick: (row: HierarchicalRow, mes?: string) => void;
|
||||||
|
getRowStyle: (row: HierarchicalRow) => string;
|
||||||
|
getIndentStyle: (level: number) => React.CSSProperties;
|
||||||
|
renderCellContent: (row: HierarchicalRow) => React.ReactNode;
|
||||||
|
mesesDisponiveis: string[];
|
||||||
|
formatCurrency: (value: number) => string;
|
||||||
|
formatCurrencyWithColor: (value: number) => { formatted: string; isNegative: boolean };
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`flex items-center gap-2 px-4 py-1 text-sm border-b border-gray-100 hover:bg-gray-50 transition-all duration-200 ease-in-out ${getRowStyle(
|
||||||
|
row
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex-1 min-w-[300px] max-w-[400px] whitespace-nowrap overflow-hidden"
|
||||||
|
style={getIndentStyle(row.level)}
|
||||||
|
>
|
||||||
|
{renderCellContent(row)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Colunas de valores por mês */}
|
||||||
|
{mesesDisponiveis.map((mes) => (
|
||||||
|
<div key={mes} className="flex min-w-[240px] max-w-[300px] gap-2">
|
||||||
|
<div
|
||||||
|
className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
||||||
|
onClick={() => handleRowClick(row, mes)}
|
||||||
|
title={
|
||||||
|
row.valoresPorMes && row.valoresPorMes[mes]
|
||||||
|
? formatCurrency(row.valoresPorMes[mes])
|
||||||
|
: "-"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{row.valoresPorMes && row.valoresPorMes[mes]
|
||||||
|
? (() => {
|
||||||
|
const { formatted, isNegative } =
|
||||||
|
formatCurrencyWithColor(row.valoresPorMes[mes]);
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
isNegative
|
||||||
|
? "text-red-600 font-bold"
|
||||||
|
: "text-gray-900"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatted}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
: "-"}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex-1 min-w-[100px] text-center font-medium cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
||||||
|
onClick={() => handleRowClick(row, mes)}
|
||||||
|
title={
|
||||||
|
row.percentuaisPorMes &&
|
||||||
|
row.percentuaisPorMes[mes] !== undefined
|
||||||
|
? `${row.percentuaisPorMes[mes].toFixed(1)}%`
|
||||||
|
: "-"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{row.percentuaisPorMes &&
|
||||||
|
row.percentuaisPorMes[mes] !== undefined
|
||||||
|
? `${row.percentuaisPorMes[mes].toFixed(1)}%`
|
||||||
|
: "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Coluna Total */}
|
||||||
|
<div
|
||||||
|
className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
||||||
|
onClick={() => handleRowClick(row)}
|
||||||
|
title={row.total ? formatCurrency(row.total) : "-"}
|
||||||
|
>
|
||||||
|
{(() => {
|
||||||
|
const { formatted, isNegative } = formatCurrencyWithColor(
|
||||||
|
row.total!
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
isNegative ? "text-red-600 font-bold" : "text-gray-900"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatted}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TableRow.displayName = 'TableRow';
|
||||||
|
|
||||||
export default function Teste() {
|
export default function Teste() {
|
||||||
const [data, setData] = useState<DREItem[]>([]);
|
const [data, setData] = useState<DREItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
@ -277,35 +394,41 @@ export default function Teste() {
|
||||||
setAnaliticoFiltros(novosFiltros);
|
setAnaliticoFiltros(novosFiltros);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleGroup = (grupo: string) => {
|
const toggleGroup = useCallback((grupo: string) => {
|
||||||
const newExpanded = new Set(expandedGroups);
|
setExpandedGroups(prev => {
|
||||||
|
const newExpanded = new Set(prev);
|
||||||
if (newExpanded.has(grupo)) {
|
if (newExpanded.has(grupo)) {
|
||||||
newExpanded.delete(grupo);
|
newExpanded.delete(grupo);
|
||||||
} else {
|
} else {
|
||||||
newExpanded.add(grupo);
|
newExpanded.add(grupo);
|
||||||
}
|
}
|
||||||
setExpandedGroups(newExpanded);
|
return newExpanded;
|
||||||
};
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const toggleSubgrupo = (subgrupo: string) => {
|
const toggleSubgrupo = useCallback((subgrupo: string) => {
|
||||||
const newExpanded = new Set(expandedSubgrupos);
|
setExpandedSubgrupos(prev => {
|
||||||
|
const newExpanded = new Set(prev);
|
||||||
if (newExpanded.has(subgrupo)) {
|
if (newExpanded.has(subgrupo)) {
|
||||||
newExpanded.delete(subgrupo);
|
newExpanded.delete(subgrupo);
|
||||||
} else {
|
} else {
|
||||||
newExpanded.add(subgrupo);
|
newExpanded.add(subgrupo);
|
||||||
}
|
}
|
||||||
setExpandedSubgrupos(newExpanded);
|
return newExpanded;
|
||||||
};
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const toggleCentro = (centro: string) => {
|
const toggleCentro = useCallback((centro: string) => {
|
||||||
const newExpanded = new Set(expandedCentros);
|
setExpandedCentros(prev => {
|
||||||
|
const newExpanded = new Set(prev);
|
||||||
if (newExpanded.has(centro)) {
|
if (newExpanded.has(centro)) {
|
||||||
newExpanded.delete(centro);
|
newExpanded.delete(centro);
|
||||||
} else {
|
} else {
|
||||||
newExpanded.add(centro);
|
newExpanded.add(centro);
|
||||||
}
|
}
|
||||||
setExpandedCentros(newExpanded);
|
return newExpanded;
|
||||||
};
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleFiltroChange = (campo: string, valor: string) => {
|
const handleFiltroChange = (campo: string, valor: string) => {
|
||||||
setFiltros(prev => ({
|
setFiltros(prev => ({
|
||||||
|
|
@ -351,28 +474,29 @@ export default function Teste() {
|
||||||
setContasSelecionadas([]);
|
setContasSelecionadas([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleExpandAll = () => {
|
const toggleExpandAll = useCallback(() => {
|
||||||
if (isAllExpanded) {
|
if (isAllExpanded) {
|
||||||
// Recolher tudo
|
// Recolher tudo - usar startTransition para atualizações não urgentes
|
||||||
|
startTransition(() => {
|
||||||
setExpandedGroups(new Set());
|
setExpandedGroups(new Set());
|
||||||
setExpandedSubgrupos(new Set());
|
setExpandedSubgrupos(new Set());
|
||||||
setExpandedCentros(new Set());
|
setExpandedCentros(new Set());
|
||||||
setIsAllExpanded(false);
|
setIsAllExpanded(false);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Expandir todos os grupos usando dados originais
|
// Expandir todos os grupos usando dados originais - usar startTransition para atualizações não urgentes
|
||||||
|
startTransition(() => {
|
||||||
const todosGrupos = [...new Set(data.map(item => item.grupo))];
|
const todosGrupos = [...new Set(data.map(item => item.grupo))];
|
||||||
setExpandedGroups(new Set(todosGrupos));
|
|
||||||
|
|
||||||
// Expandir todos os subgrupos usando dados originais
|
|
||||||
const todosSubgrupos = [...new Set(data.map(item => `${item.grupo}-${item.subgrupo}`))];
|
const todosSubgrupos = [...new Set(data.map(item => `${item.grupo}-${item.subgrupo}`))];
|
||||||
setExpandedSubgrupos(new Set(todosSubgrupos));
|
|
||||||
|
|
||||||
// Expandir todos os centros de custo usando dados originais (isso também expande as contas automaticamente)
|
|
||||||
const todosCentros = [...new Set(data.map(item => `${item.grupo}-${item.subgrupo}-${item.centro_custo}`))];
|
const todosCentros = [...new Set(data.map(item => `${item.grupo}-${item.subgrupo}-${item.centro_custo}`))];
|
||||||
|
|
||||||
|
setExpandedGroups(new Set(todosGrupos));
|
||||||
|
setExpandedSubgrupos(new Set(todosSubgrupos));
|
||||||
setExpandedCentros(new Set(todosCentros));
|
setExpandedCentros(new Set(todosCentros));
|
||||||
setIsAllExpanded(true);
|
setIsAllExpanded(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
}, [isAllExpanded, data]);
|
||||||
|
|
||||||
const limparFiltros = () => {
|
const limparFiltros = () => {
|
||||||
const agora = new Date();
|
const agora = new Date();
|
||||||
|
|
@ -802,12 +926,12 @@ export default function Teste() {
|
||||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleGroup(row.grupo!)}
|
onClick={() => toggleGroup(row.grupo!)}
|
||||||
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
|
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-150 ease-in-out flex items-center justify-center w-8 h-8 flex-shrink-0 transform hover:scale-105"
|
||||||
>
|
>
|
||||||
{row.isExpanded ? (
|
{row.isExpanded ? (
|
||||||
<ChevronDown className="w-4 h-4 text-blue-600" />
|
<ChevronDown className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight className="w-4 h-4 text-blue-600" />
|
<ChevronRight className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -831,12 +955,12 @@ export default function Teste() {
|
||||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleSubgrupo(`${row.grupo}-${row.subgrupo}`)}
|
onClick={() => toggleSubgrupo(`${row.grupo}-${row.subgrupo}`)}
|
||||||
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
|
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-150 ease-in-out flex items-center justify-center w-8 h-8 flex-shrink-0 transform hover:scale-105"
|
||||||
>
|
>
|
||||||
{row.isExpanded ? (
|
{row.isExpanded ? (
|
||||||
<ChevronDown className="w-4 h-4 text-blue-600" />
|
<ChevronDown className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight className="w-4 h-4 text-blue-600" />
|
<ChevronRight className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -856,12 +980,12 @@ export default function Teste() {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
toggleCentro(`${row.grupo}-${row.subgrupo}-${row.centro_custo}`)
|
toggleCentro(`${row.grupo}-${row.subgrupo}-${row.centro_custo}`)
|
||||||
}
|
}
|
||||||
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-200 flex items-center justify-center w-8 h-8 flex-shrink-0"
|
className="p-2 hover:bg-blue-100 rounded-lg transition-all duration-150 ease-in-out flex items-center justify-center w-8 h-8 flex-shrink-0 transform hover:scale-105"
|
||||||
>
|
>
|
||||||
{row.isExpanded ? (
|
{row.isExpanded ? (
|
||||||
<ChevronDown className="w-4 h-4 text-blue-600" />
|
<ChevronDown className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight className="w-4 h-4 text-blue-600" />
|
<ChevronRight className="w-4 h-4 text-blue-600 transition-transform duration-150" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -921,7 +1045,7 @@ export default function Teste() {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={toggleExpandAll}
|
onClick={toggleExpandAll}
|
||||||
disabled={!filtrosAplicados || hierarchicalData.length === 0}
|
disabled={!filtrosAplicados || hierarchicalData.length === 0}
|
||||||
className="flex items-center gap-2 text-xs h-8 px-3"
|
className="flex items-center gap-2 text-xs h-8 px-3 transition-all duration-150 ease-in-out hover:scale-105 disabled:hover:scale-100"
|
||||||
>
|
>
|
||||||
{isAllExpanded ? (
|
{isAllExpanded ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -1263,85 +1387,21 @@ export default function Teste() {
|
||||||
{/* Table Body */}
|
{/* Table Body */}
|
||||||
<div className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
|
<div className="max-h-[500px] overflow-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
|
||||||
{hierarchicalData.map((row, index) => (
|
{hierarchicalData.map((row, index) => (
|
||||||
<div
|
<TableRow
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex items-center gap-2 px-4 py-1 text-sm border-b border-gray-100 hover:bg-gray-50 transition-colors ${getRowStyle(
|
row={row}
|
||||||
row
|
index={index}
|
||||||
)}`}
|
toggleGroup={toggleGroup}
|
||||||
>
|
toggleSubgrupo={toggleSubgrupo}
|
||||||
<div
|
toggleCentro={toggleCentro}
|
||||||
className="flex-1 min-w-[300px] max-w-[400px] whitespace-nowrap overflow-hidden"
|
handleRowClick={handleRowClick}
|
||||||
style={getIndentStyle(row.level)}
|
getRowStyle={getRowStyle}
|
||||||
>
|
getIndentStyle={getIndentStyle}
|
||||||
{renderCellContent(row)}
|
renderCellContent={renderCellContent}
|
||||||
</div>
|
mesesDisponiveis={mesesDisponiveis}
|
||||||
{mesesDisponiveis.map((mes) => (
|
formatCurrency={formatCurrency}
|
||||||
<div key={mes} className="flex min-w-[240px] max-w-[300px] gap-2">
|
formatCurrencyWithColor={formatCurrencyWithColor}
|
||||||
<div
|
/>
|
||||||
className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
|
||||||
onClick={() => handleRowClick(row, mes)}
|
|
||||||
title={
|
|
||||||
row.valoresPorMes && row.valoresPorMes[mes]
|
|
||||||
? formatCurrency(row.valoresPorMes[mes])
|
|
||||||
: "-"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{row.valoresPorMes && row.valoresPorMes[mes]
|
|
||||||
? (() => {
|
|
||||||
const { formatted, isNegative } =
|
|
||||||
formatCurrencyWithColor(row.valoresPorMes[mes]);
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
isNegative
|
|
||||||
? "text-red-600 font-bold"
|
|
||||||
: "text-gray-900"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatted}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
: "-"}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex-1 min-w-[100px] text-center font-medium cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
|
||||||
onClick={() => handleRowClick(row, mes)}
|
|
||||||
title={
|
|
||||||
row.percentuaisPorMes &&
|
|
||||||
row.percentuaisPorMes[mes] !== undefined
|
|
||||||
? `${row.percentuaisPorMes[mes].toFixed(1)}%`
|
|
||||||
: "-"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{row.percentuaisPorMes &&
|
|
||||||
row.percentuaisPorMes[mes] !== undefined
|
|
||||||
? `${row.percentuaisPorMes[mes].toFixed(1)}%`
|
|
||||||
: "-"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div
|
|
||||||
className="flex-1 min-w-[120px] text-right font-semibold cursor-pointer hover:bg-blue-50/50 transition-colors duration-200 whitespace-nowrap overflow-hidden"
|
|
||||||
onClick={() => handleRowClick(row)}
|
|
||||||
title={row.total ? formatCurrency(row.total) : "-"}
|
|
||||||
>
|
|
||||||
{(() => {
|
|
||||||
const { formatted, isNegative } = formatCurrencyWithColor(
|
|
||||||
row.total!
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
isNegative ? "text-red-600 font-bold" : "text-gray-900"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatted}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue