|
|
|
|
@ -1,7 +1,19 @@
|
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import * as React from "react";
|
|
|
|
|
import { DataGrid, GridToolbar, GridColDef, GridFilterModel } from "@mui/x-data-grid";
|
|
|
|
|
import { DataGridPremium, GridToolbar, GridColDef, GridFilterModel } from "@mui/x-data-grid-premium";
|
|
|
|
|
import { LicenseInfo } from '@mui/x-license-pro';
|
|
|
|
|
|
|
|
|
|
// Garantir que a licença seja aplicada no componente
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
try {
|
|
|
|
|
const PERPETUAL_LICENSE_KEY = 'e0d9bb8070ce0054c9d9ecb6e82cb58fTz0wLEU9MzI0NzIxNDQwMDAwMDAsUz1wcmVtaXVtLExNPXBlcnBldHVhbCxLVj0y';
|
|
|
|
|
LicenseInfo.setLicenseKey(PERPETUAL_LICENSE_KEY);
|
|
|
|
|
console.log('✅ Licença MUI X aplicada no componente Analítico');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('⚠️ Erro ao aplicar licença no componente:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
@ -71,6 +83,7 @@ interface AnaliticoProps {
|
|
|
|
|
interface ExcelFilterProps {
|
|
|
|
|
column: GridColDef;
|
|
|
|
|
data: any[];
|
|
|
|
|
filteredData: any[]; // Dados filtrados para mostrar apenas valores disponíveis
|
|
|
|
|
onFilterChange: (field: string, values: string[]) => void;
|
|
|
|
|
onSortChange: (field: string, direction: 'asc' | 'desc' | null) => void;
|
|
|
|
|
currentFilter?: string[];
|
|
|
|
|
@ -80,6 +93,7 @@ interface ExcelFilterProps {
|
|
|
|
|
const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
|
|
|
|
column,
|
|
|
|
|
data,
|
|
|
|
|
filteredData,
|
|
|
|
|
onFilterChange,
|
|
|
|
|
onSortChange,
|
|
|
|
|
currentFilter = [],
|
|
|
|
|
@ -90,9 +104,9 @@ const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
|
|
|
|
const [selectedValues, setSelectedValues] = React.useState<string[]>(currentFilter);
|
|
|
|
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
|
|
|
|
|
|
|
|
// Obter valores únicos da coluna
|
|
|
|
|
// Obter valores únicos da coluna baseado nos dados filtrados
|
|
|
|
|
const uniqueValues = React.useMemo(() => {
|
|
|
|
|
const values = data
|
|
|
|
|
const values = filteredData
|
|
|
|
|
.map((row) => {
|
|
|
|
|
const value = row[column.field];
|
|
|
|
|
if (value === null || value === undefined) return "";
|
|
|
|
|
@ -102,7 +116,7 @@ const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
|
|
|
|
.sort();
|
|
|
|
|
|
|
|
|
|
return values;
|
|
|
|
|
}, [data, column.field]);
|
|
|
|
|
}, [filteredData, column.field]);
|
|
|
|
|
|
|
|
|
|
// Filtrar valores baseado na busca
|
|
|
|
|
const filteredValues = React.useMemo(() => {
|
|
|
|
|
@ -274,8 +288,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
const [open, setOpen] = React.useState(false);
|
|
|
|
|
const [columnFilters, setColumnFilters] = React.useState<Record<string, string[]>>({});
|
|
|
|
|
const [columnSorts, setColumnSorts] = React.useState<Record<string, 'asc' | 'desc' | null>>({});
|
|
|
|
|
const [isFooterAnchored, setIsFooterAnchored] = React.useState(false);
|
|
|
|
|
const footerRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
|
const [conditions, setConditions] = React.useState([
|
|
|
|
|
{ column: "", operator: "contains", value: "" },
|
|
|
|
|
]);
|
|
|
|
|
@ -298,6 +310,26 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
}));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Função para contar filtros aplicados
|
|
|
|
|
const getFilterCount = React.useCallback(() => {
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
|
|
|
|
// Contar filtros de coluna
|
|
|
|
|
count += Object.keys(columnFilters).length;
|
|
|
|
|
|
|
|
|
|
// Contar filtro global
|
|
|
|
|
if (globalFilter && globalFilter.trim() !== "") {
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Contar filtros externos (se aplicáveis)
|
|
|
|
|
if (filtrosExternos.codigoConta || filtrosExternos.centroCusto) {
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}, [columnFilters, globalFilter, filtrosExternos]);
|
|
|
|
|
|
|
|
|
|
// Função para limpar todos os filtros
|
|
|
|
|
const clearAllFilters = React.useCallback(() => {
|
|
|
|
|
setColumnFilters({});
|
|
|
|
|
@ -371,6 +403,25 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
fetchData();
|
|
|
|
|
}, [fetchData]);
|
|
|
|
|
|
|
|
|
|
// Filtrar dados baseado nos filtros de coluna
|
|
|
|
|
const filteredData = React.useMemo(() => {
|
|
|
|
|
if (!data || data.length === 0) return data;
|
|
|
|
|
|
|
|
|
|
return data.filter((row) => {
|
|
|
|
|
return Object.entries(columnFilters).every(([field, filterValues]) => {
|
|
|
|
|
if (!filterValues || filterValues.length === 0) return true;
|
|
|
|
|
|
|
|
|
|
const cellValue = (row as any)[field];
|
|
|
|
|
const stringValue = cellValue === null || cellValue === undefined ? "" : String(cellValue);
|
|
|
|
|
|
|
|
|
|
return filterValues.includes(stringValue);
|
|
|
|
|
});
|
|
|
|
|
}).map((row, index) => ({
|
|
|
|
|
...row,
|
|
|
|
|
id: `filtered-${row.id || row.recnum || index}` // Garantir ID único e estável
|
|
|
|
|
}));
|
|
|
|
|
}, [data, columnFilters]);
|
|
|
|
|
|
|
|
|
|
// Definir colunas do DataGridPro
|
|
|
|
|
const columns = React.useMemo(() => {
|
|
|
|
|
const baseColumns = [
|
|
|
|
|
@ -449,14 +500,15 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "valor",
|
|
|
|
|
headerName: "Vl.Realizado",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 140,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
{
|
|
|
|
|
field: "valor",
|
|
|
|
|
headerName: "Vl.Realizado",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 140,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
aggregable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
const value = params.value;
|
|
|
|
|
if (value === null || value === undefined || value === "") return "-";
|
|
|
|
|
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
|
|
|
@ -466,20 +518,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(numValue);
|
|
|
|
|
return (
|
|
|
|
|
<span className={numValue < 0 ? "text-red-600" : "text-gray-900"}>
|
|
|
|
|
<span className={`text-sm font-semibold ${numValue < 0 ? "text-red-600" : "text-gray-900"}`}>
|
|
|
|
|
{formatted}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "valor_previsto",
|
|
|
|
|
headerName: "Vl.Previsto",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 130,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
{
|
|
|
|
|
field: "valor_previsto",
|
|
|
|
|
headerName: "Vl.Previsto",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 130,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
aggregable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
const value = params.value;
|
|
|
|
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
|
|
|
|
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
|
|
|
@ -489,20 +542,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(numValue);
|
|
|
|
|
return (
|
|
|
|
|
<span className={numValue < 0 ? "text-red-600" : "text-gray-900"}>
|
|
|
|
|
<span className={`text-sm font-semibold ${numValue < 0 ? "text-red-600" : "text-gray-900"}`}>
|
|
|
|
|
{formatted}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "valor_confirmado",
|
|
|
|
|
headerName: "Vl.Confirmado",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 140,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
{
|
|
|
|
|
field: "valor_confirmado",
|
|
|
|
|
headerName: "Vl.Confirmado",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 140,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
aggregable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
const value = params.value;
|
|
|
|
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
|
|
|
|
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
|
|
|
@ -512,20 +566,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(numValue);
|
|
|
|
|
return (
|
|
|
|
|
<span className={numValue < 0 ? "text-red-600" : "text-gray-900"}>
|
|
|
|
|
<span className={`text-sm font-semibold ${numValue < 0 ? "text-red-600" : "text-gray-900"}`}>
|
|
|
|
|
{formatted}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: "valor_pago",
|
|
|
|
|
headerName: "Vl.Pago",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 130,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
{
|
|
|
|
|
field: "valor_pago",
|
|
|
|
|
headerName: "Vl.Pago",
|
|
|
|
|
type: "number" as const,
|
|
|
|
|
width: 130,
|
|
|
|
|
sortable: true,
|
|
|
|
|
resizable: true,
|
|
|
|
|
aggregable: true,
|
|
|
|
|
renderCell: (params: any) => {
|
|
|
|
|
const value = params.value;
|
|
|
|
|
if (value === null || value === undefined || value === "" || value === 0) return "-";
|
|
|
|
|
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
|
|
|
@ -535,7 +590,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(numValue);
|
|
|
|
|
return (
|
|
|
|
|
<span className={numValue < 0 ? "text-red-600" : "text-gray-900"}>
|
|
|
|
|
<span className={`text-sm font-semibold ${numValue < 0 ? "text-red-600" : "text-gray-900"}`}>
|
|
|
|
|
{formatted}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
@ -577,6 +632,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
<ExcelFilter
|
|
|
|
|
column={col}
|
|
|
|
|
data={data}
|
|
|
|
|
filteredData={filteredData}
|
|
|
|
|
onFilterChange={handleColumnFilterChange}
|
|
|
|
|
onSortChange={handleColumnSortChange}
|
|
|
|
|
currentFilter={columnFilters[col.field] || []}
|
|
|
|
|
@ -586,26 +642,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
}, [data, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
|
|
|
|
|
|
|
|
|
// Filtrar dados baseado nos filtros de coluna
|
|
|
|
|
const filteredData = React.useMemo(() => {
|
|
|
|
|
if (!data || data.length === 0) return data;
|
|
|
|
|
|
|
|
|
|
return data.filter((row) => {
|
|
|
|
|
return Object.entries(columnFilters).every(([field, filterValues]) => {
|
|
|
|
|
if (!filterValues || filterValues.length === 0) return true;
|
|
|
|
|
|
|
|
|
|
const cellValue = (row as any)[field];
|
|
|
|
|
const stringValue = cellValue === null || cellValue === undefined ? "" : String(cellValue);
|
|
|
|
|
|
|
|
|
|
return filterValues.includes(stringValue);
|
|
|
|
|
});
|
|
|
|
|
}).map((row, index) => ({
|
|
|
|
|
...row,
|
|
|
|
|
id: `filtered-${row.id || row.recnum || index}` // Garantir ID único e estável
|
|
|
|
|
}));
|
|
|
|
|
}, [data, columnFilters]);
|
|
|
|
|
}, [data, filteredData, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
|
|
|
|
|
|
|
|
|
// Ordenar dados baseado na ordenação de coluna
|
|
|
|
|
const sortedAndFilteredData = React.useMemo(() => {
|
|
|
|
|
@ -630,68 +667,48 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
});
|
|
|
|
|
}, [filteredData, columnSorts]);
|
|
|
|
|
|
|
|
|
|
// Detectar se o footer está sendo coberto e ancorá-lo na base da tela
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (!footerRef.current) return;
|
|
|
|
|
|
|
|
|
|
const observer = new IntersectionObserver(
|
|
|
|
|
(entries) => {
|
|
|
|
|
const [entry] = entries;
|
|
|
|
|
// Se o footer não está visível (está sendo coberto), ancora na base
|
|
|
|
|
setIsFooterAnchored(!entry.isIntersecting);
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
threshold: 0.1, // Detectar quando 10% do footer está visível
|
|
|
|
|
rootMargin: '0px 0px -50px 0px' // Margem para detectar antes de ser completamente coberto
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
observer.observe(footerRef.current);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
observer.disconnect();
|
|
|
|
|
};
|
|
|
|
|
}, [sortedAndFilteredData.length]); // Re-executar quando os dados mudarem
|
|
|
|
|
|
|
|
|
|
// Calcular totais das colunas de valores (usando dados filtrados)
|
|
|
|
|
const columnTotals = React.useMemo(() => {
|
|
|
|
|
if (!sortedAndFilteredData || sortedAndFilteredData.length === 0) {
|
|
|
|
|
return {
|
|
|
|
|
valorRealizado: 0,
|
|
|
|
|
valorPrevisto: 0,
|
|
|
|
|
valorConfirmado: 0,
|
|
|
|
|
valorPago: 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const valorRealizado = sortedAndFilteredData.reduce((sum, item) => {
|
|
|
|
|
const valor = typeof item.valor === "string" ? parseFloat(item.valor) : item.valor;
|
|
|
|
|
return sum + (isNaN(valor) ? 0 : valor);
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
const valorPrevisto = sortedAndFilteredData.reduce((sum, item) => {
|
|
|
|
|
const valor = typeof item.valor_previsto === "string" ? parseFloat(item.valor_previsto) : (item.valor_previsto || 0);
|
|
|
|
|
return sum + (isNaN(valor) ? 0 : valor);
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
const valorConfirmado = sortedAndFilteredData.reduce((sum, item) => {
|
|
|
|
|
const valor = typeof item.valor_confirmado === "string" ? parseFloat(item.valor_confirmado) : (item.valor_confirmado || 0);
|
|
|
|
|
return sum + (isNaN(valor) ? 0 : valor);
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
const valorPago = sortedAndFilteredData.reduce((sum, item) => {
|
|
|
|
|
const valor = typeof item.valor_pago === "string" ? parseFloat(item.valor_pago) : (item.valor_pago || 0);
|
|
|
|
|
return sum + (isNaN(valor) ? 0 : valor);
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
valorRealizado,
|
|
|
|
|
valorPrevisto,
|
|
|
|
|
valorConfirmado,
|
|
|
|
|
valorPago,
|
|
|
|
|
};
|
|
|
|
|
// Calcular valor total dos dados filtrados
|
|
|
|
|
const valorTotal = React.useMemo(() => {
|
|
|
|
|
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
|
|
|
|
|
}, [sortedAndFilteredData]);
|
|
|
|
|
|
|
|
|
|
// Limpar filtros de colunas que não têm mais valores disponíveis
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
const updatedFilters = { ...columnFilters };
|
|
|
|
|
let hasChanges = false;
|
|
|
|
|
|
|
|
|
|
Object.keys(columnFilters).forEach(field => {
|
|
|
|
|
const currentFilterValues = columnFilters[field] || [];
|
|
|
|
|
if (currentFilterValues.length === 0) return;
|
|
|
|
|
|
|
|
|
|
// Obter valores únicos disponíveis para esta coluna nos dados filtrados
|
|
|
|
|
const availableValues = filteredData
|
|
|
|
|
.map(row => {
|
|
|
|
|
const value = (row as any)[field];
|
|
|
|
|
return value === null || value === undefined ? "" : String(value);
|
|
|
|
|
})
|
|
|
|
|
.filter((value, index, self) => self.indexOf(value) === index && value !== "");
|
|
|
|
|
|
|
|
|
|
// Filtrar apenas os valores que ainda estão disponíveis
|
|
|
|
|
const validFilterValues = currentFilterValues.filter(value =>
|
|
|
|
|
availableValues.includes(value)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (validFilterValues.length !== currentFilterValues.length) {
|
|
|
|
|
if (validFilterValues.length === 0) {
|
|
|
|
|
delete updatedFilters[field];
|
|
|
|
|
} else {
|
|
|
|
|
updatedFilters[field] = validFilterValues;
|
|
|
|
|
}
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
setColumnFilters(updatedFilters);
|
|
|
|
|
}
|
|
|
|
|
}, [filteredData, columnFilters]);
|
|
|
|
|
|
|
|
|
|
// Exportação XLSX
|
|
|
|
|
const exportToExcel = () => {
|
|
|
|
|
if (sortedAndFilteredData.length === 0) return;
|
|
|
|
|
@ -722,9 +739,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
const ws = XLSX.utils.json_to_sheet(exportData);
|
|
|
|
|
|
|
|
|
|
const resumoData = [
|
|
|
|
|
{ Métrica: "Total de Registros", Valor: data.length },
|
|
|
|
|
{ Métrica: "Valor Total", Valor: columnTotals.valorRealizado },
|
|
|
|
|
{ Métrica: "Filtros Aplicados", Valor: "Sim" },
|
|
|
|
|
{ Métrica: "Total de Registros", Valor: sortedAndFilteredData.length },
|
|
|
|
|
{ Métrica: "Valor Total", Valor: valorTotal },
|
|
|
|
|
{ Métrica: "Filtros Aplicados", Valor: Object.keys(columnFilters).length > 0 ? "Sim" : "Não" },
|
|
|
|
|
];
|
|
|
|
|
const wsResumo = XLSX.utils.json_to_sheet(resumoData);
|
|
|
|
|
|
|
|
|
|
@ -815,14 +832,19 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={clearFilters}
|
|
|
|
|
className="bg-white border-gray-300 hover:bg-red-50 hover:border-red-300 text-gray-700"
|
|
|
|
|
className="bg-white border-gray-300 hover:bg-red-50 hover:border-red-300 text-gray-700 flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-4 h-4 mr-2" />
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
Limpar Filtros
|
|
|
|
|
{getFilterCount() > 0 && (
|
|
|
|
|
<span className="bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center font-semibold">
|
|
|
|
|
{getFilterCount()}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
{data.length > 0 && (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={clearAllFilters}
|
|
|
|
|
variant="outline"
|
|
|
|
|
@ -831,6 +853,11 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
>
|
|
|
|
|
<X className="h-4 w-4" />
|
|
|
|
|
Limpar Filtros
|
|
|
|
|
{getFilterCount() > 0 && (
|
|
|
|
|
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
|
|
|
|
{getFilterCount()}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={exportToExcel}
|
|
|
|
|
@ -854,21 +881,21 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<h2 className="text-lg font-semibold">
|
|
|
|
|
Total de Registros: <span className="text-blue-600">{data.length}</span>
|
|
|
|
|
Total de Registros: <span className="text-blue-600">{sortedAndFilteredData.length}</span>
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="text-sm text-gray-600">
|
|
|
|
|
Valor Total: <span className={`font-bold ${columnTotals.valorRealizado < 0 ? "text-red-600" : "text-green-600"}`}>
|
|
|
|
|
Valor Total: <span className={`font-bold ${valorTotal < 0 ? 'text-red-600' : 'text-green-600'}`}>
|
|
|
|
|
{new Intl.NumberFormat("pt-BR", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(columnTotals.valorRealizado)}
|
|
|
|
|
}).format(valorTotal)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style={{ height: "calc(100% - 2rem)", width: "100%", position: "relative" }}>
|
|
|
|
|
<DataGrid
|
|
|
|
|
<DataGridPremium
|
|
|
|
|
key={`datagrid-${sortedAndFilteredData.length}-${Object.keys(columnFilters).length}`}
|
|
|
|
|
rows={sortedAndFilteredData}
|
|
|
|
|
columns={columns}
|
|
|
|
|
@ -878,11 +905,17 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
slots={{ toolbar: GridToolbar }}
|
|
|
|
|
disableColumnMenu={true}
|
|
|
|
|
disableColumnSorting={true}
|
|
|
|
|
hideFooter={true}
|
|
|
|
|
getRowId={(row) => row.id || `row-${row.recnum || Math.random()}`}
|
|
|
|
|
initialState={{
|
|
|
|
|
sorting: { sortModel: [{ field: "data_vencimento", sort: "desc" }] },
|
|
|
|
|
aggregation: {
|
|
|
|
|
model: {
|
|
|
|
|
valor: 'sum',
|
|
|
|
|
valor_previsto: 'sum',
|
|
|
|
|
valor_confirmado: 'sum',
|
|
|
|
|
valor_pago: 'sum',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
getRowId={(row: any) => row.id || `row-${row.recnum || Math.random()}`}
|
|
|
|
|
sx={{
|
|
|
|
|
"& .MuiDataGrid-columnHeaders": {
|
|
|
|
|
position: "sticky",
|
|
|
|
|
@ -962,64 +995,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Footer com Totalizadores - Posicionado no lugar do footer nativo */}
|
|
|
|
|
{sortedAndFilteredData.length > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
ref={footerRef}
|
|
|
|
|
className={`${isFooterAnchored ? 'fixed bottom-0 left-0 right-0 z-50' : 'absolute bottom-0 left-0 right-0'} bg-white border-t border-gray-200 px-4 py-2`}
|
|
|
|
|
style={{
|
|
|
|
|
height: "48px",
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
fontSize: "0.75rem",
|
|
|
|
|
zIndex: isFooterAnchored ? 50 : 1
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center w-full">
|
|
|
|
|
{/* Espaçamento para alinhar com as colunas da tabela */}
|
|
|
|
|
<div className="w-[150px]"></div> {/* Dt Venc */}
|
|
|
|
|
<div className="w-[130px]"></div> {/* Dt Caixa */}
|
|
|
|
|
<div className="w-[100px]"></div> {/* Entidade */}
|
|
|
|
|
<div className="w-[140px]"></div> {/* Cod.Fornec */}
|
|
|
|
|
<div className="flex-1"></div> {/* Fornecedor */}
|
|
|
|
|
<div className="w-[130px]"></div> {/* C Custo */}
|
|
|
|
|
<div className="w-[150px]"></div> {/* Cod.Conta */}
|
|
|
|
|
<div className="flex-1"></div> {/* Conta */}
|
|
|
|
|
|
|
|
|
|
{/* Totalizadores das colunas de valor */}
|
|
|
|
|
<div className={`w-[140px] text-right font-semibold ${columnTotals.valorRealizado < 0 ? 'text-red-600 font-semibold' : 'text-gray-800'}`}>
|
|
|
|
|
{new Intl.NumberFormat("pt-BR", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(columnTotals.valorRealizado)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`w-[130px] text-right font-semibold ${columnTotals.valorPrevisto < 0 ? 'text-red-600 font-semibold' : 'text-gray-800'}`}>
|
|
|
|
|
{new Intl.NumberFormat("pt-BR", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(columnTotals.valorPrevisto)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`w-[140px] text-right font-semibold ${columnTotals.valorConfirmado < 0 ? 'text-red-600 font-semibold' : 'text-gray-800'}`}>
|
|
|
|
|
{new Intl.NumberFormat("pt-BR", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(columnTotals.valorConfirmado)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={`w-[130px] text-right font-semibold ${columnTotals.valorPago < 0 ? 'text-red-600 font-semibold' : 'text-gray-800'}`}>
|
|
|
|
|
{new Intl.NumberFormat("pt-BR", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "BRL",
|
|
|
|
|
}).format(columnTotals.valorPago)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Espaçamento para o resto */}
|
|
|
|
|
<div className="flex-1"></div> {/* Historico */}
|
|
|
|
|
<div className="flex-1"></div> {/* Historico 2 */}
|
|
|
|
|
<div className="w-[80px]"></div> {/* Num.Lanc */}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
@ -1067,7 +1043,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
@ -1095,7 +1071,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
<SelectItem value="notEmpty">não está vazio</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{!(cond.operator === "empty" || cond.operator === "notEmpty") && (
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
@ -1112,7 +1088,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
placeholder="Digite o valor"
|
|
|
|
|
className="w-full bg-white border-gray-300"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{conditions.length > 1 && (
|
|
|
|
|
@ -1128,8 +1104,8 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
>
|
|
|
|
|
✕
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
@ -1154,9 +1130,14 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={clearFilters}
|
|
|
|
|
className="flex-1 border-gray-300 text-gray-700 hover:bg-gray-50"
|
|
|
|
|
className="flex-1 border-gray-300 text-gray-700 hover:bg-gray-50 flex items-center justify-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
Limpar filtros avançados
|
|
|
|
|
{getFilterCount() > 0 && (
|
|
|
|
|
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
|
|
|
|
{getFilterCount()}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={applyFilters}
|
|
|
|
|
|