Fix script paths and move assets to public/ folder for Vite build compatibility
This commit is contained in:
349
public/js/sections/perfis-auto-loader.js
Normal file
349
public/js/sections/perfis-auto-loader.js
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* CARREGADOR AUTOMÁTICO PARA TODOS OS PERFIS
|
||||
* Gera automaticamente as funções de carregamento para cada tipo de perfil
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gera função de carregamento forçado para um tipo de perfil
|
||||
*/
|
||||
function gerarFuncaoCarregamento(tipo) {
|
||||
const config = window.PERFIS_CONFIG[tipo];
|
||||
if (!config) return null;
|
||||
|
||||
const tipoId = tipo.replace(/-/g, '_');
|
||||
const funcName = `forcarCarregamento${capitalize(tipoId)}`;
|
||||
|
||||
window[funcName] = async function() {
|
||||
console.log(`🚨 CARREGAMENTO FORÇADO INICIADO: ${config.nome}`);
|
||||
|
||||
const tbody = document.getElementById(`${tipoId}-tbody`);
|
||||
if (!tbody) {
|
||||
alert(`❌ Erro: Elemento da tabela não encontrado!`);
|
||||
console.error(`❌ Elemento ${tipoId}-tbody não encontrado`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mostrar loading
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="${config.colunasTabel.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 dados...</div>
|
||||
<div style="font-size: 14px; color: #666; margin-top: 8px;">Aguarde alguns segundos</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
try {
|
||||
// Carregar CSV
|
||||
console.log(`📥 Carregando CSV: ${config.csv}`);
|
||||
const response = await fetch(config.csv);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const csvText = await response.text();
|
||||
const linhas = csvText.trim().split('\n');
|
||||
|
||||
console.log(`📊 CSV carregado: ${linhas.length} linhas`);
|
||||
|
||||
// Processar dados
|
||||
const dados = [];
|
||||
for (let i = 1; i < linhas.length; i++) {
|
||||
const linha = linhas[i].trim();
|
||||
if (!linha) continue;
|
||||
|
||||
const colunas = linha.split(',');
|
||||
if (colunas.length >= config.colunas.length) {
|
||||
const item = {};
|
||||
config.colunas.forEach((col, index) => {
|
||||
let value = colunas[index].trim();
|
||||
// Converter números
|
||||
if (col.includes('_mm') || col.includes('_kg') || col.includes('_cm') || col.includes('_m2')) {
|
||||
value = parseFloat(value) || 0;
|
||||
}
|
||||
item[col] = value;
|
||||
});
|
||||
dados.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Processados: ${dados.length} itens`);
|
||||
|
||||
if (dados.length === 0) {
|
||||
throw new Error('Nenhum dado encontrado no CSV');
|
||||
}
|
||||
|
||||
// Exibir na tabela
|
||||
tbody.innerHTML = dados.map(item => `
|
||||
<tr>
|
||||
${config.colunasTabel.map(col => {
|
||||
let value = item[col.key];
|
||||
if (col.decimals && typeof value === 'number') {
|
||||
value = value.toFixed(col.decimals);
|
||||
}
|
||||
if (col.badge) {
|
||||
return `<td><span class="badge badge-info">${value}</span></td>`;
|
||||
}
|
||||
return `<td>${col.key === 'nome' ? '<strong>' + value + '</strong>' : value}</td>`;
|
||||
}).join('')}
|
||||
<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Atualizar contador
|
||||
const totalEl = document.getElementById(`${tipoId}-total`);
|
||||
if (totalEl) {
|
||||
totalEl.textContent = dados.length;
|
||||
}
|
||||
|
||||
// Armazenar globalmente
|
||||
window[`${tipoId}Data`] = dados;
|
||||
|
||||
console.log(`🎉 CARREGAMENTO CONCLUÍDO: ${config.nome}`);
|
||||
|
||||
// Notificar usuário
|
||||
alert(`✅ ${dados.length} ${config.nome.toLowerCase()} carregados com sucesso!`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Erro no carregamento: ${error.message}`);
|
||||
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="${config.colunasTabel.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;">Erro ao carregar dados</div>
|
||||
<div style="font-size: 14px; color: #666; margin-top: 8px;">${error.message}</div>
|
||||
<button class="btn btn-primary" onclick="${funcName}()" style="margin-top: 16px;">
|
||||
🔄 Tentar Novamente
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
alert(`❌ Erro ao carregar dados: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`✅ Função criada: ${funcName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera funções de filtro para um tipo de perfil
|
||||
*/
|
||||
function gerarFuncoesFiltro(tipo) {
|
||||
const config = window.PERFIS_CONFIG[tipo];
|
||||
if (!config) return;
|
||||
|
||||
const tipoId = tipo.replace(/-/g, '_');
|
||||
|
||||
// Função de filtrar
|
||||
window[`filtrar${capitalize(tipoId)}`] = function() {
|
||||
const dados = window[`${tipoId}Data`];
|
||||
if (!dados) {
|
||||
console.warn('⚠️ Dados não carregados ainda');
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementar filtros básicos
|
||||
console.log(`🔍 Filtrando ${config.nome}...`);
|
||||
// TODO: Implementar lógica de filtro
|
||||
};
|
||||
|
||||
// Função de limpar filtros
|
||||
window[`limparFiltros${capitalize(tipoId)}`] = function() {
|
||||
console.log(`🧹 Limpando filtros de ${config.nome}`);
|
||||
|
||||
// Limpar campos
|
||||
config.filtros.forEach(filtro => {
|
||||
const el = document.getElementById(`${tipoId}-${filtro.id}`);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
|
||||
// Reexibir todos os dados
|
||||
const dados = window[`${tipoId}Data`];
|
||||
if (dados) {
|
||||
const tbody = document.getElementById(`${tipoId}-tbody`);
|
||||
if (tbody) {
|
||||
tbody.innerHTML = dados.map(item => `
|
||||
<tr>
|
||||
${config.colunasTabel.map(col => {
|
||||
let value = item[col.key];
|
||||
if (col.decimals && typeof value === 'number') {
|
||||
value = value.toFixed(col.decimals);
|
||||
}
|
||||
if (col.badge) {
|
||||
return `<td><span class="badge badge-info">${value}</span></td>`;
|
||||
}
|
||||
return `<td>${col.key === 'nome' ? '<strong>' + value + '</strong>' : value}</td>`;
|
||||
}).join('')}
|
||||
<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera função de conteúdo para um tipo de perfil
|
||||
*/
|
||||
function gerarFuncaoConteudo(tipo) {
|
||||
const config = window.PERFIS_CONFIG[tipo];
|
||||
if (!config) return;
|
||||
|
||||
const funcName = `get${capitalize(tipo.replace(/-/g, '_'))}Content`;
|
||||
|
||||
window[funcName] = function() {
|
||||
console.log(`🔧 ${funcName}() chamada`);
|
||||
return gerarConteudoPerfil(tipo);
|
||||
};
|
||||
|
||||
console.log(`✅ Função criada: ${funcName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa todas as funções para todos os perfis
|
||||
*/
|
||||
function inicializarTodosPerfis() {
|
||||
console.log('🚀 Inicializando funções para todos os perfis...');
|
||||
|
||||
Object.keys(window.PERFIS_CONFIG).forEach(tipo => {
|
||||
gerarFuncaoConteudo(tipo);
|
||||
gerarFuncaoCarregamento(tipo);
|
||||
gerarFuncoesFiltro(tipo);
|
||||
gerarFuncaoAtualizarFonte(tipo);
|
||||
});
|
||||
|
||||
console.log('✅ Todas as funções de perfis inicializadas!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitaliza primeira letra
|
||||
*/
|
||||
function capitalize(str) {
|
||||
return str.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Inicializar quando o script carregar
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', inicializarTodosPerfis);
|
||||
} else {
|
||||
inicializarTodosPerfis();
|
||||
}
|
||||
|
||||
console.log('✅ Auto-loader de perfis carregado');
|
||||
|
||||
// ========================================
|
||||
// Atualização direta a partir da fonte (CSV)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Mapeia o ID do template (com hífen) para a chave do DataManager (com underscore)
|
||||
*/
|
||||
function mapToDataManagerKey(tipo) {
|
||||
return tipo.replace(/-/g, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera função de atualização de dados a partir da fonte oficial (DataManager)
|
||||
* Cria window["atualizarFonte<Cap>"] para cada tipo. O botão existe apenas em Tubos RHS.
|
||||
*/
|
||||
function gerarFuncaoAtualizarFonte(tipo) {
|
||||
const config = window.PERFIS_CONFIG[tipo];
|
||||
if (!config) return;
|
||||
const tipoId = tipo.replace(/-/g, '_');
|
||||
const funcName = `atualizarFonte${capitalize(tipoId)}`;
|
||||
const dmKey = mapToDataManagerKey(tipo);
|
||||
|
||||
window[funcName] = async function() {
|
||||
try {
|
||||
const tbody = document.getElementById(`${tipoId}-tbody`);
|
||||
const tableContainer = document.getElementById(`${tipoId}-table-container`);
|
||||
if (!tbody || !tableContainer) {
|
||||
alert('❌ Elementos da tabela não encontrados.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Loader visual
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="${config.colunasTabel.length + 1}" style="text-align:center; padding:32px;">
|
||||
<div style="font-size:42px;">⏳</div>
|
||||
<div style="margin-top:8px; color: var(--color-text-secondary);">Atualizando da fonte: ${config.csv}</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
// Atualizar via DataManager para registrar metadados e cache
|
||||
const result = await window.dataManager.updateTypeData(dmKey);
|
||||
const dados = result.data || [];
|
||||
|
||||
if (!dados.length) {
|
||||
throw new Error('Fonte retornou 0 itens');
|
||||
}
|
||||
|
||||
// Preencher tabela com os novos dados
|
||||
tbody.innerHTML = dados.map(item => `
|
||||
<tr>
|
||||
${config.colunasTabel.map(col => {
|
||||
let value = item[col.key];
|
||||
if (col.decimals && typeof value === 'number') {
|
||||
value = value.toFixed(col.decimals);
|
||||
}
|
||||
if (col.badge) {
|
||||
return `<td><span class="badge badge-info">${value}</span></td>`;
|
||||
}
|
||||
return `<td>${col.key === 'nome' ? '<strong>' + value + '</strong>' : value}</td>`;
|
||||
}).join('')}
|
||||
<td><button class="btn btn-sm btn-primary">👁️ Ver</button></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Atualizar contadores e metadados visuais
|
||||
const totalEl = document.getElementById(`${tipoId}-total`);
|
||||
const fonteEl = document.getElementById(`${tipoId}-fonte`);
|
||||
const lastEl = document.getElementById(`${tipoId}-last`);
|
||||
if (totalEl) totalEl.textContent = dados.length;
|
||||
if (fonteEl) fonteEl.textContent = result.source || config.csv;
|
||||
if (lastEl) lastEl.textContent = new Date(result.lastUpdate || Date.now()).toLocaleString('pt-BR');
|
||||
|
||||
// Atualizar descrição do cabeçalho (quantidade)
|
||||
const headerDesc = document.querySelector('.section-description');
|
||||
if (headerDesc && headerDesc.textContent.includes(config.descricao)) {
|
||||
headerDesc.textContent = `${config.descricao} - ${dados.length} modelos disponíveis`;
|
||||
}
|
||||
|
||||
// Atualizar notas nas outras abas (preços, especificações, fabricantes, aplicações)
|
||||
['perfil-tab-1', 'perfil-tab-2', 'perfil-tab-3', 'perfil-tab-4'].forEach(tabId => {
|
||||
const tab = document.getElementById(tabId);
|
||||
if (tab) {
|
||||
const noteId = `${tipoId}-update-note-${tabId}`;
|
||||
let note = document.getElementById(noteId);
|
||||
if (!note) {
|
||||
note = document.createElement('div');
|
||||
note.id = noteId;
|
||||
note.style.marginTop = '12px';
|
||||
note.style.fontSize = '12px';
|
||||
note.style.color = 'var(--color-text-secondary)';
|
||||
tab.appendChild(note);
|
||||
}
|
||||
note.textContent = `Dados atualizados (${dados.length} itens) • Fonte: ${result.source} • ${new Date(result.lastUpdate).toLocaleString('pt-BR')}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Disponibilizar globalmente
|
||||
window[`${tipoId}Data`] = dados;
|
||||
|
||||
alert(`✅ ${config.nome} atualizado da fonte com ${dados.length} itens.`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Erro ao atualizar da fonte (${tipo}):`, error);
|
||||
alert('❌ Erro ao atualizar da fonte: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`✅ Função criada: ${funcName}`);
|
||||
}
|
||||
3665
public/js/sections/perfis-catalog.js
Normal file
3665
public/js/sections/perfis-catalog.js
Normal file
File diff suppressed because it is too large
Load Diff
417
public/js/sections/perfis-templates.js
Normal file
417
public/js/sections/perfis-templates.js
Normal file
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* TEMPLATES PARA CATÁLOGO DE PERFIS
|
||||
* Sistema automatizado para gerar conteúdo de todos os perfis
|
||||
*/
|
||||
|
||||
// Configuração de cada tipo de perfil
|
||||
const PERFIS_CONFIG = {
|
||||
'barras-redondas': {
|
||||
nome: 'Barras Redondas',
|
||||
icone: '⚫',
|
||||
descricao: 'Barras de aço redondas laminadas a quente',
|
||||
quantidade: 25,
|
||||
csv: 'BD/perfis/barras_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'diametro_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'diametro_mm', label: 'Diâmetro (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (Ø6-Ø16)' },
|
||||
{ value: 'Médio', label: 'Médio (Ø20-Ø32)' },
|
||||
{ value: 'Grande', label: 'Grande (Ø38-Ø50)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (Ø63+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 20' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: Ø25' }
|
||||
]
|
||||
},
|
||||
|
||||
'tubos-circulares': {
|
||||
nome: 'Tubos Circulares',
|
||||
icone: '⭕',
|
||||
descricao: 'Tubos de aço circulares sem costura e com costura',
|
||||
quantidade: 30,
|
||||
csv: 'BD/perfis/tubos_circulares_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'diametro_externo_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'diametro_externo_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: 'tipo', label: 'Categoria', badge: true }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Diâmetro', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (Ø21-Ø60)' },
|
||||
{ value: 'Médio', label: 'Médio (Ø73-Ø114)' },
|
||||
{ value: 'Grande', label: 'Grande (Ø141-Ø219)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (Ø273+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 50' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: Ø114' }
|
||||
]
|
||||
},
|
||||
|
||||
'perfis-i': {
|
||||
nome: 'Perfis I (IPE)',
|
||||
icone: '🏗️',
|
||||
descricao: 'Perfis I laminados a quente - Série IPE',
|
||||
quantidade: 20,
|
||||
csv: 'BD/perfis/perfis_i_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'altura_mm', label: 'Altura (mm)' },
|
||||
{ key: 'largura_mm', label: 'Largura (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (IPE80-IPE160)' },
|
||||
{ value: 'Médio', label: 'Médio (IPE180-IPE270)' },
|
||||
{ value: 'Grande', label: 'Grande (IPE300-IPE400)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (IPE450+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 100' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: IPE200' }
|
||||
]
|
||||
},
|
||||
|
||||
'perfis-w': {
|
||||
nome: 'Perfis W',
|
||||
icone: '🏛️',
|
||||
descricao: 'Perfis W laminados a quente - Série americana',
|
||||
quantidade: 25,
|
||||
csv: 'BD/perfis/perfis_w_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'altura_mm', label: 'Altura (mm)' },
|
||||
{ key: 'largura_mm', label: 'Largura (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (W150-W250)' },
|
||||
{ value: 'Médio', label: 'Médio (W310-W410)' },
|
||||
{ value: 'Grande', label: 'Grande (W460-W610)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (W690+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 150' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: W310' }
|
||||
]
|
||||
},
|
||||
|
||||
'tubos-rhs': {
|
||||
nome: 'Tubos RHS',
|
||||
icone: '▭',
|
||||
descricao: 'Tubos retangulares e quadrados estruturais',
|
||||
quantidade: 35,
|
||||
csv: 'BD/perfis/tubos_rhs_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'largura_mm', 'altura_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ 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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (até 50x50)' },
|
||||
{ value: 'Médio', label: 'Médio (60x40 a 100x100)' },
|
||||
{ value: 'Grande', label: 'Grande (120x80 a 200x100)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (200x200+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 80' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: 100x100' }
|
||||
]
|
||||
},
|
||||
|
||||
'chapas': {
|
||||
nome: 'Chapas',
|
||||
icone: '📄',
|
||||
descricao: 'Chapas de aço laminadas a quente e a frio',
|
||||
quantidade: 15,
|
||||
csv: 'BD/perfis/chapas_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'espessura_mm', 'peso_kg_m2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'espessura_mm', label: 'Espessura (mm)' },
|
||||
{ key: 'peso_kg_m2', label: 'Peso (kg/m²)', decimals: 2 },
|
||||
{ key: 'tipo', label: 'Categoria', badge: true }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Espessura', type: 'select', options: [
|
||||
{ value: '', label: 'Todas' },
|
||||
{ value: 'Fina', label: 'Fina (até 6mm)' },
|
||||
{ value: 'Média', label: 'Média (8-16mm)' },
|
||||
{ value: 'Grossa', label: 'Grossa (19-32mm)' },
|
||||
{ value: 'Extra-Grossa', label: 'Extra-Grossa (38mm+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m²)', type: 'number', placeholder: 'Ex: 100' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: 12.5mm' }
|
||||
]
|
||||
},
|
||||
|
||||
'perfis-hp': {
|
||||
nome: 'Perfis HP',
|
||||
icone: '🏗️',
|
||||
descricao: 'Perfis HP para estacas e pilares',
|
||||
quantidade: 12,
|
||||
csv: 'BD/perfis/perfis_hp_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'altura_mm', 'largura_mm', 'espessura_alma_mm', 'espessura_mesa_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'altura_mm', label: 'Altura (mm)' },
|
||||
{ key: 'largura_mm', label: 'Largura (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (HP200-HP250)' },
|
||||
{ value: 'Médio', label: 'Médio (HP310-HP360)' },
|
||||
{ value: 'Grande', label: 'Grande (HP400+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 150' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: HP310' }
|
||||
]
|
||||
},
|
||||
|
||||
'barras-roscadas': {
|
||||
nome: 'Barras Roscadas',
|
||||
icone: '🔩',
|
||||
descricao: 'Barras roscadas de aço para fixação',
|
||||
quantidade: 18,
|
||||
csv: 'BD/perfis/barras_roscadas_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'diametro_mm', 'passo_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'diametro_mm', label: 'Diâmetro (mm)' },
|
||||
{ key: 'passo_mm', label: 'Passo (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Diâmetro', type: 'select', options: [
|
||||
{ value: '', label: 'Todos' },
|
||||
{ value: 'Pequeno', label: 'Pequeno (M6-M12)' },
|
||||
{ value: 'Médio', label: 'Médio (M16-M24)' },
|
||||
{ value: 'Grande', label: 'Grande (M30-M36)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (M42+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 10' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: M20' }
|
||||
]
|
||||
},
|
||||
|
||||
'barras-chatas': {
|
||||
nome: 'Barras Chatas',
|
||||
icone: '▬',
|
||||
descricao: 'Barras chatas de aço laminadas a quente',
|
||||
quantidade: 22,
|
||||
csv: 'BD/perfis/barras_chatas_brasil_completo.csv',
|
||||
colunas: ['id', 'nome', 'largura_mm', 'espessura_mm', 'peso_kg_m', 'area_cm2', 'tipo'],
|
||||
colunasTabel: [
|
||||
{ key: 'nome', label: 'Designação' },
|
||||
{ key: 'largura_mm', label: 'Largura (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 }
|
||||
],
|
||||
filtros: [
|
||||
{ id: 'tamanho', label: 'Tamanho', type: 'select', options: [
|
||||
{ value: '', label: 'Todas' },
|
||||
{ value: 'Pequena', label: 'Pequena (até 25mm)' },
|
||||
{ value: 'Média', label: 'Média (32-50mm)' },
|
||||
{ value: 'Grande', label: 'Grande (63-100mm)' },
|
||||
{ value: 'Extra-Grande', label: 'Extra-Grande (125mm+)' }
|
||||
]},
|
||||
{ id: 'peso-max', label: 'Peso Máximo (kg/m)', type: 'number', placeholder: 'Ex: 30' },
|
||||
{ id: 'busca', label: 'Buscar por Nome', type: 'text', placeholder: 'Ex: 50x6' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gera o HTML completo para um tipo de perfil
|
||||
*/
|
||||
function gerarConteudoPerfil(tipo) {
|
||||
const config = PERFIS_CONFIG[tipo];
|
||||
if (!config) {
|
||||
console.error(`Configuração não encontrada para: ${tipo}`);
|
||||
return '<p>Erro: Tipo de perfil não configurado</p>';
|
||||
}
|
||||
|
||||
const tipoId = tipo.replace(/-/g, '_');
|
||||
|
||||
return `
|
||||
<div class="section-header">
|
||||
<div class="section-title">${config.icone} ${config.nome} - Catálogo Completo Brasil</div>
|
||||
<div class="section-description">${config.descricao} - ${config.quantidade} modelos disponíveis</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs de Navegação -->
|
||||
<div class="tabs-container">
|
||||
<div class="tabs-nav">
|
||||
<button class="tab-btn active" onclick="switchPerfilTab(0)">📊 Tabela Técnica</button>
|
||||
<button class="tab-btn" onclick="switchPerfilTab(1)">📋 Especificações</button>
|
||||
<button class="tab-btn" onclick="switchPerfilTab(2)">🏭 Fabricantes</button>
|
||||
<button class="tab-btn" onclick="switchPerfilTab(3)">💰 Preços 2025</button>
|
||||
<button class="tab-btn" onclick="switchPerfilTab(4)">🔧 Aplicações</button>
|
||||
</div>
|
||||
|
||||
<!-- TAB 1: TABELA TÉCNICA -->
|
||||
<div class="tab-content active" id="perfil-tab-0">
|
||||
<div class="card">
|
||||
<div class="card-title">🔍 Filtros de Busca</div>
|
||||
<div class="form-grid">
|
||||
${config.filtros.map((filtro, index) => `
|
||||
<div class="form-group">
|
||||
<label class="form-label">${filtro.label}</label>
|
||||
${filtro.type === 'select' ? `
|
||||
<select class="form-control" id="${tipoId}-${filtro.id}" onchange="filtrar${capitalize(tipoId)}()">
|
||||
${filtro.options.map(opt => `<option value="${opt.value}">${opt.label}</option>`).join('')}
|
||||
</select>
|
||||
` : `
|
||||
<input type="${filtro.type}" class="form-control" id="${tipoId}-${filtro.id}"
|
||||
placeholder="${filtro.placeholder}"
|
||||
${filtro.type === 'number' ? 'onchange' : 'oninput'}="filtrar${capitalize(tipoId)}()">
|
||||
`}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="limparFiltros${capitalize(tipoId)}()">🔄 Limpar Filtros</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
📊 Tabela de ${config.nome} (${config.quantidade} modelos)
|
||||
<button class="btn btn-primary" id="btn-carregar-${tipoId}"
|
||||
onclick="forcarCarregamento${capitalize(tipoId)}()"
|
||||
style="float: right; margin-top: -4px;">
|
||||
🔄 Carregar Dados
|
||||
</button>
|
||||
${tipo === 'tubos-rhs' ? `
|
||||
<button class="btn btn-secondary" id="btn-atualizar-${tipoId}"
|
||||
onclick="atualizarFonte${capitalize(tipoId)}()"
|
||||
style="float: right; margin-right: 8px; margin-top: -4px;">
|
||||
🗄️ Atualizar da Fonte
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
<div id="${tipoId}-table-container">
|
||||
<div class="table-container">
|
||||
<table id="${tipoId}-table">
|
||||
<thead>
|
||||
<tr>
|
||||
${config.colunasTabel.map(col => `<th>${col.label}</th>`).join('')}
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="${tipoId}-tbody">
|
||||
<!-- Será preenchido via JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 16px; padding: 12px; background: var(--color-bg-2); border-radius: 8px;">
|
||||
<strong>📌 Total:</strong> <span id="${tipoId}-total">${config.quantidade}</span> modelos encontrados
|
||||
${tipo === 'tubos-rhs' ? `
|
||||
<div style="margin-top:8px; font-size:12px; color: var(--color-text-secondary);">
|
||||
Fonte: <span id="${tipoId}-fonte">${config.csv}</span> |
|
||||
Última atualização: <span id="${tipoId}-last">—</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 2: ESPECIFICAÇÕES -->
|
||||
<div class="tab-content" id="perfil-tab-1">
|
||||
<div class="card" style="background: var(--color-bg-1);">
|
||||
<div class="card-title">📋 Especificações Técnicas Completas</div>
|
||||
<h3 style="color: var(--color-primary); margin-top: 20px;">🔧 Descrição Geral</h3>
|
||||
<p><strong>${config.descricao}</strong></p>
|
||||
<p>Perfis estruturais amplamente utilizados em construção civil, naval e industrial.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 3: FABRICANTES -->
|
||||
<div class="tab-content" id="perfil-tab-2">
|
||||
<div class="card" style="background: var(--color-bg-1);">
|
||||
<div class="card-title">🏭 Fabricantes e Distribuição no Brasil</div>
|
||||
<p>Principais fabricantes nacionais disponíveis.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 4: PREÇOS -->
|
||||
<div class="tab-content" id="perfil-tab-3">
|
||||
<div class="card" style="background: var(--color-bg-1);">
|
||||
<div class="card-title">💰 Preços e Fatores de Custo (2025)</div>
|
||||
<p>Informações de preços atualizadas para ${new Date().getFullYear()}.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 5: APLICAÇÕES -->
|
||||
<div class="tab-content" id="perfil-tab-4">
|
||||
<div class="card" style="background: var(--color-bg-1);">
|
||||
<div class="card-title">🔧 Aplicações Principais e Recomendações</div>
|
||||
<p>Aplicações típicas e recomendações de uso.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitaliza primeira letra
|
||||
*/
|
||||
function capitalize(str) {
|
||||
return str.split('_').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Exportar configurações e funções
|
||||
window.PERFIS_CONFIG = PERFIS_CONFIG;
|
||||
window.gerarConteudoPerfil = gerarConteudoPerfil;
|
||||
|
||||
console.log('✅ Templates de perfis carregados');
|
||||
|
||||
// Fallback: registrar switchPerfilTab global se não existir
|
||||
if (typeof window.switchPerfilTab !== 'function') {
|
||||
window.switchPerfilTab = function(index) {
|
||||
try {
|
||||
const btns = document.querySelectorAll('.tabs-nav .tab-btn');
|
||||
const tabs = document.querySelectorAll('.tab-content');
|
||||
btns.forEach((btn, i) => btn.classList.toggle('active', i === index));
|
||||
tabs.forEach((tab, i) => tab.classList.toggle('active', i === index));
|
||||
} catch (err) {
|
||||
console.warn('⚠️ Fallback switchPerfilTab falhou:', err);
|
||||
}
|
||||
};
|
||||
console.log('✅ Fallback switchPerfilTab registrado no templates');
|
||||
}
|
||||
Reference in New Issue
Block a user