Initial commit SteelBase - Oficiais e Funcionando

This commit is contained in:
Marcos
2026-03-22 16:56:47 -03:00
commit f10278909b
194 changed files with 87242 additions and 0 deletions

1279
js/database/admin-panel.js Normal file

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

741
js/database/data-manager.js Normal file
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');