Merge pull request #12 from JurunenseDevInterno/dev

Dev
This commit is contained in:
Alessandro Gonçalves 2025-10-23 14:39:37 -03:00 committed by GitHub
commit abaf9ab85e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 2870 additions and 264 deletions

30
docs/mui_license_key.txt Normal file
View File

@ -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));

2689
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,14 @@
"lint": "eslint"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.914.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.4",
"@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-license-pro": "^6.10.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",

View File

@ -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 = [
@ -456,6 +507,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
width: 140,
sortable: true,
resizable: true,
aggregable: true,
renderCell: (params: any) => {
const value = params.value;
if (value === null || value === undefined || value === "") return "-";
@ -466,7 +518,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>
);
@ -479,6 +531,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
width: 130,
sortable: true,
resizable: true,
aggregable: true,
renderCell: (params: any) => {
const value = params.value;
if (value === null || value === undefined || value === "" || value === 0) return "-";
@ -489,7 +542,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>
);
@ -502,6 +555,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
width: 140,
sortable: true,
resizable: true,
aggregable: true,
renderCell: (params: any) => {
const value = params.value;
if (value === null || value === undefined || value === "" || value === 0) return "-";
@ -512,7 +566,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>
);
@ -525,6 +579,7 @@ export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
width: 130,
sortable: true,
resizable: true,
aggregable: true,
renderCell: (params: any) => {
const value = params.value;
if (value === null || value === undefined || value === "" || value === 0) return "-";
@ -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,67 +667,47 @@ 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;
// Calcular valor total dos dados filtrados
const valorTotal = React.useMemo(() => {
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
}, [sortedAndFilteredData]);
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
}
// 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)
);
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,
};
if (validFilterValues.length !== currentFilterValues.length) {
if (validFilterValues.length === 0) {
delete updatedFilters[field];
} else {
updatedFilters[field] = validFilterValues;
}
hasChanges = true;
}
});
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,
};
}, [sortedAndFilteredData]);
if (hasChanges) {
setColumnFilters(updatedFilters);
}
}, [filteredData, columnFilters]);
// Exportação XLSX
const exportToExcel = () => {
@ -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,10 +832,15 @@ 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 && (
@ -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,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>
</CardContent>
</Card>
@ -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}

View File

@ -1337,14 +1337,14 @@ export default function Teste() {
if (isSelected) {
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) {
case "grupo":
if (isCalculado) {
// Destacar grupos calculados com cor mais vibrante
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`;
// Destacar grupos calculados com cor azul
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`;
case "subgrupo":

View File

@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { Inter, JetBrains_Mono } from 'next/font/google';
import './globals.css';
import '../lib/mui-license'; // Aplicar licença do MUI X Premium
const inter = Inter({
variable: '--font-inter',

28
src/lib/mui-license.ts Normal file
View File

@ -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;