Fix script paths and move assets to public/ folder for Vite build compatibility
This commit is contained in:
1279
public/js/database/admin-panel.js
Normal file
1279
public/js/database/admin-panel.js
Normal file
File diff suppressed because it is too large
Load Diff
4443
public/js/database/banco-dados-completo.js
Normal file
4443
public/js/database/banco-dados-completo.js
Normal file
File diff suppressed because it is too large
Load Diff
324
public/js/database/carregador-universal.js
Normal file
324
public/js/database/carregador-universal.js
Normal 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));
|
||||
177
public/js/database/dados-embutidos.js
Normal file
177
public/js/database/dados-embutidos.js
Normal 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
public/js/database/data-manager.js
Normal file
741
public/js/database/data-manager.js
Normal 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');
|
||||
342
public/js/database/importador-csv.js
Normal file
342
public/js/database/importador-csv.js
Normal 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');
|
||||
330
public/js/database/perfis-loader.js
Normal file
330
public/js/database/perfis-loader.js
Normal 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');
|
||||
Reference in New Issue
Block a user