commit
716af28a10
|
|
@ -106,6 +106,11 @@ const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
||||||
const [selectedValues, setSelectedValues] = React.useState<string[]>(currentFilter);
|
const [selectedValues, setSelectedValues] = React.useState<string[]>(currentFilter);
|
||||||
const [selectAll, setSelectAll] = React.useState(false);
|
const [selectAll, setSelectAll] = React.useState(false);
|
||||||
|
|
||||||
|
// Sincronizar selectedValues com currentFilter quando ele mudar
|
||||||
|
React.useEffect(() => {
|
||||||
|
setSelectedValues(currentFilter);
|
||||||
|
}, [currentFilter]);
|
||||||
|
|
||||||
// Obter valores únicos da coluna baseado nos dados filtrados
|
// Obter valores únicos da coluna baseado nos dados filtrados
|
||||||
const uniqueValues = React.useMemo(() => {
|
const uniqueValues = React.useMemo(() => {
|
||||||
const values = filteredData
|
const values = filteredData
|
||||||
|
|
@ -128,30 +133,25 @@ const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
||||||
);
|
);
|
||||||
}, [uniqueValues, searchTerm]);
|
}, [uniqueValues, searchTerm]);
|
||||||
|
|
||||||
// Sincronizar estado local com filtros atuais
|
|
||||||
React.useEffect(() => {
|
|
||||||
setSelectedValues(currentFilter);
|
|
||||||
}, [currentFilter]);
|
|
||||||
|
|
||||||
// Verificar se todos estão selecionados
|
|
||||||
React.useEffect(() => {
|
|
||||||
setSelectAll(selectedValues.length === filteredValues.length && filteredValues.length > 0);
|
|
||||||
}, [selectedValues, filteredValues]);
|
|
||||||
|
|
||||||
const handleSelectAll = (checked: boolean) => {
|
const handleSelectAll = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSelectedValues(filteredValues);
|
setSelectedValues(filteredValues);
|
||||||
|
setSelectAll(true);
|
||||||
} else {
|
} else {
|
||||||
setSelectedValues([]);
|
setSelectedValues([]);
|
||||||
|
setSelectAll(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValueToggle = (value: string, checked: boolean) => {
|
const handleValueToggle = (value: string, checked: boolean) => {
|
||||||
|
let newValues: string[];
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSelectedValues([...selectedValues, value]);
|
newValues = [...selectedValues, value];
|
||||||
} else {
|
} else {
|
||||||
setSelectedValues(selectedValues.filter((v) => v !== value));
|
newValues = selectedValues.filter((v) => v !== value);
|
||||||
}
|
}
|
||||||
|
setSelectedValues(newValues);
|
||||||
|
setSelectAll(newValues.length === filteredValues.length && filteredValues.length > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
|
|
@ -294,6 +294,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
{ column: "", operator: "contains", value: "" },
|
{ column: "", operator: "contains", value: "" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Estados para o card de agregação customizado (simplificado)
|
||||||
|
const [aggregationCardRef, setAggregationCardRef] = React.useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// Estado para armazenar filtros externos (vindos do teste.tsx)
|
// Estado para armazenar filtros externos (vindos do teste.tsx)
|
||||||
const [filtrosExternos, setFiltrosExternos] = React.useState(filtros);
|
const [filtrosExternos, setFiltrosExternos] = React.useState(filtros);
|
||||||
|
|
||||||
|
|
@ -426,6 +429,26 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
}));
|
}));
|
||||||
}, [data, columnFilters]);
|
}, [data, columnFilters]);
|
||||||
|
|
||||||
|
// Função para renderizar header com filtro Excel
|
||||||
|
const renderHeaderWithFilter = React.useCallback((column: GridColDef) => {
|
||||||
|
return (params: any) => (
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span className="text-sm font-medium">{column.headerName}</span>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<ExcelFilter
|
||||||
|
column={column}
|
||||||
|
data={data}
|
||||||
|
filteredData={filteredData}
|
||||||
|
onFilterChange={handleColumnFilterChange}
|
||||||
|
onSortChange={handleColumnSortChange}
|
||||||
|
currentFilter={columnFilters[column.field] || []}
|
||||||
|
currentSort={columnSorts[column.field] || null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [data, filteredData, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
||||||
|
|
||||||
// Definir colunas do DataGridPro
|
// Definir colunas do DataGridPro
|
||||||
const columns = React.useMemo(() => {
|
const columns = React.useMemo(() => {
|
||||||
const baseColumns = [
|
const baseColumns = [
|
||||||
|
|
@ -511,7 +534,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
width: 140,
|
width: 140,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
aggregable: true,
|
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const value = params.value;
|
const value = params.value;
|
||||||
if (value === null || value === undefined || value === "") return "-";
|
if (value === null || value === undefined || value === "") return "-";
|
||||||
|
|
@ -535,7 +557,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
width: 130,
|
width: 130,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
aggregable: true,
|
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const value = params.value;
|
const value = params.value;
|
||||||
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
||||||
|
|
@ -559,7 +580,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
width: 140,
|
width: 140,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
aggregable: true,
|
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const value = params.value;
|
const value = params.value;
|
||||||
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
||||||
|
|
@ -583,7 +603,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
width: 130,
|
width: 130,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
aggregable: true,
|
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const value = params.value;
|
const value = params.value;
|
||||||
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
||||||
|
|
@ -629,24 +648,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
// Adicionar renderHeader com filtro Excel para todas as colunas
|
// Adicionar renderHeader com filtro Excel para todas as colunas
|
||||||
return baseColumns.map((col) => ({
|
return baseColumns.map((col) => ({
|
||||||
...col,
|
...col,
|
||||||
renderHeader: (params: any) => (
|
renderHeader: renderHeaderWithFilter(col),
|
||||||
<div className="flex items-center justify-between w-full">
|
|
||||||
<span className="text-sm font-medium">{col.headerName}</span>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ExcelFilter
|
|
||||||
column={col}
|
|
||||||
data={data}
|
|
||||||
filteredData={filteredData}
|
|
||||||
onFilterChange={handleColumnFilterChange}
|
|
||||||
onSortChange={handleColumnSortChange}
|
|
||||||
currentFilter={columnFilters[col.field] || []}
|
|
||||||
currentSort={columnSorts[col.field] || null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
}, [data, filteredData, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
}, [renderHeaderWithFilter]);
|
||||||
|
|
||||||
// Ordenar dados baseado na ordenação de coluna
|
// Ordenar dados baseado na ordenação de coluna
|
||||||
const sortedAndFilteredData = React.useMemo(() => {
|
const sortedAndFilteredData = React.useMemo(() => {
|
||||||
|
|
@ -676,6 +680,16 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
|
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
|
||||||
}, [sortedAndFilteredData]);
|
}, [sortedAndFilteredData]);
|
||||||
|
|
||||||
|
// Calcular totais das colunas de valor para o card de agregação
|
||||||
|
const columnTotals = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
valor: sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0),
|
||||||
|
valor_previsto: sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor_previsto) || 0), 0),
|
||||||
|
valor_confirmado: sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor_confirmado) || 0), 0),
|
||||||
|
valor_pago: sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor_pago) || 0), 0),
|
||||||
|
};
|
||||||
|
}, [sortedAndFilteredData]);
|
||||||
|
|
||||||
// Limpar filtros de colunas que não têm mais valores disponíveis
|
// Limpar filtros de colunas que não têm mais valores disponíveis
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const updatedFilters = { ...columnFilters };
|
const updatedFilters = { ...columnFilters };
|
||||||
|
|
@ -771,7 +785,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-none mx-auto p-2">
|
<div className="w-full max-w-none mx-auto p-2" style={{ overflowAnchor: 'none' }}>
|
||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
|
@ -880,8 +894,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* DataGridPro */}
|
{/* DataGridPro */}
|
||||||
<Card className="w-full h-[40vh] shadow-lg rounded-2xl">
|
<Card className="w-full h-[40vh] shadow-lg rounded-2xl" style={{ overflowAnchor: 'none' }}>
|
||||||
<CardContent className="p-4 h-full">
|
<CardContent className="p-4 h-full" style={{ overflowAnchor: 'none' }}>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
|
|
@ -911,18 +925,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
disableColumnSorting={true}
|
disableColumnSorting={true}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
disableVirtualization={false}
|
disableVirtualization={false}
|
||||||
initialState={{
|
|
||||||
aggregation: {
|
|
||||||
model: {
|
|
||||||
valor: 'sum',
|
|
||||||
valor_previsto: 'sum',
|
|
||||||
valor_confirmado: 'sum',
|
|
||||||
valor_pago: 'sum',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
getRowId={(row: any) => row.id || `row-${row.recnum || Math.random()}`}
|
getRowId={(row: any) => row.id || `row-${row.recnum || Math.random()}`}
|
||||||
sx={{
|
sx={{
|
||||||
|
overflowAnchor: 'none',
|
||||||
"& .MuiDataGrid-columnHeaders": {
|
"& .MuiDataGrid-columnHeaders": {
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|
@ -936,7 +941,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
},
|
},
|
||||||
"& .MuiDataGrid-virtualScroller": {
|
"& .MuiDataGrid-virtualScroller": {
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
maxHeight: "calc(40vh - 120px)"
|
maxHeight: "calc(40vh - 120px)",
|
||||||
|
overflowAnchor: 'none'
|
||||||
},
|
},
|
||||||
"& .MuiDataGrid-toolbarContainer": {
|
"& .MuiDataGrid-toolbarContainer": {
|
||||||
backgroundColor: "#f8fafc",
|
backgroundColor: "#f8fafc",
|
||||||
|
|
@ -1004,7 +1010,66 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Card de Agregação Customizado - Usando position: sticky */}
|
||||||
|
{sortedAndFilteredData.length > 0 && (
|
||||||
|
<div
|
||||||
|
ref={setAggregationCardRef}
|
||||||
|
className="w-full bg-gray-50 border-t border-gray-200 sticky bottom-0 z-50 shadow-lg"
|
||||||
|
style={{ overflowAnchor: 'none' }}
|
||||||
|
>
|
||||||
|
<div className="px-4 py-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
Vl.Realizado:
|
||||||
|
<span className={`ml-2 font-semibold ${columnTotals.valor < 0 ? 'text-red-600' : 'text-gray-900'}`}>
|
||||||
|
{new Intl.NumberFormat("pt-BR", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "BRL",
|
||||||
|
}).format(columnTotals.valor)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
Vl.Previsto:
|
||||||
|
<span className={`ml-2 font-semibold ${columnTotals.valor_previsto < 0 ? 'text-red-600' : 'text-gray-900'}`}>
|
||||||
|
{new Intl.NumberFormat("pt-BR", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "BRL",
|
||||||
|
}).format(columnTotals.valor_previsto)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
Vl.Confirmado:
|
||||||
|
<span className={`ml-2 font-semibold ${columnTotals.valor_confirmado < 0 ? 'text-red-600' : 'text-gray-900'}`}>
|
||||||
|
{new Intl.NumberFormat("pt-BR", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "BRL",
|
||||||
|
}).format(columnTotals.valor_confirmado)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm font-medium text-gray-700">
|
||||||
|
Vl.Pago:
|
||||||
|
<span className={`ml-2 font-semibold ${columnTotals.valor_pago < 0 ? 'text-red-600' : 'text-gray-900'}`}>
|
||||||
|
{new Intl.NumberFormat("pt-BR", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "BRL",
|
||||||
|
}).format(columnTotals.valor_pago)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
Total de Registros: <span className="font-semibold text-blue-600">{sortedAndFilteredData.length}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -1052,7 +1117,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
|
@ -1145,7 +1210,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
{getFilterCount() > 0 && (
|
{getFilterCount() > 0 && (
|
||||||
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
||||||
{getFilterCount()}
|
{getFilterCount()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -2214,10 +2214,8 @@ export default function Teste() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Componente Analítico */}
|
{/* Componente Analítico - Sempre renderizado para evitar violação das Rules of Hooks */}
|
||||||
{!loading && data.length > 0 && (
|
<AnaliticoComponent filtros={analiticoFiltros} />
|
||||||
<AnaliticoComponent filtros={analiticoFiltros} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue