Cangaceiros — Matchmaker Completo

Cangaceiros Matchmaker

Balanceamento & Partidas

Balancear Times

Como Usar

1. Preencha os nicks dos 10 jogadores

2. As lanes e elos serão carregados automaticamente

3. Clique em "Balancear Times"

4. Os times serão formados automaticamente

* Apenas administradores podem iniciar e salvar partidas

* Os elos são calculados automaticamente com base nas vitórias/derrotas

Ranking

Veja os melhores do servidor — filtro rápido e visual moderno

Histórico

Últimas partidas realizadas
`); win.document.close(); }/* ====== ESTATÍSTICAS ====== */async function updateStats() { try { const playersSnap = await getDocs(collection(db, "players")); const matchesSnap = await getDocs(collection(db, "matches")); const players = []; playersSnap.forEach(d => players.push(d.data())); const matches = []; matchesSnap.forEach(d => matches.push(d.data())); window.allMatches = matches; if (typeof calculatePlayerLaneStats === 'function') { calculatePlayerLaneStats(matches, players); } else { console.error("calculatePlayerLaneStats não está definida"); } const totalPlayers = players.length; const totalMatches = matches.length; const totalPoints = players.reduce((sum, p) => sum + (p.pontos || 0), 0); const avgPoints = totalPlayers > 0 ? (totalPoints / totalPlayers).toFixed(1) : 0; const topPlayer = players.length > 0 ? players.reduce((max, p) => (p.pontos || 0) > (max.pontos || 0) ? p : max) : null; const azulWins = matches.filter(m => m.vencedor === 'azul').length; const vermelhoWins = matches.filter(m => m.vencedor === 'vermelho').length; const statsContent = document.getElementById("statsContent"); if (statsContent) { statsContent.innerHTML = `

Total de Jogadores

${totalPlayers}

Total de Partidas

${totalMatches}

Média de Pontos

${avgPoints}

Vitórias Azul

${azulWins}

Vitórias Vermelho

${vermelhoWins}
${topPlayer ? `

Top Player

${topPlayer.nickname}
${topPlayer.pontos || 0} pontos
` : ''} `; } } catch (error) { console.error("Erro ao carregar estatísticas:", error); } }/* ====== RANKING INICIAL ====== */async function loadInitialRanking() { try { // Verificar se db está definido if (typeof db === 'undefined' || !db) { console.error("Firebase não está inicializado!"); return; } console.log("Carregando ranking inicial..."); const snap = await getDocs(collection(db, "players")); const arr = []; snap.forEach(d => arr.push(d.data())); arr.forEach(p => { p.pontos = p.pontos || 0; p.vitorias = p.vitorias || 0; p.derrotas = p.derrotas || 0; p.mvp = p.mvp || 0; p.ace = p.ace || 0; }); window.rankingData = arr; const matchesSnap = await getDocs(collection(db, "matches")); const matches = []; matchesSnap.forEach(d => matches.push(d.data())); window.allMatches = matches; if (typeof calculatePlayerLaneStats === 'function') { calculatePlayerLaneStats(matches, arr); } else { console.error("calculatePlayerLaneStats não está definida"); } if (typeof renderRanking === 'function') { renderRanking(); } // Carregar tabela de elos inicial if (typeof calcularEloCompleto === 'function') { const elosCalculados = arr.map(player => calcularEloCompleto(player)); // Ordenar usando calcularValorElo para considerar elo+divisão+pdls corretamente (maior primeiro) elosCalculados.sort((a, b) => { const valorA = calcularValorElo(a); const valorB = calcularValorElo(b); return valorB - valorA; }); if (typeof loadEloTable === 'function') { window.rankingData = arr; const filterAtual = window.currentEloTableFilter || 'all'; loadEloTable(elosCalculados, filterAtual); } else { console.error("loadEloTable não está definida"); } } else { console.error("calcularEloCompleto não está definida"); } } catch (error) { console.error("Erro ao carregar ranking:", error); } }/* ====== PROCESSAR PARÂMETROS DE URL ====== */function processUrlParams() { const urlParams = new URLSearchParams(window.location.search); const voteType = urlParams.get('vote'); const matchId = urlParams.get('match'); if (voteType && matchId) { createVotePage(matchId, voteType); window.history.replaceState({}, document.title, window.location.pathname); } }/* ====== INICIALIZAÇÃO FINAL ====== */// Aguardar DOM estar completamente pronto function startApp() { // Carregar imagens dos elos do Firebase if (typeof loadEloImagesFromFirebase === 'function') { loadEloImagesFromFirebase(); } else if (typeof loadEloImages === 'function') { loadEloImages(); } // Aguardar um pouco mais para garantir que todos os elementos estejam no DOM setTimeout(() => { // Inicializar UI if (typeof updateUIForUserType === 'function') { updateUIForUserType(); }// Carregar dados iniciais if (typeof loadInitialRanking === 'function') { loadInitialRanking(); } }, 100); }// Inicializar quando DOM estiver pronto if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startApp); } else { // DOM já está pronto startApp(); }// Listener em tempo real para players (atualiza ranking e tabela de elos automaticamente) // Verificar se db está definido antes de criar o listener function setupPlayersListener() { if (typeof db !== 'undefined' && db) { onSnapshot(collection(db, "players"), async (snap) => { try { const arr = []; snap.forEach(d => arr.push(d.data())); arr.forEach(p => { p.pontos = p.pontos || 0; p.vitorias = p.vitorias || 0; p.derrotas = p.derrotas || 0; p.mvp = p.mvp || 0; p.ace = p.ace || 0; }); window.rankingData = arr; // Usar matches já em cache (atualizado pelo onSnapshot de matches) // Só busca do Firebase se ainda não tiver cache let matches = window.allMatches; if (!matches) { const matchesSnap = await getDocs(collection(db, "matches")); matches = []; matchesSnap.forEach(d => matches.push(d.data())); window.allMatches = matches; } if (typeof calculatePlayerLaneStats === 'function') { calculatePlayerLaneStats(matches, arr); } else { console.error("calculatePlayerLaneStats não está definida"); } if (typeof renderRanking === 'function') { renderRanking(); } // Atualizar tabela de elos // Primeiro calcular elos sem filtro para determinar lane principal if (typeof calcularEloCompleto === 'function') { const elosCalculados = arr.map(player => calcularEloCompleto(player)); elosCalculados.sort((a, b) => { const nivelA = ELOS[a.elo]?.nivel || 0; const nivelB = ELOS[b.elo]?.nivel || 0; if (nivelB !== nivelA) return nivelB - nivelA; if (a.divisao !== b.divisao) return a.divisao - b.divisao; return (b.pdls || 0) - (a.pdls || 0); }); // Armazenar dados dos players para uso no filtro window.rankingData = arr; const eloFilter = document.getElementById('eloFilter'); if (typeof loadEloTable === 'function') { loadEloTable(elosCalculados, eloFilter?.value || 'all'); } else { console.error("loadEloTable não está definida"); } } else { console.error("calcularEloCompleto não está definida"); // Armazenar dados dos players mesmo se não conseguir calcular elos window.rankingData = arr; } } catch (error) { console.error("Erro no listener de players:", error); } }, (error) => { console.error("Erro ao configurar listener de players:", error); }); } else { // Firebase não está pronto ainda, tentar novamente setTimeout(setupPlayersListener, 500); } }// Configurar listener quando Firebase estiver pronto setupPlayersListener();// Verificar parâmetros de URL window.addEventListener('load', processUrlParams);// Atualizar estatísticas periodicamente setInterval(updateStats, 30000);// Fechar modais ao clicar fora const voteModal = document.getElementById('voteModal'); if (voteModal) { voteModal.addEventListener('click', (e) => { if (e.target.id === 'voteModal') { e.target.classList.remove('show'); } }); }/* ====== SISTEMA DE UPLOAD DE AVATAR DO RANKING ====== */// Event listener para clicar no avatar document.addEventListener('click', (e) => { const avatarEl = e.target.closest('.rank-avatar[data-nick]'); if (avatarEl && isAdmin) { const nickname = avatarEl.dataset.nick; if (nickname) { openAvatarUploadModal(nickname); } } });function openAvatarUploadModal(nickname) { const modal = document.getElementById('avatarUploadModal'); const playerNameEl = document.getElementById('avatarPlayerName'); const previewEl = document.getElementById('avatarPreview'); const letterEl = document.getElementById('avatarLetter'); const fileInput = document.getElementById('avatarFileInput'); const saveBtn = document.getElementById('saveAvatarBtn'); if (!modal) return; // Buscar dados do jogador const player = window.rankingData?.find(p => p.nickname === nickname); playerNameEl.textContent = nickname; letterEl.textContent = nickname.charAt(0).toUpperCase(); // Mostrar avatar atual se existir if (player?.avatarUrl) { previewEl.innerHTML = ``; } else { previewEl.innerHTML = `${nickname.charAt(0).toUpperCase()}`; } // Resetar file input fileInput.value = ''; saveBtn.disabled = true; // Preview da nova imagem fileInput.onchange = (e) => { const file = e.target.files[0]; if (file) { if (!file.type.startsWith('image/')) { showToast('Por favor, selecione um arquivo de imagem!', 'error'); return; } const reader = new FileReader(); reader.onload = (e) => { previewEl.innerHTML = ``; saveBtn.disabled = false; }; reader.readAsDataURL(file); } }; // Mostrar modal modal.style.display = 'flex'; // Configurar botões document.getElementById('cancelAvatarBtn').onclick = () => { modal.style.display = 'none'; }; document.getElementById('removeAvatarBtn').onclick = async () => { if (!confirm(`Remover avatar de ${nickname}?`)) return; try { const playerRef = doc(db, "players", nickname); await updateDoc(playerRef, { avatarUrl: null }); showToast('Avatar removido com sucesso!', 'success'); modal.style.display = 'none'; // Recarregar ranking if (typeof loadInitialRanking === 'function') { loadInitialRanking(); } } catch (error) { console.error('Erro ao remover avatar:', error); showToast('Erro ao remover avatar!', 'error'); } }; document.getElementById('saveAvatarBtn').onclick = async () => { const file = fileInput.files[0]; if (!file) { showToast('Selecione uma imagem!', 'error'); return; } saveBtn.disabled = true; saveBtn.textContent = 'Salvando...'; try { // Converter para base64 com Promise para poder usar try/catch const base64Image = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target.result); reader.onerror = () => reject(new Error('Falha ao ler o arquivo')); reader.readAsDataURL(file); }); // Salvar no Firebase const playerRef = doc(db, "players", nickname); await updateDoc(playerRef, { avatarUrl: base64Image }); showToast('Avatar atualizado com sucesso!', 'success'); modal.style.display = 'none'; // Recarregar ranking if (typeof loadInitialRanking === 'function') { loadInitialRanking(); } } catch (error) { console.error('Erro ao salvar avatar:', error); showToast('Erro ao salvar avatar!', 'error'); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Salvar Avatar'; } }; }// Fechar modal ao clicar fora document.getElementById('avatarUploadModal')?.addEventListener('click', (e) => { if (e.target.id === 'avatarUploadModal') { e.target.style.display = 'none'; } });// Event listener para upload de campeoes favoritos (2 e 3) document.addEventListener('click', (e) => { const placeholder = e.target.closest('.rank-champ-mini-placeholder[data-nick]'); if (placeholder && isAdmin) { const nickname = placeholder.dataset.nick; const champKey = placeholder.dataset.champ; // 'champ2' or 'champ3' if (nickname && champKey) { openChampUploadModal(nickname, champKey); } } // Clique no icone do campeo para remover (admin) const champIcon = e.target.closest('.rank-champ-mini[data-nick]'); if (champIcon && isAdmin) { const nickname = champIcon.dataset.nick; const champKey = champIcon.dataset.champ; if (nickname && champKey) { openChampUploadModal(nickname, champKey); } } });function openChampUploadModal(nickname, champKey) { const label = champKey === 'champ2' ? '2° Campeão Favorito' : '3° Campeão Favorito'; const player = window.rankingData?.find(p => p.nickname === nickname); const currentUrl = player?.[champKey + 'Url'] || null;// Criar input de arquivo dinamico const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.style.display = 'none'; document.body.appendChild(fileInput);fileInput.onchange = async (e) => { const file = e.target.files[0]; document.body.removeChild(fileInput); if (!file) return; if (!file.type.startsWith('image/')) { showToast('Selecione uma imagem valida!', 'error'); return; } if (file.size > 200 * 1024) { showToast('Imagem muito grande! Max 200KB.', 'error'); return; } try { showToast('Salvando campeo...', 'info'); const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (ev) => resolve(ev.target.result); reader.onerror = () => reject(new Error('Falha ao ler arquivo')); reader.readAsDataURL(file); }); const playerRef = doc(db, 'players', nickname); await updateDoc(playerRef, { [champKey + 'Url']: base64 }); showToast(label + ' salvo!', 'success'); if (typeof loadInitialRanking === 'function') loadInitialRanking(); } catch (err) { showToast('Erro ao salvar: ' + err.message, 'error'); } };// Se ja tem imagem, perguntar se quer substituir ou remover if (currentUrl && isAdmin) { const action = confirm(label + ' de ' + nickname + '\n\nOK = Substituir imagem\nCancelar = Remover imagem atual'); if (action) { fileInput.click(); } else { document.body.removeChild(fileInput); // Remover imagem (async () => { try { const playerRef = doc(db, 'players', nickname); await updateDoc(playerRef, { [champKey + 'Url']: null }); showToast(label + ' removido!', 'success'); if (typeof loadInitialRanking === 'function') loadInitialRanking(); } catch (err) { showToast('Erro ao remover: ' + err.message, 'error'); } })(); } } else { fileInput.click(); } }// Limpar monitoramento ao fechar window.addEventListener('beforeunload', () => { stopLiveVotingUpdates(); });// Atualizar variáveis globais (já inicializadas acima) window.duos = duos; window.isAdmin = isAdmin;/* ====== TOGGLE PAINÉIS AVANÇADOS ====== */ let advancedPanelsVisible = false;function closeAdvancedPanels() { advancedPanelsVisible = false; const resetEl = document.getElementById('resetElosSection'); const duoEl = document.getElementById('duoSection'); const btn = document.getElementById('toggleAdvancedPanels'); if (resetEl) resetEl.style.display = 'none'; if (duoEl) duoEl.style.display = 'none'; if (btn) btn.textContent = '⚙️ Painéis Avançados'; }document.addEventListener('click', function(e) { if (e.target.id !== 'toggleAdvancedPanels') return; advancedPanelsVisible = !advancedPanelsVisible; const resetEl = document.getElementById('resetElosSection'); const duoEl = document.getElementById('duoSection'); if (resetEl) resetEl.style.display = advancedPanelsVisible ? 'block' : 'none'; if (duoEl) duoEl.style.display = advancedPanelsVisible ? 'block' : 'none'; e.target.textContent = advancedPanelsVisible ? '⚙️ Ocultar Painéis Avançados' : '⚙️ Painéis Avançados'; });/* ====== RESET DE TEMPORADA ====== */ document.addEventListener('click', async (e) => { if (e.target.id !== 'resetElosBtn') return; const senha = prompt('🔐 Digite a senha para resetar os elos:'); if (senha !== '2824') { alert('Senha incorreta!'); return; } const confirma = confirm('⚠️ ATENÇÃO: Isso vai zerar TODOS os dados de todos os jogadores (vitórias, derrotas, PDLs, pontos, MVPs e ACEs).\n\nOs elos voltarão ao eloBase cadastrado.\n\nEssa ação NÃO pode ser desfeita. Confirmar?'); if (!confirma) return; const btn = document.getElementById('resetElosBtn'); btn.disabled = true; btn.textContent = 'Resetando...'; try { const snap = await getDocs(collection(db, 'players')); const batch = writeBatch(db); snap.forEach(docSnap => { const data = docSnap.data(); const ref = doc(db, 'players', docSnap.id); // Resetar laneStats mantendo lanes configuradas (elo e prioridade) let laneStatsReset = {}; if (data.laneStats) { Object.keys(data.laneStats).forEach(lane => { laneStatsReset[lane] = { jogou: 0, venceu: 0, perdeu: 0, pdls: 0, elo: data.laneStats[lane]?.elo || data.eloBase || 'platina', prioridade: data.laneStats[lane]?.prioridade || 0 }; }); } batch.update(ref, { vitorias: 0, derrotas: 0, pontos: 0, mvp: 0, ace: 0, partidas: 0, winStreak: 0, loseStreak: 0, lastResult: null, streaksResetadas: false, bestWinStreak: 0, bestLoseStreak: 0, laneStats: laneStatsReset, eloManual: (() => { const eb = data.eloBase || 'platina'; const isMp = (eb === 'mestre' || eb === 'grão-mestre' || eb === 'challenger'); // pdlsIniciais do elo base (ex: Mestre=100, Grão-Mestre=600, Challenger=1000) // Para exibir "0 PDLs" na tela precisamos usar o pdlsIniciais do elo const pdlsBase = { 'ferro':0,'bronze':0,'prata':0,'ouro':0,'platina':0,'esmeralda':0,'diamante':0,'mestre':100,'grão-mestre':600,'challenger':1000 }; return { enabled: true, elo: eb, divisao: isMp ? 0 : 4, pdls: pdlsBase[eb] || 0 }; })() }); }); await batch.commit(); showToast('✅ Reset concluído! Todos os elos voltaram ao padrão.', 'success'); if (typeof loadInitialRanking === 'function') loadInitialRanking(); } catch (err) { console.error('Erro no reset:', err); showToast('Erro ao resetar: ' + err.message, 'error'); } finally { btn.disabled = false; btn.textContent = '🔄 Resetar Todos os Elos (Nova Temporada)'; } });/* ====== IMAGENS DOS ELOS (FIREBASE) ====== */const ELO_KEYS = ['ferro','bronze','prata','ouro','platina','esmeralda','diamante','mestre','grao_mestre','challenger']; const ELO_LABELS = { ferro: 'Ferro', bronze: 'Bronze', prata: 'Prata', ouro: 'Ouro', platina: 'Platina', esmeralda: 'Esmeralda', diamante: 'Diamante', mestre: 'Mestre', grao_mestre: 'Grão-Mestre', challenger: 'Challenger' };async function loadEloImagesFromFirebase() { try { const snap = await getDoc(doc(db, 'config', 'eloImages')); if (snap.exists()) { eloImagesCache = snap.data() || {}; localStorage.setItem('eloImages', JSON.stringify(eloImagesCache)); } } catch (e) { // fallback para localStorage const cached = localStorage.getItem('eloImages'); if (cached) { try { eloImagesCache = JSON.parse(cached); } catch(e2) {} } } renderEloImageGrid(); }function renderEloImageGrid() { const grid = document.getElementById('eloImageGrid'); if (!grid) return; grid.innerHTML = ELO_KEYS.map(key => { const imgUrl = eloImagesCache[key] || ''; return `
${!imgUrl ? '🛡️' : ''}
${ELO_LABELS[key]}
${imgUrl ? `` : ''}
`; }).join(''); // Eventos upload grid.querySelectorAll('.elo-image-upload-input').forEach(input => { input.addEventListener('change', async (e) => { const file = e.target.files[0]; const eloKey = e.target.dataset.elo; if (!file || !eloKey) return; if (!file.type.startsWith('image/')) { showToast('Selecione uma imagem válida!', 'error'); return; } // Verificar tamanho (max 200KB para caber no Firestore) if (file.size > 200 * 1024) { showToast('Imagem muito grande! Use no máximo 200KB.', 'error'); return; } showToast('Salvando imagem...', 'info'); try { // Ler o arquivo como base64 usando Promise (corrige bug de async dentro de onload) const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (ev) => resolve(ev.target.result); reader.onerror = () => reject(new Error('Falha ao ler arquivo')); reader.readAsDataURL(file); }); eloImagesCache[eloKey] = base64; // Salvar no Firebase const configRef = doc(db, 'config', 'eloImages'); const existing = await getDoc(configRef); if (existing.exists()) { await updateDoc(configRef, { [eloKey]: base64 }); } else { await setDoc(configRef, eloImagesCache); } // Atualizar localStorage localStorage.setItem('eloImages', JSON.stringify(eloImagesCache)); showToast(`✅ Imagem do ${ELO_LABELS[eloKey]} salva!`, 'success'); renderEloImageGrid(); if (typeof loadInitialRanking === 'function') loadInitialRanking(); } catch (err) { console.error('Erro ao salvar imagem:', err); showToast('Erro ao salvar imagem: ' + err.message, 'error'); } }); }); // Eventos remover grid.querySelectorAll('[data-remove-elo]').forEach(btn => { btn.addEventListener('click', async () => { const eloKey = btn.dataset.removeElo; if (!confirm(`Remover imagem do ${ELO_LABELS[eloKey]}?`)) return; try { delete eloImagesCache[eloKey]; const configRef = doc(db, 'config', 'eloImages'); await updateDoc(configRef, { [eloKey]: null }); localStorage.setItem('eloImages', JSON.stringify(eloImagesCache)); showToast(`Imagem removida!`, 'success'); renderEloImageGrid(); if (typeof loadInitialRanking === 'function') loadInitialRanking(); } catch (err) { showToast('Erro ao remover: ' + err.message, 'error'); } }); }); }// Expor funções críticas ao escopo global (necessário pois estão em type="module") window.calcularEloCompleto = calcularEloCompleto; window.calcularValorElo = calcularValorElo; window.renderRanking = renderRanking; window.loadEloTable = loadEloTable; window.calculatePlayerLaneStats = calculatePlayerLaneStats; window.loadInitialRanking = loadInitialRanking; window.updateStats = updateStats; window.startApp = startApp; window.setupPlayersListener = setupPlayersListener;// Log function function log(message) { const logElement = document.getElementById('log'); if (logElement) { logElement.textContent = message + '\n' + logElement.textContent; } }