Fix script paths and move assets to public/ folder for Vite build compatibility

This commit is contained in:
Marcos
2026-03-22 20:45:20 -03:00
parent 304504b758
commit 57ba9d1c5f
155 changed files with 10614 additions and 26 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
/**
* CARREGADOR UNIVERSAL DE PERFIS
* Sistema que carrega automaticamente TODOS os tipos de perfis
* usando o banco de dados embutido
*/
// Configuração de mapeamento de tipos
const MAPEAMENTO_PERFIS = {
'cantoneiras': {
tbodyId: 'cantoneiras-tbody',
totalId: 'cant-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'lado_mm', label: 'Lado (mm)' },
{ key: 'espessura_mm', label: 'Espessura (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 2 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'momento_inercia_cm4', label: 'Momento Inércia (cm⁴)', decimals: 2 },
{ key: 'raio_giracao_cm', label: 'Raio Giração (cm)', decimals: 2 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'barras_redondas': {
tbodyId: 'barras_redondas-tbody',
totalId: 'barras_redondas-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'diametro_mm', label: 'Diâmetro (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 3 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 3 },
{ key: 'momento_inercia_cm4', label: 'Momento Inércia (cm⁴)', decimals: 2 },
{ key: 'raio_giracao_cm', label: 'Raio Giração (cm)', decimals: 2 },
{ key: 'aplicacao', label: 'Aplicação' },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'tubos_circulares': {
tbodyId: 'tubos_circulares-tbody',
totalId: 'tubos_circulares-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'diametro_mm', label: 'Ø Externo (mm)' },
{ key: 'espessura_mm', label: 'Espessura (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 2 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'momento_inercia_cm4', label: 'Momento Inércia (cm⁴)', decimals: 2 },
{ key: 'raio_giracao_cm', label: 'Raio Giração (cm)', decimals: 2 },
{ key: 'aplicacao', label: 'Aplicação' },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'perfis_i': {
tbodyId: 'perfis_i-tbody',
totalId: 'perfis_i-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'altura_mm', label: 'Altura (mm)' },
{ key: 'largura_mm', label: 'Largura (mm)' },
{ key: 'espessura_alma_mm', label: 'Esp. Alma (mm)' },
{ key: 'espessura_mesa_mm', label: 'Esp. Mesa (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 2 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'perfis_w': {
tbodyId: 'perfis_w-tbody',
totalId: 'perfis_w-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'altura_mm', label: 'Altura (mm)' },
{ key: 'largura_mm', label: 'Largura (mm)' },
{ key: 'espessura_alma_mm', label: 'Esp. Alma (mm)' },
{ key: 'espessura_mesa_mm', label: 'Esp. Mesa (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 2 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'tubos_rhs': {
tbodyId: 'tubos_rhs-tbody',
totalId: 'tubos_rhs-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'largura_mm', label: 'Largura (mm)' },
{ key: 'altura_mm', label: 'Altura (mm)' },
{ key: 'espessura_mm', label: 'Espessura (mm)' },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 2 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'chapas': {
tbodyId: 'chapas-tbody',
totalId: 'chapas-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'espessura_mm', label: 'Espessura (mm)' },
{ key: 'peso_kg_m2', label: 'Peso (kg/m²)', decimals: 2 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'perfis_hp': {
tbodyId: 'perfis_hp-tbody',
totalId: 'perfis_hp-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'altura_mm', label: 'Altura (mm)' },
{ key: 'aba_mm', label: 'Aba (mm)' },
{ key: 'espessura_alma_mm', label: 'Esp. Alma (mm)', decimals: 1 },
{ key: 'espessura_aba_mm', label: 'Esp. Aba (mm)', decimals: 1 },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 1 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 1 },
{ key: 'tipo', label: 'Categoria', badge: true }
]
},
'barras_roscadas': {
tbodyId: 'barras_roscadas-tbody',
totalId: 'barras_roscadas-total',
colunas: [
{ key: 'tipo_rosca', label: 'Tipo', badge: true },
{ key: 'diametro_nominal_mm', label: 'Diâmetro (mm)', decimals: 2 },
{ key: 'passo_mm', label: 'Passo (mm)', decimals: 2 },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 3 },
{ key: 'aplicacao', label: 'Aplicação' },
{ key: 'tamanho', label: 'Categoria', badge: true }
]
},
'barras_chatas': {
tbodyId: 'barras_chatas-tbody',
totalId: 'barras_chatas-total',
colunas: [
{ key: 'nome', label: 'Designação', bold: true },
{ key: 'largura_mm', label: 'Largura (mm)', decimals: 1 },
{ key: 'espessura_mm', label: 'Espessura (mm)', decimals: 1 },
{ key: 'peso_kg_m', label: 'Peso (kg/m)', decimals: 3 },
{ key: 'area_cm2', label: 'Área (cm²)', decimals: 2 },
{ key: 'tamanho', label: 'Categoria', badge: true }
]
}
};
/**
* Carrega dados de um tipo específico de perfil
*/
function carregarPerfilUniversal(tipo) {
console.log(`🚀 Carregando perfil: ${tipo}`);
const config = MAPEAMENTO_PERFIS[tipo];
if (!config) {
console.error(`❌ Configuração não encontrada para: ${tipo}`);
return false;
}
const tbody = document.getElementById(config.tbodyId);
if (!tbody) {
console.warn(`⚠️ Elemento ${config.tbodyId} não encontrado`);
return false;
}
// Obter dados do banco
const dados = window.BANCO_DADOS_PERFIS?.[tipo];
if (!dados || dados.length === 0) {
console.error(`❌ Dados não encontrados para: ${tipo}`);
tbody.innerHTML = `
<tr>
<td colspan="${config.colunas.length + 1}" style="text-align: center; padding: 40px;">
<div style="font-size: 48px; margin-bottom: 16px;">❌</div>
<div style="font-size: 18px; font-weight: bold; color: #ef4444;">Dados não encontrados</div>
<div style="font-size: 14px; color: #666; margin-top: 8px;">Tipo: ${tipo}</div>
</td>
</tr>
`;
return false;
}
// Gerar HTML da tabela
const html = dados.map(item => {
const colunas = config.colunas.map(col => {
let valor = item[col.key];
// Formatação
if (col.decimals && typeof valor === 'number') {
valor = valor.toFixed(col.decimals);
}
// Estilo
if (col.bold) {
return `<td><strong>${valor}</strong></td>`;
} else if (col.badge) {
return `<td><span class="badge badge-info">${valor}</span></td>`;
} else {
return `<td>${valor}</td>`;
}
}).join('');
return `<tr>${colunas}<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td></tr>`;
}).join('');
tbody.innerHTML = html;
// Atualizar contador
const totalEl = document.getElementById(config.totalId);
if (totalEl) {
totalEl.textContent = dados.length;
}
// Armazenar globalmente
window[`${tipo}Data`] = dados;
console.log(`${tipo}: ${dados.length} itens carregados`);
return true;
}
/**
* Observer universal que detecta qualquer tabela de perfil
*/
function iniciarObserverUniversal() {
console.log('👁️ Iniciando observer universal para todos os perfis');
const observer = new MutationObserver((mutations) => {
// Verificar cada tipo de perfil
Object.keys(MAPEAMENTO_PERFIS).forEach(tipo => {
const config = MAPEAMENTO_PERFIS[tipo];
const tbody = document.getElementById(config.tbodyId);
if (tbody) {
// Verificar se está vazio
const conteudo = tbody.textContent.trim();
const estaVazio = tbody.children.length === 0 ||
conteudo.includes('Será preenchido') ||
conteudo.includes('preenchido via JavaScript') ||
(tbody.children.length === 1 && tbody.children[0].children.length === 1);
if (estaVazio) {
console.log(`🎯 Tabela vazia detectada: ${tipo}`);
// Aguardar um pouco e carregar
setTimeout(() => {
const sucesso = carregarPerfilUniversal(tipo);
if (sucesso) {
console.log(`✅ Auto-carregamento concluído: ${tipo}`);
}
}, 300);
}
}
});
});
// Observar mudanças no body
observer.observe(document.body, {
childList: true,
subtree: true
});
// Tentar carregar imediatamente também
setTimeout(() => {
Object.keys(MAPEAMENTO_PERFIS).forEach(tipo => {
const config = MAPEAMENTO_PERFIS[tipo];
const tbody = document.getElementById(config.tbodyId);
if (tbody) {
carregarPerfilUniversal(tipo);
}
});
}, 1000);
console.log('✅ Observer universal ativo para todos os perfis');
}
// Gerar funções de carregamento forçado para cada tipo
Object.keys(MAPEAMENTO_PERFIS).forEach(tipo => {
const funcName = `forcarCarregamento${tipo.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join('')}`;
window[funcName] = function() {
console.log(`🚨 Carregamento forçado: ${tipo}`);
const config = MAPEAMENTO_PERFIS[tipo];
const tbody = document.getElementById(config.tbodyId);
if (!tbody) {
alert(`❌ Erro: Tabela não encontrada para ${tipo}`);
return;
}
// Mostrar loading
tbody.innerHTML = `
<tr>
<td colspan="${config.colunas.length + 1}" style="text-align: center; padding: 40px;">
<div style="font-size: 48px; margin-bottom: 16px;">⏳</div>
<div style="font-size: 18px; font-weight: bold;">Carregando ${tipo}...</div>
</td>
</tr>
`;
// Carregar após delay
setTimeout(() => {
const sucesso = carregarPerfilUniversal(tipo);
if (sucesso) {
const dados = window.BANCO_DADOS_PERFIS[tipo];
alert(`${dados.length} ${tipo} carregados com sucesso!`);
} else {
alert(`❌ Erro ao carregar ${tipo}`);
}
}, 500);
};
console.log(`✅ Função criada: ${funcName}`);
});
// Inicializar quando DOM estiver pronto
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', iniciarObserverUniversal);
} else {
iniciarObserverUniversal();
}
// Exportar funções
window.carregarPerfilUniversal = carregarPerfilUniversal;
window.MAPEAMENTO_PERFIS = MAPEAMENTO_PERFIS;
console.log('✅ Carregador universal configurado para todos os perfis');
console.log('📊 Perfis suportados:', Object.keys(MAPEAMENTO_PERFIS));

View File

@@ -0,0 +1,177 @@
/**
* DADOS EMBUTIDOS - Todos os perfis em JavaScript
* Solução autônoma que funciona sem servidor e sem CSVs
* Pronto para desktop, mobile e deploy
*/
const DADOS_PERFIS = {
cantoneiras: [
{id: 'l25_25_3', nome: 'L25x25x3', lado_mm: 25, espessura_mm: 3, peso_kg_m: 1.12, area_cm2: 1.43, momento_inercia_cm4: 0.38, raio_giracao_cm: 0.52, tipo: 'Pequena'},
{id: 'l25_25_4', nome: 'L25x25x4', lado_mm: 25, espessura_mm: 4, peso_kg_m: 1.47, area_cm2: 1.87, momento_inercia_cm4: 0.47, raio_giracao_cm: 0.5, tipo: 'Pequena'},
{id: 'l32_32_3', nome: 'L32x32x3', lado_mm: 32, espessura_mm: 3, peso_kg_m: 1.45, area_cm2: 1.85, momento_inercia_cm4: 0.69, raio_giracao_cm: 0.61, tipo: 'Pequena'},
{id: 'l32_32_4', nome: 'L32x32x4', lado_mm: 32, espessura_mm: 4, peso_kg_m: 1.91, area_cm2: 2.43, momento_inercia_cm4: 0.86, raio_giracao_cm: 0.59, tipo: 'Pequena'},
{id: 'l40_40_3', nome: 'L40x40x3', lado_mm: 40, espessura_mm: 3, peso_kg_m: 1.86, area_cm2: 2.37, momento_inercia_cm4: 1.4, raio_giracao_cm: 0.77, tipo: 'Pequena'},
{id: 'l40_40_4', nome: 'L40x40x4', lado_mm: 40, espessura_mm: 4, peso_kg_m: 2.46, area_cm2: 3.13, momento_inercia_cm4: 1.74, raio_giracao_cm: 0.75, tipo: 'Pequena'},
{id: 'l40_40_5', nome: 'L40x40x5', lado_mm: 40, espessura_mm: 5, peso_kg_m: 3.03, area_cm2: 3.86, momento_inercia_cm4: 2.06, raio_giracao_cm: 0.73, tipo: 'Pequena'},
{id: 'l50_50_3', nome: 'L50x50x3', lado_mm: 50, espessura_mm: 3, peso_kg_m: 2.36, area_cm2: 3.0, momento_inercia_cm4: 2.7, raio_giracao_cm: 0.95, tipo: 'Pequena'},
{id: 'l50_50_4', nome: 'L50x50x4', lado_mm: 50, espessura_mm: 4, peso_kg_m: 3.14, area_cm2: 4.0, momento_inercia_cm4: 3.5, raio_giracao_cm: 0.93, tipo: 'Pequena'},
{id: 'l50_50_5', nome: 'L50x50x5', lado_mm: 50, espessura_mm: 5, peso_kg_m: 3.89, area_cm2: 4.96, momento_inercia_cm4: 4.25, raio_giracao_cm: 0.93, tipo: 'Pequena'},
{id: 'l50_50_6', nome: 'L50x50x6', lado_mm: 50, espessura_mm: 6, peso_kg_m: 4.62, area_cm2: 5.89, momento_inercia_cm4: 4.95, raio_giracao_cm: 0.92, tipo: 'Pequena'},
{id: 'l63_63_5', nome: 'L63x63x5', lado_mm: 63, espessura_mm: 5, peso_kg_m: 4.96, area_cm2: 6.31, momento_inercia_cm4: 8.47, raio_giracao_cm: 1.16, tipo: 'Média'},
{id: 'l63_63_6', nome: 'L63x63x6', lado_mm: 63, espessura_mm: 6, peso_kg_m: 5.91, area_cm2: 7.53, momento_inercia_cm4: 9.9, raio_giracao_cm: 1.15, tipo: 'Média'},
{id: 'l63_63_8', nome: 'L63x63x8', lado_mm: 63, espessura_mm: 8, peso_kg_m: 7.73, area_cm2: 9.85, momento_inercia_cm4: 12.6, raio_giracao_cm: 1.13, tipo: 'Média'},
{id: 'l75_75_5', nome: 'L75x75x5', lado_mm: 75, espessura_mm: 5, peso_kg_m: 5.91, area_cm2: 7.53, momento_inercia_cm4: 13.5, raio_giracao_cm: 1.34, tipo: 'Média'},
{id: 'l75_75_6', nome: 'L75x75x6', lado_mm: 75, espessura_mm: 6, peso_kg_m: 7.05, area_cm2: 8.98, momento_inercia_cm4: 15.8, raio_giracao_cm: 1.33, tipo: 'Média'},
{id: 'l75_75_7', nome: 'L75x75x7', lado_mm: 75, espessura_mm: 7, peso_kg_m: 8.17, area_cm2: 10.4, momento_inercia_cm4: 18.0, raio_giracao_cm: 1.32, tipo: 'Média'},
{id: 'l75_75_8', nome: 'L75x75x8', lado_mm: 75, espessura_mm: 8, peso_kg_m: 9.27, area_cm2: 11.8, momento_inercia_cm4: 20.1, raio_giracao_cm: 1.31, tipo: 'Média'},
{id: 'l100_100_6', nome: 'L100x100x6', lado_mm: 100, espessura_mm: 6, peso_kg_m: 9.46, area_cm2: 12.1, momento_inercia_cm4: 37.4, raio_giracao_cm: 1.76, tipo: 'Grande'},
{id: 'l100_100_8', nome: 'L100x100x8', lado_mm: 100, espessura_mm: 8, peso_kg_m: 12.5, area_cm2: 15.9, momento_inercia_cm4: 48.8, raio_giracao_cm: 1.75, tipo: 'Grande'},
{id: 'l100_100_10', nome: 'L100x100x10', lado_mm: 100, espessura_mm: 10, peso_kg_m: 15.4, area_cm2: 19.6, momento_inercia_cm4: 59.5, raio_giracao_cm: 1.74, tipo: 'Grande'},
{id: 'l100_100_12', nome: 'L100x100x12', lado_mm: 100, espessura_mm: 12, peso_kg_m: 18.2, area_cm2: 23.2, momento_inercia_cm4: 69.5, raio_giracao_cm: 1.73, tipo: 'Grande'},
{id: 'l125_125_8', nome: 'L125x125x8', lado_mm: 125, espessura_mm: 8, peso_kg_m: 15.7, area_cm2: 20.0, momento_inercia_cm4: 95.3, raio_giracao_cm: 2.18, tipo: 'Muito Grande'},
{id: 'l125_125_10', nome: 'L125x125x10', lado_mm: 125, espessura_mm: 10, peso_kg_m: 19.5, area_cm2: 24.8, momento_inercia_cm4: 117, raio_giracao_cm: 2.17, tipo: 'Muito Grande'},
{id: 'l125_125_12', nome: 'L125x125x12', lado_mm: 125, espessura_mm: 12, peso_kg_m: 23.2, area_cm2: 29.5, momento_inercia_cm4: 137, raio_giracao_cm: 2.16, tipo: 'Muito Grande'},
{id: 'l125_125_16', nome: 'L125x125x16', lado_mm: 125, espessura_mm: 16, peso_kg_m: 30.3, area_cm2: 38.6, momento_inercia_cm4: 175, raio_giracao_cm: 2.13, tipo: 'Muito Grande'},
{id: 'l150_150_10', nome: 'L150x150x10', lado_mm: 150, espessura_mm: 10, peso_kg_m: 23.6, area_cm2: 30.1, momento_inercia_cm4: 201, raio_giracao_cm: 2.59, tipo: 'Extra-Grande'},
{id: 'l150_150_12', nome: 'L150x150x12', lado_mm: 150, espessura_mm: 12, peso_kg_m: 28.1, area_cm2: 35.8, momento_inercia_cm4: 237, raio_giracao_cm: 2.57, tipo: 'Extra-Grande'},
{id: 'l150_150_15', nome: 'L150x150x15', lado_mm: 150, espessura_mm: 15, peso_kg_m: 34.8, area_cm2: 44.3, momento_inercia_cm4: 289, raio_giracao_cm: 2.56, tipo: 'Extra-Grande'},
{id: 'l150_150_20', nome: 'L150x150x20', lado_mm: 150, espessura_mm: 20, peso_kg_m: 45.6, area_cm2: 58.1, momento_inercia_cm4: 371, raio_giracao_cm: 2.53, tipo: 'Extra-Grande'},
{id: 'l200_200_16', nome: 'L200x200x16', lado_mm: 200, espessura_mm: 16, peso_kg_m: 50.3, area_cm2: 64.1, momento_inercia_cm4: 712, raio_giracao_cm: 3.33, tipo: 'Massiva'},
{id: 'l200_200_20', nome: 'L200x200x20', lado_mm: 200, espessura_mm: 20, peso_kg_m: 62.4, area_cm2: 79.5, momento_inercia_cm4: 873, raio_giracao_cm: 3.31, tipo: 'Massiva'},
{id: 'l200_200_25', nome: 'L200x200x25', lado_mm: 200, espessura_mm: 25, peso_kg_m: 77.3, area_cm2: 98.5, momento_inercia_cm4: 1060, raio_giracao_cm: 3.28, tipo: 'Massiva'}
]
};
/**
* Função para obter dados de um tipo de perfil
*/
function obterDadosPerfil(tipo) {
console.log(`📊 Obtendo dados embutidos: ${tipo}`);
const dados = DADOS_PERFIS[tipo];
if (!dados) {
console.error(`❌ Tipo de perfil não encontrado: ${tipo}`);
return [];
}
console.log(`${dados.length} itens encontrados para ${tipo}`);
return dados;
}
/**
* Carrega dados automaticamente para cantoneiras
*/
function carregarCantoneirasAutomatico() {
console.log('🚀 Carregamento automático de cantoneiras (dados embutidos)');
const tbody = document.getElementById('cantoneiras-tbody');
if (!tbody) {
console.warn('⚠️ Elemento tbody não encontrado ainda');
return false;
}
const dados = obterDadosPerfil('cantoneiras');
if (dados.length === 0) {
console.error('❌ Nenhum dado encontrado');
return false;
}
// Exibir na tabela
tbody.innerHTML = dados.map(item => `
<tr>
<td><strong>${item.nome}</strong></td>
<td>${item.lado_mm}</td>
<td>${item.espessura_mm}</td>
<td>${item.peso_kg_m.toFixed(2)}</td>
<td>${item.area_cm2.toFixed(2)}</td>
<td>${item.momento_inercia_cm4.toFixed(2)}</td>
<td>${item.raio_giracao_cm.toFixed(2)}</td>
<td><span class="badge badge-info">${item.tipo}</span></td>
<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td>
</tr>
`).join('');
// Atualizar contador
const totalEl = document.getElementById('cant-total');
if (totalEl) {
totalEl.textContent = dados.length;
}
// Armazenar globalmente
window.cantoneirasData = dados;
console.log(`${dados.length} cantoneiras carregadas automaticamente!`);
return true;
}
// Exportar para uso global
window.DADOS_PERFIS = DADOS_PERFIS;
window.obterDadosPerfil = obterDadosPerfil;
window.carregarCantoneirasAutomatico = carregarCantoneirasAutomatico;
console.log('✅ Dados embutidos carregados - 33 cantoneiras disponíveis');
// ========================================
// AUTO-CARREGAMENTO ROBUSTO
// ========================================
/**
* Observer que detecta quando a tabela de cantoneiras aparece
* e carrega os dados automaticamente
*/
function iniciarAutoCarregamentoRobusto() {
console.log('👁️ Iniciando observer robusto para auto-carregamento');
const observer = new MutationObserver((mutations) => {
const tbody = document.getElementById('cantoneiras-tbody');
if (tbody) {
// Verificar se está vazio ou com placeholder
const conteudo = tbody.textContent.trim();
const estaVazio = tbody.children.length === 0 ||
conteudo.includes('Será preenchido') ||
conteudo.includes('preenchido via JavaScript') ||
tbody.children.length === 1 && tbody.children[0].children.length === 1;
if (estaVazio) {
console.log('🎯 Tabela vazia detectada - carregando automaticamente...');
// Aguardar um pouco e carregar
setTimeout(() => {
const sucesso = carregarCantoneirasAutomatico();
if (sucesso) {
console.log('✅ Auto-carregamento concluído com sucesso!');
observer.disconnect(); // Parar de observar
}
}, 300);
}
}
});
// Observar mudanças no body
observer.observe(document.body, {
childList: true,
subtree: true
});
// Tentar carregar imediatamente também
setTimeout(() => {
const tbody = document.getElementById('cantoneiras-tbody');
if (tbody) {
carregarCantoneirasAutomatico();
}
}, 1000);
console.log('✅ Observer robusto ativo');
}
// Iniciar quando o DOM estiver pronto
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', iniciarAutoCarregamentoRobusto);
} else {
iniciarAutoCarregamentoRobusto();
}
console.log('✅ Sistema de auto-carregamento robusto configurado');

View File

@@ -0,0 +1,741 @@
/**
* SISTEMA DE GERENCIAMENTO DE DADOS - AÇO CALC PRO
*
* Sistema intermediário que gerencia dados dos CSVs,
* criando um cache inteligente e permitindo atualizações
* dinâmicas sem afetar a performance do aplicativo.
*
* @author AÇO CALC PRO v7.5
* @date 2025
*/
class DataManager {
constructor() {
this.version = '1.0.0';
this.cachePrefix = 'acoCalcPro_cache_';
this.metadataKey = 'acoCalcPro_metadata';
this.typesMetadataKey = 'acoCalcPro_types_metadata';
this.csvConfigs = {
cantoneiras: {
file: 'BD/perfis/cantoneiras_brasil_completo.csv',
columns: ['id', 'nome', 'lado_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'momento_inercia_cm4', 'raio_giracao_cm', 'tipo'],
keyField: 'id',
displayName: 'Cantoneiras'
},
barras_redondas: {
file: 'BD/perfis/barras_brasil_completo.csv',
columns: ['id', 'nome', 'diametro_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Barras Redondas'
},
tubos_circulares: {
file: 'BD/perfis/tubos_circulares_brasil_completo.csv',
columns: ['id', 'nome', 'diametro_externo_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Tubos Circulares'
},
perfis_i: {
file: 'BD/perfis/perfis_i_brasil_completo.csv',
columns: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Perfis I (IPE)'
},
perfis_w: {
file: 'BD/perfis/perfis_w_brasil_completo.csv',
columns: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Perfis W'
},
tubos_rhs: {
file: 'BD/perfis/tubos_rhs_brasil_completo.csv',
columns: ['id', 'nome', 'largura_mm', 'altura_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Tubos RHS'
},
chapas: {
file: 'BD/perfis/chapas_brasil_completo.csv',
columns: ['id', 'nome', 'espessura_mm', 'peso_kg_m2', 'tipo'],
keyField: 'id',
displayName: 'Chapas'
},
perfis_hp: {
file: 'BD/perfis/perfis_hp_brasil_completo.csv',
columns: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Perfis HP'
},
barras_roscadas: {
file: 'BD/perfis/barras_roscadas_brasil_completo.csv',
columns: ['id', 'nome', 'diametro_mm', 'passo_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Barras Roscadas'
},
barras_chatas: {
file: 'BD/perfis/barras_chatas_brasil_completo.csv',
columns: ['id', 'nome', 'largura_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
keyField: 'id',
displayName: 'Barras Chatas'
}
};
this.init();
}
/**
* Inicializa o gerenciador de dados
*/
async init() {
console.log('🗄️ Inicializando Data Manager v' + this.version);
// Verificar se há dados em cache
const metadata = this.getMetadata();
if (!metadata || this.needsUpdate(metadata)) {
console.log('📥 Cache vazio ou desatualizado. Carregando dados...');
await this.updateAllData();
} else {
console.log('✅ Cache válido encontrado. Dados prontos para uso.');
}
}
/**
* Verifica se os dados precisam ser atualizados
*/
needsUpdate(metadata) {
// Verificar versão
if (metadata.version !== this.version) {
return true;
}
// Verificar TTL (24 horas)
const now = Date.now();
const ttl = 24 * 60 * 60 * 1000; // 24 horas
if (now - metadata.lastUpdate > ttl) {
return true;
}
return false;
}
/**
* Atualiza todos os dados dos CSVs
*/
async updateAllData() {
console.log('🔄 Iniciando atualização completa dos dados...');
const results = {
success: [],
errors: []
};
for (const [key, config] of Object.entries(this.csvConfigs)) {
try {
console.log(`📊 Carregando ${config.displayName}...`);
const data = await this.loadCSV(config);
this.saveToCache(key, data);
const prevMeta = this.getTypeMetadata(key) || {};
const normalizedPrevDoc = prevMeta.docSource ? this.normalizeDocSource(prevMeta.docSource) : null;
this.setTypeMetadata(key, {
source: config.file,
lastUpdate: Date.now(),
count: data.length,
name: config.displayName,
docSource: normalizedPrevDoc || null,
docStatus: prevMeta.docStatus || null
});
results.success.push({
key,
name: config.displayName,
count: data.length
});
console.log(`${config.displayName}: ${data.length} itens carregados`);
} catch (error) {
console.error(`❌ Erro ao carregar ${config.displayName}:`, error);
results.errors.push({
key,
name: config.displayName,
error: error.message
});
}
}
// Atualizar metadata
this.updateMetadata(results);
console.log('🎉 Atualização completa finalizada!');
console.log(`✅ Sucessos: ${results.success.length}`);
console.log(`❌ Erros: ${results.errors.length}`);
return results;
}
/**
* Atualiza dados de um tipo específico e registra metadados
*/
async updateTypeData(type, options = {}) {
const config = this.csvConfigs[type];
if (!config) {
throw new Error(`Tipo de dados não configurado: ${type}`);
}
// Limpa cache anterior do tipo
try { localStorage.removeItem(this.cachePrefix + type); } catch(_) {}
let data = await this.loadCSV(config);
this.saveToCache(type, data);
// Preparar metadados, preservando docSource anterior se não fornecido
const prevMeta = this.getTypeMetadata(type) || {};
const meta = {
source: config.file,
lastUpdate: Date.now(),
count: data.length,
name: config.displayName,
docSource: options.docSource || prevMeta.docSource || null,
docStatus: null
};
// Normalizar e validar docSource (.md)
if (meta.docSource && typeof meta.docSource === 'string' && meta.docSource.trim().length > 0) {
const originalDoc = meta.docSource.trim();
const normalized = this.normalizeDocSource(originalDoc);
meta.docSource = normalized; // armazenar caminho normalizado
try {
const resp = await fetch(normalized, { cache: 'no-cache' });
if (resp.ok) {
meta.docStatus = 'ok';
const text = await resp.text();
// Extrair insights do documento para o tipo
const insights = this.parseMarkdownDocForType(type, text);
// Se o documento fornecer tabela técnica, mesclar/substituir dados
if (insights && insights.technicalItems && insights.technicalItems.length) {
data = this.mergeDataWithDoc(type, data, insights.technicalItems);
}
meta.docInsights = {
found: !!insights,
sections: insights?.sections || {},
priceHints: insights?.priceHints || [],
manufacturers: insights?.manufacturers || [],
applications: insights?.applications || [],
recommendations: insights?.recommendations || []
};
} else {
meta.docStatus = `não encontrado (HTTP ${resp.status})`;
}
} catch (e) {
// Mensagem mais clara para caminhos absolutos ou externos
if (originalDoc.toLowerCase().startsWith('file://') || /^[a-zA-Z]:\\/.test(originalDoc)) {
meta.docStatus = 'caminho absoluto não suportado; use relativo';
} else {
meta.docStatus = 'erro ao carregar';
}
console.warn(`Falha ao carregar documento .md: '${originalDoc}' -> '${normalized}'`, e);
}
}
this.setTypeMetadata(type, meta);
// Atualiza metadata global (última operação)
const metaGlobal = this.getMetadata() || { version: this.version };
metaGlobal.lastUpdate = Date.now();
try { localStorage.setItem(this.metadataKey, JSON.stringify(metaGlobal)); } catch(_) {}
return { key: type, name: config.displayName, count: data.length, source: config.file, lastUpdate: Date.now(), data, docSource: meta.docSource, docStatus: meta.docStatus, docInsights: meta.docInsights };
}
/**
* Carrega e processa um arquivo CSV
*/
async loadCSV(config) {
const response = await fetch(config.file);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const csvText = await response.text();
const lines = csvText.trim().split('\n');
if (lines.length < 2) {
throw new Error('Arquivo CSV vazio ou inválido');
}
const data = [];
const headers = lines[0].split(',').map(h => h.trim());
// Processar dados
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const values = line.split(',').map(v => v.trim());
if (values.length < config.columns.length) continue;
const item = {};
config.columns.forEach((col, index) => {
let value = values[index] || '';
// Converter números
if (col.includes('_mm') || col.includes('_kg') || col.includes('_cm') || col.includes('_m2')) {
value = parseFloat(value) || 0;
} else {
value = value.replace(/"/g, ''); // Remover aspas
}
item[col] = value;
});
// Adicionar metadados
item._metadata = {
source: config.file,
loadedAt: new Date().toISOString(),
version: this.version
};
data.push(item);
}
return data;
}
/**
* Normaliza diferentes formatos de caminho para arquivos .md
* Aceita entradas como:
* - file:///C:/projeto/conhecimento/aco/tubos_rhs.md
* - C:\projeto\BD\docs\arquivo.md
* - conhecimento/aco/tubos_rhs.md
* Retorna caminho relativo utilizável pelo servidor estático: ex. "conhecimento/aco/tubos_rhs.md".
*/
normalizeDocSource(inputPath) {
if (!inputPath || typeof inputPath !== 'string') return null;
let p = inputPath.trim();
// Remover prefixo file:// se existir
if (p.toLowerCase().startsWith('file://')) {
p = p.replace(/^file:\/\//i, '');
}
// Substituir barras invertidas por barras normais
p = p.replace(/\\/g, '/');
// Remover drive letter (ex.: C:/ ou I:/)
p = p.replace(/^[a-zA-Z]:\//, '');
// Procurar marcadores conhecidos (BD/ ou conhecimento/)
const lower = p.toLowerCase();
let idx = -1;
if ((idx = lower.lastIndexOf('/bd/')) !== -1) {
p = p.substring(idx + 1); // manter a partir de BD/
} else if ((idx = lower.lastIndexOf('/conhecimento/')) !== -1) {
p = p.substring(idx + 1); // manter a partir de conhecimento/
}
// Garantir que usamos somente partes relativas
p = p.replace(/^\/+/, '');
// Validar extensão .md
if (!p.toLowerCase().endsWith('.md')) {
return p; // permitir salvar mesmo sem extensão correta; validação ocorrerá ao tentar buscar
}
return p;
}
// =====================
// PARSERS DE MARKDOWN
// =====================
/**
* Converte uma tabela markdown (pipe format) para array de objetos
*/
parseMarkdownTables(text) {
const lines = text.split(/\r?\n/);
const tables = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detecta título imediatamente acima de tabela
if (/^\s*\|.+\|\s*$/.test(line) && i > 0) {
// Cabeçalho
const header = line.trim().slice(1, -1).split('|').map(s => s.trim());
// Separador
const sep = lines[i + 1] || '';
if (!/^\s*\|?\s*[-:]+/.test(sep)) continue;
const rows = [];
let j = i + 2;
while (j < lines.length && /^\s*\|.+\|\s*$/.test(lines[j])) {
const cols = lines[j].trim().slice(1, -1).split('|').map(s => s.trim());
const obj = {};
header.forEach((h, idx) => { obj[h] = cols[idx]; });
rows.push(obj);
j++;
}
// Título busca uma linha anterior iniciada por '#', '##' ou '###'
let title = '';
for (let k = i - 1; k >= Math.max(0, i - 4); k--) {
if (/^\s*#{1,6}\s+/.test(lines[k])) { title = lines[k].replace(/^\s*#{1,6}\s+/, '').trim(); break; }
if (/\*\*.+\*\*/.test(lines[k])) { title = lines[k].replace(/\*\*/g, '').trim(); break; }
}
tables.push({ title, header, rows });
i = j - 1;
}
}
return tables;
}
/**
* Extrai texto de uma seção por título (## Seção)
*/
extractSection(text, title) {
const regex = new RegExp(`^##\s+${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\s*$`, 'mi');
const match = regex.exec(text);
if (!match) return null;
const start = match.index + match[0].length;
const rest = text.slice(start);
// Até próxima seção de nível 2
const next = /\n##\s+/m.exec(rest);
const sectionText = next ? rest.slice(0, next.index) : rest;
return sectionText.trim();
}
/**
* Parser específico por tipo
*/
parseMarkdownDocForType(type, text) {
const tables = this.parseMarkdownTables(text);
const sections = {};
const getBullets = (t) => (t || '').split(/\r?\n/).filter(l => /^\s*[-*]/.test(l)).map(l => l.replace(/^\s*[-*•]\s*/, '').trim());
// Seções comuns
sections.visaoGeral = this.extractSection(text, 'VISÃO GERAL RHS');
sections.normasTecnicas = this.extractSection(text, 'NORMAS TÉCNICAS');
sections.recomendacoesTecnicas = this.extractSection(text, 'Recomendações Técnicas') || this.extractSection(text, 'Recomendações');
sections.fabricantesBrasil = this.extractSection(text, 'Fabricantes Brasil') || this.extractSection(text, 'Fabricantes');
sections.aplicacoes = this.extractSection(text, 'Seleção por Aplicação') || this.extractSection(text, 'Aplicações');
const priceHints = [];
// Capturar ocorrências de preços (R$ xx ou xx R$/kg)
const priceRegex = /(R\$\s*\d+(?:[.,]\d+)?(?:\s*[-]\s*\d+(?:[.,]\d+)?)?\s*(?:R\$)?\s*(?:\/kg|kg)?)/gi;
let m;
while ((m = priceRegex.exec(text)) !== null) { priceHints.push(m[0]); }
if (type === 'tubos_rhs') {
const master = tables.find(t => /Tabela Master/i.test(t.title));
let technicalItems = [];
if (master) {
technicalItems = master.rows.map(row => {
// Designação: ex. "RHS 120×80×4" → extrair dims
const des = (row['Designação'] || row['Designacao'] || row['Nome'] || '').replace(/^RHS\s*/i, '').trim();
const parts = des.split(/[×x]/).map(s => s.trim());
const largura = parseFloat(parts[0]) || parseFloat(row['Largura (mm)']) || parseFloat(row['Dimensão (mm)']) || null;
const altura = parts[1] ? parseFloat(parts[1]) : (parseFloat(row['Altura (mm)']) || null);
const esp = parts[2] ? parseFloat(parts[2]) : (parseFloat((row['Espes. (mm)'] || row['Espes.'])) || null);
const peso = parseFloat(String(row['Peso (kg/m)'] || '').replace(',', '.'));
const area = parseFloat(String(row['Área (cm²)'] || '').replace(',', '.'));
const tipo = row['Tipo'] || this.classificarRhsPorDimensao(largura, altura);
return {
id: row['ID'] || null,
nome: des || row['Designação'] || '',
largura_mm: largura,
altura_mm: altura ?? largura,
espessura_mm: esp,
peso_kg_m: isNaN(peso) ? null : peso,
area_cm2: isNaN(area) ? null : area,
tipo: tipo || '—'
};
});
}
return {
sections,
priceHints,
technicalItems,
manufacturers: getBullets(sections.fabricantesBrasil),
applications: getBullets(sections.aplicacoes),
recommendations: getBullets(sections.recomendacoesTecnicas)
};
}
// Tipos não suportados ainda
return { sections, priceHints };
}
classificarRhsPorDimensao(l, h) {
const max = Math.max(l || 0, h || 0);
if (max >= 200) return 'Massiva';
if (max >= 150) return 'Muito Grande';
if (max >= 120) return 'Grande';
if (max >= 80) return 'Médio';
return 'Pequeno';
}
/**
* Mescla dados carregados (CSV) com itens técnicos do documento
* Preferência para valores do documento quando disponíveis
*/
mergeDataWithDoc(type, baseData, docItems) {
if (type !== 'tubos_rhs') return baseData;
const byNome = new Map((baseData || []).map(it => [String(it.nome).toLowerCase(), it]));
docItems.forEach(d => {
const key = String(d.nome || '').toLowerCase();
if (byNome.has(key)) {
const curr = byNome.get(key);
byNome.set(key, {
...curr,
largura_mm: d.largura_mm ?? curr.largura_mm,
altura_mm: d.altura_mm ?? curr.altura_mm,
espessura_mm: d.espessura_mm ?? curr.espessura_mm,
peso_kg_m: d.peso_kg_m ?? curr.peso_kg_m,
area_cm2: d.area_cm2 ?? curr.area_cm2,
tipo: d.tipo ?? curr.tipo
});
} else {
byNome.set(key, d);
}
});
return Array.from(byNome.values());
}
/**
* Salva dados no cache
*/
saveToCache(key, data) {
try {
const cacheKey = this.cachePrefix + key;
localStorage.setItem(cacheKey, JSON.stringify(data));
console.log(`💾 Cache salvo: ${key} (${data.length} itens)`);
} catch (error) {
console.error(`❌ Erro ao salvar cache ${key}:`, error);
throw error;
}
}
/**
* Carrega dados do cache
*/
loadFromCache(key) {
try {
const cacheKey = this.cachePrefix + key;
const data = localStorage.getItem(cacheKey);
if (!data) return null;
const parsed = JSON.parse(data);
console.log(`📂 Cache carregado: ${key} (${parsed.length} itens)`);
return parsed;
} catch (error) {
console.error(`❌ Erro ao carregar cache ${key}:`, error);
return null;
}
}
/**
* Obtém dados de um tipo específico
*/
async getData(type) {
// Tentar carregar do cache primeiro
let data = this.loadFromCache(type);
if (!data) {
// Se não há cache, carregar do CSV
console.log(`📥 Cache não encontrado para ${type}. Carregando do CSV...`);
const config = this.csvConfigs[type];
if (!config) {
throw new Error(`Tipo de dados não configurado: ${type}`);
}
data = await this.loadCSV(config);
this.saveToCache(type, data);
}
return data;
}
/**
* Filtra dados baseado em critérios
*/
filterData(data, filters = {}) {
if (!data || !Array.isArray(data)) return [];
return data.filter(item => {
for (const [key, value] of Object.entries(filters)) {
if (value === null || value === undefined || value === '') continue;
const itemValue = item[key];
if (typeof value === 'string') {
if (!itemValue || !itemValue.toString().toLowerCase().includes(value.toLowerCase())) {
return false;
}
} else if (typeof value === 'number') {
if (itemValue !== value) {
return false;
}
} else if (typeof value === 'object' && value.min !== undefined) {
if (itemValue < value.min) return false;
} else if (typeof value === 'object' && value.max !== undefined) {
if (itemValue > value.max) return false;
}
}
return true;
});
}
/**
* Busca dados por texto
*/
searchData(data, searchTerm, searchFields = ['nome']) {
if (!searchTerm || !data) return data;
const term = searchTerm.toLowerCase();
return data.filter(item => {
return searchFields.some(field => {
const value = item[field];
return value && value.toString().toLowerCase().includes(term);
});
});
}
/**
* Obtém metadados do cache
*/
getMetadata() {
try {
const data = localStorage.getItem(this.metadataKey);
return data ? JSON.parse(data) : null;
} catch (error) {
console.error('❌ Erro ao carregar metadata:', error);
return null;
}
}
/**
* Atualiza metadados
*/
updateMetadata(results) {
const metadata = {
version: this.version,
lastUpdate: Date.now(),
results: results,
totalTypes: Object.keys(this.csvConfigs).length,
successCount: results.success.length,
errorCount: results.errors.length
};
try {
localStorage.setItem(this.metadataKey, JSON.stringify(metadata));
console.log('💾 Metadata atualizado');
} catch (error) {
console.error('❌ Erro ao salvar metadata:', error);
}
}
/**
* Metadados por tipo (fonte e última atualização)
*/
getAllTypesMetadata() {
try {
const raw = localStorage.getItem(this.typesMetadataKey);
return raw ? JSON.parse(raw) : {};
} catch (e) {
console.error('❌ Erro ao ler metadados por tipo:', e);
return {};
}
}
getTypeMetadata(type) {
const all = this.getAllTypesMetadata();
return all[type] || null;
}
setTypeMetadata(type, metadata) {
try {
const all = this.getAllTypesMetadata();
all[type] = metadata;
localStorage.setItem(this.typesMetadataKey, JSON.stringify(all));
console.log(`💾 Metadata do tipo salvo: ${type}`);
} catch (e) {
console.error('❌ Erro ao salvar metadados por tipo:', e);
}
}
/**
* Limpa todo o cache
*/
clearCache() {
console.log('🗑️ Limpando cache...');
// Remover dados
Object.keys(this.csvConfigs).forEach(key => {
const cacheKey = this.cachePrefix + key;
localStorage.removeItem(cacheKey);
});
// Remover metadata
localStorage.removeItem(this.metadataKey);
localStorage.removeItem(this.typesMetadataKey);
console.log('✅ Cache limpo');
}
/**
* Obtém estatísticas do cache
*/
getCacheStats() {
const metadata = this.getMetadata();
const stats = {
version: this.version,
hasCache: !!metadata,
lastUpdate: metadata?.lastUpdate,
types: {}
};
Object.keys(this.csvConfigs).forEach(key => {
const data = this.loadFromCache(key);
stats.types[key] = {
name: this.csvConfigs[key].displayName,
count: data ? data.length : 0,
cached: !!data,
meta: this.getTypeMetadata(key)
};
});
return stats;
}
}
// Instância global
window.dataManager = new DataManager();
console.log('✅ Data Manager carregado e disponível globalmente');
// ========================================
// ATUALIZAÇÃO DO BADGE DE STATUS
// ========================================
/**
* Atualiza o badge de status do cache no header
*/
function atualizarBadgeStatus() {
const badge = document.getElementById('cache-status-badge');
const icon = document.getElementById('cache-icon');
const text = document.getElementById('cache-text');
if (!badge || !icon || !text) return;
try {
const stats = window.dataManager.getCacheStats();
// Remover classes antigas
badge.classList.remove('cache-active', 'cache-empty');
if (stats.hasCache) {
// Cache ativo
icon.textContent = '✅';
text.textContent = 'Cache Ativo';
badge.classList.add('cache-active');
badge.title = `Cache ativo - ${Object.values(stats.types).reduce((sum, t) => sum + t.count, 0)} itens carregados`;
} else {
// Cache vazio
icon.textContent = '❌';
text.textContent = 'Sem Cache';
badge.classList.add('cache-empty');
badge.title = 'Cache vazio - Clique para carregar dados';
}
} catch (error) {
console.error('❌ Erro ao atualizar badge:', error);
icon.textContent = '⚠️';
text.textContent = 'Erro';
badge.title = 'Erro ao verificar cache';
}
}
// Atualizar badge quando Data Manager estiver pronto
if (window.dataManager) {
// Aguardar um pouco para garantir que o DOM está pronto
setTimeout(() => {
atualizarBadgeStatus();
// Atualizar a cada 5 segundos
setInterval(atualizarBadgeStatus, 5000);
}, 1000);
}
console.log('✅ Badge de status configurado');

View File

@@ -0,0 +1,342 @@
/**
* IMPORTADOR DE CSV - Ferramenta de Atualização do BD Interno
* Permite carregar CSVs externos e atualizar o banco de dados interno
*/
/**
* Abre modal de importação de CSV
*/
function abrirImportadorCSV() {
console.log('📥 Abrindo importador de CSV');
const modalHTML = `
<div class="modal active" id="modal-importador-csv" onclick="fecharImportadorCSV(event)">
<div class="modal-content" onclick="event.stopPropagation()" style="max-width: 900px;">
<div class="modal-header">
<div class="modal-title">📥 Importador de CSV - Atualizar BD Interno</div>
<button class="close-btn" onclick="fecharImportadorCSV()">×</button>
</div>
<div class="modal-body">
<!-- Instruções -->
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">📋 Como Usar</h3>
<ol style="margin: 0; padding-left: 20px;">
<li>Selecione o tipo de perfil que deseja atualizar</li>
<li>Escolha o arquivo CSV com os novos dados</li>
<li>Clique em "Importar" para atualizar o BD interno</li>
<li>Os dados serão salvos no localStorage e usados imediatamente</li>
</ol>
</div>
<!-- Seleção de Tipo -->
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">🎯 Selecionar Tipo de Perfil</h3>
<div class="form-group">
<label class="form-label">Tipo de Perfil:</label>
<select class="form-control" id="tipo-perfil-importar">
<option value="">Selecione um tipo...</option>
<option value="cantoneiras">Cantoneiras</option>
<option value="barras_redondas">Barras Redondas</option>
<option value="tubos_circulares">Tubos Circulares</option>
<option value="perfis_i">Perfis I (IPE)</option>
<option value="perfis_w">Perfis W</option>
<option value="tubos_rhs">Tubos RHS</option>
<option value="chapas">Chapas</option>
<option value="perfis_hp">Perfis HP</option>
<option value="barras_roscadas">Barras Roscadas</option>
<option value="barras_chatas">Barras Chatas</option>
</select>
</div>
</div>
<!-- Upload de Arquivo -->
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px;">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">📁 Selecionar Arquivo CSV</h3>
<div class="form-group">
<label class="form-label">Arquivo CSV:</label>
<input type="file" class="form-control" id="arquivo-csv" accept=".csv" onchange="visualizarCSV()">
<small style="color: var(--color-text-secondary); margin-top: 8px; display: block;">
Selecione um arquivo CSV com os dados dos perfis
</small>
</div>
</div>
<!-- Preview do CSV -->
<div class="card" style="background: var(--color-bg-1); margin-bottom: 20px; display: none;" id="preview-csv">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">👁️ Preview do Arquivo</h3>
<div id="preview-conteudo" style="max-height: 300px; overflow: auto;"></div>
<div id="preview-stats" style="margin-top: 12px; padding: 12px; background: var(--color-bg-2); border-radius: 6px;"></div>
</div>
<!-- Log de Importação -->
<div class="card" style="background: var(--color-bg-1); display: none;" id="log-importacao">
<h3 style="color: var(--color-primary); margin: 0 0 16px 0;">📝 Log de Importação</h3>
<div id="log-conteudo" style="background: #000; color: #0f0; padding: 16px; border-radius: 8px; font-family: monospace; font-size: 12px; max-height: 200px; overflow-y: auto;"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="fecharImportadorCSV()">Cancelar</button>
<button class="btn btn-primary" onclick="executarImportacao()" id="btn-importar" disabled>
📥 Importar Dados
</button>
</div>
</div>
</div>
`;
// Remover modal existente
const existingModal = document.getElementById('modal-importador-csv');
if (existingModal) {
existingModal.remove();
}
// Adicionar novo modal
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
/**
* Fecha o importador de CSV
*/
function fecharImportadorCSV(event) {
if (event && event.target !== event.currentTarget) return;
const modal = document.getElementById('modal-importador-csv');
if (modal) {
modal.remove();
}
}
/**
* Visualiza o conteúdo do CSV selecionado
*/
function visualizarCSV() {
const arquivo = document.getElementById('arquivo-csv').files[0];
const tipoSelecionado = document.getElementById('tipo-perfil-importar').value;
if (!arquivo) {
document.getElementById('preview-csv').style.display = 'none';
document.getElementById('btn-importar').disabled = true;
return;
}
if (!tipoSelecionado) {
alert('⚠️ Selecione o tipo de perfil primeiro!');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const csvText = e.target.result;
const linhas = csvText.trim().split('\n');
// Mostrar preview
const previewDiv = document.getElementById('preview-conteudo');
const statsDiv = document.getElementById('preview-stats');
// Primeiras 5 linhas
const preview = linhas.slice(0, 6).map((linha, index) => {
const colunas = linha.split(',');
const isHeader = index === 0;
return `
<div style="padding: 8px; background: ${isHeader ? 'var(--color-primary)' : 'var(--color-bg-2)'};
color: ${isHeader ? 'white' : 'inherit'}; margin-bottom: 2px; border-radius: 4px;">
<strong>${isHeader ? 'CABEÇALHO:' : `Linha ${index}:`}</strong>
${colunas.join(' | ')}
</div>
`;
}).join('');
previewDiv.innerHTML = preview;
// Estatísticas
statsDiv.innerHTML = `
<strong>📊 Estatísticas:</strong><br>
• Total de linhas: ${linhas.length}<br>
• Linhas de dados: ${linhas.length - 1}<br>
• Colunas: ${linhas[0] ? linhas[0].split(',').length : 0}<br>
• Tamanho: ${(csvText.length / 1024).toFixed(2)} KB
`;
// Mostrar preview
document.getElementById('preview-csv').style.display = 'block';
document.getElementById('btn-importar').disabled = false;
};
reader.readAsText(arquivo);
}
/**
* Executa a importação do CSV
*/
function executarImportacao() {
const arquivo = document.getElementById('arquivo-csv').files[0];
const tipo = document.getElementById('tipo-perfil-importar').value;
if (!arquivo || !tipo) {
alert('⚠️ Selecione o tipo de perfil e o arquivo CSV!');
return;
}
// Mostrar log
document.getElementById('log-importacao').style.display = 'block';
const logDiv = document.getElementById('log-conteudo');
function log(msg) {
const time = new Date().toLocaleTimeString();
logDiv.innerHTML += `<div>[${time}] ${msg}</div>`;
logDiv.scrollTop = logDiv.scrollHeight;
}
log('🚀 Iniciando importação...');
log(`📊 Tipo: ${tipo}`);
log(`📁 Arquivo: ${arquivo.name}`);
const reader = new FileReader();
reader.onload = function(e) {
try {
const csvText = e.target.result;
const linhas = csvText.trim().split('\n');
log(`📄 Arquivo lido: ${linhas.length} linhas`);
if (linhas.length < 2) {
throw new Error('Arquivo CSV deve ter pelo menos cabeçalho + 1 linha de dados');
}
// Processar dados
const cabecalho = linhas[0].split(',').map(h => h.trim().replace(/"/g, ''));
log(`📋 Cabeçalho: ${cabecalho.join(', ')}`);
const dados = [];
for (let i = 1; i < linhas.length; i++) {
const linha = linhas[i].trim();
if (!linha) continue;
const valores = linha.split(',').map(v => v.trim().replace(/"/g, ''));
if (valores.length < cabecalho.length) continue;
const item = {};
cabecalho.forEach((col, index) => {
let valor = valores[index] || '';
// Converter números
if (col.includes('_mm') || col.includes('_kg') || col.includes('_cm') || col.includes('_m2')) {
valor = parseFloat(valor) || 0;
}
item[col] = valor;
});
dados.push(item);
}
log(`✅ Processados: ${dados.length} itens`);
if (dados.length === 0) {
throw new Error('Nenhum dado válido encontrado no CSV');
}
// Atualizar banco de dados interno
if (!window.BANCO_DADOS_PERFIS) {
window.BANCO_DADOS_PERFIS = {};
}
window.BANCO_DADOS_PERFIS[tipo] = dados;
// Salvar no localStorage
const chaveCache = `acoCalcPro_dados_${tipo}`;
localStorage.setItem(chaveCache, JSON.stringify(dados));
log(`💾 Dados salvos no localStorage: ${chaveCache}`);
// Atualizar metadata
const metadata = {
tipo: tipo,
arquivo: arquivo.name,
dataImportacao: new Date().toISOString(),
totalItens: dados.length,
colunas: cabecalho
};
localStorage.setItem(`acoCalcPro_metadata_${tipo}`, JSON.stringify(metadata));
log(`📊 Metadata salvo`);
// Recarregar dados na tabela se estiver visível
if (typeof carregarPerfilUniversal === 'function') {
const sucesso = carregarPerfilUniversal(tipo);
if (sucesso) {
log(`🔄 Tabela atualizada automaticamente`);
}
}
log(`🎉 IMPORTAÇÃO CONCLUÍDA COM SUCESSO!`);
// Notificar usuário
setTimeout(() => {
alert(`✅ Importação concluída!\n\n📊 ${dados.length} itens de ${tipo} importados\n💾 Dados salvos no BD interno\n🔄 Tabela atualizada`);
}, 1000);
} catch (error) {
log(`❌ ERRO: ${error.message}`);
alert(`❌ Erro na importação: ${error.message}`);
}
};
reader.readAsText(arquivo);
}
/**
* Exporta dados atuais para CSV
*/
function exportarDadosCSV(tipo) {
if (!window.BANCO_DADOS_PERFIS || !window.BANCO_DADOS_PERFIS[tipo]) {
alert(`❌ Nenhum dado encontrado para ${tipo}`);
return;
}
const dados = window.BANCO_DADOS_PERFIS[tipo];
if (dados.length === 0) {
alert(`❌ Dados vazios para ${tipo}`);
return;
}
// Gerar CSV
const cabecalho = Object.keys(dados[0]);
const linhas = [cabecalho.join(',')];
dados.forEach(item => {
const linha = cabecalho.map(col => {
let valor = item[col];
if (typeof valor === 'string' && valor.includes(',')) {
valor = `"${valor}"`;
}
return valor;
}).join(',');
linhas.push(linha);
});
const csvContent = linhas.join('\n');
// Download
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${tipo}_${new Date().toISOString().split('T')[0]}.csv`;
a.click();
URL.revokeObjectURL(url);
console.log(`✅ Dados de ${tipo} exportados para CSV`);
}
// Exportar funções
window.abrirImportadorCSV = abrirImportadorCSV;
window.fecharImportadorCSV = fecharImportadorCSV;
window.visualizarCSV = visualizarCSV;
window.executarImportacao = executarImportacao;
window.exportarDadosCSV = exportarDadosCSV;
console.log('✅ Importador de CSV carregado');

View File

@@ -0,0 +1,330 @@
/**
* CARREGADOR DE PERFIS - Usando Data Manager
*
* Funções específicas para carregar e exibir dados dos perfis
* usando o sistema de cache inteligente.
*/
// ========================================
// CANTONEIRAS
// ========================================
/**
* Carrega dados das cantoneiras usando o Data Manager
*/
async function carregarCantoneirasV2() {
console.log('🔧 carregarCantoneirasV2() - Nova versão com Data Manager');
try {
// Verificar se o elemento existe
const tbody = document.getElementById('cantoneiras-tbody');
if (!tbody) {
console.error('❌ Elemento cantoneiras-tbody não encontrado!');
return;
}
console.log('✅ Elemento tbody encontrado');
// Mostrar loading
tbody.innerHTML = `
<tr>
<td colspan="9" style="text-align: center; padding: 20px;">
<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
<div class="spinner"></div>
📊 Carregando cantoneiras do banco de dados...
</div>
</td>
</tr>
`;
// Carregar dados via Data Manager
const dados = await window.dataManager.getData('cantoneiras');
if (!dados || dados.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="9" style="text-align: center; padding: 20px; color: var(--color-error);">
❌ Nenhuma cantoneira encontrada no banco de dados.
<br><br>
<button class="btn btn-primary" onclick="atualizarDadosCantoneiras()">
🔄 Atualizar Dados
</button>
</td>
</tr>
`;
return;
}
console.log(`${dados.length} cantoneiras carregadas do Data Manager`);
console.log('🔍 Primeiro item:', dados[0]);
// Armazenar globalmente para filtros
window.cantoneirasData = dados;
console.log('💾 Dados armazenados em window.cantoneirasData');
// Exibir na tabela
console.log('🎨 Chamando exibirCantoneirasV2...');
console.log('🔍 Tipo de exibirCantoneirasV2:', typeof exibirCantoneirasV2);
if (typeof exibirCantoneirasV2 === 'function') {
exibirCantoneirasV2(dados);
} else {
console.error('❌ exibirCantoneirasV2 não é uma função!');
// Fallback: exibir diretamente
console.log('🔄 Usando fallback para exibir dados...');
const tbody = document.getElementById('cantoneiras-tbody');
if (tbody) {
tbody.innerHTML = dados.map(item => `
<tr>
<td><strong>${item.nome}</strong></td>
<td>${item.lado_mm}</td>
<td>${item.espessura_mm}</td>
<td>${item.peso_kg_m.toFixed(2)}</td>
<td>${item.area_cm2.toFixed(2)}</td>
<td>${item.momento_inercia_cm4.toFixed(2)}</td>
<td>${item.raio_giracao_cm.toFixed(2)}</td>
<td><span class="badge">${item.tipo}</span></td>
<td><button class="btn btn-sm">Ver</button></td>
</tr>
`).join('');
console.log('✅ Tabela preenchida via fallback!');
} else {
console.error('❌ Elemento tbody não encontrado no fallback!');
}
}
// Atualizar contador
const totalEl = document.getElementById('cant-total');
if (totalEl) {
totalEl.textContent = dados.length;
}
} catch (error) {
console.error('❌ Erro ao carregar cantoneiras:', error);
const tbody = document.getElementById('cantoneiras-tbody');
if (tbody) {
tbody.innerHTML = `
<tr>
<td colspan="9" style="text-align: center; padding: 20px; color: var(--color-error);">
❌ Erro ao carregar dados: ${error.message}
<br><br>
<button class="btn btn-primary" onclick="atualizarDadosCantoneiras()">
🔄 Tentar Novamente
</button>
</td>
</tr>
`;
}
}
}
/**
* Exibe cantoneiras na tabela (versão otimizada)
*/
function exibirCantoneirasV2(dados) {
console.log(`📋 Exibindo ${dados.length} cantoneiras na tabela`);
const tbody = document.getElementById('cantoneiras-tbody');
if (!tbody) {
console.error('❌ Elemento tbody não encontrado');
return;
}
if (!dados || dados.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="9" style="text-align: center; padding: 20px;">
Nenhuma cantoneira encontrada
</td>
</tr>
`;
return;
}
// Gerar HTML otimizado
const html = dados.map(item => {
const badgeColor = getBadgeColorV2(item.tipo);
return `
<tr>
<td><strong>${item.nome}</strong></td>
<td>${item.lado_mm}</td>
<td>${item.espessura_mm}</td>
<td>${item.peso_kg_m.toFixed(2)}</td>
<td>${item.area_cm2.toFixed(2)}</td>
<td>${item.momento_inercia_cm4.toFixed(2)}</td>
<td>${item.raio_giracao_cm.toFixed(2)}</td>
<td><span class="badge badge-${badgeColor}">${item.tipo}</span></td>
<td>
<button class="btn btn-sm btn-primary" onclick="verDetalhesCantoneira('${item.id}')">
👁️ Ver
</button>
</td>
</tr>
`;
}).join('');
tbody.innerHTML = html;
console.log('✅ Tabela atualizada com sucesso');
}
/**
* Filtra cantoneiras usando Data Manager
*/
function filtrarCantoneirasV2() {
if (!window.cantoneirasData) {
console.warn('⚠️ Dados não carregados ainda');
return;
}
// Obter filtros
const tamanho = document.getElementById('cant-tamanho')?.value || '';
const pesoMax = parseFloat(document.getElementById('cant-peso-max')?.value) || Infinity;
const busca = document.getElementById('cant-busca')?.value || '';
console.log('🔍 Aplicando filtros:', { tamanho, pesoMax, busca });
// Aplicar filtros usando Data Manager
let filtrados = window.cantoneirasData;
// Filtro por tipo/tamanho
if (tamanho) {
filtrados = window.dataManager.filterData(filtrados, { tipo: tamanho });
}
// Filtro por peso máximo
if (pesoMax < Infinity) {
filtrados = window.dataManager.filterData(filtrados, { peso_kg_m: { max: pesoMax } });
}
// Busca por nome
if (busca) {
filtrados = window.dataManager.searchData(filtrados, busca, ['nome', 'id']);
}
console.log(`🎯 Filtrados: ${filtrados.length} de ${window.cantoneirasData.length}`);
// Exibir resultados
exibirCantoneirasV2(filtrados);
// Atualizar contador
const totalEl = document.getElementById('cant-total');
if (totalEl) {
totalEl.textContent = filtrados.length;
}
}
/**
* Limpa filtros das cantoneiras
*/
function limparFiltrosCantoneirasV2() {
console.log('🧹 Limpando filtros');
// Limpar campos
const tamanhoEl = document.getElementById('cant-tamanho');
const pesoEl = document.getElementById('cant-peso-max');
const buscaEl = document.getElementById('cant-busca');
if (tamanhoEl) tamanhoEl.value = '';
if (pesoEl) pesoEl.value = '';
if (buscaEl) buscaEl.value = '';
// Exibir todos os dados
if (window.cantoneirasData) {
exibirCantoneirasV2(window.cantoneirasData);
const totalEl = document.getElementById('cant-total');
if (totalEl) {
totalEl.textContent = window.cantoneirasData.length;
}
}
}
/**
* Força atualização dos dados das cantoneiras
*/
async function atualizarDadosCantoneiras() {
console.log('🔄 Forçando atualização dos dados das cantoneiras...');
try {
// Limpar cache específico
localStorage.removeItem('acoCalcPro_cache_cantoneiras');
// Recarregar
await carregarCantoneirasV2();
// Notificar sucesso
alert('✅ Dados das cantoneiras atualizados com sucesso!');
} catch (error) {
console.error('❌ Erro ao atualizar dados:', error);
alert('❌ Erro ao atualizar dados: ' + error.message);
}
}
/**
* Retorna cor do badge (versão otimizada)
*/
function getBadgeColorV2(tipo) {
const cores = {
'Pequena': 'info',
'Média': 'success',
'Grande': 'warning',
'Muito Grande': 'warning',
'Extra-Grande': 'error',
'Massiva': 'error'
};
return cores[tipo] || 'info';
}
/**
* Ver detalhes de uma cantoneira específica
*/
function verDetalhesCantoneira(id) {
if (!window.cantoneirasData) {
alert('❌ Dados não carregados');
return;
}
const item = window.cantoneirasData.find(c => c.id === id);
if (!item) {
alert('❌ Cantoneira não encontrada');
return;
}
alert(`
📐 ${item.nome}
Lado: ${item.lado_mm} mm
Espessura: ${item.espessura_mm} mm
Peso: ${item.peso_kg_m.toFixed(2)} kg/m
Área: ${item.area_cm2.toFixed(2)} cm²
Momento de Inércia: ${item.momento_inercia_cm4.toFixed(2)} cm⁴
Raio de Giração: ${item.raio_giracao_cm.toFixed(2)} cm
Tipo: ${item.tipo}
`);
}
// Adicionar CSS para spinner
if (!document.getElementById('spinner-styles')) {
const style = document.createElement('style');
style.id = 'spinner-styles';
style.textContent = `
.spinner {
width: 20px;
height: 20px;
border: 2px solid var(--color-border);
border-top: 2px solid var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
console.log('✅ Perfis Loader V2 carregado com Data Manager');