/** * PAINEL DE ADMINISTRAÇÃO DE DADOS * * Interface para gerenciar o cache de dados, atualizar CSVs * e monitorar o status do sistema de dados. */ // Variáveis globais para gerenciamento de configurações let adminConfigManager = null; let backupManager = null; /** * Inicializa o sistema de configurações administrativas */ async function initAdminConfig() { try { // Evitar duplicação: todos os managers são carregados via index.html // Aguarde até que as classes e instâncias globais estejam disponíveis const ready = await waitForManagers(); if (!ready) { console.error('❌ Managers não carregados após tempo de espera.'); return false; } // Vincular instâncias globais, sem recriar adminConfigManager = window.adminConfigManager || (typeof AdminConfigManager !== 'undefined' ? new AdminConfigManager() : null); backupManager = window.backupManager || (typeof BackupManager !== 'undefined' ? new BackupManager() : null); console.log('✅ Sistema de configurações administrativas inicializado'); return true; } catch (error) { console.error('❌ Erro ao inicializar sistema de configurações:', error); return false; } } /** * Aguarda até que os managers essenciais estejam disponíveis * Retorna true quando AdminConfigManager, BackupManager e ToastManager existem */ async function waitForManagers(timeoutMs = 3000, intervalMs = 100) { const start = Date.now(); return new Promise((resolve) => { const check = () => { const classesReady = (typeof AdminConfigManager !== 'undefined') && (typeof BackupManager !== 'undefined') && (typeof ToastManager !== 'undefined'); const instancesReady = !!(window.adminConfigManager || window.backupManager || window.toastManager); if (classesReady || instancesReady) { resolve(true); return; } if (Date.now() - start >= timeoutMs) { resolve(false); return; } setTimeout(check, intervalMs); }; check(); }); } /** * Carrega um script dinamicamente * @param {string} src - Caminho do script * @returns {Promise} Promise que resolve quando o script for carregado */ function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } /** * Abre o painel de administração de dados */ async function abrirPainelDados() { console.log('🔧 Abrindo painel de administração de dados'); // Inicializar sistema de configurações se necessário if (!adminConfigManager) { const configLoaded = await initAdminConfig(); if (!configLoaded) { alert('❌ Erro ao carregar sistema de configurações'); return; } } const stats = window.dataManager.getCacheStats(); const metadata = window.dataManager.getMetadata(); const adminConfig = adminConfigManager ? adminConfigManager.getConfig() : null; const modalHTML = ` `; // Remover modal existente se houver const existingModal = document.getElementById('modal-admin-dados'); if (existingModal) { existingModal.remove(); } // Adicionar estilos CSS para os campos de formulário addAdminPanelStyles(); // Adicionar novo modal document.body.insertAdjacentHTML('beforeend', modalHTML); } /** * Fecha o painel de administração */ function fecharPainelDados(event) { if (event && event.target !== event.currentTarget) return; const modal = document.getElementById('modal-admin-dados'); if (modal) { modal.remove(); } } /** * Atualiza todos os dados */ async function atualizarTodosDados() { if (!confirm('🔄 Deseja atualizar todos os dados? Isso pode levar alguns segundos.')) { return; } const logEl = document.getElementById('admin-log'); if (logEl) { logEl.innerHTML = '
🔄 Iniciando atualização completa...
'; } try { const results = await window.dataManager.updateAllData(); if (logEl) { logEl.innerHTML = `
✅ Atualização completa finalizada!
📊 Sucessos: ${results.success.length}
${results.errors.length > 0 ? `
❌ Erros: ${results.errors.length}
` : ''}
Detalhes:
${results.success.map(s => `
✅ ${s.name}: ${s.count} itens
`).join('')} ${results.errors.map(e => `
❌ ${e.name}: ${e.error}
`).join('')} `; } // Recarregar painel setTimeout(() => { fecharPainelDados(); abrirPainelDados(); }, 2000); } catch (error) { console.error('❌ Erro ao atualizar dados:', error); if (logEl) { logEl.innerHTML = `
❌ Erro: ${error.message}
`; } } } /** * Limpa todo o cache */ function limparCacheCompleto() { if (!confirm('🗑️ Deseja limpar todo o cache? Os dados serão recarregados na próxima vez.')) { return; } try { window.dataManager.clearCache(); alert('✅ Cache limpo com sucesso!'); // Recarregar painel fecharPainelDados(); abrirPainelDados(); } catch (error) { console.error('❌ Erro ao limpar cache:', error); alert('❌ Erro ao limpar cache: ' + error.message); } } /** * Atualiza um tipo específico de dados */ async function atualizarTipoEspecifico(tipo) { if (!confirm(`🔄 Deseja atualizar os dados de ${tipo}?`)) { return; } try { // Ler docSource do campo (se preenchido) const input = document.getElementById(`docfile-${tipo}`); let docSource = input ? input.value.trim() : null; if (docSource) { docSource = window.dataManager.normalizeDocSource(docSource); } // Atualizar via DataManager (com registro de metadata por tipo) const result = await window.dataManager.updateTypeData(tipo, { docSource }); const config = window.dataManager.csvConfigs[tipo]; const docMsg = result.docSource ? `\n📄 Documento: ${result.docSource} (${result.docStatus || '—'})` : ''; alert(`✅ ${config.displayName} atualizado com sucesso! ${result.count} itens carregados.${docMsg}`); // Recarregar painel fecharPainelDados(); abrirPainelDados(); } catch (error) { console.error(`❌ Erro ao atualizar ${tipo}:`, error); alert(`❌ Erro ao atualizar ${tipo}: ` + error.message); } } /** * Salva a fonte de documento (.md) para um tipo */ function salvarDocFonte(tipo) { try { const input = document.getElementById(`docfile-${tipo}`); if (!input) return; const value = input.value.trim(); const normalized = window.dataManager.normalizeDocSource(value); // Atualizar metadados mantendo os demais campos const prev = window.dataManager.getTypeMetadata(tipo) || {}; const updated = { ...prev, docSource: normalized || null }; // Tentar validar imediatamente para refletir status if (normalized) { fetch(normalized, { cache: 'no-cache' }) .then(resp => { updated.docStatus = resp.ok ? 'ok' : `não encontrado (HTTP ${resp.status})`; window.dataManager.setTypeMetadata(tipo, updated); alert('📄 Documento fonte definido para ' + tipo + `\nArquivo: ${normalized}\nStatus: ${updated.docStatus}`); // Atualizar UI do painel fecharPainelDados(); abrirPainelDados(); }) .catch(err => { updated.docStatus = 'erro ao carregar'; window.dataManager.setTypeMetadata(tipo, updated); alert('⚠️ Não foi possível validar o documento. Caminho salvo como: ' + normalized); fecharPainelDados(); abrirPainelDados(); }); } else { window.dataManager.setTypeMetadata(tipo, updated); alert('📄 Documento fonte definido para ' + tipo); fecharPainelDados(); abrirPainelDados(); } } catch (e) { console.error('❌ Erro ao salvar doc fonte:', e); alert('❌ Erro ao salvar doc fonte: ' + e.message); } } /** * Limpa cache de um tipo específico */ function limparTipoEspecifico(tipo) { if (!confirm(`🗑️ Deseja limpar o cache de ${tipo}?`)) { return; } try { localStorage.removeItem(`acoCalcPro_cache_${tipo}`); alert(`✅ Cache de ${tipo} limpo com sucesso!`); // Recarregar painel fecharPainelDados(); abrirPainelDados(); } catch (error) { console.error(`❌ Erro ao limpar cache de ${tipo}:`, error); alert(`❌ Erro ao limpar cache: ` + error.message); } } /** * Exporta dados para JSON */ function exportarDados() { try { const stats = window.dataManager.getCacheStats(); const exportData = { metadata: window.dataManager.getMetadata(), stats: stats, exportedAt: new Date().toISOString() }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `aco-calc-pro-data-export-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); alert('✅ Dados exportados com sucesso!'); } catch (error) { console.error('❌ Erro ao exportar dados:', error); alert('❌ Erro ao exportar dados: ' + error.message); } } /** * Verifica integridade dos dados */ async function verificarIntegridade() { const logEl = document.getElementById('admin-log'); if (logEl) { logEl.innerHTML = '
🔍 Verificando integridade dos dados...
'; } try { const stats = window.dataManager.getCacheStats(); const issues = []; // Verificar cada tipo for (const [key, info] of Object.entries(stats.types)) { if (!info.cached) { issues.push(`❌ ${info.name}: Sem cache`); } else if (info.count === 0) { issues.push(`⚠️ ${info.name}: Cache vazio`); } } if (logEl) { if (issues.length === 0) { logEl.innerHTML = '
✅ Todos os dados estão íntegros!
'; } else { logEl.innerHTML = `
⚠️ Problemas encontrados:
${issues.map(i => `
${i}
`).join('')} `; } } if (issues.length === 0) { alert('✅ Todos os dados estão íntegros!'); } else { alert(`⚠️ ${issues.length} problema(s) encontrado(s). Verifique o log.`); } } catch (error) { console.error('❌ Erro ao verificar integridade:', error); if (logEl) { logEl.innerHTML = `
❌ Erro: ${error.message}
`; } } } console.log('✅ Admin Panel carregado'); /** * Adiciona estilos CSS para o painel de administração */ function addAdminPanelStyles() { // Verificar se os estilos já existem if (document.querySelector('#admin-panel-styles')) { return; } const styles = document.createElement('style'); styles.id = 'admin-panel-styles'; styles.textContent = ` /* Estilos para campos de formulário do painel admin */ .form-control { width: 100%; padding: 8px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-bg-2); color: var(--color-text); font-size: 14px; transition: all 0.2s ease; } .form-control:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .form-control:hover { border-color: var(--color-primary-light); } /* Estilos para checkboxes */ input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--color-primary); cursor: pointer; } /* Estilos para badges */ .badge { display: inline-flex; align-items: center; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; } .badge-success { background: rgba(16, 185, 129, 0.1); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.2); } .badge-error { background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.2); } .badge-primary { background: rgba(59, 130, 246, 0.1); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.2); } .badge-secondary { background: rgba(107, 114, 128, 0.1); color: #6b7280; border: 1px solid rgba(107, 114, 128, 0.2); } /* Estilos para botões pequenos */ .btn-sm { padding: 6px 12px; font-size: 12px; border-radius: 4px; } /* Estilos para containers de tabelas */ .table-container { background: var(--color-bg-2); border-radius: 8px; overflow: hidden; border: 1px solid var(--color-border); } /* Cabeçalhos de tabela no painel Admin: alto contraste */ .modal-admin .table-container th { background: var(--color-bg-2); color: var(--color-gray-200); font-weight: 700; border-bottom: 2px solid var(--color-border); text-align: left; padding: 10px; } .modal-admin .table-container th sup { color: inherit; font-weight: 700; opacity: 1; } /* Animações para inputs */ @keyframes input-focus { from { transform: scale(0.98); opacity: 0.8; } to { transform: scale(1); opacity: 1; } } .form-control:focus { animation: input-focus 0.2s ease-out; } /* Responsividade para o painel */ @media (max-width: 768px) { .modal-content { margin: 10px; max-height: 90vh; overflow-y: auto; } .form-control { font-size: 16px; /* Previne zoom no iOS */ } } /* Branding preview */ .branding-logo-preview { width: 180px; height: 180px; border: 1px dashed var(--color-border); border-radius: 12px; display: flex; align-items: center; justify-content: center; background: var(--color-bg-2); } .branding-logo-preview img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 8px; } .branding-logo-fallback { font-size: 64px; } `; document.head.appendChild(styles); } // ======================================== // ATALHO DE TECLADO // ======================================== /** * Atalho de teclado: Ctrl + Shift + D */ document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey && e.key === 'D') { e.preventDefault(); abrirPainelDados(); console.log('🗄️ Painel de dados aberto via atalho de teclado'); } }); console.log('💡 Dica: Pressione Ctrl + Shift + D para abrir o painel de administração de dados'); /** * Salva uma configuração específica * @param {string} key - Chave da configuração (ex: 'app.name') * @param {*} value - Valor a ser salvo */ async function salvarConfiguracao(key, value) { try { if (!adminConfigManager) { console.error('❌ AdminConfigManager não inicializado'); return; } // Atualizar configuração adminConfigManager.updateConfig(key, value); // Mostrar feedback visual se o ToastManager estiver disponível if (window.toastManager) { window.toastManager.success(`Configuração "${key}" salva com sucesso!`); } else { console.log(`✅ Configuração "${key}" salva com sucesso`); } } catch (error) { console.error('❌ Erro ao salvar configuração:', error); if (window.toastManager) { window.toastManager.error(`Erro ao salvar configuração: ${error.message}`); } } } /** * Salva o logotipo enviado no campo de branding */ async function salvarLogoBranding() { try { const input = document.getElementById('branding-logo-input'); if (!input || !input.files || !input.files[0]) { alert('Selecione um arquivo de imagem (PNG ou SVG).'); return; } const file = input.files[0]; const reader = new FileReader(); reader.onload = async (e) => { const dataUrl = e.target.result; // Persistir em admin config await salvarConfiguracao('branding.logo', dataUrl); // Atualizar preview const preview = document.getElementById('branding-logo-preview'); if (preview) { preview.innerHTML = `Preview`; } // Aplicar imediatamente if (window.applyAdminConfig) window.applyAdminConfig(); if (window.toastManager) window.toastManager.success('Logotipo salvo e aplicado!'); }; reader.readAsDataURL(file); } catch (error) { console.error('❌ Erro ao salvar logotipo:', error); if (window.toastManager) { window.toastManager.error('Erro ao salvar logotipo: ' + error.message); } } } /** * Remove o logotipo personalizado e restaura o padrão */ function removerLogoBranding() { try { salvarConfiguracao('branding.logo', null); const preview = document.getElementById('branding-logo-preview'); if (preview) { preview.innerHTML = '
🏗️
'; } if (window.applyAdminConfig) window.applyAdminConfig(); if (window.toastManager) window.toastManager.info('Logotipo restaurado para o padrão'); } catch (error) { console.error('❌ Erro ao remover logotipo:', error); } } /** * Aplica um tema específico * @param {string} themeName - Nome do tema (dark, light, auto) */ function aplicarTema(themeName) { try { if (!adminConfigManager) { console.error('❌ AdminConfigManager não inicializado'); return; } // Aplicar tema usando o AdminConfigManager adminConfigManager.applyTheme(themeName); console.log(`✅ Tema "${themeName}" aplicado com sucesso`); } catch (error) { console.error('❌ Erro ao aplicar tema:', error); if (window.toastManager) { window.toastManager.error(`Erro ao aplicar tema: ${error.message}`); } } } /** * Cria um backup manual */ async function criarBackupManual() { try { if (!backupManager) { console.error('❌ BackupManager não inicializado'); if (window.toastManager) { window.toastManager.error('Sistema de backup não inicializado'); } return; } // Mostrar loading const loadingToast = window.toastManager ? window.toastManager.loading('Criando backup...') : null; // Criar backup const backup = await backupManager.createBackup(); // Remover loading if (loadingToast && window.toastManager) { window.toastManager.removeToast(loadingToast); } // Mostrar sucesso if (window.toastManager) { window.toastManager.success(`Backup criado com sucesso! ID: ${backup.id}`); } console.log('✅ Backup manual criado:', backup); } catch (error) { console.error('❌ Erro ao criar backup:', error); if (window.toastManager) { window.toastManager.error(`Erro ao criar backup: ${error.message}`); } } } /** * Abre o gerenciador de backups */ function abrirGerenciadorBackups() { try { if (!backupManager) { console.error('❌ BackupManager não inicializado'); if (window.toastManager) { window.toastManager.error('Sistema de backup não inicializado'); } return; } // Obter lista de backups const backups = backupManager.getBackups(); if (backups.length === 0) { if (window.toastManager) { window.toastManager.info('Nenhum backup disponível'); } return; } // Criar modal de gerenciamento const modalHTML = ` `; // Adicionar modal ao body document.body.insertAdjacentHTML('beforeend', modalHTML); } catch (error) { console.error('❌ Erro ao abrir gerenciador de backups:', error); if (window.toastManager) { window.toastManager.error(`Erro ao abrir gerenciador: ${error.message}`); } } } /** * Fecha o gerenciador de backups */ function fecharGerenciadorBackups(event) { if (event && event.target !== event.currentTarget) return; const modal = document.getElementById('modal-backup-manager'); if (modal) { modal.remove(); } } /** * Restaura um backup específico * @param {string} backupId - ID do backup */ async function restaurarBackup(backupId) { try { if (!backupManager) { console.error('❌ BackupManager não inicializado'); return; } const resumo = backupManager.getBackup(backupId); const dataStr = resumo ? new Date(resumo.createdAt || resumo.timestamp).toLocaleString('pt-BR') : 'desconhecida'; if (!confirm(`⚠️ Tem certeza que deseja restaurar o backup de ${dataStr}?\n\nIsso substituirá todas as configurações atuais.`)) { return; } // Mostrar loading const loadingToast = window.toastManager ? window.toastManager.loading('Restaurando backup...') : null; // Restaurar backup await backupManager.restoreBackup(backupId); // Remover loading if (loadingToast && window.toastManager) { window.toastManager.removeToast(loadingToast); } // Mostrar sucesso if (window.toastManager) { window.toastManager.success('Backup restaurado com sucesso!'); } // Recarregar painel fecharGerenciadorBackups(); fecharPainelDados(); setTimeout(() => abrirPainelDados(), 500); console.log('✅ Backup restaurado:', backupId); } catch (error) { console.error('❌ Erro ao restaurar backup:', error); if (window.toastManager) { window.toastManager.error(`Erro ao restaurar backup: ${error.message}`); } } } /** * Remove um backup específico * @param {string} backupId - ID do backup */ function removerBackup(backupId) { try { if (!backupManager) { console.error('❌ BackupManager não inicializado'); return; } if (!confirm('⚠️ Tem certeza que deseja remover este backup?')) { return; } // Remover backup backupManager.removeBackup(backupId); // Mostrar sucesso if (window.toastManager) { window.toastManager.success('Backup removido com sucesso!'); } // Recarregar gerenciador fecharGerenciadorBackups(); setTimeout(() => abrirGerenciadorBackups(), 100); console.log('✅ Backup removido:', backupId); } catch (error) { console.error('❌ Erro ao remover backup:', error); if (window.toastManager) { window.toastManager.error(`Erro ao remover backup: ${error.message}`); } } } /** * Exporta as configurações atuais */ function exportarConfiguracoes() { try { if (!adminConfigManager) { console.error('❌ AdminConfigManager não inicializado'); if (window.toastManager) { window.toastManager.error('Sistema de configurações não inicializado'); } return; } // Obter configurações atuais const config = adminConfigManager.getConfig(); // Criar blob e download const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `aco-calc-pro-config-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); // Mostrar sucesso if (window.toastManager) { window.toastManager.success('Configurações exportadas com sucesso!'); } console.log('✅ Configurações exportadas'); } catch (error) { console.error('❌ Erro ao exportar configurações:', error); if (window.toastManager) { window.toastManager.error(`Erro ao exportar configurações: ${error.message}`); } } } /** * Importa configurações de um arquivo */ function importarConfiguracoes() { try { if (!adminConfigManager) { console.error('❌ AdminConfigManager não inicializado'); if (window.toastManager) { window.toastManager.error('Sistema de configurações não inicializado'); } return; } // Criar input file const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; try { // Ler arquivo const text = await file.text(); const config = JSON.parse(text); // Confirmar importação if (!confirm('⚠️ Tem certeza que deseja importar estas configurações?\n\nIsso substituirá as configurações atuais.')) { return; } // Importar configurações await adminConfigManager.setConfig(config); // Mostrar sucesso if (window.toastManager) { window.toastManager.success('Configurações importadas com sucesso!'); } // Recarregar painel fecharPainelDados(); setTimeout(() => abrirPainelDados(), 500); console.log('✅ Configurações importadas'); } catch (error) { console.error('❌ Erro ao importar configurações:', error); if (window.toastManager) { window.toastManager.error(`Erro ao importar configurações: ${error.message}`); } } }; input.click(); } catch (error) { console.error('❌ Erro ao importar configurações:', error); if (window.toastManager) { window.toastManager.error(`Erro ao importar configurações: ${error.message}`); } } } /** * Reseta as configurações para os valores padrão */ function resetarConfiguracoes() { try { if (!adminConfigManager) { console.error('❌ AdminConfigManager não inicializado'); if (window.toastManager) { window.toastManager.error('Sistema de configurações não inicializado'); } return; } if (!confirm('⚠️ Tem certeza que deseja resetar todas as configurações?\n\nIsso irá restaurar os valores padrão.')) { return; } // Resetar configurações adminConfigManager.resetConfig(); // Mostrar sucesso if (window.toastManager) { window.toastManager.success('Configurações resetadas com sucesso!'); } // Recarregar painel fecharPainelDados(); setTimeout(() => abrirPainelDados(), 500); console.log('✅ Configurações resetadas'); } catch (error) { console.error('❌ Erro ao resetar configurações:', error); if (window.toastManager) { window.toastManager.error(`Erro ao resetar configurações: ${error.message}`); } } }