Automação para o quadro de agendamento
// ==UserScript==
// @name DealerQuadro
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Automação para o quadro de agendamento
// @author Igor Lima
// @match https://*.dealernetworkflow.com.br/QuadroAgendamentoMod03/*
// @grant none
// @license MIT
// ==/UserScript==
/*
Este código foi gerado com ajuda de um modelo de IA. Embora tenha sido projetado para ser funcional,
pode ser necessário realizar uma revisão, testes ou modificações para atender às suas necessidades específicas.
Verifique o código quanto à correção e adequação antes de utilizá-lo em ambientes de produção.
*/
(function() {
'use strict';
// ============================================
// CONFIGURAÇÕES PADRÃO
// ============================================
const CONFIG_KEY = 'dealerquadro_config';
let config = {
empresaNome: "",
data: "",
hora: "",
consultorNome: "",
agendamentosPorPagina: "15",
logoUrl: "",
logoScale: "50"
};
// ============================================
// Carregar configurações salvas
function carregarConfig() {
const saved = localStorage.getItem(CONFIG_KEY);
if (saved) {
try {
config = { ...config, ...JSON.parse(saved) };
console.log('[DealerQuadro] Configurações carregadas');
} catch (e) {
console.error('[DealerQuadro] Erro ao carregar configurações:', e);
}
}
}
// Salvar configurações
function salvarConfig() {
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
console.log('[DealerQuadro] Configurações salvas');
}
carregarConfig();
// Usar valores da config ao invés de constantes
const EMPRESA_NOME = config.empresaNome;
const DATA = config.data;
const HORA = config.hora;
const CONSULTOR_NOME = config.consultorNome;
const AGENDAMENTOS_POR_PAGINA = config.agendamentosPorPagina;
const LOGO_URL = config.logoUrl;
const LOGO_SCALE = config.logoScale;
// Configurações de timeout e tentativas
// A página de vez em quando trava, principalmente se a internet der um pico
// enquanto o quadro estava mudando de página.
const TIMEOUT_LOADING = 30000; // 30 segundos
const TIMEOUT_TRAVAMENTO = 30000; // 30 segundos para detectar travamento
const MAX_TENTATIVAS_CONSULTA = 10; // Máximo de tentativas se tabela vazia
// ============================================
// FIM DAS CONFIGURAÇÕES
// ============================================
let loadingTimeout = null;
let timerTravamento = null;
let estaCarregando = false;
const CHAVE_TENTATIVAS = 'tentativasConsulta';
console.log('[DealerQuadro] Script iniciado');
// ============================================
// FUNÇÕES DE VERIFICAÇÃO DE ERROS
// ============================================
function verificarErros() {
const bodyText = document.body.innerText;
if (document.title.includes('503') || bodyText.includes('503')) {
console.log('[DealerQuadro] Erro 503 detectado - Recarregando página...');
setTimeout(() => location.reload(), 2000);
return true;
}
if (bodyText.includes('An error occurred while processing your request')) {
console.log('[DealerQuadro] Erro de processamento detectado - Recarregando página...');
setTimeout(() => location.reload(), 2000);
return true;
}
return false;
}
if (verificarErros()) {
return;
}
// ============================================
// FUNÇÕES DE MONITORAMENTO DE TRAVAMENTO
// ============================================
function recarregarPorTravamento() {
console.log('[DealerQuadro] ⚠️ Loading travado há mais de ${TIMEOUT_TRAVAMENTO/100} segundos - Recarregando...');
location.reload();
}
function iniciarTimerTravamento() {
if (timerTravamento) {
clearTimeout(timerTravamento);
}
timerTravamento = setTimeout(recarregarPorTravamento, TIMEOUT_TRAVAMENTO);
}
function pararTimerTravamento() {
if (timerTravamento) {
clearTimeout(timerTravamento);
timerTravamento = null;
}
}
function monitorarElementoCarregamento() {
const elementoCarregamento = document.getElementById('loading');
if (!elementoCarregamento) return;
const estiloComputado = window.getComputedStyle(elementoCarregamento);
const estaVisivel = estiloComputado.display !== 'none';
if (estaVisivel && !estaCarregando) {
estaCarregando = true;
iniciarTimerTravamento();
console.log('[DealerQuadro] Loading detectado - Monitorando travamento...');
} else if (!estaVisivel && estaCarregando) {
estaCarregando = false;
pararTimerTravamento();
console.log('[DealerQuadro] Loading concluído');
}
}
function iniciarMonitoramentoTravamento() {
const elementoCarregamento = document.getElementById('loading');
if (!elementoCarregamento) {
setTimeout(iniciarMonitoramentoTravamento, 1000);
return;
}
monitorarElementoCarregamento();
const observador = new MutationObserver(() => {
monitorarElementoCarregamento();
});
observador.observe(elementoCarregamento, {
attributes: true,
attributeFilter: ['style']
});
console.log('[DealerQuadro] ✓ Monitoramento de travamento iniciado');
}
// ============================================
// FUNÇÕES DE CONTROLE DE TENTATIVAS
// ============================================
function obterNumeroTentativas() {
const tentativas = sessionStorage.getItem(CHAVE_TENTATIVAS);
return tentativas ? parseInt(tentativas) : 0;
}
function incrementarTentativas() {
const novasTentativas = obterNumeroTentativas() + 1;
sessionStorage.setItem(CHAVE_TENTATIVAS, novasTentativas.toString());
return novasTentativas;
}
function resetarTentativas() {
sessionStorage.removeItem(CHAVE_TENTATIVAS);
}
// ============================================
// FUNÇÕES DE ESPERA
// ============================================
function esperarElemento(seletor, timeout = 10000) {
return new Promise((resolve, reject) => {
const elemento = document.querySelector(seletor);
if (elemento) {
resolve(elemento);
return;
}
const observer = new MutationObserver((mutations, obs) => {
const el = document.querySelector(seletor);
if (el) {
obs.disconnect();
resolve(el);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout: elemento ${seletor} não encontrado`));
}, timeout);
});
}
function esperarLoadingDesaparecer() {
return new Promise((resolve) => {
const verificarLoading = () => {
const loading = document.querySelector('#loading');
if (!loading) {
resolve();
return;
}
const estiloComputado = window.getComputedStyle(loading);
const estaVisivel = estiloComputado.display !== 'none';
if (!estaVisivel) {
resolve();
} else {
setTimeout(verificarLoading, 500);
}
};
verificarLoading();
});
}
// ============================================
// FUNÇÕES AUXILIARES
// ============================================
function obterDataHoje() {
const hoje = new Date();
const dia = String(hoje.getDate()).padStart(2, '0');
const mes = String(hoje.getMonth() + 1).padStart(2, '0');
const ano = hoje.getFullYear();
return `${dia}/${mes}/${ano}`;
}
// ============================================
// FUNÇÕES PRINCIPAIS
// ============================================
function verificarTabelaERetentar() {
setTimeout(() => {
const tabela = document.querySelector('table.table tbody');
if (tabela) {
const mensagemVazia = tabela.querySelector('td[colspan="8"]');
if (mensagemVazia && mensagemVazia.textContent.includes('Nenhum Agendamento encontrado')) {
const tentativasAtuais = obterNumeroTentativas();
if (tentativasAtuais < MAX_TENTATIVAS_CONSULTA) {
const novasTentativas = incrementarTentativas();
console.log(`[DealerQuadro] Nenhum agendamento encontrado. Tentativa ${novasTentativas} de ${MAX_TENTATIVAS_CONSULTA}...`);
const botaoConsultar = document.querySelector('button[type="submit"].btn.btn-primary.mb-2');
if (botaoConsultar && botaoConsultar.textContent.trim() === 'Consultar') {
setTimeout(() => {
botaoConsultar.click();
console.log('[DealerQuadro] Clicando em Consultar novamente...');
verificarTabelaERetentar();
}, 1000);
}
} else {
console.log(`[DealerQuadro] Máximo de ${MAX_TENTATIVAS_CONSULTA} tentativas atingido.`);
resetarTentativas();
finalizarConfiguracoes();
}
} else {
console.log('[DealerQuadro] ✓ Agendamentos encontrados!');
resetarTentativas();
finalizarConfiguracoes();
}
}
}, 500);
}
function finalizarConfiguracoes() {
esperarLoadingDesaparecer().then(() => {
// Configurar agendamentos por página
if (AGENDAMENTOS_POR_PAGINA) {
const inputPorPagina = document.getElementById('agendamentosPorPagina');
if (inputPorPagina) {
inputPorPagina.value = AGENDAMENTOS_POR_PAGINA;
inputPorPagina.dispatchEvent(new Event('input', { bubbles: true }));
inputPorPagina.dispatchEvent(new Event('change', { bubbles: true }));
console.log('[DealerQuadro] ✓ Agendamentos por página:', AGENDAMENTOS_POR_PAGINA);
}
}
// Trocar logo se especificado
if (LOGO_URL) {
const logoDiv = document.getElementById('logo');
if (logoDiv) {
const imgLogo = logoDiv.querySelector('img');
if (imgLogo) {
imgLogo.src = LOGO_URL;
console.log('[DealerQuadro] ✓ Logo customizado aplicado');
}
}
}
// Aplicar escala ao logo se especificado
if (LOGO_SCALE) {
const logoDiv = document.getElementById('logo');
if (logoDiv) {
const imgLogo = logoDiv.querySelector('img');
if (imgLogo) {
const aplicarEscala = () => {
const scale = parseFloat(LOGO_SCALE) / 100; // Converter porcentagem para decimal
if (!isNaN(scale) && scale > 0) {
// Aplicar escala à imagem
imgLogo.style.transform = `scale(${scale})`;
imgLogo.style.transformOrigin = 'center';
// Obter dimensões originais da imagem
const imgWidth = imgLogo.naturalWidth || imgLogo.width;
const imgHeight = imgLogo.naturalHeight || imgLogo.height;
// Calcular novos tamanhos
const newWidth = imgWidth * scale;
const newHeight = imgHeight * scale;
// Aplicar ao container
logoDiv.style.width = `${newWidth}px`;
logoDiv.style.height = `${newHeight}px`;
logoDiv.style.display = 'flex';
logoDiv.style.alignItems = 'center';
logoDiv.style.justifyContent = 'center';
logoDiv.style.overflow = 'visible';
// Garantir que a imagem não seja cortada
imgLogo.style.maxWidth = 'none';
imgLogo.style.maxHeight = 'none';
console.log('[DealerQuadro] ✓ Escala do logo aplicada:', scale);
}
};
// Se a imagem já estiver carregada, aplicar imediatamente
if (imgLogo.complete && imgLogo.naturalWidth > 0) {
aplicarEscala();
} else {
// Caso contrário, aguardar o carregamento
imgLogo.addEventListener('load', aplicarEscala);
// Fallback: tentar novamente após um tempo
setTimeout(aplicarEscala, 500);
}
}
}
}
// Esconder o painel completo
const panel = document.querySelector('.panel.panel-default');
if (panel) {
panel.style.display = 'none';
console.log('[DealerQuadro] ✓ Painel escondido');
}
console.log('[DealerQuadro] ✅ Configuração concluída com sucesso!');
});
}
function preencherCamposEConsultar() {
// Preenche data (hoje se não especificado)
const dataParaUsar = DATA || obterDataHoje();
const inputData = document.getElementById('inputdata');
if (inputData) {
inputData.value = dataParaUsar;
inputData.dispatchEvent(new Event('input', { bubbles: true }));
console.log('[DealerQuadro] Data preenchida:', dataParaUsar);
}
// Preenche hora (opcional)
if (HORA) {
const inputHora = document.getElementById('inputhora');
if (inputHora) {
inputHora.value = HORA;
inputHora.dispatchEvent(new Event('input', { bubbles: true }));
console.log('[DealerQuadro] Hora preenchida:', HORA);
}
}
// Seleciona consultor (opcional)
if (CONSULTOR_NOME) {
const selectConsultor = document.getElementById('inputconsultor');
if (selectConsultor) {
const opcaoConsultor = Array.from(selectConsultor.options).find(opcao =>
opcao.textContent.includes(CONSULTOR_NOME)
);
if (opcaoConsultor) {
selectConsultor.value = opcaoConsultor.value;
console.log('[DealerQuadro] Consultor selecionado:', CONSULTOR_NOME);
}
}
}
// Clica em Consultar
const botaoConsultar = document.querySelector('button[type="submit"].btn.btn-primary.mb-2');
if (botaoConsultar && botaoConsultar.textContent.trim() === 'Consultar') {
setTimeout(() => {
botaoConsultar.click();
console.log('[DealerQuadro] Botão Consultar clicado');
verificarTabelaERetentar();
}, 100);
}
}
async function selecionarEmpresa() {
try {
console.log('[DealerQuadro] Aguardando select de empresas...');
const selectEmpresas = await esperarElemento('#inputempresas');
console.log('[DealerQuadro] Select encontrado, procurando empresa:', EMPRESA_NOME);
// Encontrar a opção da empresa
const opcaoEmpresa = Array.from(selectEmpresas.options).find(opcao =>
opcao.textContent.trim() === EMPRESA_NOME
);
if (!opcaoEmpresa) {
console.error('[DealerQuadro] ✗ Empresa não encontrada:', EMPRESA_NOME);
console.log('[DealerQuadro] Opções disponíveis:');
Array.from(selectEmpresas.options).forEach(opcao => {
console.log(` - "${opcao.textContent.trim()}"`);
});
return;
}
// Selecionar a empresa
selectEmpresas.value = opcaoEmpresa.value;
selectEmpresas.dispatchEvent(new Event('change', { bubbles: true }));
console.log('[DealerQuadro] ✓ Empresa selecionada:', EMPRESA_NOME);
// Encontrar e clicar no botão Selecionar
const btnSelecionar = document.getElementById('selecionaempresa');
if (!btnSelecionar) {
console.error('[DealerQuadro] ✗ Botão Selecionar não encontrado');
return;
}
setTimeout(() => {
btnSelecionar.click();
console.log('[DealerQuadro] Botão Selecionar clicado');
// Aguarda loading e preenche campos
esperarLoadingDesaparecer().then(() => {
if (verificarErros()) return;
setTimeout(() => {
preencherCamposEConsultar();
}, 500);
});
}, 100);
} catch (erro) {
console.error('[DealerQuadro] Erro:', erro);
}
}
// ============================================
// INTERFACE DE CONFIGURAÇÃO
// ============================================
function criarInterfaceConfig() {
// Botão flutuante
const btnConfig = document.createElement('button');
btnConfig.innerHTML = '⚙️';
btnConfig.style.cssText = `
position: fixed;
bottom: 22px;
left: 15px;
width: 25px;
height: 25px;
background: transparent;
color: white;
border: none;
font-size: 24px;
cursor: pointer;
z-index: 10;
transition: transform 0.2s;
opacity: 1.0;
`;
btnConfig.onmouseover = () => btnConfig.style.transform = 'scale(1.1)';
btnConfig.onmouseout = () => btnConfig.style.transform = 'scale(1)';
// Modal de configuração
const modal = document.createElement('div');
modal.style.cssText = `
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 10001;
justify-content: center;
align-items: center;
`;
const modalContent = document.createElement('div');
modalContent.style.cssText = `
background: white;
padding: 25px;
border-radius: 10px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
`;
modalContent.innerHTML = `
<h3 style="margin-top: 0; color: #333;">⚙️ Configurações DealerQuadro</h3>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Empresa:</label>
<select id="config-empresa" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<option value="">Selecione</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Data (deixe vazio para hoje):</label>
<input type="text" id="config-data" placeholder="DD/MM/YYYY" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Hora:</label>
<input type="text" id="config-hora" placeholder="Ex: 8:30 PM" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Consultor:</label>
<select id="config-consultor" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<option value="">Selecione</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Agendamentos por página:</label>
<input type="number" id="config-agendamentos" min="10" value="10" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">URL do Logo:</label>
<input type="text" id="config-logo-url" placeholder="https://exemplo.com/logo.png" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<div id="logo-preview" style="margin-top: 10px; text-align: center; min-height: 100px; border: 1px dashed #ddd; border-radius: 4px; padding: 10px; display: flex; align-items: center; justify-content: center;">
<span style="color: #999;">Preview do logo aparecerá aqui</span>
</div>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Escala do Logo (%):</label>
<input type="number" id="config-logo-scale" min="10" max="500" value="100" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="display: flex; gap: 10px;">
<button id="config-save" style="flex: 1; padding: 10px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Salvar
</button>
<button id="config-cancel" style="flex: 1; padding: 10px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Cancelar
</button>
</div>
`;
modal.appendChild(modalContent);
document.body.appendChild(btnConfig);
document.body.appendChild(modal);
// Preencher dropdowns
function preencherDropdowns() {
const selectEmpresa = document.getElementById('config-empresa');
const selectConsultor = document.getElementById('config-consultor');
// Preencher empresas
const inputEmpresas = document.getElementById('inputempresas');
if (inputEmpresas) {
Array.from(inputEmpresas.options).forEach(option => {
if (option.value) {
const opt = document.createElement('option');
opt.value = option.textContent.trim();
opt.textContent = option.textContent.trim();
if (opt.value === config.empresaNome) opt.selected = true;
selectEmpresa.appendChild(opt);
}
});
}
// Preencher consultores
const inputConsultor = document.getElementById('inputconsultor');
if (inputConsultor) {
Array.from(inputConsultor.options).forEach(option => {
if (option.value !== "0") {
const opt = document.createElement('option');
opt.value = option.textContent.trim();
opt.textContent = option.textContent.trim();
if (opt.value === config.consultorNome) opt.selected = true;
selectConsultor.appendChild(opt);
}
});
}
}
// Preencher campos com valores atuais
function preencherCampos() {
document.getElementById('config-data').value = config.data;
document.getElementById('config-hora').value = config.hora;
document.getElementById('config-agendamentos').value = config.agendamentosPorPagina;
document.getElementById('config-logo-url').value = config.logoUrl;
document.getElementById('config-logo-scale').value = config.logoScale;
atualizarPreview();
}
// Atualizar preview do logo
function atualizarPreview() {
const logoUrl = document.getElementById('config-logo-url').value;
const logoScale = document.getElementById('config-logo-scale').value;
const preview = document.getElementById('logo-preview');
if (logoUrl) {
const scale = parseInt(logoScale) / 100;
preview.innerHTML = `<img src="${logoUrl}" style="max-width: 100%; max-height: 150px; transform: scale(${scale});" onerror="this.parentElement.innerHTML='<span style=color:red>Erro ao carregar imagem</span>'">`;
} else {
preview.innerHTML = '<span style="color: #999;">Preview do logo aparecerá aqui</span>';
}
}
// Eventos
btnConfig.onclick = () => {
modal.style.display = 'flex';
preencherDropdowns();
preencherCampos();
};
modal.onclick = (e) => {
if (e.target === modal) modal.style.display = 'none';
};
document.getElementById('config-cancel').onclick = () => {
modal.style.display = 'none';
};
document.getElementById('config-logo-url').oninput = atualizarPreview;
document.getElementById('config-logo-scale').oninput = atualizarPreview;
document.getElementById('config-save').onclick = () => {
config.empresaNome = document.getElementById('config-empresa').value;
config.data = document.getElementById('config-data').value;
config.hora = document.getElementById('config-hora').value;
config.consultorNome = document.getElementById('config-consultor').value;
config.agendamentosPorPagina = document.getElementById('config-agendamentos').value;
config.logoUrl = document.getElementById('config-logo-url').value;
config.logoScale = document.getElementById('config-logo-scale').value;
salvarConfig();
modal.style.display = 'none';
alert('⚠️ Configurações salvas! Recarregue a página para aplicar as mudanças.');
};
}
// ============================================
// INICIALIZAÇÃO
// ============================================
function iniciar() {
// Verificar se modal está pronto
const modal = document.querySelector('.modal-content');
const elementoSelect = document.getElementById('inputempresas');
if (modal && elementoSelect) {
const estiloModal = window.getComputedStyle(modal.closest('.modal') || modal);
if (estiloModal.display !== 'none') {
selecionarEmpresa();
return true;
}
}
return false;
}
function iniciarScript() {
if (iniciar()) return;
// Usar MutationObserver se não estiver pronto
const observador = new MutationObserver(() => {
if (iniciar()) {
observador.disconnect();
}
});
observador.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class']
});
// Parar de observar após 10 segundos
setTimeout(() => observador.disconnect(), 10000);
}
// Inicializar
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
criarInterfaceConfig(); // Criar interface primeiro
iniciarScript();
iniciarMonitoramentoTravamento();
});
} else {
criarInterfaceConfig(); // Criar interface primeiro
iniciarScript();
iniciarMonitoramentoTravamento();
}
// Limpar timers antes de descarregar
window.addEventListener('beforeunload', () => {
if (timerTravamento) clearTimeout(timerTravamento);
if (loadingTimeout) clearTimeout(loadingTimeout);
});
// Monitorar erros continuamente
setInterval(verificarErros, 5000);
})();