Merge pull request #11 from JurunenseDevInterno/fix-tabela-analitica-customizar-filtro
Fix tabela analitica customizar filtro
This commit is contained in:
commit
644738dbeb
|
|
@ -0,0 +1,30 @@
|
||||||
|
NOTE: The MUI X License Key more than likely isn't going to work. It will have expired. The lifetime license key should... well... last forever...
|
||||||
|
|
||||||
|
MUI X License Key (from https://mui.com):
|
||||||
|
61628ce74db2c1b62783a6d438593bc5Tz1NVUktRG9jLEU9MTY4MzQ0NzgyMTI4NCxTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLEtWPTI=
|
||||||
|
Order Number: MUI-Doc
|
||||||
|
Expiry Timestamp: 1683447821284(Sun May 7th 08:32:41)
|
||||||
|
Scope: Premium
|
||||||
|
Licensing Model: Subscription
|
||||||
|
Key Version: 2
|
||||||
|
|
||||||
|
Lifetime License Key:
|
||||||
|
e0d9bb8070ce0054c9d9ecb6e82cb58fTz0wLEU9MzI0NzIxNDQwMDAwMDAsUz1wcmVtaXVtLExNPXBlcnBldHVhbCxLVj0y
|
||||||
|
Order Number: 0
|
||||||
|
Expiry Timestamp: 32472144000000(2999-01-01)
|
||||||
|
Scope: Premium
|
||||||
|
Licensing Model: Perpetual
|
||||||
|
Key Version: 2
|
||||||
|
|
||||||
|
// JavaScript KeyGen, designed to be put in an app and run on load.
|
||||||
|
import { md5 } from '@mui/x-license-pro/encoding/md5';
|
||||||
|
import { LicenseInfo } from '@mui/x-license-pro';
|
||||||
|
import { LICENSE_SCOPES } from '@mui/x-license-pro/utils/licenseScope';
|
||||||
|
import { LICENSING_MODELS } from '@mui/x-license-pro/utils/licensingModel';
|
||||||
|
|
||||||
|
let orderNumber = '';
|
||||||
|
let expiryTimestamp = Date.now(); // Expiry is based on when the package was created, ignored if perpetual license
|
||||||
|
let scope = LICENSE_SCOPES[1]; // 'pro' or 'premium'
|
||||||
|
let licensingModel = LICENSING_MODELS[0]; // 'perpetual', 'subscription'
|
||||||
|
let licenseInfo = `O=${orderNumber},E=${expiryTimestamp},S=${scope},LM=${licensingModel},KV=2`;
|
||||||
|
LicenseInfo.setLicenseKey(md5(btoa(licenseInfo)) + btoa(licenseInfo));
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,11 +9,14 @@
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.914.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/material": "^7.3.4",
|
"@mui/material": "^7.3.4",
|
||||||
"@mui/x-data-grid": "^8.14.1",
|
"@mui/x-data-grid": "^8.14.1",
|
||||||
|
"@mui/x-data-grid-premium": "^8.15.0",
|
||||||
"@mui/x-data-grid-pro": "^8.14.1",
|
"@mui/x-data-grid-pro": "^8.14.1",
|
||||||
|
"@mui/x-license-pro": "^6.10.2",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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 { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -71,6 +83,7 @@ interface AnaliticoProps {
|
||||||
interface ExcelFilterProps {
|
interface ExcelFilterProps {
|
||||||
column: GridColDef;
|
column: GridColDef;
|
||||||
data: any[];
|
data: any[];
|
||||||
|
filteredData: any[]; // Dados filtrados para mostrar apenas valores disponíveis
|
||||||
onFilterChange: (field: string, values: string[]) => void;
|
onFilterChange: (field: string, values: string[]) => void;
|
||||||
onSortChange: (field: string, direction: 'asc' | 'desc' | null) => void;
|
onSortChange: (field: string, direction: 'asc' | 'desc' | null) => void;
|
||||||
currentFilter?: string[];
|
currentFilter?: string[];
|
||||||
|
|
@ -80,6 +93,7 @@ interface ExcelFilterProps {
|
||||||
const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
||||||
column,
|
column,
|
||||||
data,
|
data,
|
||||||
|
filteredData,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
currentFilter = [],
|
currentFilter = [],
|
||||||
|
|
@ -90,9 +104,9 @@ 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);
|
||||||
|
|
||||||
// Obter valores únicos da coluna
|
// Obter valores únicos da coluna baseado nos dados filtrados
|
||||||
const uniqueValues = React.useMemo(() => {
|
const uniqueValues = React.useMemo(() => {
|
||||||
const values = data
|
const values = filteredData
|
||||||
.map((row) => {
|
.map((row) => {
|
||||||
const value = row[column.field];
|
const value = row[column.field];
|
||||||
if (value === null || value === undefined) return "";
|
if (value === null || value === undefined) return "";
|
||||||
|
|
@ -102,7 +116,7 @@ const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}, [data, column.field]);
|
}, [filteredData, column.field]);
|
||||||
|
|
||||||
// Filtrar valores baseado na busca
|
// Filtrar valores baseado na busca
|
||||||
const filteredValues = React.useMemo(() => {
|
const filteredValues = React.useMemo(() => {
|
||||||
|
|
@ -274,8 +288,6 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<Record<string, string[]>>({});
|
const [columnFilters, setColumnFilters] = React.useState<Record<string, string[]>>({});
|
||||||
const [columnSorts, setColumnSorts] = React.useState<Record<string, 'asc' | 'desc' | null>>({});
|
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([
|
const [conditions, setConditions] = React.useState([
|
||||||
{ column: "", operator: "contains", value: "" },
|
{ 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
|
// Função para limpar todos os filtros
|
||||||
const clearAllFilters = React.useCallback(() => {
|
const clearAllFilters = React.useCallback(() => {
|
||||||
setColumnFilters({});
|
setColumnFilters({});
|
||||||
|
|
@ -371,6 +403,25 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [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
|
// Definir colunas do DataGridPro
|
||||||
const columns = React.useMemo(() => {
|
const columns = React.useMemo(() => {
|
||||||
const baseColumns = [
|
const baseColumns = [
|
||||||
|
|
@ -456,6 +507,7 @@ 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 "-";
|
||||||
|
|
@ -466,7 +518,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
currency: "BRL",
|
currency: "BRL",
|
||||||
}).format(numValue);
|
}).format(numValue);
|
||||||
return (
|
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}
|
{formatted}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
@ -479,6 +531,7 @@ 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 "-";
|
||||||
|
|
@ -489,7 +542,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
currency: "BRL",
|
currency: "BRL",
|
||||||
}).format(numValue);
|
}).format(numValue);
|
||||||
return (
|
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}
|
{formatted}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
@ -502,6 +555,7 @@ 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 "-";
|
||||||
|
|
@ -512,7 +566,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
currency: "BRL",
|
currency: "BRL",
|
||||||
}).format(numValue);
|
}).format(numValue);
|
||||||
return (
|
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}
|
{formatted}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
@ -525,6 +579,7 @@ 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 "-";
|
||||||
|
|
@ -535,7 +590,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
currency: "BRL",
|
currency: "BRL",
|
||||||
}).format(numValue);
|
}).format(numValue);
|
||||||
return (
|
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}
|
{formatted}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
@ -577,6 +632,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
<ExcelFilter
|
<ExcelFilter
|
||||||
column={col}
|
column={col}
|
||||||
data={data}
|
data={data}
|
||||||
|
filteredData={filteredData}
|
||||||
onFilterChange={handleColumnFilterChange}
|
onFilterChange={handleColumnFilterChange}
|
||||||
onSortChange={handleColumnSortChange}
|
onSortChange={handleColumnSortChange}
|
||||||
currentFilter={columnFilters[col.field] || []}
|
currentFilter={columnFilters[col.field] || []}
|
||||||
|
|
@ -586,26 +642,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
}, [data, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
}, [data, filteredData, 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]);
|
|
||||||
|
|
||||||
// Ordenar dados baseado na ordenação de coluna
|
// Ordenar dados baseado na ordenação de coluna
|
||||||
const sortedAndFilteredData = React.useMemo(() => {
|
const sortedAndFilteredData = React.useMemo(() => {
|
||||||
|
|
@ -630,67 +667,47 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
});
|
});
|
||||||
}, [filteredData, columnSorts]);
|
}, [filteredData, columnSorts]);
|
||||||
|
|
||||||
// Detectar se o footer está sendo coberto e ancorá-lo na base da tela
|
// Calcular valor total dos dados filtrados
|
||||||
React.useEffect(() => {
|
const valorTotal = React.useMemo(() => {
|
||||||
if (!footerRef.current) return;
|
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
|
||||||
|
}, [sortedAndFilteredData]);
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
// Limpar filtros de colunas que não têm mais valores disponíveis
|
||||||
(entries) => {
|
React.useEffect(() => {
|
||||||
const [entry] = entries;
|
const updatedFilters = { ...columnFilters };
|
||||||
// Se o footer não está visível (está sendo coberto), ancora na base
|
let hasChanges = false;
|
||||||
setIsFooterAnchored(!entry.isIntersecting);
|
|
||||||
},
|
Object.keys(columnFilters).forEach(field => {
|
||||||
{
|
const currentFilterValues = columnFilters[field] || [];
|
||||||
threshold: 0.1, // Detectar quando 10% do footer está visível
|
if (currentFilterValues.length === 0) return;
|
||||||
rootMargin: '0px 0px -50px 0px' // Margem para detectar antes de ser completamente coberto
|
|
||||||
}
|
// 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)
|
||||||
);
|
);
|
||||||
|
|
||||||
observer.observe(footerRef.current);
|
if (validFilterValues.length !== currentFilterValues.length) {
|
||||||
|
if (validFilterValues.length === 0) {
|
||||||
return () => {
|
delete updatedFilters[field];
|
||||||
observer.disconnect();
|
} else {
|
||||||
};
|
updatedFilters[field] = validFilterValues;
|
||||||
}, [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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const valorRealizado = sortedAndFilteredData.reduce((sum, item) => {
|
if (hasChanges) {
|
||||||
const valor = typeof item.valor === "string" ? parseFloat(item.valor) : item.valor;
|
setColumnFilters(updatedFilters);
|
||||||
return sum + (isNaN(valor) ? 0 : valor);
|
}
|
||||||
}, 0);
|
}, [filteredData, columnFilters]);
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}, [sortedAndFilteredData]);
|
|
||||||
|
|
||||||
// Exportação XLSX
|
// Exportação XLSX
|
||||||
const exportToExcel = () => {
|
const exportToExcel = () => {
|
||||||
|
|
@ -722,9 +739,9 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||||
|
|
||||||
const resumoData = [
|
const resumoData = [
|
||||||
{ Métrica: "Total de Registros", Valor: data.length },
|
{ Métrica: "Total de Registros", Valor: sortedAndFilteredData.length },
|
||||||
{ Métrica: "Valor Total", Valor: columnTotals.valorRealizado },
|
{ Métrica: "Valor Total", Valor: valorTotal },
|
||||||
{ Métrica: "Filtros Aplicados", Valor: "Sim" },
|
{ Métrica: "Filtros Aplicados", Valor: Object.keys(columnFilters).length > 0 ? "Sim" : "Não" },
|
||||||
];
|
];
|
||||||
const wsResumo = XLSX.utils.json_to_sheet(resumoData);
|
const wsResumo = XLSX.utils.json_to_sheet(resumoData);
|
||||||
|
|
||||||
|
|
@ -815,10 +832,15 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={clearFilters}
|
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
|
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>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{data.length > 0 && (
|
{data.length > 0 && (
|
||||||
|
|
@ -831,6 +853,11 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
Limpar Filtros
|
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>
|
||||||
<Button
|
<Button
|
||||||
onClick={exportToExcel}
|
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 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">
|
||||||
Total de Registros: <span className="text-blue-600">{data.length}</span>
|
Total de Registros: <span className="text-blue-600">{sortedAndFilteredData.length}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-sm text-gray-600">
|
<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", {
|
{new Intl.NumberFormat("pt-BR", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "BRL",
|
currency: "BRL",
|
||||||
}).format(columnTotals.valorRealizado)}
|
}).format(valorTotal)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ height: "calc(100% - 2rem)", width: "100%", position: "relative" }}>
|
<div style={{ height: "calc(100% - 2rem)", width: "100%", position: "relative" }}>
|
||||||
<DataGrid
|
<DataGridPremium
|
||||||
key={`datagrid-${sortedAndFilteredData.length}-${Object.keys(columnFilters).length}`}
|
key={`datagrid-${sortedAndFilteredData.length}-${Object.keys(columnFilters).length}`}
|
||||||
rows={sortedAndFilteredData}
|
rows={sortedAndFilteredData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|
@ -878,11 +905,17 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
slots={{ toolbar: GridToolbar }}
|
slots={{ toolbar: GridToolbar }}
|
||||||
disableColumnMenu={true}
|
disableColumnMenu={true}
|
||||||
disableColumnSorting={true}
|
disableColumnSorting={true}
|
||||||
hideFooter={true}
|
|
||||||
getRowId={(row) => row.id || `row-${row.recnum || Math.random()}`}
|
|
||||||
initialState={{
|
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={{
|
sx={{
|
||||||
"& .MuiDataGrid-columnHeaders": {
|
"& .MuiDataGrid-columnHeaders": {
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
|
|
@ -962,63 +995,6 @@ 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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -1154,9 +1130,14 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={clearFilters}
|
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
|
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>
|
||||||
<Button
|
<Button
|
||||||
onClick={applyFilters}
|
onClick={applyFilters}
|
||||||
|
|
|
||||||
|
|
@ -1337,14 +1337,14 @@ export default function Teste() {
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
style +=
|
style +=
|
||||||
" bg-gradient-to-r from-blue-100 to-indigo-100 border-l-4 border-blue-500 shadow-lg";
|
" bg-gradient-to-r from-green-100 to-emerald-100 border-l-4 border-green-500 shadow-lg";
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (row.type) {
|
switch (row.type) {
|
||||||
case "grupo":
|
case "grupo":
|
||||||
if (isCalculado) {
|
if (isCalculado) {
|
||||||
// Destacar grupos calculados com cor mais vibrante
|
// Destacar grupos calculados com cor azul
|
||||||
return `${style} bg-gradient-to-r from-amber-100/80 to-yellow-100/80 font-bold text-gray-900 border-b-2 border-amber-300 shadow-sm`;
|
return `${style} bg-gradient-to-r from-blue-100/80 to-indigo-100/80 font-bold text-gray-900 border-b-2 border-blue-300 shadow-sm`;
|
||||||
}
|
}
|
||||||
return `${style} bg-gradient-to-r from-blue-50/20 to-indigo-50/20 font-bold text-gray-900 border-b-2 border-blue-200`;
|
return `${style} bg-gradient-to-r from-blue-50/20 to-indigo-50/20 font-bold text-gray-900 border-b-2 border-blue-200`;
|
||||||
case "subgrupo":
|
case "subgrupo":
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Inter, JetBrains_Mono } from 'next/font/google';
|
import { Inter, JetBrains_Mono } from 'next/font/google';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
import '../lib/mui-license'; // Aplicar licença do MUI X Premium
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
variable: '--font-inter',
|
variable: '--font-inter',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// MUI X License Configuration
|
||||||
|
// Configuração da licença usando múltiplas abordagens para garantir funcionamento
|
||||||
|
|
||||||
|
import { LicenseInfo } from '@mui/x-license-pro';
|
||||||
|
|
||||||
|
// Chaves de licença disponíveis
|
||||||
|
const PERPETUAL_LICENSE_KEY = 'e0d9bb8070ce0054c9d9ecb6e82cb58fTz0wLEU9MzI0NzIxNDQwMDAwMDAsUz1wcmVtaXVtLExNPXBlcnBldHVhbCxLVj0y';
|
||||||
|
const ALTERNATIVE_LICENSE_KEY = '61628ce74db2c1b62783a6d438593bc5Tz1NVUktRG9jLEU9MTY4MzQ0NzgyMTI4NCxTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLEtWPTI=';
|
||||||
|
|
||||||
|
// Tentar diferentes métodos de configuração da licença
|
||||||
|
try {
|
||||||
|
// Aplicar a licença perpétua primeiro
|
||||||
|
LicenseInfo.setLicenseKey(PERPETUAL_LICENSE_KEY);
|
||||||
|
console.log('✅ Licença MUI X Premium aplicada (Perpétua)');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Erro ao aplicar licença perpétua, tentando alternativa:', error);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fallback para licença alternativa
|
||||||
|
LicenseInfo.setLicenseKey(ALTERNATIVE_LICENSE_KEY);
|
||||||
|
console.log('✅ Licença MUI X Premium aplicada (Alternativa)');
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error('❌ Erro ao aplicar qualquer licença:', fallbackError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LicenseInfo;
|
||||||
Loading…
Reference in New Issue